From e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 22 May 2024 21:25:21 +0200 Subject: Fix line endlings --- indra/llui/llaccordionctrl.cpp | 1910 +++---- indra/llui/llaccordionctrl.h | 400 +- indra/llui/llaccordionctrltab.cpp | 2252 ++++---- indra/llui/llaccordionctrltab.h | 502 +- indra/llui/llbadge.cpp | 778 +-- indra/llui/llbadge.h | 354 +- indra/llui/llbutton.cpp | 2642 ++++----- indra/llui/llbutton.h | 812 +-- indra/llui/llchat.h | 224 +- indra/llui/llchatentry.cpp | 502 +- indra/llui/llchatentry.h | 212 +- indra/llui/llcheckboxctrl.cpp | 608 +-- indra/llui/llcheckboxctrl.h | 314 +- indra/llui/llcombobox.cpp | 2462 ++++----- indra/llui/llcombobox.h | 568 +- indra/llui/llconsole.cpp | 806 +-- indra/llui/llconsole.h | 312 +- indra/llui/llcontainerview.cpp | 600 +-- indra/llui/llcontainerview.h | 188 +- indra/llui/llctrlselectioninterface.cpp | 126 +- indra/llui/llctrlselectioninterface.h | 208 +- indra/llui/lldockablefloater.cpp | 526 +- indra/llui/lldockablefloater.h | 304 +- indra/llui/lldraghandle.cpp | 774 +-- indra/llui/lldraghandle.h | 278 +- indra/llui/lleditmenuhandler.h | 142 +- indra/llui/llfiltereditor.cpp | 92 +- indra/llui/llflashtimer.cpp | 196 +- indra/llui/llflashtimer.h | 148 +- indra/llui/llflatlistview.cpp | 2878 +++++----- indra/llui/llflatlistview.h | 1070 ++-- indra/llui/llfloater.cpp | 7470 +++++++++++++------------- indra/llui/llfloater.h | 1282 ++--- indra/llui/llfloaterreg.cpp | 1218 ++--- indra/llui/llfloaterreg.h | 316 +- indra/llui/llflyoutbutton.cpp | 154 +- indra/llui/llflyoutbutton.h | 136 +- indra/llui/llfocusmgr.cpp | 1018 ++-- indra/llui/llfocusmgr.h | 320 +- indra/llui/llfolderview.cpp | 4258 +++++++-------- indra/llui/llfolderview.h | 896 ++-- indra/llui/llfolderviewitem.cpp | 4736 ++++++++--------- indra/llui/llfolderviewitem.h | 996 ++-- indra/llui/llfolderviewmodel.h | 948 ++-- indra/llui/lliconctrl.cpp | 346 +- indra/llui/lliconctrl.h | 214 +- indra/llui/llkeywords.cpp | 1626 +++--- indra/llui/lllayoutstack.cpp | 2044 +++---- indra/llui/lllayoutstack.h | 432 +- indra/llui/lllineeditor.cpp | 5470 +++++++++---------- indra/llui/lllineeditor.h | 944 ++-- indra/llui/lllocalcliprect.cpp | 220 +- indra/llui/lllocalcliprect.h | 126 +- indra/llui/llmenubutton.cpp | 492 +- indra/llui/llmenubutton.h | 202 +- indra/llui/llmenugl.cpp | 8808 +++++++++++++++---------------- indra/llui/llmenugl.h | 1956 +++---- indra/llui/llmodaldialog.cpp | 696 +-- indra/llui/llmodaldialog.h | 166 +- indra/llui/llmultifloater.cpp | 1044 ++-- indra/llui/llmultifloater.h | 204 +- indra/llui/llmultislider.cpp | 1760 +++--- indra/llui/llmultislider.h | 322 +- indra/llui/llmultisliderctrl.cpp | 1050 ++-- indra/llui/llmultisliderctrl.h | 348 +- indra/llui/llnotifications.cpp | 4018 +++++++------- indra/llui/llnotifications.h | 2266 ++++---- indra/llui/llpanel.cpp | 1750 +++--- indra/llui/llpanel.h | 636 +-- indra/llui/llradiogroup.cpp | 1042 ++-- indra/llui/llradiogroup.h | 228 +- indra/llui/llresizebar.cpp | 752 +-- indra/llui/llresizebar.h | 176 +- indra/llui/llresizehandle.cpp | 770 +-- indra/llui/llresizehandle.h | 158 +- indra/llui/llresmgr.cpp | 534 +- indra/llui/llscrollbar.cpp | 1332 ++--- indra/llui/llscrollbar.h | 334 +- indra/llui/llscrollcontainer.cpp | 1604 +++--- indra/llui/llscrollcontainer.h | 314 +- indra/llui/llscrollingpanellist.cpp | 504 +- indra/llui/llscrollingpanellist.h | 204 +- indra/llui/llscrolllistcell.cpp | 1342 ++--- indra/llui/llscrolllistcell.h | 560 +- indra/llui/llscrolllistcolumn.cpp | 680 +-- indra/llui/llscrolllistcolumn.h | 344 +- indra/llui/llscrolllistctrl.cpp | 6896 ++++++++++++------------ indra/llui/llscrolllistctrl.h | 1128 ++-- indra/llui/llscrolllistitem.cpp | 408 +- indra/llui/llscrolllistitem.h | 284 +- indra/llui/llsearcheditor.cpp | 436 +- indra/llui/llsearcheditor.h | 228 +- indra/llui/llslider.cpp | 766 +-- indra/llui/llslider.h | 216 +- indra/llui/llsliderctrl.cpp | 978 ++-- indra/llui/llsliderctrl.h | 352 +- indra/llui/llspinctrl.cpp | 1008 ++-- indra/llui/llspinctrl.h | 248 +- indra/llui/llstatbar.cpp | 1432 ++--- indra/llui/llstatbar.h | 238 +- indra/llui/llstatgraph.cpp | 252 +- indra/llui/llstatgraph.h | 282 +- indra/llui/llstatview.cpp | 128 +- indra/llui/llstyle.cpp | 208 +- indra/llui/llstyle.h | 236 +- indra/llui/lltabcontainer.cpp | 4408 ++++++++-------- indra/llui/lltabcontainer.h | 660 +-- indra/llui/lltextbase.cpp | 7780 +++++++++++++-------------- indra/llui/lltextbase.h | 1516 +++--- indra/llui/lltextbox.cpp | 366 +- indra/llui/lltextbox.h | 174 +- indra/llui/lltexteditor.cpp | 6140 ++++++++++----------- indra/llui/lltexteditor.h | 702 +-- indra/llui/lltextparser.cpp | 478 +- indra/llui/lltimectrl.cpp | 908 ++-- indra/llui/lltimectrl.h | 258 +- indra/llui/lltoggleablemenu.cpp | 216 +- indra/llui/lltoggleablemenu.h | 142 +- indra/llui/lltoolbar.cpp | 2532 ++++----- indra/llui/lltoolbar.h | 662 +-- indra/llui/lltooltip.cpp | 1296 ++--- indra/llui/lltooltip.h | 370 +- indra/llui/lltransutil.cpp | 148 +- indra/llui/llui.cpp | 1476 +++--- indra/llui/lluictrl.cpp | 2274 ++++---- indra/llui/lluictrl.h | 682 +-- indra/llui/lluictrlfactory.cpp | 558 +- indra/llui/llundo.cpp | 348 +- indra/llui/llundo.h | 136 +- indra/llui/llurlentry.cpp | 3476 ++++++------ indra/llui/llurlentry.h | 1172 ++-- indra/llui/llview.cpp | 5764 ++++++++++---------- indra/llui/llview.h | 1474 +++--- indra/llui/llviewborder.cpp | 540 +- indra/llui/llviewborder.h | 220 +- indra/llui/llviewereventrecorder.cpp | 592 +-- indra/llui/llviewquery.cpp | 272 +- indra/llui/llviewquery.h | 274 +- indra/llui/llvirtualtrackball.cpp | 1040 ++-- indra/llui/llvirtualtrackball.h | 326 +- indra/llui/llxuiparser.cpp | 3530 ++++++------- indra/llui/llxyvector.h | 244 +- indra/llui/tests/llurlentry_test.cpp | 1870 +++---- 143 files changed, 82851 insertions(+), 82851 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp index c40e78233d..26f5e4fbe2 100644 --- a/indra/llui/llaccordionctrl.cpp +++ b/indra/llui/llaccordionctrl.cpp @@ -1,955 +1,955 @@ -/** - * @file llaccordionctrl.cpp - * @brief Accordion panel 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 "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 BORDER_MARGIN = 2; -static const S32 PARENT_BORDER_MARGIN = 5; -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 t2("accordion"); - -LLAccordionCtrl::LLAccordionCtrl(const Params& params):LLPanel(params) - , mFitParent(params.fit_parent) - , mAutoScrolling( false ) - , mAutoScrollRate( 0.f ) - , mSelectedTab( NULL ) - , mTabComparator( NULL ) - , mNoVisibleTabsHelpText(NULL) - , mNoVisibleTabsOrigString(params.no_visible_tabs_text.initial_value().asString()) - , mSkipScrollToChild(false) -{ - initNoTabsWidget(params.no_matched_tabs_text); - - mSingleExpansion = params.single_expansion; - if (mFitParent && !mSingleExpansion) - { - LL_INFOS() << "fit_parent works best when combined with single_expansion" << LL_ENDL; - } -} - -LLAccordionCtrl::LLAccordionCtrl() : LLPanel() - , mAutoScrolling( false ) - , mAutoScrollRate( 0.f ) - , mSelectedTab( NULL ) - , mNoVisibleTabsHelpText(NULL) -{ - initNoTabsWidget(LLTextBox::Params()); - - mSingleExpansion = false; - mFitParent = false; - buildFromFile( "accordion_parent.xml"); -} - -//--------------------------------------------------------------------------------- -void LLAccordionCtrl::draw() -{ - if (mAutoScrolling) - { - // add acceleration to autoscroll - mAutoScrollRate = llmin(mAutoScrollRate + (LLFrameTimer::getFrameDeltaTimeF32() * AUTO_SCROLL_RATE_ACCEL), MAX_AUTO_SCROLL_RATE); - } - else - { - // reset to minimum for next time - mAutoScrollRate = MIN_AUTO_SCROLL_RATE; - } - // clear this flag to be set on next call to autoScroll - mAutoScrolling = false; - - LLRect local_rect(0, getRect().getHeight(), getRect().getWidth(), 0); - - LLLocalClipRect clip(local_rect); - - LLPanel::draw(); -} - -//--------------------------------------------------------------------------------- -bool LLAccordionCtrl::postBuild() -{ - static LLUICachedControl 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(sbparams); - LLView::addChild(mScrollbar); - mScrollbar->setVisible(false); - mScrollbar->setFollowsRight(); - mScrollbar->setFollowsTop(); - mScrollbar->setFollowsBottom(); - - //if it was created from xml... - std::vector accordion_tabs; - for (child_list_const_iter_t it = getChildList()->begin(); - getChildList()->end() != it; ++it) - { - LLAccordionCtrlTab* accordion_tab = dynamic_cast(*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::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); - } - } - - updateNoTabsHelpTextVisibility(); - - 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; - - // get textbox a chance to reshape its content - mNoVisibleTabsHelpText->reshape(width, height, called_from_parent); - - setRect(rcLocal); - - // assume that help text is always fit accordion. - // necessary text paddings can be set via h_pad and v_pad - mNoVisibleTabsHelpText->setRect(getLocalRect()); - - 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 scrollbar_size ("UIScrollbarSize", 0); - - ctrlSetLeftTopAndSize(mScrollbar - , width - scrollbar_size - PARENT_BORDER_MARGIN / 2 - , height - PARENT_BORDER_MARGIN - , scrollbar_size - , height - PARENT_BORDER_MARGIN * 2); - - 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()) - return; - mScrollbar->setVisible(false); - - static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); - - S32 panel_width = width - 2*BORDER_MARGIN; - - // Reshape all accordions 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.empty()) - { - 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::iterator panel; - for(panel=mAccordionTabs.begin(); panel!=mAccordionTabs.end(); ++panel) - { - LLAccordionCtrlTab* accordion_tab = dynamic_cast(*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(view); - if (!accordion_tab) - return; - if (std::find(beginChild(), endChild(), accordion_tab) == endChild()) - addChild(accordion_tab); - mAccordionTabs.push_back(accordion_tab); - - accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLAccordionCtrl::onCollapseCtrlCloseOpen, this, (S16)(mAccordionTabs.size() - 1)) ); - arrange(); -} - -void LLAccordionCtrl::removeCollapsibleCtrl(LLView* view) -{ - LLAccordionCtrlTab* accordion_tab = dynamic_cast(view); - if(!accordion_tab) - return; - - if(std::find(beginChild(), endChild(), accordion_tab) != endChild()) - removeChild(accordion_tab); - - for (std::vector::iterator iter = mAccordionTabs.begin(); - iter != mAccordionTabs.end(); ++iter) - { - if (accordion_tab == (*iter)) - { - mAccordionTabs.erase(iter); - break; - } - } - - // if removed is selected - reset selection - if (mSelectedTab == view) - { - mSelectedTab = NULL; - } -} - -void LLAccordionCtrl::initNoTabsWidget(const LLTextBox::Params& tb_params) -{ - LLTextBox::Params tp = tb_params; - tp.rect(getLocalRect()); - mNoMatchedTabsOrigString = tp.initial_value().asString(); - mNoVisibleTabsHelpText = LLUICtrlFactory::create(tp, this); -} - -void LLAccordionCtrl::updateNoTabsHelpTextVisibility() -{ - bool visible_exists = false; - std::vector::const_iterator it = mAccordionTabs.begin(); - const std::vector::const_iterator it_end = mAccordionTabs.end(); - while (it < it_end) - { - if ((*(it++))->getVisible()) - { - visible_exists = true; - break; - } - } - - mNoVisibleTabsHelpText->setVisible(!visible_exists); -} - -void LLAccordionCtrl::arrangeSingle() -{ - 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; - S32 panel_height; - - S32 collapsed_height = 0; - - for (size_t i = 0; i < mAccordionTabs.size(); ++i) - { - LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); - - if (!accordion_tab->getVisible()) // Skip hidden accordion tabs - continue; - if (!accordion_tab->isExpanded() ) - { - collapsed_height+=mAccordionTabs[i]->getRect().getHeight(); - } - } - - S32 expanded_height = getRect().getHeight() - BORDER_MARGIN - collapsed_height; - - for (size_t i = 0; i < mAccordionTabs.size(); ++i) - { - LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); - - if (!accordion_tab->getVisible()) // Skip hidden accordion tabs - continue; - if (!accordion_tab->isExpanded() ) - { - panel_height = accordion_tab->getRect().getHeight(); - } - else - { - if (mFitParent) - { - panel_height = expanded_height; - } - else - { - if (accordion_tab->getAccordionView()) - { - panel_height = accordion_tab->getAccordionView()->getRect().getHeight() + - accordion_tab->getHeaderHeight() + BORDER_MARGIN * 2; - } - else - { - panel_height = accordion_tab->getRect().getHeight(); - } - } - } - - // make sure at least header is shown - panel_height = llmax(panel_height, accordion_tab->getHeaderHeight()); - - ctrlSetLeftTopAndSize(mAccordionTabs[i], panel_left, panel_top, panel_width, panel_height); - panel_top -= mAccordionTabs[i]->getRect().getHeight(); - } - - show_hide_scrollbar(getRect().getWidth(), getRect().getHeight()); - updateLayout(getRect().getWidth(), getRect().getHeight()); -} - -void LLAccordionCtrl::arrangeMultiple() -{ - 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; - - //Calculate params - for (size_t i = 0; i < mAccordionTabs.size(); i++ ) - { - LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); - - if (!accordion_tab->getVisible()) // 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()); -} - - -void LLAccordionCtrl::arrange() -{ - updateNoTabsHelpTextVisibility(); - - if (mAccordionTabs.empty()) - { - // Nothing to arrange - return; - } - - if (mAccordionTabs.size() == 1) - { - S32 panel_top = getRect().getHeight() - BORDER_MARGIN; // Top coordinate of the first panel - S32 panel_width = getRect().getWidth() - 4; - - LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[0]); - - LLRect panel_rect = accordion_tab->getRect(); - - S32 panel_height = getRect().getHeight() - BORDER_MARGIN * 2; - if (accordion_tab->getFitParent()) - panel_height = accordion_tab->getRect().getHeight(); - - ctrlSetLeftTopAndSize(accordion_tab, panel_rect.mLeft, panel_top, panel_width, panel_height); - - show_hide_scrollbar(getRect().getWidth(), getRect().getHeight()); - return; - } - - if (mSingleExpansion) - arrangeSingle(); - else - arrangeMultiple(); -} - -//--------------------------------------------------------------------------------- - -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); -} - -bool LLAccordionCtrl::handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - // Scroll folder view if needed. Never accepts a drag or drop. - *accept = ACCEPT_NO; - bool handled = autoScroll(x, y); - - if (!handled) - { - handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, - cargo_data, accept, tooltip_msg) != NULL; - } - return true; -} - -bool LLAccordionCtrl::autoScroll(S32 x, S32 y) -{ - static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); - - bool scrolling = false; - if (mScrollbar->getVisible()) - { - LLRect rect_local(0, getRect().getHeight(), getRect().getWidth() - scrollbar_size, 0); - LLRect screen_local_extents; - - // clip rect against root view - screenRectToLocal(getRootView()->getLocalRect(), &screen_local_extents); - rect_local.intersectWith(screen_local_extents); - - // autoscroll region should take up no more than one third of visible scroller area - S32 auto_scroll_region_height = llmin(rect_local.getHeight() / 3, 10); - S32 auto_scroll_speed = ll_round(mAutoScrollRate * LLFrameTimer::getFrameDeltaTimeF32()); - - LLRect bottom_scroll_rect = screen_local_extents; - bottom_scroll_rect.mTop = rect_local.mBottom + auto_scroll_region_height; - if (bottom_scroll_rect.pointInRect( x, y ) && (mScrollbar->getDocPos() < mScrollbar->getDocPosMax())) - { - mScrollbar->setDocPos(mScrollbar->getDocPos() + auto_scroll_speed); - mAutoScrolling = true; - scrolling = true; - } - - LLRect top_scroll_rect = screen_local_extents; - top_scroll_rect.mBottom = rect_local.mTop - auto_scroll_region_height; - if (top_scroll_rect.pointInRect(x, y) && (mScrollbar->getDocPos() > 0)) - { - mScrollbar->setDocPos(mScrollbar->getDocPos() - auto_scroll_speed); - mAutoScrolling = true; - scrolling = true; - } - } - - return scrolling; -} - -void LLAccordionCtrl::updateLayout(S32 width, S32 height) -{ - S32 panel_top = height - BORDER_MARGIN ; - if (mScrollbar->getVisible()) - panel_top += mScrollbar->getDocPos(); - - S32 panel_width = width - BORDER_MARGIN * 2; - - static LLUICachedControl 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()); -} - -// virtual -void LLAccordionCtrl::onUpdateScrollToChild(const LLUICtrl *cntrl) -{ - if (mScrollbar && mScrollbar->getVisible() && !mSkipScrollToChild) - { - // same as scrollToShowRect - 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); -} - -void LLAccordionCtrl::onOpen(const LLSD& key) -{ - for (size_t i = 0; i < mAccordionTabs.size(); ++i) - { - LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); - LLPanel* panel = dynamic_cast(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; - } - if (str_action == "select_next") - { - for (size_t i = 0; i < mAccordionTabs.size(); ++i) - { - LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); - if (accordion_tab->hasFocus()) - { - while (++i < mAccordionTabs.size()) - { - if (mAccordionTabs[i]->getVisible()) - break; - } - if (i < mAccordionTabs.size()) - { - accordion_tab = dynamic_cast(mAccordionTabs[i]); - accordion_tab->notify(LLSD().with("action","select_first")); - return 1; - } - break; - } - } - return 0; - } - if (str_action == "select_prev") - { - for (size_t i = 0; i < mAccordionTabs.size(); ++i) - { - LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); - if (accordion_tab->hasFocus() && i > 0) - { - bool prev_visible_tab_found = false; - while (i > 0) - { - if (mAccordionTabs[--i]->getVisible()) - { - prev_visible_tab_found = true; - break; - } - } - - if (prev_visible_tab_found) - { - accordion_tab = dynamic_cast(mAccordionTabs[i]); - accordion_tab->notify(LLSD().with("action","select_last")); - return 1; - } - break; - } - } - return 0; - } - if (str_action == "select_current") - { - for (size_t i = 0; i < mAccordionTabs.size(); ++i) - { - // Set selection to the currently focused tab. - if (mAccordionTabs[i]->hasFocus()) - { - if (mAccordionTabs[i] != mSelectedTab) - { - if (mSelectedTab) - { - mSelectedTab->setSelected(false); - } - mSelectedTab = mAccordionTabs[i]; - mSelectedTab->setSelected(true); - } - - return 1; - } - } - return 0; - } - if (str_action == "deselect_current") - { - // Reset selection to the currently selected tab. - if (mSelectedTab) - { - mSelectedTab->setSelected(false); - mSelectedTab = NULL; - 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; - } - else if (info.has("child_visibility_change")) - { - bool new_visibility = info["child_visibility_change"]; - if (new_visibility) - { - // there is at least one visible tab - mNoVisibleTabsHelpText->setVisible(false); - } - else - { - // it could be the latest visible tab, check all of them - updateNoTabsHelpTextVisibility(); - } - } - return LLPanel::notifyParent(info); -} - -void LLAccordionCtrl::reset() -{ - if (mScrollbar) - mScrollbar->setDocPos(0); -} - -void LLAccordionCtrl::expandDefaultTab() -{ - if (!mAccordionTabs.empty()) - { - LLAccordionCtrlTab* tab = mAccordionTabs.front(); - - if (!tab->getDisplayChildren()) - { - tab->setDisplayChildren(true); - } - - for (size_t i = 1; i < mAccordionTabs.size(); ++i) - { - tab = mAccordionTabs[i]; - - if (tab->getDisplayChildren()) - { - tab->setDisplayChildren(false); - } - } - - arrange(); - } -} - -void LLAccordionCtrl::sort() -{ - if (!mTabComparator) - { - LL_WARNS() << "No comparator specified for sorting accordion tabs." << LL_ENDL; - return; - } - - std::sort(mAccordionTabs.begin(), mAccordionTabs.end(), LLComparatorAdaptor(*mTabComparator)); - arrange(); -} - -void LLAccordionCtrl::setFilterSubString(const std::string& filter_string) -{ - LLStringUtil::format_map_t args; - args["[SEARCH_TERM]"] = LLURI::escape(filter_string); - std::string text = filter_string.empty() ? mNoVisibleTabsOrigString : mNoMatchedTabsOrigString; - LLStringUtil::format(text, args); - - mNoVisibleTabsHelpText->setValue(text); -} - -const LLAccordionCtrlTab* LLAccordionCtrl::getExpandedTab() const -{ - typedef std::vector::const_iterator tabs_const_iterator; - - const LLAccordionCtrlTab* result = 0; - - for (tabs_const_iterator i = mAccordionTabs.begin(); i != mAccordionTabs.end(); ++i) - { - if ((*i)->isExpanded()) - { - result = *i; - break; - } - } - - return result; -} - -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; -} - -void LLAccordionCtrl::collapseAllTabs() -{ - if (mAccordionTabs.size() > 0) - { - for (size_t i = 0; i < mAccordionTabs.size(); ++i) - { - LLAccordionCtrlTab *tab = mAccordionTabs[i]; - - if (tab->getDisplayChildren()) - { - tab->setDisplayChildren(false); - } - } - arrange(); - } -} +/** + * @file llaccordionctrl.cpp + * @brief Accordion panel 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 "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 BORDER_MARGIN = 2; +static const S32 PARENT_BORDER_MARGIN = 5; +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 t2("accordion"); + +LLAccordionCtrl::LLAccordionCtrl(const Params& params):LLPanel(params) + , mFitParent(params.fit_parent) + , mAutoScrolling( false ) + , mAutoScrollRate( 0.f ) + , mSelectedTab( NULL ) + , mTabComparator( NULL ) + , mNoVisibleTabsHelpText(NULL) + , mNoVisibleTabsOrigString(params.no_visible_tabs_text.initial_value().asString()) + , mSkipScrollToChild(false) +{ + initNoTabsWidget(params.no_matched_tabs_text); + + mSingleExpansion = params.single_expansion; + if (mFitParent && !mSingleExpansion) + { + LL_INFOS() << "fit_parent works best when combined with single_expansion" << LL_ENDL; + } +} + +LLAccordionCtrl::LLAccordionCtrl() : LLPanel() + , mAutoScrolling( false ) + , mAutoScrollRate( 0.f ) + , mSelectedTab( NULL ) + , mNoVisibleTabsHelpText(NULL) +{ + initNoTabsWidget(LLTextBox::Params()); + + mSingleExpansion = false; + mFitParent = false; + buildFromFile( "accordion_parent.xml"); +} + +//--------------------------------------------------------------------------------- +void LLAccordionCtrl::draw() +{ + if (mAutoScrolling) + { + // add acceleration to autoscroll + mAutoScrollRate = llmin(mAutoScrollRate + (LLFrameTimer::getFrameDeltaTimeF32() * AUTO_SCROLL_RATE_ACCEL), MAX_AUTO_SCROLL_RATE); + } + else + { + // reset to minimum for next time + mAutoScrollRate = MIN_AUTO_SCROLL_RATE; + } + // clear this flag to be set on next call to autoScroll + mAutoScrolling = false; + + LLRect local_rect(0, getRect().getHeight(), getRect().getWidth(), 0); + + LLLocalClipRect clip(local_rect); + + LLPanel::draw(); +} + +//--------------------------------------------------------------------------------- +bool LLAccordionCtrl::postBuild() +{ + static LLUICachedControl 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(sbparams); + LLView::addChild(mScrollbar); + mScrollbar->setVisible(false); + mScrollbar->setFollowsRight(); + mScrollbar->setFollowsTop(); + mScrollbar->setFollowsBottom(); + + //if it was created from xml... + std::vector accordion_tabs; + for (child_list_const_iter_t it = getChildList()->begin(); + getChildList()->end() != it; ++it) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast(*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::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); + } + } + + updateNoTabsHelpTextVisibility(); + + 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; + + // get textbox a chance to reshape its content + mNoVisibleTabsHelpText->reshape(width, height, called_from_parent); + + setRect(rcLocal); + + // assume that help text is always fit accordion. + // necessary text paddings can be set via h_pad and v_pad + mNoVisibleTabsHelpText->setRect(getLocalRect()); + + 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 scrollbar_size ("UIScrollbarSize", 0); + + ctrlSetLeftTopAndSize(mScrollbar + , width - scrollbar_size - PARENT_BORDER_MARGIN / 2 + , height - PARENT_BORDER_MARGIN + , scrollbar_size + , height - PARENT_BORDER_MARGIN * 2); + + 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()) + return; + mScrollbar->setVisible(false); + + static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); + + S32 panel_width = width - 2*BORDER_MARGIN; + + // Reshape all accordions 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.empty()) + { + 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::iterator panel; + for(panel=mAccordionTabs.begin(); panel!=mAccordionTabs.end(); ++panel) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast(*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(view); + if (!accordion_tab) + return; + if (std::find(beginChild(), endChild(), accordion_tab) == endChild()) + addChild(accordion_tab); + mAccordionTabs.push_back(accordion_tab); + + accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLAccordionCtrl::onCollapseCtrlCloseOpen, this, (S16)(mAccordionTabs.size() - 1)) ); + arrange(); +} + +void LLAccordionCtrl::removeCollapsibleCtrl(LLView* view) +{ + LLAccordionCtrlTab* accordion_tab = dynamic_cast(view); + if(!accordion_tab) + return; + + if(std::find(beginChild(), endChild(), accordion_tab) != endChild()) + removeChild(accordion_tab); + + for (std::vector::iterator iter = mAccordionTabs.begin(); + iter != mAccordionTabs.end(); ++iter) + { + if (accordion_tab == (*iter)) + { + mAccordionTabs.erase(iter); + break; + } + } + + // if removed is selected - reset selection + if (mSelectedTab == view) + { + mSelectedTab = NULL; + } +} + +void LLAccordionCtrl::initNoTabsWidget(const LLTextBox::Params& tb_params) +{ + LLTextBox::Params tp = tb_params; + tp.rect(getLocalRect()); + mNoMatchedTabsOrigString = tp.initial_value().asString(); + mNoVisibleTabsHelpText = LLUICtrlFactory::create(tp, this); +} + +void LLAccordionCtrl::updateNoTabsHelpTextVisibility() +{ + bool visible_exists = false; + std::vector::const_iterator it = mAccordionTabs.begin(); + const std::vector::const_iterator it_end = mAccordionTabs.end(); + while (it < it_end) + { + if ((*(it++))->getVisible()) + { + visible_exists = true; + break; + } + } + + mNoVisibleTabsHelpText->setVisible(!visible_exists); +} + +void LLAccordionCtrl::arrangeSingle() +{ + 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; + S32 panel_height; + + S32 collapsed_height = 0; + + for (size_t i = 0; i < mAccordionTabs.size(); ++i) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); + + if (!accordion_tab->getVisible()) // Skip hidden accordion tabs + continue; + if (!accordion_tab->isExpanded() ) + { + collapsed_height+=mAccordionTabs[i]->getRect().getHeight(); + } + } + + S32 expanded_height = getRect().getHeight() - BORDER_MARGIN - collapsed_height; + + for (size_t i = 0; i < mAccordionTabs.size(); ++i) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); + + if (!accordion_tab->getVisible()) // Skip hidden accordion tabs + continue; + if (!accordion_tab->isExpanded() ) + { + panel_height = accordion_tab->getRect().getHeight(); + } + else + { + if (mFitParent) + { + panel_height = expanded_height; + } + else + { + if (accordion_tab->getAccordionView()) + { + panel_height = accordion_tab->getAccordionView()->getRect().getHeight() + + accordion_tab->getHeaderHeight() + BORDER_MARGIN * 2; + } + else + { + panel_height = accordion_tab->getRect().getHeight(); + } + } + } + + // make sure at least header is shown + panel_height = llmax(panel_height, accordion_tab->getHeaderHeight()); + + ctrlSetLeftTopAndSize(mAccordionTabs[i], panel_left, panel_top, panel_width, panel_height); + panel_top -= mAccordionTabs[i]->getRect().getHeight(); + } + + show_hide_scrollbar(getRect().getWidth(), getRect().getHeight()); + updateLayout(getRect().getWidth(), getRect().getHeight()); +} + +void LLAccordionCtrl::arrangeMultiple() +{ + 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; + + //Calculate params + for (size_t i = 0; i < mAccordionTabs.size(); i++ ) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); + + if (!accordion_tab->getVisible()) // 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()); +} + + +void LLAccordionCtrl::arrange() +{ + updateNoTabsHelpTextVisibility(); + + if (mAccordionTabs.empty()) + { + // Nothing to arrange + return; + } + + if (mAccordionTabs.size() == 1) + { + S32 panel_top = getRect().getHeight() - BORDER_MARGIN; // Top coordinate of the first panel + S32 panel_width = getRect().getWidth() - 4; + + LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[0]); + + LLRect panel_rect = accordion_tab->getRect(); + + S32 panel_height = getRect().getHeight() - BORDER_MARGIN * 2; + if (accordion_tab->getFitParent()) + panel_height = accordion_tab->getRect().getHeight(); + + ctrlSetLeftTopAndSize(accordion_tab, panel_rect.mLeft, panel_top, panel_width, panel_height); + + show_hide_scrollbar(getRect().getWidth(), getRect().getHeight()); + return; + } + + if (mSingleExpansion) + arrangeSingle(); + else + arrangeMultiple(); +} + +//--------------------------------------------------------------------------------- + +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); +} + +bool LLAccordionCtrl::handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // Scroll folder view if needed. Never accepts a drag or drop. + *accept = ACCEPT_NO; + bool handled = autoScroll(x, y); + + if (!handled) + { + handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, + cargo_data, accept, tooltip_msg) != NULL; + } + return true; +} + +bool LLAccordionCtrl::autoScroll(S32 x, S32 y) +{ + static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); + + bool scrolling = false; + if (mScrollbar->getVisible()) + { + LLRect rect_local(0, getRect().getHeight(), getRect().getWidth() - scrollbar_size, 0); + LLRect screen_local_extents; + + // clip rect against root view + screenRectToLocal(getRootView()->getLocalRect(), &screen_local_extents); + rect_local.intersectWith(screen_local_extents); + + // autoscroll region should take up no more than one third of visible scroller area + S32 auto_scroll_region_height = llmin(rect_local.getHeight() / 3, 10); + S32 auto_scroll_speed = ll_round(mAutoScrollRate * LLFrameTimer::getFrameDeltaTimeF32()); + + LLRect bottom_scroll_rect = screen_local_extents; + bottom_scroll_rect.mTop = rect_local.mBottom + auto_scroll_region_height; + if (bottom_scroll_rect.pointInRect( x, y ) && (mScrollbar->getDocPos() < mScrollbar->getDocPosMax())) + { + mScrollbar->setDocPos(mScrollbar->getDocPos() + auto_scroll_speed); + mAutoScrolling = true; + scrolling = true; + } + + LLRect top_scroll_rect = screen_local_extents; + top_scroll_rect.mBottom = rect_local.mTop - auto_scroll_region_height; + if (top_scroll_rect.pointInRect(x, y) && (mScrollbar->getDocPos() > 0)) + { + mScrollbar->setDocPos(mScrollbar->getDocPos() - auto_scroll_speed); + mAutoScrolling = true; + scrolling = true; + } + } + + return scrolling; +} + +void LLAccordionCtrl::updateLayout(S32 width, S32 height) +{ + S32 panel_top = height - BORDER_MARGIN ; + if (mScrollbar->getVisible()) + panel_top += mScrollbar->getDocPos(); + + S32 panel_width = width - BORDER_MARGIN * 2; + + static LLUICachedControl 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()); +} + +// virtual +void LLAccordionCtrl::onUpdateScrollToChild(const LLUICtrl *cntrl) +{ + if (mScrollbar && mScrollbar->getVisible() && !mSkipScrollToChild) + { + // same as scrollToShowRect + 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); +} + +void LLAccordionCtrl::onOpen(const LLSD& key) +{ + for (size_t i = 0; i < mAccordionTabs.size(); ++i) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); + LLPanel* panel = dynamic_cast(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; + } + if (str_action == "select_next") + { + for (size_t i = 0; i < mAccordionTabs.size(); ++i) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); + if (accordion_tab->hasFocus()) + { + while (++i < mAccordionTabs.size()) + { + if (mAccordionTabs[i]->getVisible()) + break; + } + if (i < mAccordionTabs.size()) + { + accordion_tab = dynamic_cast(mAccordionTabs[i]); + accordion_tab->notify(LLSD().with("action","select_first")); + return 1; + } + break; + } + } + return 0; + } + if (str_action == "select_prev") + { + for (size_t i = 0; i < mAccordionTabs.size(); ++i) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast(mAccordionTabs[i]); + if (accordion_tab->hasFocus() && i > 0) + { + bool prev_visible_tab_found = false; + while (i > 0) + { + if (mAccordionTabs[--i]->getVisible()) + { + prev_visible_tab_found = true; + break; + } + } + + if (prev_visible_tab_found) + { + accordion_tab = dynamic_cast(mAccordionTabs[i]); + accordion_tab->notify(LLSD().with("action","select_last")); + return 1; + } + break; + } + } + return 0; + } + if (str_action == "select_current") + { + for (size_t i = 0; i < mAccordionTabs.size(); ++i) + { + // Set selection to the currently focused tab. + if (mAccordionTabs[i]->hasFocus()) + { + if (mAccordionTabs[i] != mSelectedTab) + { + if (mSelectedTab) + { + mSelectedTab->setSelected(false); + } + mSelectedTab = mAccordionTabs[i]; + mSelectedTab->setSelected(true); + } + + return 1; + } + } + return 0; + } + if (str_action == "deselect_current") + { + // Reset selection to the currently selected tab. + if (mSelectedTab) + { + mSelectedTab->setSelected(false); + mSelectedTab = NULL; + 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; + } + else if (info.has("child_visibility_change")) + { + bool new_visibility = info["child_visibility_change"]; + if (new_visibility) + { + // there is at least one visible tab + mNoVisibleTabsHelpText->setVisible(false); + } + else + { + // it could be the latest visible tab, check all of them + updateNoTabsHelpTextVisibility(); + } + } + return LLPanel::notifyParent(info); +} + +void LLAccordionCtrl::reset() +{ + if (mScrollbar) + mScrollbar->setDocPos(0); +} + +void LLAccordionCtrl::expandDefaultTab() +{ + if (!mAccordionTabs.empty()) + { + LLAccordionCtrlTab* tab = mAccordionTabs.front(); + + if (!tab->getDisplayChildren()) + { + tab->setDisplayChildren(true); + } + + for (size_t i = 1; i < mAccordionTabs.size(); ++i) + { + tab = mAccordionTabs[i]; + + if (tab->getDisplayChildren()) + { + tab->setDisplayChildren(false); + } + } + + arrange(); + } +} + +void LLAccordionCtrl::sort() +{ + if (!mTabComparator) + { + LL_WARNS() << "No comparator specified for sorting accordion tabs." << LL_ENDL; + return; + } + + std::sort(mAccordionTabs.begin(), mAccordionTabs.end(), LLComparatorAdaptor(*mTabComparator)); + arrange(); +} + +void LLAccordionCtrl::setFilterSubString(const std::string& filter_string) +{ + LLStringUtil::format_map_t args; + args["[SEARCH_TERM]"] = LLURI::escape(filter_string); + std::string text = filter_string.empty() ? mNoVisibleTabsOrigString : mNoMatchedTabsOrigString; + LLStringUtil::format(text, args); + + mNoVisibleTabsHelpText->setValue(text); +} + +const LLAccordionCtrlTab* LLAccordionCtrl::getExpandedTab() const +{ + typedef std::vector::const_iterator tabs_const_iterator; + + const LLAccordionCtrlTab* result = 0; + + for (tabs_const_iterator i = mAccordionTabs.begin(); i != mAccordionTabs.end(); ++i) + { + if ((*i)->isExpanded()) + { + result = *i; + break; + } + } + + return result; +} + +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; +} + +void LLAccordionCtrl::collapseAllTabs() +{ + if (mAccordionTabs.size() > 0) + { + for (size_t i = 0; i < mAccordionTabs.size(); ++i) + { + LLAccordionCtrlTab *tab = mAccordionTabs[i]; + + if (tab->getDisplayChildren()) + { + tab->setDisplayChildren(false); + } + } + arrange(); + } +} diff --git a/indra/llui/llaccordionctrl.h b/indra/llui/llaccordionctrl.h index baeed0a3a0..1dfa9100f6 100644 --- a/indra/llui/llaccordionctrl.h +++ b/indra/llui/llaccordionctrl.h @@ -1,200 +1,200 @@ -/** - * @file LLAccordionCtrl.h - * @brief Accordion Panel implementation - * - * $LicenseInfo:firstyear=2004&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$ - */ - -#ifndef LL_ACCORDIONCTRL_H -#define LL_ACCORDIONCTRL_H - -#include "llpanel.h" -#include "lltextbox.h" -#include "llscrollbar.h" - -#include -#include -#include - -class LLAccordionCtrlTab; - -class LLAccordionCtrl: public LLPanel -{ -private: - - std::vector mAccordionTabs; - - void ctrlSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S32 width, S32 height); - void ctrlShiftVertical(LLView* panel,S32 delta); - - void onCollapseCtrlCloseOpen(S16 panel_num); - void shiftAccordionTabs(S16 panel_num, S32 delta); - - -public: - /** - * Abstract comparator for accordion tabs. - */ - class LLTabComparator - { - public: - LLTabComparator() {}; - virtual ~LLTabComparator() {}; - - /** Returns true if tab1 < tab2, false otherwise */ - virtual bool compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const = 0; - }; - - struct Params - : public LLInitParam::Block - { - Optional single_expansion, - fit_parent; /* Accordion will fit its parent size, controls that are placed into - accordion tabs are responsible for scrolling their content. - *NOTE fit_parent works best when combined with single_expansion. - Accordion view should implement getRequiredRect() and provide valid height*/ - Optional no_matched_tabs_text; - Optional no_visible_tabs_text; - - Params() - : single_expansion("single_expansion",false) - , fit_parent("fit_parent", false) - , no_matched_tabs_text("no_matched_tabs_text") - , no_visible_tabs_text("no_visible_tabs_text") - {}; - }; - - LLAccordionCtrl(const Params& params); - - LLAccordionCtrl(); - virtual ~LLAccordionCtrl(); - - virtual bool postBuild(); - - virtual bool handleRightMouseDown ( S32 x, S32 y, MASK mask); - virtual bool handleScrollWheel ( S32 x, S32 y, S32 clicks ); - virtual bool handleKeyHere (KEY key, MASK mask); - virtual bool handleDragAndDrop (S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - // - - // Call reshape after changing splitter's size - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - - void addCollapsibleCtrl(LLView* view); - void removeCollapsibleCtrl(LLView* view); - void arrange(); - - - void draw(); - - void onScrollPosChangeCallback(S32, LLScrollbar*); - virtual void onUpdateScrollToChild(const LLUICtrl * cntrl); - - void onOpen (const LLSD& key); - S32 notifyParent(const LLSD& info); - - void reset (); - void expandDefaultTab(); - - void setComparator(const LLTabComparator* comp) { mTabComparator = comp; } - void sort(); - - void collapseAllTabs(); - - /** - * Sets filter substring as a search_term for help text when there are no any visible tabs. - */ - void setFilterSubString(const std::string& filter_string); - - /** - * This method returns the first expanded accordion tab. - * It is expected to be called for accordion which doesn't allow multiple - * tabs to be expanded. Use with care. - */ - const LLAccordionCtrlTab* getExpandedTab() const; - - LLAccordionCtrlTab* getSelectedTab() const { return mSelectedTab; } - - bool getFitParent() const {return mFitParent;} - - void setSkipScrollToChild(bool skip) { mSkipScrollToChild = skip; } - -private: - void initNoTabsWidget(const LLTextBox::Params& tb_params); - void updateNoTabsHelpTextVisibility(); - - void arrangeSingle(); - void arrangeMultiple(); - - // Calc Splitter's height that is necessary to display all child content - S32 calcRecuiredHeight(); - S32 getRecuiredHeight() const { return mInnerRect.getHeight(); } - S32 calcExpandedTabHeight(S32 tab_index = 0, S32 available_height = 0); - - void updateLayout (S32 width, S32 height); - - void show_hide_scrollbar (S32 width, S32 height); - - void showScrollbar (S32 width, S32 height); - void hideScrollbar (S32 width, S32 height); - - bool autoScroll (S32 x, S32 y); - - /** - * An adaptor for LLTabComparator - */ - struct LLComparatorAdaptor - { - LLComparatorAdaptor(const LLTabComparator& comparator) : mComparator(comparator) {}; - - bool operator()(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) - { - return mComparator.compare(tab1, tab2); - } - - const LLTabComparator& mComparator; - }; - -private: - LLRect mInnerRect; - LLScrollbar* mScrollbar; - bool mSingleExpansion; - bool mFitParent; - bool mAutoScrolling; - F32 mAutoScrollRate; - LLTextBox* mNoVisibleTabsHelpText; - - bool mSkipScrollToChild; - - std::string mNoMatchedTabsOrigString; - std::string mNoVisibleTabsOrigString; - - LLAccordionCtrlTab* mSelectedTab; - const LLTabComparator* mTabComparator; -}; - - -#endif // LL_LLSPLITTER_H +/** + * @file LLAccordionCtrl.h + * @brief Accordion Panel implementation + * + * $LicenseInfo:firstyear=2004&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$ + */ + +#ifndef LL_ACCORDIONCTRL_H +#define LL_ACCORDIONCTRL_H + +#include "llpanel.h" +#include "lltextbox.h" +#include "llscrollbar.h" + +#include +#include +#include + +class LLAccordionCtrlTab; + +class LLAccordionCtrl: public LLPanel +{ +private: + + std::vector mAccordionTabs; + + void ctrlSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S32 width, S32 height); + void ctrlShiftVertical(LLView* panel,S32 delta); + + void onCollapseCtrlCloseOpen(S16 panel_num); + void shiftAccordionTabs(S16 panel_num, S32 delta); + + +public: + /** + * Abstract comparator for accordion tabs. + */ + class LLTabComparator + { + public: + LLTabComparator() {}; + virtual ~LLTabComparator() {}; + + /** Returns true if tab1 < tab2, false otherwise */ + virtual bool compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const = 0; + }; + + struct Params + : public LLInitParam::Block + { + Optional single_expansion, + fit_parent; /* Accordion will fit its parent size, controls that are placed into + accordion tabs are responsible for scrolling their content. + *NOTE fit_parent works best when combined with single_expansion. + Accordion view should implement getRequiredRect() and provide valid height*/ + Optional no_matched_tabs_text; + Optional no_visible_tabs_text; + + Params() + : single_expansion("single_expansion",false) + , fit_parent("fit_parent", false) + , no_matched_tabs_text("no_matched_tabs_text") + , no_visible_tabs_text("no_visible_tabs_text") + {}; + }; + + LLAccordionCtrl(const Params& params); + + LLAccordionCtrl(); + virtual ~LLAccordionCtrl(); + + virtual bool postBuild(); + + virtual bool handleRightMouseDown ( S32 x, S32 y, MASK mask); + virtual bool handleScrollWheel ( S32 x, S32 y, S32 clicks ); + virtual bool handleKeyHere (KEY key, MASK mask); + virtual bool handleDragAndDrop (S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + // + + // Call reshape after changing splitter's size + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + + void addCollapsibleCtrl(LLView* view); + void removeCollapsibleCtrl(LLView* view); + void arrange(); + + + void draw(); + + void onScrollPosChangeCallback(S32, LLScrollbar*); + virtual void onUpdateScrollToChild(const LLUICtrl * cntrl); + + void onOpen (const LLSD& key); + S32 notifyParent(const LLSD& info); + + void reset (); + void expandDefaultTab(); + + void setComparator(const LLTabComparator* comp) { mTabComparator = comp; } + void sort(); + + void collapseAllTabs(); + + /** + * Sets filter substring as a search_term for help text when there are no any visible tabs. + */ + void setFilterSubString(const std::string& filter_string); + + /** + * This method returns the first expanded accordion tab. + * It is expected to be called for accordion which doesn't allow multiple + * tabs to be expanded. Use with care. + */ + const LLAccordionCtrlTab* getExpandedTab() const; + + LLAccordionCtrlTab* getSelectedTab() const { return mSelectedTab; } + + bool getFitParent() const {return mFitParent;} + + void setSkipScrollToChild(bool skip) { mSkipScrollToChild = skip; } + +private: + void initNoTabsWidget(const LLTextBox::Params& tb_params); + void updateNoTabsHelpTextVisibility(); + + void arrangeSingle(); + void arrangeMultiple(); + + // Calc Splitter's height that is necessary to display all child content + S32 calcRecuiredHeight(); + S32 getRecuiredHeight() const { return mInnerRect.getHeight(); } + S32 calcExpandedTabHeight(S32 tab_index = 0, S32 available_height = 0); + + void updateLayout (S32 width, S32 height); + + void show_hide_scrollbar (S32 width, S32 height); + + void showScrollbar (S32 width, S32 height); + void hideScrollbar (S32 width, S32 height); + + bool autoScroll (S32 x, S32 y); + + /** + * An adaptor for LLTabComparator + */ + struct LLComparatorAdaptor + { + LLComparatorAdaptor(const LLTabComparator& comparator) : mComparator(comparator) {}; + + bool operator()(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) + { + return mComparator.compare(tab1, tab2); + } + + const LLTabComparator& mComparator; + }; + +private: + LLRect mInnerRect; + LLScrollbar* mScrollbar; + bool mSingleExpansion; + bool mFitParent; + bool mAutoScrolling; + F32 mAutoScrollRate; + LLTextBox* mNoVisibleTabsHelpText; + + bool mSkipScrollToChild; + + std::string mNoMatchedTabsOrigString; + std::string mNoVisibleTabsOrigString; + + LLAccordionCtrlTab* mSelectedTab; + const LLTabComparator* mTabComparator; +}; + + +#endif // LL_LLSPLITTER_H diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp index 17f9da8707..6d58a2545c 100644 --- a/indra/llui/llaccordionctrltab.cpp +++ b/indra/llui/llaccordionctrltab.cpp @@ -1,1126 +1,1126 @@ -/** - * @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() -{ - child_list_const_iter_t it = getChildList()->begin(), it_end = getChildList()->end(); - while (it != it_end) - { - LLView* child = *(it++); - if (DD_HEADER_NAME != child->getName() && child->getVisible()) - 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 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; -} +/** + * @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() +{ + child_list_const_iter_t it = getChildList()->begin(), it_end = getChildList()->end(); + while (it != it_end) + { + LLView* child = *(it++); + if (DD_HEADER_NAME != child->getName() && child->getVisible()) + 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 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; +} diff --git a/indra/llui/llaccordionctrltab.h b/indra/llui/llaccordionctrltab.h index b03ea53609..cf3569683e 100644 --- a/indra/llui/llaccordionctrltab.h +++ b/indra/llui/llaccordionctrltab.h @@ -1,251 +1,251 @@ -/** - * @file LLAccordionCtrlTab.h - * @brief Collapsible box control implementation - * - * $LicenseInfo:firstyear=2004&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$ - */ - -#ifndef LL_ACCORDIONCTRLTAB_H_ -#define LL_ACCORDIONCTRLTAB_H_ - -#include -#include "llrect.h" -#include "lluictrl.h" -#include "lluicolor.h" -#include "llstyle.h" - -class LLUICtrlFactory; -class LLUIImage; -class LLButton; -class LLTextBox; -class LLScrollbar; - - - -// LLAccordionCtrlTab is a container for other controls. -// It has a Header, by clicking on which hosted controls are shown or hidden. -// When hosted controls are show - LLAccordionCtrlTab is expanded. -// When hosted controls are hidden - LLAccordionCtrlTab is collapsed. - -class LLAccordionCtrlTab : public LLUICtrl -{ -// Interface -public: - - struct Params - : public LLInitParam::Block - { - Optional display_children, //expanded or collapsed after initialization - collapsible; - - Optional title; - - Optional header_height, - min_width, - min_height; - - // Overlay images (arrows on the left) - Mandatory header_expand_img, - header_expand_img_pressed, - header_collapse_img, - header_collapse_img_pressed; - - // Background images for the accordion tabs - Mandatory header_image, - header_image_over, - header_image_pressed, - header_image_focused; - - Optional header_bg_color, - header_text_color, - dropdown_bg_color; - - Optional header_visible; - - Optional fit_panel; - - Optional selection_enabled; - - Optional padding_left, - padding_right, - padding_top, - padding_bottom; - - Params(); - }; - - typedef LLDefaultChildRegistry child_registry_t; - - virtual ~LLAccordionCtrlTab(); - - // Registers callback for expand/collapse events. - boost::signals2::connection setDropDownStateChangedCallback(commit_callback_t cb); - - // Changes expand/collapse state - virtual void setDisplayChildren(bool display); - - // Returns expand/collapse state - virtual bool getDisplayChildren() const { return mDisplayChildren; }; - - //set LLAccordionCtrlTab panel - void setAccordionView(LLView* panel); - LLView* getAccordionView() { return mContainerPanel; }; - - std::string getTitle() const; - - // Set text and highlight substring in LLAccordionCtrlTabHeader - void setTitle(const std::string& title, const std::string& hl = LLStringUtil::null); - - // Set text font style in LLAccordionCtrlTabHeader - void setTitleFontStyle(std::string style); - - // Set text color in LLAccordionCtrlTabHeader - void setTitleColor(LLUIColor color); - - boost::signals2::connection setFocusReceivedCallback(const focus_signal_t::slot_type& cb); - boost::signals2::connection setFocusLostCallback(const focus_signal_t::slot_type& cb); - - void setSelected(bool is_selected); - - bool getCollapsible() { return mCollapsible; }; - - void setCollapsible(bool collapsible) { mCollapsible = collapsible; }; - void changeOpenClose(bool is_open); - - void canOpenClose(bool can_open_close) { mCanOpenClose = can_open_close; }; - bool canOpenClose() const { return mCanOpenClose; }; - - virtual bool postBuild(); - - S32 notifyParent(const LLSD& info); - S32 notify(const LLSD& info); - bool notifyChildren(const LLSD& info); - - void draw(); - - void storeOpenCloseState(); - void restoreOpenCloseState(); - -protected: - LLAccordionCtrlTab(const LLAccordionCtrlTab::Params&); - friend class LLUICtrlFactory; - -// Overrides -public: - - // Call reshape after changing size - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - - /** - * Raises notifyParent event with "child_visibility_change" = new_visibility - */ - void onVisibilityChange(bool new_visibility); - virtual void onUpdateScrollToChild(const LLUICtrl * cntrl); - - // Changes expand/collapse state and triggers expand/collapse callbacks - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleKey(KEY key, MASK mask, bool called_from_parent); - - virtual bool handleToolTip(S32 x, S32 y, MASK mask); - virtual bool handleScrollWheel( S32 x, S32 y, S32 clicks ); - - - virtual bool addChild(LLView* child, S32 tab_group = 0 ); - - bool isExpanded() const { return mDisplayChildren; } - - S32 getHeaderHeight(); - - // Min size functions - - void setHeaderVisible(bool value); - - bool getHeaderVisible() { return mHeaderVisible;} - - S32 mExpandedHeight; // Height of expanded ctrl. - // Used to restore height after expand. - - S32 getPaddingLeft() const { return mPaddingLeft;} - S32 getPaddingRight() const { return mPaddingRight;} - S32 getPaddingTop() const { return mPaddingTop;} - S32 getPaddingBottom() const { return mPaddingBottom;} - - void showAndFocusHeader(); - - void setFitPanel( bool fit ) { mFitPanel = true; } - bool getFitParent() const { return mFitPanel; } - - void setIgnoreResizeNotification(bool ignore) { mSkipChangesOnNotifyParent = ignore;} - -protected: - void adjustContainerPanel (const LLRect& child_rect); - void adjustContainerPanel (); - S32 getChildViewHeight (); - - void onScrollPosChangeCallback(S32, LLScrollbar*); - - void show_hide_scrollbar (const LLRect& child_rect); - void showScrollbar (const LLRect& child_rect); - void hideScrollbar (const LLRect& child_rect); - - void updateLayout ( const LLRect& child_rect ); - void ctrlSetLeftTopAndSize (LLView* panel, S32 left, S32 top, S32 width, S32 height); - - void drawChild(const LLRect& root_rect,LLView* child); - - LLView* findContainerView (); - - void selectOnFocusReceived(); - void deselectOnFocusLost(); - -private: - - class LLAccordionCtrlTabHeader; - LLAccordionCtrlTabHeader* mHeader; //Header - - bool mDisplayChildren; //Expanded/collapsed - bool mCollapsible; - bool mHeaderVisible; - - bool mCanOpenClose; - bool mFitPanel; - - S32 mPaddingLeft; - S32 mPaddingRight; - S32 mPaddingTop; - S32 mPaddingBottom; - - bool mStoredOpenCloseState; - bool mWasStateStored; - bool mSkipChangesOnNotifyParent; - - bool mSelectionEnabled; - - LLScrollbar* mScrollbar; - LLView* mContainerPanel; - - LLUIColor mDropdownBGColor; -}; - -#endif +/** + * @file LLAccordionCtrlTab.h + * @brief Collapsible box control implementation + * + * $LicenseInfo:firstyear=2004&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$ + */ + +#ifndef LL_ACCORDIONCTRLTAB_H_ +#define LL_ACCORDIONCTRLTAB_H_ + +#include +#include "llrect.h" +#include "lluictrl.h" +#include "lluicolor.h" +#include "llstyle.h" + +class LLUICtrlFactory; +class LLUIImage; +class LLButton; +class LLTextBox; +class LLScrollbar; + + + +// LLAccordionCtrlTab is a container for other controls. +// It has a Header, by clicking on which hosted controls are shown or hidden. +// When hosted controls are show - LLAccordionCtrlTab is expanded. +// When hosted controls are hidden - LLAccordionCtrlTab is collapsed. + +class LLAccordionCtrlTab : public LLUICtrl +{ +// Interface +public: + + struct Params + : public LLInitParam::Block + { + Optional display_children, //expanded or collapsed after initialization + collapsible; + + Optional title; + + Optional header_height, + min_width, + min_height; + + // Overlay images (arrows on the left) + Mandatory header_expand_img, + header_expand_img_pressed, + header_collapse_img, + header_collapse_img_pressed; + + // Background images for the accordion tabs + Mandatory header_image, + header_image_over, + header_image_pressed, + header_image_focused; + + Optional header_bg_color, + header_text_color, + dropdown_bg_color; + + Optional header_visible; + + Optional fit_panel; + + Optional selection_enabled; + + Optional padding_left, + padding_right, + padding_top, + padding_bottom; + + Params(); + }; + + typedef LLDefaultChildRegistry child_registry_t; + + virtual ~LLAccordionCtrlTab(); + + // Registers callback for expand/collapse events. + boost::signals2::connection setDropDownStateChangedCallback(commit_callback_t cb); + + // Changes expand/collapse state + virtual void setDisplayChildren(bool display); + + // Returns expand/collapse state + virtual bool getDisplayChildren() const { return mDisplayChildren; }; + + //set LLAccordionCtrlTab panel + void setAccordionView(LLView* panel); + LLView* getAccordionView() { return mContainerPanel; }; + + std::string getTitle() const; + + // Set text and highlight substring in LLAccordionCtrlTabHeader + void setTitle(const std::string& title, const std::string& hl = LLStringUtil::null); + + // Set text font style in LLAccordionCtrlTabHeader + void setTitleFontStyle(std::string style); + + // Set text color in LLAccordionCtrlTabHeader + void setTitleColor(LLUIColor color); + + boost::signals2::connection setFocusReceivedCallback(const focus_signal_t::slot_type& cb); + boost::signals2::connection setFocusLostCallback(const focus_signal_t::slot_type& cb); + + void setSelected(bool is_selected); + + bool getCollapsible() { return mCollapsible; }; + + void setCollapsible(bool collapsible) { mCollapsible = collapsible; }; + void changeOpenClose(bool is_open); + + void canOpenClose(bool can_open_close) { mCanOpenClose = can_open_close; }; + bool canOpenClose() const { return mCanOpenClose; }; + + virtual bool postBuild(); + + S32 notifyParent(const LLSD& info); + S32 notify(const LLSD& info); + bool notifyChildren(const LLSD& info); + + void draw(); + + void storeOpenCloseState(); + void restoreOpenCloseState(); + +protected: + LLAccordionCtrlTab(const LLAccordionCtrlTab::Params&); + friend class LLUICtrlFactory; + +// Overrides +public: + + // Call reshape after changing size + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + + /** + * Raises notifyParent event with "child_visibility_change" = new_visibility + */ + void onVisibilityChange(bool new_visibility); + virtual void onUpdateScrollToChild(const LLUICtrl * cntrl); + + // Changes expand/collapse state and triggers expand/collapse callbacks + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleKey(KEY key, MASK mask, bool called_from_parent); + + virtual bool handleToolTip(S32 x, S32 y, MASK mask); + virtual bool handleScrollWheel( S32 x, S32 y, S32 clicks ); + + + virtual bool addChild(LLView* child, S32 tab_group = 0 ); + + bool isExpanded() const { return mDisplayChildren; } + + S32 getHeaderHeight(); + + // Min size functions + + void setHeaderVisible(bool value); + + bool getHeaderVisible() { return mHeaderVisible;} + + S32 mExpandedHeight; // Height of expanded ctrl. + // Used to restore height after expand. + + S32 getPaddingLeft() const { return mPaddingLeft;} + S32 getPaddingRight() const { return mPaddingRight;} + S32 getPaddingTop() const { return mPaddingTop;} + S32 getPaddingBottom() const { return mPaddingBottom;} + + void showAndFocusHeader(); + + void setFitPanel( bool fit ) { mFitPanel = true; } + bool getFitParent() const { return mFitPanel; } + + void setIgnoreResizeNotification(bool ignore) { mSkipChangesOnNotifyParent = ignore;} + +protected: + void adjustContainerPanel (const LLRect& child_rect); + void adjustContainerPanel (); + S32 getChildViewHeight (); + + void onScrollPosChangeCallback(S32, LLScrollbar*); + + void show_hide_scrollbar (const LLRect& child_rect); + void showScrollbar (const LLRect& child_rect); + void hideScrollbar (const LLRect& child_rect); + + void updateLayout ( const LLRect& child_rect ); + void ctrlSetLeftTopAndSize (LLView* panel, S32 left, S32 top, S32 width, S32 height); + + void drawChild(const LLRect& root_rect,LLView* child); + + LLView* findContainerView (); + + void selectOnFocusReceived(); + void deselectOnFocusLost(); + +private: + + class LLAccordionCtrlTabHeader; + LLAccordionCtrlTabHeader* mHeader; //Header + + bool mDisplayChildren; //Expanded/collapsed + bool mCollapsible; + bool mHeaderVisible; + + bool mCanOpenClose; + bool mFitPanel; + + S32 mPaddingLeft; + S32 mPaddingRight; + S32 mPaddingTop; + S32 mPaddingBottom; + + bool mStoredOpenCloseState; + bool mWasStateStored; + bool mSkipChangesOnNotifyParent; + + bool mSelectionEnabled; + + LLScrollbar* mScrollbar; + LLView* mContainerPanel; + + LLUIColor mDropdownBGColor; +}; + +#endif diff --git a/indra/llui/llbadge.cpp b/indra/llui/llbadge.cpp index 1fa24afe8c..3397c97ee1 100644 --- a/indra/llui/llbadge.cpp +++ b/indra/llui/llbadge.cpp @@ -1,389 +1,389 @@ -/** - * @file llbadge.cpp - * @brief Implementation for badges - * - * $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$ - */ - -#define LLBADGE_CPP -#include "llbadge.h" - -#include "llscrollcontainer.h" -#include "lluictrlfactory.h" - - -static LLDefaultChildRegistry::Register r("badge"); - -static const S32 BADGE_OFFSET_NOT_SPECIFIED = 0x7FFFFFFF; - -// Compiler optimization, generate extern template -template class LLBadge* LLView::getChild(const std::string& name, bool recurse) const; - - -LLBadge::Params::Params() - : image("image") - , border_image("border_image") - , border_color("border_color") - , image_color("image_color") - , label("label") - , label_color("label_color") - , label_offset_horiz("label_offset_horiz") - , label_offset_vert("label_offset_vert") - , location("location", LLRelPos::TOP_LEFT) - , location_offset_hcenter("location_offset_hcenter") - , location_offset_vcenter("location_offset_vcenter") - , location_percent_hcenter("location_percent_hcenter") - , location_percent_vcenter("location_percent_vcenter") - , padding_horiz("padding_horiz") - , padding_vert("padding_vert") -{} - -bool LLBadge::Params::equals(const Params& a) const -{ - bool comp = true; - - // skip owner in comparison on purpose - - comp &= (border_image() == a.border_image()); - comp &= (border_color() == a.border_color()); - comp &= (image() == a.image()); - comp &= (image_color() == a.image_color()); - comp &= (label() == a.label()); - comp &= (label_color() == a.label_color()); - comp &= (label_offset_horiz() == a.label_offset_horiz()); - comp &= (label_offset_vert() == a.label_offset_vert()); - comp &= (location() == a.location()); - comp &= (location_offset_hcenter() == a.location_offset_hcenter()); - comp &= (location_offset_vcenter() == a.location_offset_vcenter()); - comp &= (location_percent_hcenter() == a.location_percent_hcenter()); - comp &= (location_percent_vcenter() == a.location_percent_vcenter()); - comp &= (padding_horiz() == a.padding_horiz()); - comp &= (padding_vert() == a.padding_vert()); - - return comp; -} - -LLBadge::LLBadge(const LLBadge::Params& p) - : LLUICtrl(p) - , mOwner(p.owner) - , mBorderImage(p.border_image) - , mBorderColor(p.border_color) - , mGLFont(p.font) - , mImage(p.image) - , mImageColor(p.image_color) - , mLabel(p.label) - , mLabelColor(p.label_color) - , mLabelOffsetHoriz(p.label_offset_horiz) - , mLabelOffsetVert(p.label_offset_vert) - , mLocation(p.location) - , mLocationOffsetHCenter(BADGE_OFFSET_NOT_SPECIFIED) - , mLocationOffsetVCenter(BADGE_OFFSET_NOT_SPECIFIED) - , mLocationPercentHCenter(0.5f) - , mLocationPercentVCenter(0.5f) - , mPaddingHoriz(p.padding_horiz) - , mPaddingVert(p.padding_vert) - , mParentScroller(NULL) - , mDrawAtParentTop(false) -{ - if (mImage.isNull()) - { - LL_WARNS() << "Badge: " << getName() << " with no image!" << LL_ENDL; - } - - if (p.location_offset_hcenter.isProvided()) - { - mLocationOffsetHCenter = p.location_offset_hcenter(); - } - - if (p.location_offset_vcenter.isProvided()) - { - mLocationOffsetVCenter = p.location_offset_vcenter(); - } - - // - // The following logic is to set the mLocationPercentHCenter and mLocationPercentVCenter - // based on the Location enum and our horizontal and vertical location percentages. The - // draw code then uses this on the owner rectangle to compute the screen location for - // the badge. - // - - if (!LLRelPos::IsCenter(mLocation)) - { - F32 h_center = p.location_percent_hcenter * 0.01f; - F32 v_center = p.location_percent_vcenter * 0.01f; - - if (LLRelPos::IsRight(mLocation)) - { - mLocationPercentHCenter = 0.5f * (1.0f + h_center); - } - else if (LLRelPos::IsLeft(mLocation)) - { - mLocationPercentHCenter = 0.5f * (1.0f - h_center); - } - - if (LLRelPos::IsTop(mLocation)) - { - mLocationPercentVCenter = 0.5f * (1.0f + v_center); - } - else if (LLRelPos::IsBottom(mLocation)) - { - mLocationPercentVCenter = 0.5f * (1.0f - v_center); - } - } -} - -LLBadge::~LLBadge() -{ -} - -bool LLBadge::addToView(LLView * view) -{ - bool child_added = view->addChild(this); - - if (child_added) - { - setShape(view->getLocalRect()); - - // Find a parent scroll container, if there is one in case we need it for positioning - - LLView * parent = mOwner.get(); - - while ((parent != NULL) && ((mParentScroller = dynamic_cast(parent)) == NULL)) - { - parent = parent->getParent(); - } - } - - return child_added; -} - -void LLBadge::setLabel(const LLStringExplicit& label) -{ - mLabel = label; -} - -// -// This is a fallback function to render a rectangle for badges without a valid image -// -void renderBadgeBackground(F32 centerX, F32 centerY, F32 width, F32 height, const LLColor4U &color) -{ - gGL.pushUIMatrix(); - gGL.loadUIIdentity(); - gGL.setSceneBlendType(LLRender::BT_REPLACE); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - gGL.color4ubv(color.mV); - gGL.texCoord2i(0, 0); - - F32 x = LLFontGL::sCurOrigin.mX + centerX - width * 0.5f; - F32 y = LLFontGL::sCurOrigin.mY + centerY - height * 0.5f; - - LLRectf screen_rect(ll_round(x), - ll_round(y), - ll_round(x) + width, - ll_round(y) + height); - - LLVector3 vertices[4]; - vertices[0] = LLVector3(screen_rect.mRight, screen_rect.mTop, 1.0f); - vertices[1] = LLVector3(screen_rect.mLeft, screen_rect.mTop, 1.0f); - vertices[2] = LLVector3(screen_rect.mLeft, screen_rect.mBottom, 1.0f); - vertices[3] = LLVector3(screen_rect.mRight, screen_rect.mBottom, 1.0f); - - gGL.begin(LLRender::QUADS); - { - gGL.vertexBatchPreTransformed(vertices, 4); - } - gGL.end(); - - gGL.popUIMatrix(); -} - - -// virtual -void LLBadge::draw() -{ - if (!mLabel.empty()) - { - LLView* owner_view = mOwner.get(); - - if (owner_view && owner_view->isInVisibleChain()) - { - // - // Calculate badge size based on label text - // - - LLWString badge_label_wstring = mLabel; - - S32 badge_label_begin_offset = 0; - S32 badge_char_length = S32_MAX; - S32 badge_pixel_length = S32_MAX; - F32 *right_position_out = NULL; - bool do_not_use_ellipses = false; - - F32 badge_width = (2.0f * mPaddingHoriz) + - mGLFont->getWidthF32(badge_label_wstring.c_str(), badge_label_begin_offset, badge_char_length); - - F32 badge_height = (2.0f * mPaddingVert) + mGLFont->getLineHeight(); - - // - // Calculate badge position based on owner - // - - LLRect owner_rect; - owner_view->localRectToOtherView(owner_view->getLocalRect(), & owner_rect, this); - - S32 location_offset_horiz = mLocationOffsetHCenter; - S32 location_offset_vert = mLocationOffsetVCenter; - - // If we're in a scroll container, do some math to keep us in the same place on screen if applicable - if (mParentScroller != NULL) - { - LLRect visibleRect = mParentScroller->getVisibleContentRect(); - - if (mLocationOffsetHCenter != BADGE_OFFSET_NOT_SPECIFIED) - { - if (LLRelPos::IsRight(mLocation)) - { - location_offset_horiz += visibleRect.mRight; - } - else if (LLRelPos::IsLeft(mLocation)) - { - location_offset_horiz += visibleRect.mLeft; - } - else // center - { - location_offset_horiz += (visibleRect.mLeft + visibleRect.mRight) / 2; - } - } - - if (mLocationOffsetVCenter != BADGE_OFFSET_NOT_SPECIFIED) - { - if (LLRelPos::IsTop(mLocation)) - { - location_offset_vert += visibleRect.mTop; - } - else if (LLRelPos::IsBottom(mLocation)) - { - location_offset_vert += visibleRect.mBottom; - } - else // center - { - location_offset_vert += (visibleRect.mBottom + visibleRect.mTop) / 2; - } - } - } - - F32 badge_center_x; - F32 badge_center_y; - - // Compute x position - if (mLocationOffsetHCenter == BADGE_OFFSET_NOT_SPECIFIED) - { - badge_center_x = owner_rect.mLeft + owner_rect.getWidth() * mLocationPercentHCenter; - } - else - { - badge_center_x = location_offset_horiz; - } - - // Compute y position - if (mLocationOffsetVCenter == BADGE_OFFSET_NOT_SPECIFIED) - { - if(mDrawAtParentTop) - { - badge_center_y = owner_rect.mTop - badge_height * 0.5f - 1; - } - else - { - badge_center_y = owner_rect.mBottom + owner_rect.getHeight() * mLocationPercentVCenter; - } - } - else - { - badge_center_y = location_offset_vert; - } - - // - // Draw button image, if available. - // Otherwise draw basic rectangular button. - // - - F32 alpha = getDrawContext().mAlpha; - - if (!mImage.isNull()) - { - F32 badge_x = badge_center_x - badge_width * 0.5f; - F32 badge_y = badge_center_y - badge_height * 0.5f; - - mImage->drawSolid((S32) badge_x, (S32) badge_y, (S32) badge_width, (S32) badge_height, mImageColor % alpha); - - if (!mBorderImage.isNull()) - { - mBorderImage->drawSolid((S32) badge_x, (S32) badge_y, (S32) badge_width, (S32) badge_height, mBorderColor % alpha); - } - } - else - { - LL_DEBUGS() << "No image for badge " << getName() << " on owner " << owner_view->getName() << LL_ENDL; - - renderBadgeBackground(badge_center_x, badge_center_y, - badge_width, badge_height, - mImageColor % alpha); - } - - // - // Draw the label - // - - mGLFont->render(badge_label_wstring, - badge_label_begin_offset, - badge_center_x + mLabelOffsetHoriz, - badge_center_y + mLabelOffsetVert, - mLabelColor % alpha, - LLFontGL::HCENTER, LLFontGL::VCENTER, // centered around the position - LLFontGL::NORMAL, // normal text (not bold, italics, etc.) - LLFontGL::DROP_SHADOW_SOFT, - badge_char_length, badge_pixel_length, - right_position_out, do_not_use_ellipses); - } - } -} - - -namespace LLInitParam -{ - void TypeValues::declareValues() - { - declare("bottom", LLRelPos::BOTTOM); - declare("bottom_left", LLRelPos::BOTTOM_LEFT); - declare("bottom_right", LLRelPos::BOTTOM_RIGHT); - declare("center", LLRelPos::CENTER); - declare("left", LLRelPos::LEFT); - declare("right", LLRelPos::RIGHT); - declare("top", LLRelPos::TOP); - declare("top_left", LLRelPos::TOP_LEFT); - declare("top_right", LLRelPos::TOP_RIGHT); - } -} - - -// eof +/** + * @file llbadge.cpp + * @brief Implementation for badges + * + * $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$ + */ + +#define LLBADGE_CPP +#include "llbadge.h" + +#include "llscrollcontainer.h" +#include "lluictrlfactory.h" + + +static LLDefaultChildRegistry::Register r("badge"); + +static const S32 BADGE_OFFSET_NOT_SPECIFIED = 0x7FFFFFFF; + +// Compiler optimization, generate extern template +template class LLBadge* LLView::getChild(const std::string& name, bool recurse) const; + + +LLBadge::Params::Params() + : image("image") + , border_image("border_image") + , border_color("border_color") + , image_color("image_color") + , label("label") + , label_color("label_color") + , label_offset_horiz("label_offset_horiz") + , label_offset_vert("label_offset_vert") + , location("location", LLRelPos::TOP_LEFT) + , location_offset_hcenter("location_offset_hcenter") + , location_offset_vcenter("location_offset_vcenter") + , location_percent_hcenter("location_percent_hcenter") + , location_percent_vcenter("location_percent_vcenter") + , padding_horiz("padding_horiz") + , padding_vert("padding_vert") +{} + +bool LLBadge::Params::equals(const Params& a) const +{ + bool comp = true; + + // skip owner in comparison on purpose + + comp &= (border_image() == a.border_image()); + comp &= (border_color() == a.border_color()); + comp &= (image() == a.image()); + comp &= (image_color() == a.image_color()); + comp &= (label() == a.label()); + comp &= (label_color() == a.label_color()); + comp &= (label_offset_horiz() == a.label_offset_horiz()); + comp &= (label_offset_vert() == a.label_offset_vert()); + comp &= (location() == a.location()); + comp &= (location_offset_hcenter() == a.location_offset_hcenter()); + comp &= (location_offset_vcenter() == a.location_offset_vcenter()); + comp &= (location_percent_hcenter() == a.location_percent_hcenter()); + comp &= (location_percent_vcenter() == a.location_percent_vcenter()); + comp &= (padding_horiz() == a.padding_horiz()); + comp &= (padding_vert() == a.padding_vert()); + + return comp; +} + +LLBadge::LLBadge(const LLBadge::Params& p) + : LLUICtrl(p) + , mOwner(p.owner) + , mBorderImage(p.border_image) + , mBorderColor(p.border_color) + , mGLFont(p.font) + , mImage(p.image) + , mImageColor(p.image_color) + , mLabel(p.label) + , mLabelColor(p.label_color) + , mLabelOffsetHoriz(p.label_offset_horiz) + , mLabelOffsetVert(p.label_offset_vert) + , mLocation(p.location) + , mLocationOffsetHCenter(BADGE_OFFSET_NOT_SPECIFIED) + , mLocationOffsetVCenter(BADGE_OFFSET_NOT_SPECIFIED) + , mLocationPercentHCenter(0.5f) + , mLocationPercentVCenter(0.5f) + , mPaddingHoriz(p.padding_horiz) + , mPaddingVert(p.padding_vert) + , mParentScroller(NULL) + , mDrawAtParentTop(false) +{ + if (mImage.isNull()) + { + LL_WARNS() << "Badge: " << getName() << " with no image!" << LL_ENDL; + } + + if (p.location_offset_hcenter.isProvided()) + { + mLocationOffsetHCenter = p.location_offset_hcenter(); + } + + if (p.location_offset_vcenter.isProvided()) + { + mLocationOffsetVCenter = p.location_offset_vcenter(); + } + + // + // The following logic is to set the mLocationPercentHCenter and mLocationPercentVCenter + // based on the Location enum and our horizontal and vertical location percentages. The + // draw code then uses this on the owner rectangle to compute the screen location for + // the badge. + // + + if (!LLRelPos::IsCenter(mLocation)) + { + F32 h_center = p.location_percent_hcenter * 0.01f; + F32 v_center = p.location_percent_vcenter * 0.01f; + + if (LLRelPos::IsRight(mLocation)) + { + mLocationPercentHCenter = 0.5f * (1.0f + h_center); + } + else if (LLRelPos::IsLeft(mLocation)) + { + mLocationPercentHCenter = 0.5f * (1.0f - h_center); + } + + if (LLRelPos::IsTop(mLocation)) + { + mLocationPercentVCenter = 0.5f * (1.0f + v_center); + } + else if (LLRelPos::IsBottom(mLocation)) + { + mLocationPercentVCenter = 0.5f * (1.0f - v_center); + } + } +} + +LLBadge::~LLBadge() +{ +} + +bool LLBadge::addToView(LLView * view) +{ + bool child_added = view->addChild(this); + + if (child_added) + { + setShape(view->getLocalRect()); + + // Find a parent scroll container, if there is one in case we need it for positioning + + LLView * parent = mOwner.get(); + + while ((parent != NULL) && ((mParentScroller = dynamic_cast(parent)) == NULL)) + { + parent = parent->getParent(); + } + } + + return child_added; +} + +void LLBadge::setLabel(const LLStringExplicit& label) +{ + mLabel = label; +} + +// +// This is a fallback function to render a rectangle for badges without a valid image +// +void renderBadgeBackground(F32 centerX, F32 centerY, F32 width, F32 height, const LLColor4U &color) +{ + gGL.pushUIMatrix(); + gGL.loadUIIdentity(); + gGL.setSceneBlendType(LLRender::BT_REPLACE); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + gGL.color4ubv(color.mV); + gGL.texCoord2i(0, 0); + + F32 x = LLFontGL::sCurOrigin.mX + centerX - width * 0.5f; + F32 y = LLFontGL::sCurOrigin.mY + centerY - height * 0.5f; + + LLRectf screen_rect(ll_round(x), + ll_round(y), + ll_round(x) + width, + ll_round(y) + height); + + LLVector3 vertices[4]; + vertices[0] = LLVector3(screen_rect.mRight, screen_rect.mTop, 1.0f); + vertices[1] = LLVector3(screen_rect.mLeft, screen_rect.mTop, 1.0f); + vertices[2] = LLVector3(screen_rect.mLeft, screen_rect.mBottom, 1.0f); + vertices[3] = LLVector3(screen_rect.mRight, screen_rect.mBottom, 1.0f); + + gGL.begin(LLRender::QUADS); + { + gGL.vertexBatchPreTransformed(vertices, 4); + } + gGL.end(); + + gGL.popUIMatrix(); +} + + +// virtual +void LLBadge::draw() +{ + if (!mLabel.empty()) + { + LLView* owner_view = mOwner.get(); + + if (owner_view && owner_view->isInVisibleChain()) + { + // + // Calculate badge size based on label text + // + + LLWString badge_label_wstring = mLabel; + + S32 badge_label_begin_offset = 0; + S32 badge_char_length = S32_MAX; + S32 badge_pixel_length = S32_MAX; + F32 *right_position_out = NULL; + bool do_not_use_ellipses = false; + + F32 badge_width = (2.0f * mPaddingHoriz) + + mGLFont->getWidthF32(badge_label_wstring.c_str(), badge_label_begin_offset, badge_char_length); + + F32 badge_height = (2.0f * mPaddingVert) + mGLFont->getLineHeight(); + + // + // Calculate badge position based on owner + // + + LLRect owner_rect; + owner_view->localRectToOtherView(owner_view->getLocalRect(), & owner_rect, this); + + S32 location_offset_horiz = mLocationOffsetHCenter; + S32 location_offset_vert = mLocationOffsetVCenter; + + // If we're in a scroll container, do some math to keep us in the same place on screen if applicable + if (mParentScroller != NULL) + { + LLRect visibleRect = mParentScroller->getVisibleContentRect(); + + if (mLocationOffsetHCenter != BADGE_OFFSET_NOT_SPECIFIED) + { + if (LLRelPos::IsRight(mLocation)) + { + location_offset_horiz += visibleRect.mRight; + } + else if (LLRelPos::IsLeft(mLocation)) + { + location_offset_horiz += visibleRect.mLeft; + } + else // center + { + location_offset_horiz += (visibleRect.mLeft + visibleRect.mRight) / 2; + } + } + + if (mLocationOffsetVCenter != BADGE_OFFSET_NOT_SPECIFIED) + { + if (LLRelPos::IsTop(mLocation)) + { + location_offset_vert += visibleRect.mTop; + } + else if (LLRelPos::IsBottom(mLocation)) + { + location_offset_vert += visibleRect.mBottom; + } + else // center + { + location_offset_vert += (visibleRect.mBottom + visibleRect.mTop) / 2; + } + } + } + + F32 badge_center_x; + F32 badge_center_y; + + // Compute x position + if (mLocationOffsetHCenter == BADGE_OFFSET_NOT_SPECIFIED) + { + badge_center_x = owner_rect.mLeft + owner_rect.getWidth() * mLocationPercentHCenter; + } + else + { + badge_center_x = location_offset_horiz; + } + + // Compute y position + if (mLocationOffsetVCenter == BADGE_OFFSET_NOT_SPECIFIED) + { + if(mDrawAtParentTop) + { + badge_center_y = owner_rect.mTop - badge_height * 0.5f - 1; + } + else + { + badge_center_y = owner_rect.mBottom + owner_rect.getHeight() * mLocationPercentVCenter; + } + } + else + { + badge_center_y = location_offset_vert; + } + + // + // Draw button image, if available. + // Otherwise draw basic rectangular button. + // + + F32 alpha = getDrawContext().mAlpha; + + if (!mImage.isNull()) + { + F32 badge_x = badge_center_x - badge_width * 0.5f; + F32 badge_y = badge_center_y - badge_height * 0.5f; + + mImage->drawSolid((S32) badge_x, (S32) badge_y, (S32) badge_width, (S32) badge_height, mImageColor % alpha); + + if (!mBorderImage.isNull()) + { + mBorderImage->drawSolid((S32) badge_x, (S32) badge_y, (S32) badge_width, (S32) badge_height, mBorderColor % alpha); + } + } + else + { + LL_DEBUGS() << "No image for badge " << getName() << " on owner " << owner_view->getName() << LL_ENDL; + + renderBadgeBackground(badge_center_x, badge_center_y, + badge_width, badge_height, + mImageColor % alpha); + } + + // + // Draw the label + // + + mGLFont->render(badge_label_wstring, + badge_label_begin_offset, + badge_center_x + mLabelOffsetHoriz, + badge_center_y + mLabelOffsetVert, + mLabelColor % alpha, + LLFontGL::HCENTER, LLFontGL::VCENTER, // centered around the position + LLFontGL::NORMAL, // normal text (not bold, italics, etc.) + LLFontGL::DROP_SHADOW_SOFT, + badge_char_length, badge_pixel_length, + right_position_out, do_not_use_ellipses); + } + } +} + + +namespace LLInitParam +{ + void TypeValues::declareValues() + { + declare("bottom", LLRelPos::BOTTOM); + declare("bottom_left", LLRelPos::BOTTOM_LEFT); + declare("bottom_right", LLRelPos::BOTTOM_RIGHT); + declare("center", LLRelPos::CENTER); + declare("left", LLRelPos::LEFT); + declare("right", LLRelPos::RIGHT); + declare("top", LLRelPos::TOP); + declare("top_left", LLRelPos::TOP_LEFT); + declare("top_right", LLRelPos::TOP_RIGHT); + } +} + + +// eof diff --git a/indra/llui/llbadge.h b/indra/llui/llbadge.h index d4b146d57b..a6d584c6d7 100644 --- a/indra/llui/llbadge.h +++ b/indra/llui/llbadge.h @@ -1,177 +1,177 @@ -/** - * @file llbadge.h - * @brief Header for badges - * - * $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$ - */ - -#ifndef LL_LLBADGE_H -#define LL_LLBADGE_H - -#include - -#include "lluicolor.h" -#include "lluictrl.h" -#include "llstring.h" -#include "lluiimage.h" -#include "llview.h" - -// -// Declarations -// - -class LLFontGL; -class LLScrollContainer; -class LLUICtrlFactory; - -// -// Relative Position Alignment -// - -namespace LLRelPos -{ - enum Location - { - CENTER = 0, - - LEFT = (1 << 0), - RIGHT = (1 << 1), - - TOP = (1 << 2), - BOTTOM = (1 << 3), - - BOTTOM_LEFT = (BOTTOM | LEFT), - BOTTOM_RIGHT = (BOTTOM | RIGHT), - - TOP_LEFT = (TOP | LEFT), - TOP_RIGHT = (TOP | RIGHT), - }; - - inline bool IsBottom(Location relPos) { return (relPos & BOTTOM) == BOTTOM; } - inline bool IsCenter(Location relPos) { return (relPos == CENTER); } - inline bool IsLeft(Location relPos) { return (relPos & LEFT) == LEFT; } - inline bool IsRight(Location relPos) { return (relPos & RIGHT) == RIGHT; } - inline bool IsTop(Location relPos) { return (relPos & TOP) == TOP; } -} - -// NOTE: This needs to occur before Optional declaration for proper compilation. -namespace LLInitParam -{ - template<> - struct TypeValues : public TypeValuesHelper - { - static void declareValues(); - }; -} - -// -// Classes -// - -class LLBadge -: public LLUICtrl -{ -public: - struct Params - : public LLInitParam::Block - { - Optional< LLHandle > owner; // Mandatory in code but not in xml - - Optional< LLUIImage* > border_image; - Optional< LLUIColor > border_color; - - Optional< LLUIImage* > image; - Optional< LLUIColor > image_color; - - Optional< std::string > label; - Optional< LLUIColor > label_color; - - Optional< S32 > label_offset_horiz; - Optional< S32 > label_offset_vert; - - Optional< LLRelPos::Location > location; - Optional< S32 > location_offset_hcenter; - Optional< S32 > location_offset_vcenter; - Optional< U32 > location_percent_hcenter; - Optional< U32 > location_percent_vcenter; - - Optional< F32 > padding_horiz; - Optional< F32 > padding_vert; - - Params(); - - bool equals(const Params&) const; - }; - -protected: - friend class LLUICtrlFactory; - LLBadge(const Params& p); - -public: - - ~LLBadge(); - - bool addToView(LLView * view); - - virtual void draw(); - - const std::string getLabel() const { return wstring_to_utf8str(mLabel); } - void setLabel( const LLStringExplicit& label); - - void setDrawAtParentTop(bool draw_at_top) { mDrawAtParentTop = draw_at_top;} - -private: - LLPointer< LLUIImage > mBorderImage; - LLUIColor mBorderColor; - - const LLFontGL* mGLFont; - - LLPointer< LLUIImage > mImage; - LLUIColor mImageColor; - - LLUIString mLabel; - LLUIColor mLabelColor; - - S32 mLabelOffsetHoriz; - S32 mLabelOffsetVert; - - LLRelPos::Location mLocation; - S32 mLocationOffsetHCenter; - S32 mLocationOffsetVCenter; - F32 mLocationPercentHCenter; - F32 mLocationPercentVCenter; - - LLHandle< LLView > mOwner; - - F32 mPaddingHoriz; - F32 mPaddingVert; - - LLScrollContainer* mParentScroller; - bool mDrawAtParentTop; -}; - -// Build time optimization, generate once in .cpp file -#ifndef LLBADGE_CPP -extern template class LLBadge* LLView::getChild(const std::string& name, bool recurse) const; -#endif - -#endif // LL_LLBADGE_H +/** + * @file llbadge.h + * @brief Header for badges + * + * $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$ + */ + +#ifndef LL_LLBADGE_H +#define LL_LLBADGE_H + +#include + +#include "lluicolor.h" +#include "lluictrl.h" +#include "llstring.h" +#include "lluiimage.h" +#include "llview.h" + +// +// Declarations +// + +class LLFontGL; +class LLScrollContainer; +class LLUICtrlFactory; + +// +// Relative Position Alignment +// + +namespace LLRelPos +{ + enum Location + { + CENTER = 0, + + LEFT = (1 << 0), + RIGHT = (1 << 1), + + TOP = (1 << 2), + BOTTOM = (1 << 3), + + BOTTOM_LEFT = (BOTTOM | LEFT), + BOTTOM_RIGHT = (BOTTOM | RIGHT), + + TOP_LEFT = (TOP | LEFT), + TOP_RIGHT = (TOP | RIGHT), + }; + + inline bool IsBottom(Location relPos) { return (relPos & BOTTOM) == BOTTOM; } + inline bool IsCenter(Location relPos) { return (relPos == CENTER); } + inline bool IsLeft(Location relPos) { return (relPos & LEFT) == LEFT; } + inline bool IsRight(Location relPos) { return (relPos & RIGHT) == RIGHT; } + inline bool IsTop(Location relPos) { return (relPos & TOP) == TOP; } +} + +// NOTE: This needs to occur before Optional declaration for proper compilation. +namespace LLInitParam +{ + template<> + struct TypeValues : public TypeValuesHelper + { + static void declareValues(); + }; +} + +// +// Classes +// + +class LLBadge +: public LLUICtrl +{ +public: + struct Params + : public LLInitParam::Block + { + Optional< LLHandle > owner; // Mandatory in code but not in xml + + Optional< LLUIImage* > border_image; + Optional< LLUIColor > border_color; + + Optional< LLUIImage* > image; + Optional< LLUIColor > image_color; + + Optional< std::string > label; + Optional< LLUIColor > label_color; + + Optional< S32 > label_offset_horiz; + Optional< S32 > label_offset_vert; + + Optional< LLRelPos::Location > location; + Optional< S32 > location_offset_hcenter; + Optional< S32 > location_offset_vcenter; + Optional< U32 > location_percent_hcenter; + Optional< U32 > location_percent_vcenter; + + Optional< F32 > padding_horiz; + Optional< F32 > padding_vert; + + Params(); + + bool equals(const Params&) const; + }; + +protected: + friend class LLUICtrlFactory; + LLBadge(const Params& p); + +public: + + ~LLBadge(); + + bool addToView(LLView * view); + + virtual void draw(); + + const std::string getLabel() const { return wstring_to_utf8str(mLabel); } + void setLabel( const LLStringExplicit& label); + + void setDrawAtParentTop(bool draw_at_top) { mDrawAtParentTop = draw_at_top;} + +private: + LLPointer< LLUIImage > mBorderImage; + LLUIColor mBorderColor; + + const LLFontGL* mGLFont; + + LLPointer< LLUIImage > mImage; + LLUIColor mImageColor; + + LLUIString mLabel; + LLUIColor mLabelColor; + + S32 mLabelOffsetHoriz; + S32 mLabelOffsetVert; + + LLRelPos::Location mLocation; + S32 mLocationOffsetHCenter; + S32 mLocationOffsetVCenter; + F32 mLocationPercentHCenter; + F32 mLocationPercentVCenter; + + LLHandle< LLView > mOwner; + + F32 mPaddingHoriz; + F32 mPaddingVert; + + LLScrollContainer* mParentScroller; + bool mDrawAtParentTop; +}; + +// Build time optimization, generate once in .cpp file +#ifndef LLBADGE_CPP +extern template class LLBadge* LLView::getChild(const std::string& name, bool recurse) const; +#endif + +#endif // LL_LLBADGE_H diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index b343f5b4b4..e6c045250e 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -1,1321 +1,1321 @@ - -/** - * @file llbutton.cpp - * @brief LLButton base 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" - -#define LLBUTTON_CPP -#include "llbutton.h" - -// Linden library includes -#include "v4color.h" -#include "llstring.h" - -// Project includes -#include "llkeyboard.h" -#include "llui.h" -#include "lluiconstants.h" -#include "llresmgr.h" -#include "llcriticaldamp.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "llwindow.h" -#include "llnotificationsutil.h" -#include "llrender.h" -#include "lluictrlfactory.h" -#include "lluiusage.h" -#include "llhelp.h" -#include "lldockablefloater.h" -#include "llviewereventrecorder.h" - -static LLDefaultChildRegistry::Register r("button"); - -// Compiler optimization, generate extern template -template class LLButton* LLView::getChild( - const std::string& name, bool recurse) const; - -// globals -S32 LLBUTTON_H_PAD = 4; -S32 BTN_HEIGHT_SMALL= 23; -S32 BTN_HEIGHT = 23; -S32 BTN_DROP_SHADOW = 2; - -LLButton::Params::Params() -: label_selected("label_selected"), // requires is_toggle true - label_shadow("label_shadow", true), - auto_resize("auto_resize", false), - use_ellipses("use_ellipses", false), - use_font_color("use_font_color", true), - image_unselected("image_unselected"), - image_selected("image_selected"), - image_hover_selected("image_hover_selected"), - image_hover_unselected("image_hover_unselected"), - image_disabled_selected("image_disabled_selected"), - image_disabled("image_disabled"), - image_pressed("image_pressed"), - image_pressed_selected("image_pressed_selected"), - image_overlay("image_overlay"), - image_overlay_alignment("image_overlay_alignment", std::string("center")), - image_top_pad("image_top_pad"), - image_bottom_pad("image_bottom_pad"), - imgoverlay_label_space("imgoverlay_label_space", 1), - label_color("label_color"), - label_color_selected("label_color_selected"), // requires is_toggle true - label_color_disabled("label_color_disabled"), - label_color_disabled_selected("label_color_disabled_selected"), - image_color("image_color"), - image_color_disabled("image_color_disabled"), - image_overlay_color("image_overlay_color", LLColor4::white % 0.75f), - image_overlay_disabled_color("image_overlay_disabled_color", LLColor4::white % 0.3f), - image_overlay_selected_color("image_overlay_selected_color", LLColor4::white), - flash_color("flash_color"), - pad_right("pad_right", LLBUTTON_H_PAD), - pad_left("pad_left", LLBUTTON_H_PAD), - pad_bottom("pad_bottom"), - click_callback("click_callback"), - mouse_down_callback("mouse_down_callback"), - mouse_up_callback("mouse_up_callback"), - mouse_held_callback("mouse_held_callback"), - is_toggle("is_toggle", false), - scale_image("scale_image", true), - hover_glow_amount("hover_glow_amount"), - commit_on_return("commit_on_return", true), - commit_on_capture_lost("commit_on_capture_lost", false), - display_pressed_state("display_pressed_state", true), - use_draw_context_alpha("use_draw_context_alpha", true), - badge("badge"), - handle_right_mouse("handle_right_mouse"), - held_down_delay("held_down_delay"), - button_flash_enable("button_flash_enable", false), - button_flash_count("button_flash_count"), - button_flash_rate("button_flash_rate") -{ - addSynonym(is_toggle, "toggle"); - changeDefault(initial_value, LLSD(false)); -} - - -LLButton::LLButton(const LLButton::Params& p) -: LLUICtrl(p), - LLBadgeOwner(getHandle()), - mMouseDownFrame(0), - mMouseHeldDownCount(0), - mBorderEnabled( false ), - mFlashing( false ), - mCurGlowStrength(0.f), - mNeedsHighlight(false), - mUnselectedLabel(p.label()), - mSelectedLabel(p.label_selected()), - mGLFont(p.font), - mHeldDownDelay(p.held_down_delay.seconds), // seconds until held-down callback is called - mHeldDownFrameDelay(p.held_down_delay.frames), - mImageUnselected(p.image_unselected), - mImageSelected(p.image_selected), - mImageDisabled(p.image_disabled), - mImageDisabledSelected(p.image_disabled_selected), - mImageFlash(p.image_flash), - mImagePressed(p.image_pressed), - mImagePressedSelected(p.image_pressed_selected), - mImageHoverSelected(p.image_hover_selected), - mImageHoverUnselected(p.image_hover_unselected), - mUnselectedLabelColor(p.label_color()), - mSelectedLabelColor(p.label_color_selected()), - mDisabledLabelColor(p.label_color_disabled()), - mDisabledSelectedLabelColor(p.label_color_disabled_selected()), - mImageColor(p.image_color()), - mFlashBgColor(p.flash_color()), - mDisabledImageColor(p.image_color_disabled()), - mImageOverlay(p.image_overlay()), - mImageOverlayColor(p.image_overlay_color()), - mImageOverlayDisabledColor(p.image_overlay_disabled_color()), - mImageOverlaySelectedColor(p.image_overlay_selected_color()), - mImageOverlayAlignment(LLFontGL::hAlignFromName(p.image_overlay_alignment)), - mImageOverlayTopPad(p.image_top_pad), - mImageOverlayBottomPad(p.image_bottom_pad), - mImgOverlayLabelSpace(p.imgoverlay_label_space), - mIsToggle(p.is_toggle), - mScaleImage(p.scale_image), - mDropShadowedText(p.label_shadow), - mAutoResize(p.auto_resize), - mUseEllipses( p.use_ellipses ), - mUseFontColor( p.use_font_color), - mHAlign(p.font_halign), - mLeftHPad(p.pad_left), - mRightHPad(p.pad_right), - mBottomVPad(p.pad_bottom), - mHoverGlowStrength(p.hover_glow_amount), - mCommitOnReturn(p.commit_on_return), - mCommitOnCaptureLost(p.commit_on_capture_lost), - mFadeWhenDisabled(false), - mForcePressedState(false), - mDisplayPressedState(p.display_pressed_state), - mLastDrawCharsCount(0), - mMouseDownSignal(NULL), - mMouseUpSignal(NULL), - mHeldDownSignal(NULL), - mUseDrawContextAlpha(p.use_draw_context_alpha), - mHandleRightMouse(p.handle_right_mouse), - mFlashingTimer(NULL) -{ - if (p.button_flash_enable) - { - // If optional parameter "p.button_flash_count" is not provided, LLFlashTimer will be - // used instead it a "default" value from gSavedSettings.getS32("FlashCount")). - // Likewise, missing "p.button_flash_rate" is replaced by gSavedSettings.getF32("FlashPeriod"). - // Note: flashing should be allowed in settings.xml (boolean key "EnableButtonFlashing"). - S32 flash_count = p.button_flash_count.isProvided()? p.button_flash_count : 0; - F32 flash_rate = p.button_flash_rate.isProvided()? p.button_flash_rate : 0.0; - mFlashingTimer = new LLFlashTimer ((LLFlashTimer::callback_t)NULL, flash_count, flash_rate); - } - else - { - mButtonFlashCount = p.button_flash_count; - mButtonFlashRate = p.button_flash_rate; - } - - static LLUICachedControl llbutton_orig_h_pad ("UIButtonOrigHPad", 0); - static Params default_params(LLUICtrlFactory::getDefaultParams()); - - if (!p.label_selected.isProvided()) - { - mSelectedLabel = mUnselectedLabel; - } - - // Hack to make sure there is space for at least one character - if (getRect().mRight >= 0 && getRect().getWidth() > 0 && - getRect().getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(std::string(" "))) - { - // Use old defaults - mLeftHPad = llbutton_orig_h_pad; - mRightHPad = llbutton_orig_h_pad; - } - - mMouseDownTimer.stop(); - - // if custom unselected button image provided... - if (p.image_unselected != default_params.image_unselected) - { - //...fade it out for disabled image by default... - if (p.image_disabled() == default_params.image_disabled() ) - { - mImageDisabled = p.image_unselected; - mFadeWhenDisabled = true; - } - - if (p.image_pressed_selected == default_params.image_pressed_selected) - { - mImagePressedSelected = mImageUnselected; - } - } - - // if custom selected button image provided... - if (p.image_selected != default_params.image_selected) - { - //...fade it out for disabled image by default... - if (p.image_disabled_selected() == default_params.image_disabled_selected()) - { - mImageDisabledSelected = p.image_selected; - mFadeWhenDisabled = true; - } - - if (p.image_pressed == default_params.image_pressed) - { - mImagePressed = mImageSelected; - } - } - - if (!p.image_pressed.isProvided()) - { - mImagePressed = mImageSelected; - } - - if (!p.image_pressed_selected.isProvided()) - { - mImagePressedSelected = mImageUnselected; - } - - if (mImageUnselected.isNull()) - { - LL_WARNS() << "Button: " << getName() << " with no image!" << LL_ENDL; - } - - if (p.click_callback.isProvided()) - { - setCommitCallback(initCommitCallback(p.click_callback)); // alias -> commit_callback - } - if (p.mouse_down_callback.isProvided()) - { - setMouseDownCallback(initCommitCallback(p.mouse_down_callback)); - } - if (p.mouse_up_callback.isProvided()) - { - setMouseUpCallback(initCommitCallback(p.mouse_up_callback)); - } - if (p.mouse_held_callback.isProvided()) - { - setHeldDownCallback(initCommitCallback(p.mouse_held_callback)); - } - - if (p.badge.isProvided()) - { - LLBadgeOwner::initBadgeParams(p.badge()); - } -} - -LLButton::~LLButton() -{ - delete mMouseDownSignal; - delete mMouseUpSignal; - delete mHeldDownSignal; - - if (mFlashingTimer) - { - mFlashingTimer->unset(); - } -} - -// HACK: Committing a button is the same as instantly clicking it. -// virtual -void LLButton::onCommit() -{ - // WARNING: Sometimes clicking a button destroys the floater or - // panel containing it. Therefore we need to call LLUICtrl::onCommit() - // LAST, otherwise this becomes deleted memory. - - if (mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); - - if (mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); - - if (getSoundFlags() & MOUSE_DOWN) - { - make_ui_sound("UISndClick"); - } - - if (getSoundFlags() & MOUSE_UP) - { - make_ui_sound("UISndClickRelease"); - } - - if (mIsToggle) - { - toggleState(); - } - - // do this last, as it can result in destroying this button - LLUICtrl::onCommit(); -} - -boost::signals2::connection LLButton::setClickedCallback(const CommitCallbackParam& cb) -{ - return setClickedCallback(initCommitCallback(cb)); -} -boost::signals2::connection LLButton::setMouseDownCallback(const CommitCallbackParam& cb) -{ - return setMouseDownCallback(initCommitCallback(cb)); -} -boost::signals2::connection LLButton::setMouseUpCallback(const CommitCallbackParam& cb) -{ - return setMouseUpCallback(initCommitCallback(cb)); -} -boost::signals2::connection LLButton::setHeldDownCallback(const CommitCallbackParam& cb) -{ - return setHeldDownCallback(initCommitCallback(cb)); -} - - -boost::signals2::connection LLButton::setClickedCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mCommitSignal) mCommitSignal = new commit_signal_t(); - return mCommitSignal->connect(cb); -} -boost::signals2::connection LLButton::setMouseDownCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t(); - return mMouseDownSignal->connect(cb); -} -boost::signals2::connection LLButton::setMouseUpCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mMouseUpSignal) mMouseUpSignal = new commit_signal_t(); - return mMouseUpSignal->connect(cb); -} -boost::signals2::connection LLButton::setHeldDownCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mHeldDownSignal) mHeldDownSignal = new commit_signal_t(); - return mHeldDownSignal->connect(cb); -} - - -// *TODO: Deprecate (for backwards compatibility only) -boost::signals2::connection LLButton::setClickedCallback( button_callback_t cb, void* data ) -{ - return setClickedCallback(boost::bind(cb, data)); -} -boost::signals2::connection LLButton::setMouseDownCallback( button_callback_t cb, void* data ) -{ - return setMouseDownCallback(boost::bind(cb, data)); -} -boost::signals2::connection LLButton::setMouseUpCallback( button_callback_t cb, void* data ) -{ - return setMouseUpCallback(boost::bind(cb, data)); -} -boost::signals2::connection LLButton::setHeldDownCallback( button_callback_t cb, void* data ) -{ - return setHeldDownCallback(boost::bind(cb, data)); -} - -bool LLButton::postBuild() -{ - autoResize(); - - addBadgeToParentHolder(); - - return LLUICtrl::postBuild(); -} - -bool LLButton::handleUnicodeCharHere(llwchar uni_char) -{ - bool handled = false; - if(' ' == uni_char - && !gKeyboard->getKeyRepeated(' ')) - { - if (mIsToggle) - { - toggleState(); - } - - LLUICtrl::onCommit(); - - handled = true; - } - return handled; -} - -bool LLButton::handleKeyHere(KEY key, MASK mask ) -{ - bool handled = false; - if( mCommitOnReturn && KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) - { - if (mIsToggle) - { - toggleState(); - } - - handled = true; - - LLUICtrl::onCommit(); - } - return handled; -} - - -bool LLButton::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (!childrenHandleMouseDown(x, y, mask)) - { - // Route future Mouse messages here preemptively. (Release on mouse up.) - gFocusMgr.setMouseCapture( this ); - - if (hasTabStop() && !getIsChrome()) - { - setFocus(true); - } - - if (!mFunctionName.empty()) - { - LL_DEBUGS("UIUsage") << "calling mouse down function " << mFunctionName << LL_ENDL; - LLUIUsage::instance().logCommand(mFunctionName); - LLUIUsage::instance().logControl(getPathname()); - } - - /* - * ATTENTION! This call fires another mouse down callback. - * If you wish to remove this call emit that signal directly - * by calling LLUICtrl::mMouseDownSignal(x, y, mask); - */ - LLUICtrl::handleMouseDown(x, y, mask); - - LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); - - if(mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); - - mMouseDownTimer.start(); - mMouseDownFrame = (S32) LLFrameTimer::getFrameCount(); - mMouseHeldDownCount = 0; - - - if (getSoundFlags() & MOUSE_DOWN) - { - make_ui_sound("UISndClick"); - } - } - return true; -} - - -bool LLButton::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // We only handle the click if the click both started and ended within us - if( hasMouseCapture() ) - { - // reset timers before focus change, to not cause - // additional commits if mCommitOnCaptureLost. - resetMouseDownTimer(); - - // Always release the mouse - gFocusMgr.setMouseCapture( NULL ); - - /* - * ATTENTION! This call fires another mouse up callback. - * If you wish to remove this call emit that signal directly - * by calling LLUICtrl::mMouseUpSignal(x, y, mask); - */ - LLUICtrl::handleMouseUp(x, y, mask); - LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); - - // Regardless of where mouseup occurs, handle callback - if(mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); - - // DO THIS AT THE VERY END to allow the button to be destroyed as a result of being clicked. - // If mouseup in the widget, it's been clicked - if (pointInView(x, y)) - { - if (getSoundFlags() & MOUSE_UP) - { - make_ui_sound("UISndClickRelease"); - } - - if (mIsToggle) - { - toggleState(); - } - - LLUICtrl::onCommit(); - } - } - else - { - childrenHandleMouseUp(x, y, mask); - } - - return true; -} - -bool LLButton::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - if (mHandleRightMouse && !childrenHandleRightMouseDown(x, y, mask)) - { - // Route future Mouse messages here preemptively. (Release on mouse up.) - gFocusMgr.setMouseCapture( this ); - - if (hasTabStop() && !getIsChrome()) - { - setFocus(true); - } - -// if (pointInView(x, y)) -// { -// } - // send the mouse down signal - LLUICtrl::handleRightMouseDown(x,y,mask); - // *TODO: Return result of LLUICtrl call above? Should defer to base class - // but this might change the mouse handling of existing buttons in a bad way - // if they are not mouse opaque. - } - - return true; -} - -bool LLButton::handleRightMouseUp(S32 x, S32 y, MASK mask) -{ - if (mHandleRightMouse) - { - // We only handle the click if the click both started and ended within us - if( hasMouseCapture() ) - { - // Always release the mouse - gFocusMgr.setMouseCapture( NULL ); - - // if (pointInView(x, y)) - // { - // mRightMouseUpSignal(this, x,y,mask); - // } - } - else - { - childrenHandleRightMouseUp(x, y, mask); - } - - // send the mouse up signal - LLUICtrl::handleRightMouseUp(x,y,mask); - // *TODO: Return result of LLUICtrl call above? Should defer to base class - // but this might change the mouse handling of existing buttons in a bad way. - // if they are not mouse opaque. - } - return true; -} - -void LLButton::onMouseLeave(S32 x, S32 y, MASK mask) -{ - LLUICtrl::onMouseLeave(x, y, mask); - - mNeedsHighlight = false; -} - -void LLButton::setHighlight(bool b) -{ - mNeedsHighlight = b; -} - -bool LLButton::handleHover(S32 x, S32 y, MASK mask) -{ - if (isInEnabledChain() - && (!gFocusMgr.getMouseCapture() || gFocusMgr.getMouseCapture() == this)) - mNeedsHighlight = true; - - if (!childrenHandleHover(x, y, mask)) - { - if (mMouseDownTimer.getStarted()) - { - F32 elapsed = getHeldDownTime(); - if( mHeldDownDelay <= elapsed && mHeldDownFrameDelay <= (S32)LLFrameTimer::getFrameCount() - mMouseDownFrame) - { - LLSD param; - param["count"] = mMouseHeldDownCount++; - if (mHeldDownSignal) (*mHeldDownSignal)(this, param); - } - } - - // We only handle the click if the click both started and ended within us - getWindow()->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << LL_ENDL; - } - return true; -} - -void LLButton::getOverlayImageSize(S32& overlay_width, S32& overlay_height) -{ - overlay_width = mImageOverlay->getWidth(); - overlay_height = mImageOverlay->getHeight(); - - F32 scale_factor = llmin((F32)getRect().getWidth() / (F32)overlay_width, (F32)getRect().getHeight() / (F32)overlay_height, 1.f); - overlay_width = ll_round((F32)overlay_width * scale_factor); - overlay_height = ll_round((F32)overlay_height * scale_factor); -} - - -// virtual -void LLButton::draw() -{ - static LLCachedControl sEnableButtonFlashing(*LLUI::getInstance()->mSettingGroups["config"], "EnableButtonFlashing", true); - F32 alpha = mUseDrawContextAlpha ? getDrawContext().mAlpha : getCurrentTransparency(); - - bool pressed_by_keyboard = false; - if (hasFocus()) - { - pressed_by_keyboard = gKeyboard->getKeyDown(' ') || (mCommitOnReturn && gKeyboard->getKeyDown(KEY_RETURN)); - } - - bool mouse_pressed_and_over = false; - if (hasMouseCapture()) - { - S32 local_mouse_x ; - S32 local_mouse_y; - LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); - mouse_pressed_and_over = pointInView(local_mouse_x, local_mouse_y); - } - - bool enabled = isInEnabledChain(); - - bool pressed = pressed_by_keyboard - || mouse_pressed_and_over - || mForcePressedState; - bool selected = getToggleState(); - - bool use_glow_effect = false; - LLColor4 highlighting_color = LLColor4::white; - LLColor4 glow_color = LLColor4::white; - LLRender::eBlendType glow_type = LLRender::BT_ADD_WITH_ALPHA; - LLUIImage* imagep = NULL; - LLUIImage* image_glow = NULL; - - // Cancel sticking of color, if the button is pressed, - // or when a flashing of the previously selected button is ended - if (mFlashingTimer - && ((selected && !mFlashingTimer->isFlashingInProgress() && !mForceFlashing) || pressed)) - { - mFlashing = false; - } - - bool flash = mFlashing && sEnableButtonFlashing; - - if (pressed && mDisplayPressedState) - { - imagep = selected ? mImagePressedSelected : mImagePressed; - } - else if ( mNeedsHighlight ) - { - if (selected) - { - if (mImageHoverSelected) - { - imagep = mImageHoverSelected; - } - else - { - imagep = mImageSelected; - use_glow_effect = true; - } - } - else - { - if (mImageHoverUnselected) - { - imagep = mImageHoverUnselected; - } - else - { - imagep = mImageUnselected; - use_glow_effect = true; - } - } - } - else - { - imagep = selected ? mImageSelected : mImageUnselected; - } - - // Override if more data is available - // HACK: Use gray checked state to mean either: - // enabled and tentative - // or - // disabled but checked - if (!mImageDisabledSelected.isNull() - && - ( (enabled && getTentative()) - || (!enabled && selected ) ) ) - { - imagep = mImageDisabledSelected; - } - else if (!mImageDisabled.isNull() - && !enabled - && !selected) - { - imagep = mImageDisabled; - } - - image_glow = imagep; - - if (mFlashing) - { - if (flash && mImageFlash) - { - // if button should flash and we have icon for flashing, use it as image for button - image_glow = mImageFlash; - } - - // provide fade-in and fade-out via flash_color - if (mFlashingTimer) - { - LLColor4 flash_color = mFlashBgColor.get(); - use_glow_effect = true; - glow_type = LLRender::BT_ALPHA; // blend the glow - - if (mFlashingTimer->isCurrentlyHighlighted() || !mFlashingTimer->isFlashingInProgress()) - { - glow_color = flash_color; - } - else if (mNeedsHighlight) - { - glow_color = highlighting_color; - } - else - { - // will fade from highlight color - glow_color = flash_color; - } - } - } - - if (mNeedsHighlight && !imagep) - { - use_glow_effect = true; - } - - // Figure out appropriate color for the text - LLColor4 label_color; - - // label changes when button state changes, not when pressed - if ( enabled ) - { - if ( getToggleState() ) - { - label_color = mSelectedLabelColor.get(); - } - else - { - label_color = mUnselectedLabelColor.get(); - } - } - else - { - if ( getToggleState() ) - { - label_color = mDisabledSelectedLabelColor.get(); - } - else - { - label_color = mDisabledLabelColor.get(); - } - } - - // Highlight if needed - if( ll::ui::SearchableControl::getHighlighted() ) - label_color = ll::ui::SearchableControl::getHighlightColor(); - - // Unselected label assignments - LLWString label = getCurrentLabel(); - - // overlay with keyboard focus border - if (hasFocus()) - { - F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); - drawBorder(imagep, gFocusMgr.getFocusColor() % alpha, ll_round(lerp(1.f, 3.f, lerp_amt))); - } - - if (use_glow_effect) - { - mCurGlowStrength = lerp(mCurGlowStrength, - mFlashing ? (mFlashingTimer->isCurrentlyHighlighted() || !mFlashingTimer->isFlashingInProgress() || mNeedsHighlight? 1.f : 0.f) : mHoverGlowStrength, - LLSmoothInterpolation::getInterpolant(0.05f)); - } - else - { - mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLSmoothInterpolation::getInterpolant(0.05f)); - } - - // Draw button image, if available. - // Otherwise draw basic rectangular button. - if (imagep != NULL) - { - // apply automatic 50% alpha fade to disabled image - LLColor4 disabled_color = mFadeWhenDisabled ? mDisabledImageColor.get() % 0.5f : mDisabledImageColor.get(); - if ( mScaleImage) - { - imagep->draw(getLocalRect(), (enabled ? mImageColor.get() : disabled_color) % alpha ); - if (mCurGlowStrength > 0.01f) - { - gGL.setSceneBlendType(glow_type); - image_glow->drawSolid(0, 0, getRect().getWidth(), getRect().getHeight(), glow_color % (mCurGlowStrength * alpha)); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - } - } - else - { - S32 y = getLocalRect().getHeight() - imagep->getHeight(); - imagep->draw(0, y, (enabled ? mImageColor.get() : disabled_color) % alpha); - if (mCurGlowStrength > 0.01f) - { - gGL.setSceneBlendType(glow_type); - image_glow->drawSolid(0, y, glow_color % (mCurGlowStrength * alpha)); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - } - } - } - else - { - // no image - LL_DEBUGS() << "No image for button " << getName() << LL_ENDL; - // draw it in pink so we can find it - gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4::pink1 % alpha, false); - } - - // let overlay image and text play well together - S32 text_left = mLeftHPad; - S32 text_right = getRect().getWidth() - mRightHPad; - S32 text_width = getRect().getWidth() - mLeftHPad - mRightHPad; - - // draw overlay image - if (mImageOverlay.notNull()) - { - // get max width and height (discard level 0) - S32 overlay_width; - S32 overlay_height; - - getOverlayImageSize(overlay_width, overlay_height); - - S32 center_x = getLocalRect().getCenterX(); - S32 center_y = getLocalRect().getCenterY(); - - //FUGLY HACK FOR "DEPRESSED" BUTTONS - if (pressed && mDisplayPressedState) - { - center_y--; - center_x++; - } - - center_y += (mImageOverlayBottomPad - mImageOverlayTopPad); - // fade out overlay images on disabled buttons - LLColor4 overlay_color = mImageOverlayColor.get(); - if (!enabled) - { - overlay_color = mImageOverlayDisabledColor.get(); - } - else if (getToggleState()) - { - overlay_color = mImageOverlaySelectedColor.get(); - } - overlay_color.mV[VALPHA] *= alpha; - - switch(mImageOverlayAlignment) - { - case LLFontGL::LEFT: - text_left += overlay_width + mImgOverlayLabelSpace; - text_width -= overlay_width + mImgOverlayLabelSpace; - mImageOverlay->draw( - mLeftHPad, - center_y - (overlay_height / 2), - overlay_width, - overlay_height, - overlay_color); - break; - case LLFontGL::HCENTER: - mImageOverlay->draw( - center_x - (overlay_width / 2), - center_y - (overlay_height / 2), - overlay_width, - overlay_height, - overlay_color); - break; - case LLFontGL::RIGHT: - text_right -= overlay_width + mImgOverlayLabelSpace; - text_width -= overlay_width + mImgOverlayLabelSpace; - mImageOverlay->draw( - getRect().getWidth() - mRightHPad - overlay_width, - center_y - (overlay_height / 2), - overlay_width, - overlay_height, - overlay_color); - break; - default: - // draw nothing - break; - } - } - - // Draw label - if( !label.empty() ) - { - LLWStringUtil::trim(label); - - S32 x; - switch( mHAlign ) - { - case LLFontGL::RIGHT: - x = text_right; - break; - case LLFontGL::HCENTER: - x = text_left + (text_width / 2); - break; - case LLFontGL::LEFT: - default: - x = text_left; - break; - } - - if (pressed && mDisplayPressedState) - { - x++; - } - - // *NOTE: mantipov: before mUseEllipses is implemented in EXT-279 U32_MAX has been passed as - // max_chars. - // LLFontGL::render expects S32 max_chars variable but process in a separate way -1 value. - // Due to U32_MAX is equal to S32 -1 value I have rest this value for non-ellipses mode. - // Not sure if it is really needed. Probably S32_MAX should be always passed as max_chars. - mLastDrawCharsCount = mGLFont->render(label, 0, - (F32)x, - (F32)(getRect().getHeight() / 2 + mBottomVPad), - label_color % alpha, - mHAlign, LLFontGL::VCENTER, - LLFontGL::NORMAL, - mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW, - S32_MAX, text_width, - NULL, mUseEllipses, mUseFontColor); - } - - LLUICtrl::draw(); -} - -void LLButton::drawBorder(LLUIImage* imagep, const LLColor4& color, S32 size) -{ - if (imagep == NULL) return; - if (mScaleImage) - { - imagep->drawBorder(getLocalRect(), color, size); - } - else - { - S32 y = getLocalRect().getHeight() - imagep->getHeight(); - imagep->drawBorder(0, y, color, size); - } -} - -bool LLButton::getToggleState() const -{ - return getValue().asBoolean(); -} - -void LLButton::setToggleState(bool b) -{ - if( b != getToggleState() ) - { - setControlValue(b); // will fire LLControlVariable callbacks (if any) - setValue(b); // may or may not be redundant - setFlashing(false); // stop flash state whenever the selected/unselected state if reset - // Unselected label assignments - autoResize(); - } -} - -void LLButton::setFlashing(bool b, bool force_flashing/* = false */) -{ - mForceFlashing = force_flashing; - if (mFlashingTimer) - { - mFlashing = b; - (b ? mFlashingTimer->startFlashing() : mFlashingTimer->stopFlashing()); - } - else if (b != mFlashing) - { - mFlashing = b; - mFrameTimer.reset(); - } -} - -bool LLButton::toggleState() -{ - bool flipped = ! getToggleState(); - setToggleState(flipped); - - return flipped; -} - -void LLButton::setLabel( const std::string& label ) -{ - mUnselectedLabel = mSelectedLabel = label; -} - -void LLButton::setLabel( const LLUIString& label ) -{ - mUnselectedLabel = mSelectedLabel = label; -} - -void LLButton::setLabel( const LLStringExplicit& label ) -{ - setLabelUnselected(label); - setLabelSelected(label); -} - -//virtual -bool LLButton::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - mUnselectedLabel.setArg(key, text); - mSelectedLabel.setArg(key, text); - return true; -} - -void LLButton::setLabelUnselected( const LLStringExplicit& label ) -{ - mUnselectedLabel = label; -} - -void LLButton::setLabelSelected( const LLStringExplicit& label ) -{ - mSelectedLabel = label; -} - -bool LLButton::labelIsTruncated() const -{ - return getCurrentLabel().getString().size() > mLastDrawCharsCount; -} - -const LLUIString& LLButton::getCurrentLabel() const -{ - return getToggleState() ? mSelectedLabel : mUnselectedLabel; -} - -void LLButton::setImageUnselected(LLPointer image) -{ - mImageUnselected = image; - if (mImageUnselected.isNull()) - { - LL_WARNS() << "Setting default button image for: " << getName() << " to NULL" << LL_ENDL; - } -} - -void LLButton::autoResize() -{ - resize(getCurrentLabel()); -} - -void LLButton::resize(LLUIString label) -{ - // get label length - S32 label_width = mGLFont->getWidth(label.getString()); - // get current btn length - S32 btn_width =getRect().getWidth(); - // check if it need resize - if (mAutoResize) - { - S32 min_width = label_width + mLeftHPad + mRightHPad; - if (mImageOverlay) - { - S32 overlay_width = mImageOverlay->getWidth(); - F32 scale_factor = (getRect().getHeight() - (mImageOverlayBottomPad + mImageOverlayTopPad)) / (F32)mImageOverlay->getHeight(); - overlay_width = ll_round((F32)overlay_width * scale_factor); - - switch(mImageOverlayAlignment) - { - case LLFontGL::LEFT: - case LLFontGL::RIGHT: - min_width += overlay_width + mImgOverlayLabelSpace; - break; - case LLFontGL::HCENTER: - min_width = llmax(min_width, overlay_width + mLeftHPad + mRightHPad); - break; - default: - // draw nothing - break; - } - } - if (btn_width < min_width) - { - reshape(min_width, getRect().getHeight()); - } - } -} -void LLButton::setImages( const std::string &image_name, const std::string &selected_name ) -{ - setImageUnselected(LLUI::getUIImage(image_name)); - setImageSelected(LLUI::getUIImage(selected_name)); -} - -void LLButton::setImageSelected(LLPointer image) -{ - mImageSelected = image; -} - -void LLButton::setImageColor(const LLColor4& c) -{ - mImageColor = c; -} - -void LLButton::setColor(const LLColor4& color) -{ - setImageColor(color); -} - -void LLButton::setImageDisabled(LLPointer image) -{ - mImageDisabled = image; - mDisabledImageColor = mImageColor; - mFadeWhenDisabled = true; -} - -void LLButton::setImageDisabledSelected(LLPointer image) -{ - mImageDisabledSelected = image; - mDisabledImageColor = mImageColor; - mFadeWhenDisabled = true; -} - -void LLButton::setImagePressed(LLPointer image) -{ - mImagePressed = image; -} - -void LLButton::setImageHoverSelected(LLPointer image) -{ - mImageHoverSelected = image; -} - -void LLButton::setImageHoverUnselected(LLPointer image) -{ - mImageHoverUnselected = image; -} - -void LLButton::setImageFlash(LLPointer image) -{ - mImageFlash = image; -} - -void LLButton::setImageOverlay(const std::string& image_name, LLFontGL::HAlign alignment, const LLColor4& color) -{ - if (image_name.empty()) - { - mImageOverlay = NULL; - } - else - { - mImageOverlay = LLUI::getUIImage(image_name); - mImageOverlayAlignment = alignment; - mImageOverlayColor = color; - } -} - -void LLButton::setImageOverlay(const LLUUID& image_id, LLFontGL::HAlign alignment, const LLColor4& color) -{ - if (image_id.isNull()) - { - mImageOverlay = NULL; - } - else - { - mImageOverlay = LLUI::getUIImageByID(image_id); - mImageOverlayAlignment = alignment; - mImageOverlayColor = color; - } -} - -void LLButton::onMouseCaptureLost() -{ - if (mCommitOnCaptureLost - && mMouseDownTimer.getStarted()) - { - if (mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); - - if (mIsToggle) - { - toggleState(); - } - - LLUICtrl::onCommit(); - } - resetMouseDownTimer(); -} - -//------------------------------------------------------------------------- -// Utilities -//------------------------------------------------------------------------- -S32 round_up(S32 grid, S32 value) -{ - S32 mod = value % grid; - - if (mod > 0) - { - // not even multiple - return value + (grid - mod); - } - else - { - return value; - } -} - -void LLButton::addImageAttributeToXML(LLXMLNodePtr node, - const std::string& image_name, - const LLUUID& image_id, - const std::string& xml_tag_name) const -{ - if( !image_name.empty() ) - { - node->createChild(xml_tag_name.c_str(), true)->setStringValue(image_name); - } - else if( image_id != LLUUID::null ) - { - node->createChild((xml_tag_name + "_id").c_str(), true)->setUUIDValue(image_id); - } -} - - -// static -void LLButton::toggleFloaterAndSetToggleState(LLUICtrl* ctrl, const LLSD& sdname) -{ - bool floater_vis = LLFloaterReg::toggleInstance(sdname.asString()); - LLButton* button = dynamic_cast(ctrl); - if (button) - button->setToggleState(floater_vis); -} - -// static -// Gets called once -void LLButton::setFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) -{ - LLButton* button = dynamic_cast(ctrl); - if (!button) - return; - // Get the visibility control name for the floater - std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); - // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) - button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); - // Set the clicked callback to toggle the floater - button->setClickedCallback(boost::bind(&LLFloaterReg::toggleInstance, sdname, LLSD())); -} - -// static -void LLButton::setDockableFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) -{ - LLButton* button = dynamic_cast(ctrl); - if (!button) - return; - // Get the visibility control name for the floater - std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); - // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) - button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); - // Set the clicked callback to toggle the floater - button->setClickedCallback(boost::bind(&LLDockableFloater::toggleInstance, sdname)); -} - -// static -void LLButton::showHelp(LLUICtrl* ctrl, const LLSD& sdname) -{ - // search back through the button's parents for a panel - // with a help_topic string defined - std::string help_topic; - if (LLUI::getInstance()->mHelpImpl && - ctrl->findHelpTopic(help_topic)) - { - LLUI::getInstance()->mHelpImpl->showTopic(help_topic); - return; // success - } - - // display an error if we can't find a help_topic string. - // fix this by adding a help_topic attribute to the xui file - LLNotificationsUtil::add("UnableToFindHelpTopic"); -} - -void LLButton::resetMouseDownTimer() -{ - mMouseDownTimer.stop(); - mMouseDownTimer.reset(); -} - -bool LLButton::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - // just treat a double click as a second click - return handleMouseDown(x, y, mask); -} + +/** + * @file llbutton.cpp + * @brief LLButton base 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" + +#define LLBUTTON_CPP +#include "llbutton.h" + +// Linden library includes +#include "v4color.h" +#include "llstring.h" + +// Project includes +#include "llkeyboard.h" +#include "llui.h" +#include "lluiconstants.h" +#include "llresmgr.h" +#include "llcriticaldamp.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "llwindow.h" +#include "llnotificationsutil.h" +#include "llrender.h" +#include "lluictrlfactory.h" +#include "lluiusage.h" +#include "llhelp.h" +#include "lldockablefloater.h" +#include "llviewereventrecorder.h" + +static LLDefaultChildRegistry::Register r("button"); + +// Compiler optimization, generate extern template +template class LLButton* LLView::getChild( + const std::string& name, bool recurse) const; + +// globals +S32 LLBUTTON_H_PAD = 4; +S32 BTN_HEIGHT_SMALL= 23; +S32 BTN_HEIGHT = 23; +S32 BTN_DROP_SHADOW = 2; + +LLButton::Params::Params() +: label_selected("label_selected"), // requires is_toggle true + label_shadow("label_shadow", true), + auto_resize("auto_resize", false), + use_ellipses("use_ellipses", false), + use_font_color("use_font_color", true), + image_unselected("image_unselected"), + image_selected("image_selected"), + image_hover_selected("image_hover_selected"), + image_hover_unselected("image_hover_unselected"), + image_disabled_selected("image_disabled_selected"), + image_disabled("image_disabled"), + image_pressed("image_pressed"), + image_pressed_selected("image_pressed_selected"), + image_overlay("image_overlay"), + image_overlay_alignment("image_overlay_alignment", std::string("center")), + image_top_pad("image_top_pad"), + image_bottom_pad("image_bottom_pad"), + imgoverlay_label_space("imgoverlay_label_space", 1), + label_color("label_color"), + label_color_selected("label_color_selected"), // requires is_toggle true + label_color_disabled("label_color_disabled"), + label_color_disabled_selected("label_color_disabled_selected"), + image_color("image_color"), + image_color_disabled("image_color_disabled"), + image_overlay_color("image_overlay_color", LLColor4::white % 0.75f), + image_overlay_disabled_color("image_overlay_disabled_color", LLColor4::white % 0.3f), + image_overlay_selected_color("image_overlay_selected_color", LLColor4::white), + flash_color("flash_color"), + pad_right("pad_right", LLBUTTON_H_PAD), + pad_left("pad_left", LLBUTTON_H_PAD), + pad_bottom("pad_bottom"), + click_callback("click_callback"), + mouse_down_callback("mouse_down_callback"), + mouse_up_callback("mouse_up_callback"), + mouse_held_callback("mouse_held_callback"), + is_toggle("is_toggle", false), + scale_image("scale_image", true), + hover_glow_amount("hover_glow_amount"), + commit_on_return("commit_on_return", true), + commit_on_capture_lost("commit_on_capture_lost", false), + display_pressed_state("display_pressed_state", true), + use_draw_context_alpha("use_draw_context_alpha", true), + badge("badge"), + handle_right_mouse("handle_right_mouse"), + held_down_delay("held_down_delay"), + button_flash_enable("button_flash_enable", false), + button_flash_count("button_flash_count"), + button_flash_rate("button_flash_rate") +{ + addSynonym(is_toggle, "toggle"); + changeDefault(initial_value, LLSD(false)); +} + + +LLButton::LLButton(const LLButton::Params& p) +: LLUICtrl(p), + LLBadgeOwner(getHandle()), + mMouseDownFrame(0), + mMouseHeldDownCount(0), + mBorderEnabled( false ), + mFlashing( false ), + mCurGlowStrength(0.f), + mNeedsHighlight(false), + mUnselectedLabel(p.label()), + mSelectedLabel(p.label_selected()), + mGLFont(p.font), + mHeldDownDelay(p.held_down_delay.seconds), // seconds until held-down callback is called + mHeldDownFrameDelay(p.held_down_delay.frames), + mImageUnselected(p.image_unselected), + mImageSelected(p.image_selected), + mImageDisabled(p.image_disabled), + mImageDisabledSelected(p.image_disabled_selected), + mImageFlash(p.image_flash), + mImagePressed(p.image_pressed), + mImagePressedSelected(p.image_pressed_selected), + mImageHoverSelected(p.image_hover_selected), + mImageHoverUnselected(p.image_hover_unselected), + mUnselectedLabelColor(p.label_color()), + mSelectedLabelColor(p.label_color_selected()), + mDisabledLabelColor(p.label_color_disabled()), + mDisabledSelectedLabelColor(p.label_color_disabled_selected()), + mImageColor(p.image_color()), + mFlashBgColor(p.flash_color()), + mDisabledImageColor(p.image_color_disabled()), + mImageOverlay(p.image_overlay()), + mImageOverlayColor(p.image_overlay_color()), + mImageOverlayDisabledColor(p.image_overlay_disabled_color()), + mImageOverlaySelectedColor(p.image_overlay_selected_color()), + mImageOverlayAlignment(LLFontGL::hAlignFromName(p.image_overlay_alignment)), + mImageOverlayTopPad(p.image_top_pad), + mImageOverlayBottomPad(p.image_bottom_pad), + mImgOverlayLabelSpace(p.imgoverlay_label_space), + mIsToggle(p.is_toggle), + mScaleImage(p.scale_image), + mDropShadowedText(p.label_shadow), + mAutoResize(p.auto_resize), + mUseEllipses( p.use_ellipses ), + mUseFontColor( p.use_font_color), + mHAlign(p.font_halign), + mLeftHPad(p.pad_left), + mRightHPad(p.pad_right), + mBottomVPad(p.pad_bottom), + mHoverGlowStrength(p.hover_glow_amount), + mCommitOnReturn(p.commit_on_return), + mCommitOnCaptureLost(p.commit_on_capture_lost), + mFadeWhenDisabled(false), + mForcePressedState(false), + mDisplayPressedState(p.display_pressed_state), + mLastDrawCharsCount(0), + mMouseDownSignal(NULL), + mMouseUpSignal(NULL), + mHeldDownSignal(NULL), + mUseDrawContextAlpha(p.use_draw_context_alpha), + mHandleRightMouse(p.handle_right_mouse), + mFlashingTimer(NULL) +{ + if (p.button_flash_enable) + { + // If optional parameter "p.button_flash_count" is not provided, LLFlashTimer will be + // used instead it a "default" value from gSavedSettings.getS32("FlashCount")). + // Likewise, missing "p.button_flash_rate" is replaced by gSavedSettings.getF32("FlashPeriod"). + // Note: flashing should be allowed in settings.xml (boolean key "EnableButtonFlashing"). + S32 flash_count = p.button_flash_count.isProvided()? p.button_flash_count : 0; + F32 flash_rate = p.button_flash_rate.isProvided()? p.button_flash_rate : 0.0; + mFlashingTimer = new LLFlashTimer ((LLFlashTimer::callback_t)NULL, flash_count, flash_rate); + } + else + { + mButtonFlashCount = p.button_flash_count; + mButtonFlashRate = p.button_flash_rate; + } + + static LLUICachedControl llbutton_orig_h_pad ("UIButtonOrigHPad", 0); + static Params default_params(LLUICtrlFactory::getDefaultParams()); + + if (!p.label_selected.isProvided()) + { + mSelectedLabel = mUnselectedLabel; + } + + // Hack to make sure there is space for at least one character + if (getRect().mRight >= 0 && getRect().getWidth() > 0 && + getRect().getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(std::string(" "))) + { + // Use old defaults + mLeftHPad = llbutton_orig_h_pad; + mRightHPad = llbutton_orig_h_pad; + } + + mMouseDownTimer.stop(); + + // if custom unselected button image provided... + if (p.image_unselected != default_params.image_unselected) + { + //...fade it out for disabled image by default... + if (p.image_disabled() == default_params.image_disabled() ) + { + mImageDisabled = p.image_unselected; + mFadeWhenDisabled = true; + } + + if (p.image_pressed_selected == default_params.image_pressed_selected) + { + mImagePressedSelected = mImageUnselected; + } + } + + // if custom selected button image provided... + if (p.image_selected != default_params.image_selected) + { + //...fade it out for disabled image by default... + if (p.image_disabled_selected() == default_params.image_disabled_selected()) + { + mImageDisabledSelected = p.image_selected; + mFadeWhenDisabled = true; + } + + if (p.image_pressed == default_params.image_pressed) + { + mImagePressed = mImageSelected; + } + } + + if (!p.image_pressed.isProvided()) + { + mImagePressed = mImageSelected; + } + + if (!p.image_pressed_selected.isProvided()) + { + mImagePressedSelected = mImageUnselected; + } + + if (mImageUnselected.isNull()) + { + LL_WARNS() << "Button: " << getName() << " with no image!" << LL_ENDL; + } + + if (p.click_callback.isProvided()) + { + setCommitCallback(initCommitCallback(p.click_callback)); // alias -> commit_callback + } + if (p.mouse_down_callback.isProvided()) + { + setMouseDownCallback(initCommitCallback(p.mouse_down_callback)); + } + if (p.mouse_up_callback.isProvided()) + { + setMouseUpCallback(initCommitCallback(p.mouse_up_callback)); + } + if (p.mouse_held_callback.isProvided()) + { + setHeldDownCallback(initCommitCallback(p.mouse_held_callback)); + } + + if (p.badge.isProvided()) + { + LLBadgeOwner::initBadgeParams(p.badge()); + } +} + +LLButton::~LLButton() +{ + delete mMouseDownSignal; + delete mMouseUpSignal; + delete mHeldDownSignal; + + if (mFlashingTimer) + { + mFlashingTimer->unset(); + } +} + +// HACK: Committing a button is the same as instantly clicking it. +// virtual +void LLButton::onCommit() +{ + // WARNING: Sometimes clicking a button destroys the floater or + // panel containing it. Therefore we need to call LLUICtrl::onCommit() + // LAST, otherwise this becomes deleted memory. + + if (mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); + + if (mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); + + if (getSoundFlags() & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } + + if (getSoundFlags() & MOUSE_UP) + { + make_ui_sound("UISndClickRelease"); + } + + if (mIsToggle) + { + toggleState(); + } + + // do this last, as it can result in destroying this button + LLUICtrl::onCommit(); +} + +boost::signals2::connection LLButton::setClickedCallback(const CommitCallbackParam& cb) +{ + return setClickedCallback(initCommitCallback(cb)); +} +boost::signals2::connection LLButton::setMouseDownCallback(const CommitCallbackParam& cb) +{ + return setMouseDownCallback(initCommitCallback(cb)); +} +boost::signals2::connection LLButton::setMouseUpCallback(const CommitCallbackParam& cb) +{ + return setMouseUpCallback(initCommitCallback(cb)); +} +boost::signals2::connection LLButton::setHeldDownCallback(const CommitCallbackParam& cb) +{ + return setHeldDownCallback(initCommitCallback(cb)); +} + + +boost::signals2::connection LLButton::setClickedCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mCommitSignal) mCommitSignal = new commit_signal_t(); + return mCommitSignal->connect(cb); +} +boost::signals2::connection LLButton::setMouseDownCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t(); + return mMouseDownSignal->connect(cb); +} +boost::signals2::connection LLButton::setMouseUpCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseUpSignal) mMouseUpSignal = new commit_signal_t(); + return mMouseUpSignal->connect(cb); +} +boost::signals2::connection LLButton::setHeldDownCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mHeldDownSignal) mHeldDownSignal = new commit_signal_t(); + return mHeldDownSignal->connect(cb); +} + + +// *TODO: Deprecate (for backwards compatibility only) +boost::signals2::connection LLButton::setClickedCallback( button_callback_t cb, void* data ) +{ + return setClickedCallback(boost::bind(cb, data)); +} +boost::signals2::connection LLButton::setMouseDownCallback( button_callback_t cb, void* data ) +{ + return setMouseDownCallback(boost::bind(cb, data)); +} +boost::signals2::connection LLButton::setMouseUpCallback( button_callback_t cb, void* data ) +{ + return setMouseUpCallback(boost::bind(cb, data)); +} +boost::signals2::connection LLButton::setHeldDownCallback( button_callback_t cb, void* data ) +{ + return setHeldDownCallback(boost::bind(cb, data)); +} + +bool LLButton::postBuild() +{ + autoResize(); + + addBadgeToParentHolder(); + + return LLUICtrl::postBuild(); +} + +bool LLButton::handleUnicodeCharHere(llwchar uni_char) +{ + bool handled = false; + if(' ' == uni_char + && !gKeyboard->getKeyRepeated(' ')) + { + if (mIsToggle) + { + toggleState(); + } + + LLUICtrl::onCommit(); + + handled = true; + } + return handled; +} + +bool LLButton::handleKeyHere(KEY key, MASK mask ) +{ + bool handled = false; + if( mCommitOnReturn && KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) + { + if (mIsToggle) + { + toggleState(); + } + + handled = true; + + LLUICtrl::onCommit(); + } + return handled; +} + + +bool LLButton::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (!childrenHandleMouseDown(x, y, mask)) + { + // Route future Mouse messages here preemptively. (Release on mouse up.) + gFocusMgr.setMouseCapture( this ); + + if (hasTabStop() && !getIsChrome()) + { + setFocus(true); + } + + if (!mFunctionName.empty()) + { + LL_DEBUGS("UIUsage") << "calling mouse down function " << mFunctionName << LL_ENDL; + LLUIUsage::instance().logCommand(mFunctionName); + LLUIUsage::instance().logControl(getPathname()); + } + + /* + * ATTENTION! This call fires another mouse down callback. + * If you wish to remove this call emit that signal directly + * by calling LLUICtrl::mMouseDownSignal(x, y, mask); + */ + LLUICtrl::handleMouseDown(x, y, mask); + + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + + if(mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); + + mMouseDownTimer.start(); + mMouseDownFrame = (S32) LLFrameTimer::getFrameCount(); + mMouseHeldDownCount = 0; + + + if (getSoundFlags() & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } + } + return true; +} + + +bool LLButton::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // We only handle the click if the click both started and ended within us + if( hasMouseCapture() ) + { + // reset timers before focus change, to not cause + // additional commits if mCommitOnCaptureLost. + resetMouseDownTimer(); + + // Always release the mouse + gFocusMgr.setMouseCapture( NULL ); + + /* + * ATTENTION! This call fires another mouse up callback. + * If you wish to remove this call emit that signal directly + * by calling LLUICtrl::mMouseUpSignal(x, y, mask); + */ + LLUICtrl::handleMouseUp(x, y, mask); + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + + // Regardless of where mouseup occurs, handle callback + if(mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); + + // DO THIS AT THE VERY END to allow the button to be destroyed as a result of being clicked. + // If mouseup in the widget, it's been clicked + if (pointInView(x, y)) + { + if (getSoundFlags() & MOUSE_UP) + { + make_ui_sound("UISndClickRelease"); + } + + if (mIsToggle) + { + toggleState(); + } + + LLUICtrl::onCommit(); + } + } + else + { + childrenHandleMouseUp(x, y, mask); + } + + return true; +} + +bool LLButton::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (mHandleRightMouse && !childrenHandleRightMouseDown(x, y, mask)) + { + // Route future Mouse messages here preemptively. (Release on mouse up.) + gFocusMgr.setMouseCapture( this ); + + if (hasTabStop() && !getIsChrome()) + { + setFocus(true); + } + +// if (pointInView(x, y)) +// { +// } + // send the mouse down signal + LLUICtrl::handleRightMouseDown(x,y,mask); + // *TODO: Return result of LLUICtrl call above? Should defer to base class + // but this might change the mouse handling of existing buttons in a bad way + // if they are not mouse opaque. + } + + return true; +} + +bool LLButton::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + if (mHandleRightMouse) + { + // We only handle the click if the click both started and ended within us + if( hasMouseCapture() ) + { + // Always release the mouse + gFocusMgr.setMouseCapture( NULL ); + + // if (pointInView(x, y)) + // { + // mRightMouseUpSignal(this, x,y,mask); + // } + } + else + { + childrenHandleRightMouseUp(x, y, mask); + } + + // send the mouse up signal + LLUICtrl::handleRightMouseUp(x,y,mask); + // *TODO: Return result of LLUICtrl call above? Should defer to base class + // but this might change the mouse handling of existing buttons in a bad way. + // if they are not mouse opaque. + } + return true; +} + +void LLButton::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLUICtrl::onMouseLeave(x, y, mask); + + mNeedsHighlight = false; +} + +void LLButton::setHighlight(bool b) +{ + mNeedsHighlight = b; +} + +bool LLButton::handleHover(S32 x, S32 y, MASK mask) +{ + if (isInEnabledChain() + && (!gFocusMgr.getMouseCapture() || gFocusMgr.getMouseCapture() == this)) + mNeedsHighlight = true; + + if (!childrenHandleHover(x, y, mask)) + { + if (mMouseDownTimer.getStarted()) + { + F32 elapsed = getHeldDownTime(); + if( mHeldDownDelay <= elapsed && mHeldDownFrameDelay <= (S32)LLFrameTimer::getFrameCount() - mMouseDownFrame) + { + LLSD param; + param["count"] = mMouseHeldDownCount++; + if (mHeldDownSignal) (*mHeldDownSignal)(this, param); + } + } + + // We only handle the click if the click both started and ended within us + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << LL_ENDL; + } + return true; +} + +void LLButton::getOverlayImageSize(S32& overlay_width, S32& overlay_height) +{ + overlay_width = mImageOverlay->getWidth(); + overlay_height = mImageOverlay->getHeight(); + + F32 scale_factor = llmin((F32)getRect().getWidth() / (F32)overlay_width, (F32)getRect().getHeight() / (F32)overlay_height, 1.f); + overlay_width = ll_round((F32)overlay_width * scale_factor); + overlay_height = ll_round((F32)overlay_height * scale_factor); +} + + +// virtual +void LLButton::draw() +{ + static LLCachedControl sEnableButtonFlashing(*LLUI::getInstance()->mSettingGroups["config"], "EnableButtonFlashing", true); + F32 alpha = mUseDrawContextAlpha ? getDrawContext().mAlpha : getCurrentTransparency(); + + bool pressed_by_keyboard = false; + if (hasFocus()) + { + pressed_by_keyboard = gKeyboard->getKeyDown(' ') || (mCommitOnReturn && gKeyboard->getKeyDown(KEY_RETURN)); + } + + bool mouse_pressed_and_over = false; + if (hasMouseCapture()) + { + S32 local_mouse_x ; + S32 local_mouse_y; + LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); + mouse_pressed_and_over = pointInView(local_mouse_x, local_mouse_y); + } + + bool enabled = isInEnabledChain(); + + bool pressed = pressed_by_keyboard + || mouse_pressed_and_over + || mForcePressedState; + bool selected = getToggleState(); + + bool use_glow_effect = false; + LLColor4 highlighting_color = LLColor4::white; + LLColor4 glow_color = LLColor4::white; + LLRender::eBlendType glow_type = LLRender::BT_ADD_WITH_ALPHA; + LLUIImage* imagep = NULL; + LLUIImage* image_glow = NULL; + + // Cancel sticking of color, if the button is pressed, + // or when a flashing of the previously selected button is ended + if (mFlashingTimer + && ((selected && !mFlashingTimer->isFlashingInProgress() && !mForceFlashing) || pressed)) + { + mFlashing = false; + } + + bool flash = mFlashing && sEnableButtonFlashing; + + if (pressed && mDisplayPressedState) + { + imagep = selected ? mImagePressedSelected : mImagePressed; + } + else if ( mNeedsHighlight ) + { + if (selected) + { + if (mImageHoverSelected) + { + imagep = mImageHoverSelected; + } + else + { + imagep = mImageSelected; + use_glow_effect = true; + } + } + else + { + if (mImageHoverUnselected) + { + imagep = mImageHoverUnselected; + } + else + { + imagep = mImageUnselected; + use_glow_effect = true; + } + } + } + else + { + imagep = selected ? mImageSelected : mImageUnselected; + } + + // Override if more data is available + // HACK: Use gray checked state to mean either: + // enabled and tentative + // or + // disabled but checked + if (!mImageDisabledSelected.isNull() + && + ( (enabled && getTentative()) + || (!enabled && selected ) ) ) + { + imagep = mImageDisabledSelected; + } + else if (!mImageDisabled.isNull() + && !enabled + && !selected) + { + imagep = mImageDisabled; + } + + image_glow = imagep; + + if (mFlashing) + { + if (flash && mImageFlash) + { + // if button should flash and we have icon for flashing, use it as image for button + image_glow = mImageFlash; + } + + // provide fade-in and fade-out via flash_color + if (mFlashingTimer) + { + LLColor4 flash_color = mFlashBgColor.get(); + use_glow_effect = true; + glow_type = LLRender::BT_ALPHA; // blend the glow + + if (mFlashingTimer->isCurrentlyHighlighted() || !mFlashingTimer->isFlashingInProgress()) + { + glow_color = flash_color; + } + else if (mNeedsHighlight) + { + glow_color = highlighting_color; + } + else + { + // will fade from highlight color + glow_color = flash_color; + } + } + } + + if (mNeedsHighlight && !imagep) + { + use_glow_effect = true; + } + + // Figure out appropriate color for the text + LLColor4 label_color; + + // label changes when button state changes, not when pressed + if ( enabled ) + { + if ( getToggleState() ) + { + label_color = mSelectedLabelColor.get(); + } + else + { + label_color = mUnselectedLabelColor.get(); + } + } + else + { + if ( getToggleState() ) + { + label_color = mDisabledSelectedLabelColor.get(); + } + else + { + label_color = mDisabledLabelColor.get(); + } + } + + // Highlight if needed + if( ll::ui::SearchableControl::getHighlighted() ) + label_color = ll::ui::SearchableControl::getHighlightColor(); + + // Unselected label assignments + LLWString label = getCurrentLabel(); + + // overlay with keyboard focus border + if (hasFocus()) + { + F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); + drawBorder(imagep, gFocusMgr.getFocusColor() % alpha, ll_round(lerp(1.f, 3.f, lerp_amt))); + } + + if (use_glow_effect) + { + mCurGlowStrength = lerp(mCurGlowStrength, + mFlashing ? (mFlashingTimer->isCurrentlyHighlighted() || !mFlashingTimer->isFlashingInProgress() || mNeedsHighlight? 1.f : 0.f) : mHoverGlowStrength, + LLSmoothInterpolation::getInterpolant(0.05f)); + } + else + { + mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLSmoothInterpolation::getInterpolant(0.05f)); + } + + // Draw button image, if available. + // Otherwise draw basic rectangular button. + if (imagep != NULL) + { + // apply automatic 50% alpha fade to disabled image + LLColor4 disabled_color = mFadeWhenDisabled ? mDisabledImageColor.get() % 0.5f : mDisabledImageColor.get(); + if ( mScaleImage) + { + imagep->draw(getLocalRect(), (enabled ? mImageColor.get() : disabled_color) % alpha ); + if (mCurGlowStrength > 0.01f) + { + gGL.setSceneBlendType(glow_type); + image_glow->drawSolid(0, 0, getRect().getWidth(), getRect().getHeight(), glow_color % (mCurGlowStrength * alpha)); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } + } + else + { + S32 y = getLocalRect().getHeight() - imagep->getHeight(); + imagep->draw(0, y, (enabled ? mImageColor.get() : disabled_color) % alpha); + if (mCurGlowStrength > 0.01f) + { + gGL.setSceneBlendType(glow_type); + image_glow->drawSolid(0, y, glow_color % (mCurGlowStrength * alpha)); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } + } + } + else + { + // no image + LL_DEBUGS() << "No image for button " << getName() << LL_ENDL; + // draw it in pink so we can find it + gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4::pink1 % alpha, false); + } + + // let overlay image and text play well together + S32 text_left = mLeftHPad; + S32 text_right = getRect().getWidth() - mRightHPad; + S32 text_width = getRect().getWidth() - mLeftHPad - mRightHPad; + + // draw overlay image + if (mImageOverlay.notNull()) + { + // get max width and height (discard level 0) + S32 overlay_width; + S32 overlay_height; + + getOverlayImageSize(overlay_width, overlay_height); + + S32 center_x = getLocalRect().getCenterX(); + S32 center_y = getLocalRect().getCenterY(); + + //FUGLY HACK FOR "DEPRESSED" BUTTONS + if (pressed && mDisplayPressedState) + { + center_y--; + center_x++; + } + + center_y += (mImageOverlayBottomPad - mImageOverlayTopPad); + // fade out overlay images on disabled buttons + LLColor4 overlay_color = mImageOverlayColor.get(); + if (!enabled) + { + overlay_color = mImageOverlayDisabledColor.get(); + } + else if (getToggleState()) + { + overlay_color = mImageOverlaySelectedColor.get(); + } + overlay_color.mV[VALPHA] *= alpha; + + switch(mImageOverlayAlignment) + { + case LLFontGL::LEFT: + text_left += overlay_width + mImgOverlayLabelSpace; + text_width -= overlay_width + mImgOverlayLabelSpace; + mImageOverlay->draw( + mLeftHPad, + center_y - (overlay_height / 2), + overlay_width, + overlay_height, + overlay_color); + break; + case LLFontGL::HCENTER: + mImageOverlay->draw( + center_x - (overlay_width / 2), + center_y - (overlay_height / 2), + overlay_width, + overlay_height, + overlay_color); + break; + case LLFontGL::RIGHT: + text_right -= overlay_width + mImgOverlayLabelSpace; + text_width -= overlay_width + mImgOverlayLabelSpace; + mImageOverlay->draw( + getRect().getWidth() - mRightHPad - overlay_width, + center_y - (overlay_height / 2), + overlay_width, + overlay_height, + overlay_color); + break; + default: + // draw nothing + break; + } + } + + // Draw label + if( !label.empty() ) + { + LLWStringUtil::trim(label); + + S32 x; + switch( mHAlign ) + { + case LLFontGL::RIGHT: + x = text_right; + break; + case LLFontGL::HCENTER: + x = text_left + (text_width / 2); + break; + case LLFontGL::LEFT: + default: + x = text_left; + break; + } + + if (pressed && mDisplayPressedState) + { + x++; + } + + // *NOTE: mantipov: before mUseEllipses is implemented in EXT-279 U32_MAX has been passed as + // max_chars. + // LLFontGL::render expects S32 max_chars variable but process in a separate way -1 value. + // Due to U32_MAX is equal to S32 -1 value I have rest this value for non-ellipses mode. + // Not sure if it is really needed. Probably S32_MAX should be always passed as max_chars. + mLastDrawCharsCount = mGLFont->render(label, 0, + (F32)x, + (F32)(getRect().getHeight() / 2 + mBottomVPad), + label_color % alpha, + mHAlign, LLFontGL::VCENTER, + LLFontGL::NORMAL, + mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW, + S32_MAX, text_width, + NULL, mUseEllipses, mUseFontColor); + } + + LLUICtrl::draw(); +} + +void LLButton::drawBorder(LLUIImage* imagep, const LLColor4& color, S32 size) +{ + if (imagep == NULL) return; + if (mScaleImage) + { + imagep->drawBorder(getLocalRect(), color, size); + } + else + { + S32 y = getLocalRect().getHeight() - imagep->getHeight(); + imagep->drawBorder(0, y, color, size); + } +} + +bool LLButton::getToggleState() const +{ + return getValue().asBoolean(); +} + +void LLButton::setToggleState(bool b) +{ + if( b != getToggleState() ) + { + setControlValue(b); // will fire LLControlVariable callbacks (if any) + setValue(b); // may or may not be redundant + setFlashing(false); // stop flash state whenever the selected/unselected state if reset + // Unselected label assignments + autoResize(); + } +} + +void LLButton::setFlashing(bool b, bool force_flashing/* = false */) +{ + mForceFlashing = force_flashing; + if (mFlashingTimer) + { + mFlashing = b; + (b ? mFlashingTimer->startFlashing() : mFlashingTimer->stopFlashing()); + } + else if (b != mFlashing) + { + mFlashing = b; + mFrameTimer.reset(); + } +} + +bool LLButton::toggleState() +{ + bool flipped = ! getToggleState(); + setToggleState(flipped); + + return flipped; +} + +void LLButton::setLabel( const std::string& label ) +{ + mUnselectedLabel = mSelectedLabel = label; +} + +void LLButton::setLabel( const LLUIString& label ) +{ + mUnselectedLabel = mSelectedLabel = label; +} + +void LLButton::setLabel( const LLStringExplicit& label ) +{ + setLabelUnselected(label); + setLabelSelected(label); +} + +//virtual +bool LLButton::setLabelArg( const std::string& key, const LLStringExplicit& text ) +{ + mUnselectedLabel.setArg(key, text); + mSelectedLabel.setArg(key, text); + return true; +} + +void LLButton::setLabelUnselected( const LLStringExplicit& label ) +{ + mUnselectedLabel = label; +} + +void LLButton::setLabelSelected( const LLStringExplicit& label ) +{ + mSelectedLabel = label; +} + +bool LLButton::labelIsTruncated() const +{ + return getCurrentLabel().getString().size() > mLastDrawCharsCount; +} + +const LLUIString& LLButton::getCurrentLabel() const +{ + return getToggleState() ? mSelectedLabel : mUnselectedLabel; +} + +void LLButton::setImageUnselected(LLPointer image) +{ + mImageUnselected = image; + if (mImageUnselected.isNull()) + { + LL_WARNS() << "Setting default button image for: " << getName() << " to NULL" << LL_ENDL; + } +} + +void LLButton::autoResize() +{ + resize(getCurrentLabel()); +} + +void LLButton::resize(LLUIString label) +{ + // get label length + S32 label_width = mGLFont->getWidth(label.getString()); + // get current btn length + S32 btn_width =getRect().getWidth(); + // check if it need resize + if (mAutoResize) + { + S32 min_width = label_width + mLeftHPad + mRightHPad; + if (mImageOverlay) + { + S32 overlay_width = mImageOverlay->getWidth(); + F32 scale_factor = (getRect().getHeight() - (mImageOverlayBottomPad + mImageOverlayTopPad)) / (F32)mImageOverlay->getHeight(); + overlay_width = ll_round((F32)overlay_width * scale_factor); + + switch(mImageOverlayAlignment) + { + case LLFontGL::LEFT: + case LLFontGL::RIGHT: + min_width += overlay_width + mImgOverlayLabelSpace; + break; + case LLFontGL::HCENTER: + min_width = llmax(min_width, overlay_width + mLeftHPad + mRightHPad); + break; + default: + // draw nothing + break; + } + } + if (btn_width < min_width) + { + reshape(min_width, getRect().getHeight()); + } + } +} +void LLButton::setImages( const std::string &image_name, const std::string &selected_name ) +{ + setImageUnselected(LLUI::getUIImage(image_name)); + setImageSelected(LLUI::getUIImage(selected_name)); +} + +void LLButton::setImageSelected(LLPointer image) +{ + mImageSelected = image; +} + +void LLButton::setImageColor(const LLColor4& c) +{ + mImageColor = c; +} + +void LLButton::setColor(const LLColor4& color) +{ + setImageColor(color); +} + +void LLButton::setImageDisabled(LLPointer image) +{ + mImageDisabled = image; + mDisabledImageColor = mImageColor; + mFadeWhenDisabled = true; +} + +void LLButton::setImageDisabledSelected(LLPointer image) +{ + mImageDisabledSelected = image; + mDisabledImageColor = mImageColor; + mFadeWhenDisabled = true; +} + +void LLButton::setImagePressed(LLPointer image) +{ + mImagePressed = image; +} + +void LLButton::setImageHoverSelected(LLPointer image) +{ + mImageHoverSelected = image; +} + +void LLButton::setImageHoverUnselected(LLPointer image) +{ + mImageHoverUnselected = image; +} + +void LLButton::setImageFlash(LLPointer image) +{ + mImageFlash = image; +} + +void LLButton::setImageOverlay(const std::string& image_name, LLFontGL::HAlign alignment, const LLColor4& color) +{ + if (image_name.empty()) + { + mImageOverlay = NULL; + } + else + { + mImageOverlay = LLUI::getUIImage(image_name); + mImageOverlayAlignment = alignment; + mImageOverlayColor = color; + } +} + +void LLButton::setImageOverlay(const LLUUID& image_id, LLFontGL::HAlign alignment, const LLColor4& color) +{ + if (image_id.isNull()) + { + mImageOverlay = NULL; + } + else + { + mImageOverlay = LLUI::getUIImageByID(image_id); + mImageOverlayAlignment = alignment; + mImageOverlayColor = color; + } +} + +void LLButton::onMouseCaptureLost() +{ + if (mCommitOnCaptureLost + && mMouseDownTimer.getStarted()) + { + if (mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); + + if (mIsToggle) + { + toggleState(); + } + + LLUICtrl::onCommit(); + } + resetMouseDownTimer(); +} + +//------------------------------------------------------------------------- +// Utilities +//------------------------------------------------------------------------- +S32 round_up(S32 grid, S32 value) +{ + S32 mod = value % grid; + + if (mod > 0) + { + // not even multiple + return value + (grid - mod); + } + else + { + return value; + } +} + +void LLButton::addImageAttributeToXML(LLXMLNodePtr node, + const std::string& image_name, + const LLUUID& image_id, + const std::string& xml_tag_name) const +{ + if( !image_name.empty() ) + { + node->createChild(xml_tag_name.c_str(), true)->setStringValue(image_name); + } + else if( image_id != LLUUID::null ) + { + node->createChild((xml_tag_name + "_id").c_str(), true)->setUUIDValue(image_id); + } +} + + +// static +void LLButton::toggleFloaterAndSetToggleState(LLUICtrl* ctrl, const LLSD& sdname) +{ + bool floater_vis = LLFloaterReg::toggleInstance(sdname.asString()); + LLButton* button = dynamic_cast(ctrl); + if (button) + button->setToggleState(floater_vis); +} + +// static +// Gets called once +void LLButton::setFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) +{ + LLButton* button = dynamic_cast(ctrl); + if (!button) + return; + // Get the visibility control name for the floater + std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); + // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) + button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); + // Set the clicked callback to toggle the floater + button->setClickedCallback(boost::bind(&LLFloaterReg::toggleInstance, sdname, LLSD())); +} + +// static +void LLButton::setDockableFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) +{ + LLButton* button = dynamic_cast(ctrl); + if (!button) + return; + // Get the visibility control name for the floater + std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); + // Set the button control value (toggle state) to the floater visibility control (Sets the value as well) + button->setControlVariable(LLFloater::getControlGroup()->getControl(vis_control_name)); + // Set the clicked callback to toggle the floater + button->setClickedCallback(boost::bind(&LLDockableFloater::toggleInstance, sdname)); +} + +// static +void LLButton::showHelp(LLUICtrl* ctrl, const LLSD& sdname) +{ + // search back through the button's parents for a panel + // with a help_topic string defined + std::string help_topic; + if (LLUI::getInstance()->mHelpImpl && + ctrl->findHelpTopic(help_topic)) + { + LLUI::getInstance()->mHelpImpl->showTopic(help_topic); + return; // success + } + + // display an error if we can't find a help_topic string. + // fix this by adding a help_topic attribute to the xui file + LLNotificationsUtil::add("UnableToFindHelpTopic"); +} + +void LLButton::resetMouseDownTimer() +{ + mMouseDownTimer.stop(); + mMouseDownTimer.reset(); +} + +bool LLButton::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + // just treat a double click as a second click + return handleMouseDown(x, y, mask); +} diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index 371b716779..fed5cdcc50 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -1,406 +1,406 @@ -/** - * @file llbutton.h - * @brief Header for buttons - * - * $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$ - */ - -#ifndef LL_LLBUTTON_H -#define LL_LLBUTTON_H - -#include "lluuid.h" -#include "llbadgeowner.h" -#include "llcontrol.h" -#include "llflashtimer.h" -#include "lluictrl.h" -#include "v4color.h" -#include "llframetimer.h" -#include "llfontgl.h" -#include "lluiimage.h" -#include "lluistring.h" - -// -// Constants -// - -// PLEASE please use these "constants" when building your own buttons. -extern S32 LLBUTTON_H_PAD; -extern S32 BTN_HEIGHT_SMALL; -extern S32 BTN_HEIGHT; -extern S32 BTN_DROP_SHADOW; - -// -// Helpful functions -// -S32 round_up(S32 grid, S32 value); - - -class LLUICtrlFactory; - -// -// Classes -// - -class LLButton -: public LLUICtrl, public LLBadgeOwner -, public ll::ui::SearchableControl -{ -public: - struct Params - : public LLInitParam::Block - { - // text label - Optional label_selected; - Optional label_shadow; - Optional auto_resize; - Optional use_ellipses; - Optional use_font_color; - - // images - Optional image_unselected, - image_selected, - image_hover_selected, - image_hover_unselected, - image_disabled_selected, - image_disabled, - image_flash, - image_pressed, - image_pressed_selected, - image_overlay; - - Optional image_overlay_alignment; - - // colors - Optional label_color, - label_color_selected, - label_color_disabled, - label_color_disabled_selected, - image_color, - image_color_disabled, - image_overlay_color, - image_overlay_selected_color, - image_overlay_disabled_color, - flash_color; - - // layout - Optional pad_right; - Optional pad_left; - Optional pad_bottom; // under text label - - //image overlay paddings - Optional image_top_pad; - Optional image_bottom_pad; - - /** - * Space between image_overlay and label - */ - Optional imgoverlay_label_space; - - // callbacks - Optional click_callback, // alias -> commit_callback - mouse_down_callback, - mouse_up_callback, - mouse_held_callback; - - // misc - Optional is_toggle, - scale_image, - commit_on_return, - commit_on_capture_lost, - display_pressed_state; - - Optional hover_glow_amount; - Optional held_down_delay; - - Optional use_draw_context_alpha; - - Optional badge; - - Optional handle_right_mouse; - - Optional button_flash_enable; - Optional button_flash_count; - Optional button_flash_rate; - - Params(); - }; - -protected: - friend class LLUICtrlFactory; - LLButton(const Params&); - -public: - - ~LLButton(); - // For backward compatability only - typedef boost::function button_callback_t; - - void addImageAttributeToXML(LLXMLNodePtr node, const std::string& imageName, - const LLUUID& imageID,const std::string& xmlTagName) const; - virtual bool handleUnicodeCharHere(llwchar uni_char); - virtual bool handleKeyHere(KEY key, MASK mask); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); - virtual void draw(); - /*virtual*/ bool postBuild(); - - virtual void onMouseLeave(S32 x, S32 y, MASK mask); - virtual void onMouseCaptureLost(); - - virtual void onCommit(); - - void setUnselectedLabelColor( const LLColor4& c ) { mUnselectedLabelColor = c; } - void setSelectedLabelColor( const LLColor4& c ) { mSelectedLabelColor = c; } - void setUseEllipses( bool use_ellipses ) { mUseEllipses = use_ellipses; } - void setUseFontColor( bool use_font_color) { mUseFontColor = use_font_color; } - - - boost::signals2::connection setClickedCallback(const CommitCallbackParam& cb); - boost::signals2::connection setMouseDownCallback(const CommitCallbackParam& cb); - boost::signals2::connection setMouseUpCallback(const CommitCallbackParam& cb); - boost::signals2::connection setHeldDownCallback(const CommitCallbackParam& cb); - - boost::signals2::connection setClickedCallback( const commit_signal_t::slot_type& cb ); // mouse down and up within button - boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ); - boost::signals2::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ); // mouse up, EVEN IF NOT IN BUTTON - // Passes a 'count' parameter in the commit param payload, i.e. param["count"]) - boost::signals2::connection setHeldDownCallback( const commit_signal_t::slot_type& cb ); // Mouse button held down and in button - - - // *TODO: Deprecate (for backwards compatability only) - boost::signals2::connection setClickedCallback( button_callback_t cb, void* data ); - boost::signals2::connection setMouseDownCallback( button_callback_t cb, void* data ); - boost::signals2::connection setMouseUpCallback( button_callback_t cb, void* data ); - boost::signals2::connection setHeldDownCallback( button_callback_t cb, void* data ); - - void setHeldDownDelay( F32 seconds, S32 frames = 0) { mHeldDownDelay = seconds; mHeldDownFrameDelay = frames; } - - F32 getHeldDownTime() const { return mMouseDownTimer.getElapsedTimeF32(); } - - bool toggleState(); - bool getToggleState() const; - void setToggleState(bool b); - - void setHighlight(bool b); - void setFlashing( bool b, bool force_flashing = false ); - bool getFlashing() const { return mFlashing; } - LLFlashTimer* getFlashTimer() {return mFlashingTimer;} - void setFlashColor(const LLUIColor &color) { mFlashBgColor = color; }; - - void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; } - LLFontGL::HAlign getHAlign() const { return mHAlign; } - void setLeftHPad( S32 pad ) { mLeftHPad = pad; } - void setRightHPad( S32 pad ) { mRightHPad = pad; } - - void setImageOverlayTopPad( S32 pad ) { mImageOverlayTopPad = pad; } - S32 getImageOverlayTopPad() const { return mImageOverlayTopPad; } - void setImageOverlayBottomPad( S32 pad ) { mImageOverlayBottomPad = pad; } - S32 getImageOverlayBottomPad() const { return mImageOverlayBottomPad; } - - const std::string getLabelUnselected() const { return wstring_to_utf8str(mUnselectedLabel); } - const std::string getLabelSelected() const { return wstring_to_utf8str(mSelectedLabel); } - - void setImageColor(const std::string& color_control); - void setImageColor(const LLColor4& c); - /*virtual*/ void setColor(const LLColor4& c); - - void setImages(const std::string &image_name, const std::string &selected_name); - - void setDisabledImageColor(const LLColor4& c) { mDisabledImageColor = c; } - - void setDisabledSelectedLabelColor( const LLColor4& c ) { mDisabledSelectedLabelColor = c; } - - void setImageOverlay(const std::string& image_name, LLFontGL::HAlign alignment = LLFontGL::HCENTER, const LLColor4& color = LLColor4::white); - void setImageOverlay(const LLUUID& image_id, LLFontGL::HAlign alignment = LLFontGL::HCENTER, const LLColor4& color = LLColor4::white); - LLPointer getImageOverlay() { return mImageOverlay; } - LLFontGL::HAlign getImageOverlayHAlign() const { return mImageOverlayAlignment; } - - void autoResize(); // resize with label of current btn state - void resize(LLUIString label); // resize with label input - void setLabel(const std::string& label); - void setLabel(const LLUIString& label); - void setLabel( const LLStringExplicit& label); - virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); - void setLabelUnselected(const LLStringExplicit& label); - void setLabelSelected(const LLStringExplicit& label); - void setDisabledLabelColor( const LLColor4& c ) { mDisabledLabelColor = c; } - - void setFont(const LLFontGL *font) - { mGLFont = ( font ? font : LLFontGL::getFontSansSerif()); } - const LLFontGL* getFont() const { return mGLFont; } - const std::string& getText() const { return getCurrentLabel().getString(); } - - S32 getLastDrawCharsCount() const { return mLastDrawCharsCount; } - bool labelIsTruncated() const; - const LLUIString& getCurrentLabel() const; - - void setScaleImage(bool scale) { mScaleImage = scale; } - bool getScaleImage() const { return mScaleImage; } - - void setDropShadowedText(bool b) { mDropShadowedText = b; } - - void setBorderEnabled(bool b) { mBorderEnabled = b; } - - void setHoverGlowStrength(F32 strength) { mHoverGlowStrength = strength; } - - void setImageUnselected(LLPointer image); - void setImageSelected(LLPointer image); - void setImageHoverSelected(LLPointer image); - void setImageHoverUnselected(LLPointer image); - void setImageDisabled(LLPointer image); - void setImageDisabledSelected(LLPointer image); - void setImageFlash(LLPointer image); - void setImagePressed(LLPointer image); - - void setCommitOnReturn(bool commit) { mCommitOnReturn = commit; } - bool getCommitOnReturn() const { return mCommitOnReturn; } - - static void onHeldDown(void *userdata); // to be called by gIdleCallbacks - static void toggleFloaterAndSetToggleState(LLUICtrl* ctrl, const LLSD& sdname); - static void setFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname); - static void setDockableFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname); - static void showHelp(LLUICtrl* ctrl, const LLSD& sdname); - - void setForcePressedState(bool b) { mForcePressedState = b; } - - void setAutoResize(bool auto_resize) { mAutoResize = auto_resize; } - -protected: - LLPointer getImageUnselected() const { return mImageUnselected; } - LLPointer getImageSelected() const { return mImageSelected; } - void getOverlayImageSize(S32& overlay_width, S32& overlay_height); - - LLFrameTimer mMouseDownTimer; - bool mNeedsHighlight; - S32 mButtonFlashCount; - F32 mButtonFlashRate; - - void drawBorder(LLUIImage* imagep, const LLColor4& color, S32 size); - void resetMouseDownTimer(); - - commit_signal_t* mMouseDownSignal; - commit_signal_t* mMouseUpSignal; - commit_signal_t* mHeldDownSignal; - - const LLFontGL* mGLFont; - - S32 mMouseDownFrame; - S32 mMouseHeldDownCount; // Counter for parameter passed to held-down callback - F32 mHeldDownDelay; // seconds, after which held-down callbacks get called - S32 mHeldDownFrameDelay; // frames, after which held-down callbacks get called - S32 mLastDrawCharsCount; - - LLPointer mImageOverlay; - LLFontGL::HAlign mImageOverlayAlignment; - LLUIColor mImageOverlayColor; - LLUIColor mImageOverlaySelectedColor; - LLUIColor mImageOverlayDisabledColor; - - LLPointer mImageUnselected; - LLUIString mUnselectedLabel; - LLUIColor mUnselectedLabelColor; - - LLPointer mImageSelected; - LLUIString mSelectedLabel; - LLUIColor mSelectedLabelColor; - - LLPointer mImageHoverSelected; - - LLPointer mImageHoverUnselected; - - LLPointer mImageDisabled; - LLUIColor mDisabledLabelColor; - - LLPointer mImageDisabledSelected; - LLUIString mDisabledSelectedLabel; - LLUIColor mDisabledSelectedLabelColor; - - LLPointer mImagePressed; - LLPointer mImagePressedSelected; - - /* There are two ways an image can flash- by making changes in color according to flash_color attribute - or by changing icon from current to the one specified in image_flash. Second way is used only if - flash icon name is set in attributes(by default it isn't). First way is used otherwise. */ - LLPointer mImageFlash; - - LLUIColor mFlashBgColor; - - LLUIColor mImageColor; - LLUIColor mDisabledImageColor; - - bool mIsToggle; - bool mScaleImage; - - bool mDropShadowedText; - bool mAutoResize; - bool mUseEllipses; - bool mUseFontColor; - bool mBorderEnabled; - bool mFlashing; - - LLFontGL::HAlign mHAlign; - S32 mLeftHPad; - S32 mRightHPad; - S32 mBottomVPad; // under text label - - S32 mImageOverlayTopPad; - S32 mImageOverlayBottomPad; - - bool mUseDrawContextAlpha; - - /* - * Space between image_overlay and label - */ - S32 mImgOverlayLabelSpace; - - F32 mHoverGlowStrength; - F32 mCurGlowStrength; - - bool mCommitOnReturn; - bool mCommitOnCaptureLost; - bool mFadeWhenDisabled; - bool mForcePressedState; - bool mDisplayPressedState; - - LLFrameTimer mFrameTimer; - LLFlashTimer * mFlashingTimer; - bool mForceFlashing; // Stick flashing color even if button is pressed - bool mHandleRightMouse; - -protected: - virtual std::string _getSearchText() const - { - return getLabelUnselected() + getToolTip(); - } -}; - -// Build time optimization, generate once in .cpp file -#ifndef LLBUTTON_CPP -extern template class LLButton* LLView::getChild( - const std::string& name, bool recurse) const; -#endif - -#endif // LL_LLBUTTON_H +/** + * @file llbutton.h + * @brief Header for buttons + * + * $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$ + */ + +#ifndef LL_LLBUTTON_H +#define LL_LLBUTTON_H + +#include "lluuid.h" +#include "llbadgeowner.h" +#include "llcontrol.h" +#include "llflashtimer.h" +#include "lluictrl.h" +#include "v4color.h" +#include "llframetimer.h" +#include "llfontgl.h" +#include "lluiimage.h" +#include "lluistring.h" + +// +// Constants +// + +// PLEASE please use these "constants" when building your own buttons. +extern S32 LLBUTTON_H_PAD; +extern S32 BTN_HEIGHT_SMALL; +extern S32 BTN_HEIGHT; +extern S32 BTN_DROP_SHADOW; + +// +// Helpful functions +// +S32 round_up(S32 grid, S32 value); + + +class LLUICtrlFactory; + +// +// Classes +// + +class LLButton +: public LLUICtrl, public LLBadgeOwner +, public ll::ui::SearchableControl +{ +public: + struct Params + : public LLInitParam::Block + { + // text label + Optional label_selected; + Optional label_shadow; + Optional auto_resize; + Optional use_ellipses; + Optional use_font_color; + + // images + Optional image_unselected, + image_selected, + image_hover_selected, + image_hover_unselected, + image_disabled_selected, + image_disabled, + image_flash, + image_pressed, + image_pressed_selected, + image_overlay; + + Optional image_overlay_alignment; + + // colors + Optional label_color, + label_color_selected, + label_color_disabled, + label_color_disabled_selected, + image_color, + image_color_disabled, + image_overlay_color, + image_overlay_selected_color, + image_overlay_disabled_color, + flash_color; + + // layout + Optional pad_right; + Optional pad_left; + Optional pad_bottom; // under text label + + //image overlay paddings + Optional image_top_pad; + Optional image_bottom_pad; + + /** + * Space between image_overlay and label + */ + Optional imgoverlay_label_space; + + // callbacks + Optional click_callback, // alias -> commit_callback + mouse_down_callback, + mouse_up_callback, + mouse_held_callback; + + // misc + Optional is_toggle, + scale_image, + commit_on_return, + commit_on_capture_lost, + display_pressed_state; + + Optional hover_glow_amount; + Optional held_down_delay; + + Optional use_draw_context_alpha; + + Optional badge; + + Optional handle_right_mouse; + + Optional button_flash_enable; + Optional button_flash_count; + Optional button_flash_rate; + + Params(); + }; + +protected: + friend class LLUICtrlFactory; + LLButton(const Params&); + +public: + + ~LLButton(); + // For backward compatability only + typedef boost::function button_callback_t; + + void addImageAttributeToXML(LLXMLNodePtr node, const std::string& imageName, + const LLUUID& imageID,const std::string& xmlTagName) const; + virtual bool handleUnicodeCharHere(llwchar uni_char); + virtual bool handleKeyHere(KEY key, MASK mask); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); + virtual void draw(); + /*virtual*/ bool postBuild(); + + virtual void onMouseLeave(S32 x, S32 y, MASK mask); + virtual void onMouseCaptureLost(); + + virtual void onCommit(); + + void setUnselectedLabelColor( const LLColor4& c ) { mUnselectedLabelColor = c; } + void setSelectedLabelColor( const LLColor4& c ) { mSelectedLabelColor = c; } + void setUseEllipses( bool use_ellipses ) { mUseEllipses = use_ellipses; } + void setUseFontColor( bool use_font_color) { mUseFontColor = use_font_color; } + + + boost::signals2::connection setClickedCallback(const CommitCallbackParam& cb); + boost::signals2::connection setMouseDownCallback(const CommitCallbackParam& cb); + boost::signals2::connection setMouseUpCallback(const CommitCallbackParam& cb); + boost::signals2::connection setHeldDownCallback(const CommitCallbackParam& cb); + + boost::signals2::connection setClickedCallback( const commit_signal_t::slot_type& cb ); // mouse down and up within button + boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ); // mouse up, EVEN IF NOT IN BUTTON + // Passes a 'count' parameter in the commit param payload, i.e. param["count"]) + boost::signals2::connection setHeldDownCallback( const commit_signal_t::slot_type& cb ); // Mouse button held down and in button + + + // *TODO: Deprecate (for backwards compatability only) + boost::signals2::connection setClickedCallback( button_callback_t cb, void* data ); + boost::signals2::connection setMouseDownCallback( button_callback_t cb, void* data ); + boost::signals2::connection setMouseUpCallback( button_callback_t cb, void* data ); + boost::signals2::connection setHeldDownCallback( button_callback_t cb, void* data ); + + void setHeldDownDelay( F32 seconds, S32 frames = 0) { mHeldDownDelay = seconds; mHeldDownFrameDelay = frames; } + + F32 getHeldDownTime() const { return mMouseDownTimer.getElapsedTimeF32(); } + + bool toggleState(); + bool getToggleState() const; + void setToggleState(bool b); + + void setHighlight(bool b); + void setFlashing( bool b, bool force_flashing = false ); + bool getFlashing() const { return mFlashing; } + LLFlashTimer* getFlashTimer() {return mFlashingTimer;} + void setFlashColor(const LLUIColor &color) { mFlashBgColor = color; }; + + void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; } + LLFontGL::HAlign getHAlign() const { return mHAlign; } + void setLeftHPad( S32 pad ) { mLeftHPad = pad; } + void setRightHPad( S32 pad ) { mRightHPad = pad; } + + void setImageOverlayTopPad( S32 pad ) { mImageOverlayTopPad = pad; } + S32 getImageOverlayTopPad() const { return mImageOverlayTopPad; } + void setImageOverlayBottomPad( S32 pad ) { mImageOverlayBottomPad = pad; } + S32 getImageOverlayBottomPad() const { return mImageOverlayBottomPad; } + + const std::string getLabelUnselected() const { return wstring_to_utf8str(mUnselectedLabel); } + const std::string getLabelSelected() const { return wstring_to_utf8str(mSelectedLabel); } + + void setImageColor(const std::string& color_control); + void setImageColor(const LLColor4& c); + /*virtual*/ void setColor(const LLColor4& c); + + void setImages(const std::string &image_name, const std::string &selected_name); + + void setDisabledImageColor(const LLColor4& c) { mDisabledImageColor = c; } + + void setDisabledSelectedLabelColor( const LLColor4& c ) { mDisabledSelectedLabelColor = c; } + + void setImageOverlay(const std::string& image_name, LLFontGL::HAlign alignment = LLFontGL::HCENTER, const LLColor4& color = LLColor4::white); + void setImageOverlay(const LLUUID& image_id, LLFontGL::HAlign alignment = LLFontGL::HCENTER, const LLColor4& color = LLColor4::white); + LLPointer getImageOverlay() { return mImageOverlay; } + LLFontGL::HAlign getImageOverlayHAlign() const { return mImageOverlayAlignment; } + + void autoResize(); // resize with label of current btn state + void resize(LLUIString label); // resize with label input + void setLabel(const std::string& label); + void setLabel(const LLUIString& label); + void setLabel( const LLStringExplicit& label); + virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); + void setLabelUnselected(const LLStringExplicit& label); + void setLabelSelected(const LLStringExplicit& label); + void setDisabledLabelColor( const LLColor4& c ) { mDisabledLabelColor = c; } + + void setFont(const LLFontGL *font) + { mGLFont = ( font ? font : LLFontGL::getFontSansSerif()); } + const LLFontGL* getFont() const { return mGLFont; } + const std::string& getText() const { return getCurrentLabel().getString(); } + + S32 getLastDrawCharsCount() const { return mLastDrawCharsCount; } + bool labelIsTruncated() const; + const LLUIString& getCurrentLabel() const; + + void setScaleImage(bool scale) { mScaleImage = scale; } + bool getScaleImage() const { return mScaleImage; } + + void setDropShadowedText(bool b) { mDropShadowedText = b; } + + void setBorderEnabled(bool b) { mBorderEnabled = b; } + + void setHoverGlowStrength(F32 strength) { mHoverGlowStrength = strength; } + + void setImageUnselected(LLPointer image); + void setImageSelected(LLPointer image); + void setImageHoverSelected(LLPointer image); + void setImageHoverUnselected(LLPointer image); + void setImageDisabled(LLPointer image); + void setImageDisabledSelected(LLPointer image); + void setImageFlash(LLPointer image); + void setImagePressed(LLPointer image); + + void setCommitOnReturn(bool commit) { mCommitOnReturn = commit; } + bool getCommitOnReturn() const { return mCommitOnReturn; } + + static void onHeldDown(void *userdata); // to be called by gIdleCallbacks + static void toggleFloaterAndSetToggleState(LLUICtrl* ctrl, const LLSD& sdname); + static void setFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname); + static void setDockableFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname); + static void showHelp(LLUICtrl* ctrl, const LLSD& sdname); + + void setForcePressedState(bool b) { mForcePressedState = b; } + + void setAutoResize(bool auto_resize) { mAutoResize = auto_resize; } + +protected: + LLPointer getImageUnselected() const { return mImageUnselected; } + LLPointer getImageSelected() const { return mImageSelected; } + void getOverlayImageSize(S32& overlay_width, S32& overlay_height); + + LLFrameTimer mMouseDownTimer; + bool mNeedsHighlight; + S32 mButtonFlashCount; + F32 mButtonFlashRate; + + void drawBorder(LLUIImage* imagep, const LLColor4& color, S32 size); + void resetMouseDownTimer(); + + commit_signal_t* mMouseDownSignal; + commit_signal_t* mMouseUpSignal; + commit_signal_t* mHeldDownSignal; + + const LLFontGL* mGLFont; + + S32 mMouseDownFrame; + S32 mMouseHeldDownCount; // Counter for parameter passed to held-down callback + F32 mHeldDownDelay; // seconds, after which held-down callbacks get called + S32 mHeldDownFrameDelay; // frames, after which held-down callbacks get called + S32 mLastDrawCharsCount; + + LLPointer mImageOverlay; + LLFontGL::HAlign mImageOverlayAlignment; + LLUIColor mImageOverlayColor; + LLUIColor mImageOverlaySelectedColor; + LLUIColor mImageOverlayDisabledColor; + + LLPointer mImageUnselected; + LLUIString mUnselectedLabel; + LLUIColor mUnselectedLabelColor; + + LLPointer mImageSelected; + LLUIString mSelectedLabel; + LLUIColor mSelectedLabelColor; + + LLPointer mImageHoverSelected; + + LLPointer mImageHoverUnselected; + + LLPointer mImageDisabled; + LLUIColor mDisabledLabelColor; + + LLPointer mImageDisabledSelected; + LLUIString mDisabledSelectedLabel; + LLUIColor mDisabledSelectedLabelColor; + + LLPointer mImagePressed; + LLPointer mImagePressedSelected; + + /* There are two ways an image can flash- by making changes in color according to flash_color attribute + or by changing icon from current to the one specified in image_flash. Second way is used only if + flash icon name is set in attributes(by default it isn't). First way is used otherwise. */ + LLPointer mImageFlash; + + LLUIColor mFlashBgColor; + + LLUIColor mImageColor; + LLUIColor mDisabledImageColor; + + bool mIsToggle; + bool mScaleImage; + + bool mDropShadowedText; + bool mAutoResize; + bool mUseEllipses; + bool mUseFontColor; + bool mBorderEnabled; + bool mFlashing; + + LLFontGL::HAlign mHAlign; + S32 mLeftHPad; + S32 mRightHPad; + S32 mBottomVPad; // under text label + + S32 mImageOverlayTopPad; + S32 mImageOverlayBottomPad; + + bool mUseDrawContextAlpha; + + /* + * Space between image_overlay and label + */ + S32 mImgOverlayLabelSpace; + + F32 mHoverGlowStrength; + F32 mCurGlowStrength; + + bool mCommitOnReturn; + bool mCommitOnCaptureLost; + bool mFadeWhenDisabled; + bool mForcePressedState; + bool mDisplayPressedState; + + LLFrameTimer mFrameTimer; + LLFlashTimer * mFlashingTimer; + bool mForceFlashing; // Stick flashing color even if button is pressed + bool mHandleRightMouse; + +protected: + virtual std::string _getSearchText() const + { + return getLabelUnselected() + getToolTip(); + } +}; + +// Build time optimization, generate once in .cpp file +#ifndef LLBUTTON_CPP +extern template class LLButton* LLView::getChild( + const std::string& name, bool recurse) const; +#endif + +#endif // LL_LLBUTTON_H diff --git a/indra/llui/llchat.h b/indra/llui/llchat.h index 318e7e327e..5f75ed2f8d 100644 --- a/indra/llui/llchat.h +++ b/indra/llui/llchat.h @@ -1,112 +1,112 @@ -/** - * @file llchat.h - * @author James Cook - * @brief Chat constants and data structures. - * - * $LicenseInfo:firstyear=2006&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$ - */ - -#ifndef LL_LLCHAT_H -#define LL_LLCHAT_H - -#include "lluuid.h" -#include "v3math.h" - -// enumerations used by the chat system -typedef enum e_chat_source_type -{ - CHAT_SOURCE_SYSTEM = 0, - CHAT_SOURCE_AGENT = 1, - CHAT_SOURCE_OBJECT = 2, - CHAT_SOURCE_TELEPORT = 3, - CHAT_SOURCE_UNKNOWN = 4, - CHAT_SOURCE_REGION = 5, -} EChatSourceType; - -typedef enum e_chat_type -{ - CHAT_TYPE_WHISPER = 0, - CHAT_TYPE_NORMAL = 1, - CHAT_TYPE_SHOUT = 2, - CHAT_TYPE_START = 4, - CHAT_TYPE_STOP = 5, - CHAT_TYPE_DEBUG_MSG = 6, - CHAT_TYPE_REGION = 7, - CHAT_TYPE_OWNER = 8, - CHAT_TYPE_DIRECT = 9 // From llRegionSayTo() -} EChatType; - -typedef enum e_chat_audible_level -{ - CHAT_AUDIBLE_NOT = -1, - CHAT_AUDIBLE_BARELY = 0, - CHAT_AUDIBLE_FULLY = 1 -} EChatAudible; - -typedef enum e_chat_style -{ - CHAT_STYLE_NORMAL, - CHAT_STYLE_IRC, - CHAT_STYLE_HISTORY, - CHAT_STYLE_TELEPORT_SEP -}EChatStyle; - -// A piece of chat -class LLChat -{ -public: - LLChat(const std::string& text = std::string()) - : mText(text), - mFromName(), - mFromID(), - mNotifId(), - mOwnerID(), - mSourceType(CHAT_SOURCE_AGENT), - mChatType(CHAT_TYPE_NORMAL), - mAudible(CHAT_AUDIBLE_FULLY), - mMuted(false), - mTime(0.0), - mTimeStr(), - mPosAgent(), - mURL(), - mChatStyle(CHAT_STYLE_NORMAL), - mSessionID() - { } - - std::string mText; // UTF-8 line of text - std::string mFromName; // agent or object name - LLUUID mFromID; // agent id or object id - LLUUID mNotifId; - LLUUID mOwnerID; - EChatSourceType mSourceType; - EChatType mChatType; - EChatAudible mAudible; - bool mMuted; // pass muted chat to maintain list of chatters - F64 mTime; // viewer only, seconds from viewer start - std::string mTimeStr; - LLVector3 mPosAgent; - std::string mURL; - EChatStyle mChatStyle; - LLUUID mSessionID; -}; - -#endif +/** + * @file llchat.h + * @author James Cook + * @brief Chat constants and data structures. + * + * $LicenseInfo:firstyear=2006&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$ + */ + +#ifndef LL_LLCHAT_H +#define LL_LLCHAT_H + +#include "lluuid.h" +#include "v3math.h" + +// enumerations used by the chat system +typedef enum e_chat_source_type +{ + CHAT_SOURCE_SYSTEM = 0, + CHAT_SOURCE_AGENT = 1, + CHAT_SOURCE_OBJECT = 2, + CHAT_SOURCE_TELEPORT = 3, + CHAT_SOURCE_UNKNOWN = 4, + CHAT_SOURCE_REGION = 5, +} EChatSourceType; + +typedef enum e_chat_type +{ + CHAT_TYPE_WHISPER = 0, + CHAT_TYPE_NORMAL = 1, + CHAT_TYPE_SHOUT = 2, + CHAT_TYPE_START = 4, + CHAT_TYPE_STOP = 5, + CHAT_TYPE_DEBUG_MSG = 6, + CHAT_TYPE_REGION = 7, + CHAT_TYPE_OWNER = 8, + CHAT_TYPE_DIRECT = 9 // From llRegionSayTo() +} EChatType; + +typedef enum e_chat_audible_level +{ + CHAT_AUDIBLE_NOT = -1, + CHAT_AUDIBLE_BARELY = 0, + CHAT_AUDIBLE_FULLY = 1 +} EChatAudible; + +typedef enum e_chat_style +{ + CHAT_STYLE_NORMAL, + CHAT_STYLE_IRC, + CHAT_STYLE_HISTORY, + CHAT_STYLE_TELEPORT_SEP +}EChatStyle; + +// A piece of chat +class LLChat +{ +public: + LLChat(const std::string& text = std::string()) + : mText(text), + mFromName(), + mFromID(), + mNotifId(), + mOwnerID(), + mSourceType(CHAT_SOURCE_AGENT), + mChatType(CHAT_TYPE_NORMAL), + mAudible(CHAT_AUDIBLE_FULLY), + mMuted(false), + mTime(0.0), + mTimeStr(), + mPosAgent(), + mURL(), + mChatStyle(CHAT_STYLE_NORMAL), + mSessionID() + { } + + std::string mText; // UTF-8 line of text + std::string mFromName; // agent or object name + LLUUID mFromID; // agent id or object id + LLUUID mNotifId; + LLUUID mOwnerID; + EChatSourceType mSourceType; + EChatType mChatType; + EChatAudible mAudible; + bool mMuted; // pass muted chat to maintain list of chatters + F64 mTime; // viewer only, seconds from viewer start + std::string mTimeStr; + LLVector3 mPosAgent; + std::string mURL; + EChatStyle mChatStyle; + LLUUID mSessionID; +}; + +#endif diff --git a/indra/llui/llchatentry.cpp b/indra/llui/llchatentry.cpp index 3aa65355e5..da5afd0386 100644 --- a/indra/llui/llchatentry.cpp +++ b/indra/llui/llchatentry.cpp @@ -1,251 +1,251 @@ -/** - * @file llchatentry.cpp - * @brief LLChatEntry implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, 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 "llscrollcontainer.h" - -#include "llchatentry.h" - -static LLDefaultChildRegistry::Register r("chat_editor"); - -LLChatEntry::Params::Params() -: has_history("has_history", true), - is_expandable("is_expandable", false), - expand_lines_count("expand_lines_count", 1) -{} - -LLChatEntry::LLChatEntry(const Params& p) -: LLTextEditor(p), - mTextExpandedSignal(NULL), - mHasHistory(p.has_history), - mIsExpandable(p.is_expandable), - mExpandLinesCount(p.expand_lines_count), - mPrevLinesCount(0), - mSingleLineMode(false), - mPrevExpandedLineCount(S32_MAX) -{ - // Initialize current history line iterator - mCurrentHistoryLine = mLineHistory.begin(); - - mAutoIndent = false; - keepSelectionOnReturn(true); -} - -LLChatEntry::~LLChatEntry() -{ - delete mTextExpandedSignal; -} - -void LLChatEntry::draw() -{ - if(mIsExpandable) - { - reflow(); - expandText(); - } - LLTextEditor::draw(); -} - -void LLChatEntry::onCommit() -{ - updateHistory(); - LLTextEditor::onCommit(); -} - -boost::signals2::connection LLChatEntry::setTextExpandedCallback(const commit_signal_t::slot_type& cb) -{ - if (!mTextExpandedSignal) - { - mTextExpandedSignal = new commit_signal_t(); - } - return mTextExpandedSignal->connect(cb); -} - -void LLChatEntry::expandText() -{ - S32 line_count = mSingleLineMode ? 1 : mExpandLinesCount; - - int visible_lines_count = llabs(getVisibleLines(true).first - getVisibleLines(true).second); - bool can_changed = getLineCount() <= line_count || line_count < mPrevExpandedLineCount ; - mPrevExpandedLineCount = line_count; - - // true if pasted text has more lines than expand height limit and expand limit is not reached yet - bool text_pasted = (getLineCount() > line_count) && (visible_lines_count < line_count); - - if (mIsExpandable && (can_changed || text_pasted || mSingleLineMode) && getLineCount() != mPrevLinesCount) - { - int lines_height = 0; - if (text_pasted) - { - // text is pasted and now mLineInfoList.size() > mExpandLineCounts and mLineInfoList is not empty, - // so lines_height is the sum of the last 'expanded_line_count' lines height - lines_height = (mLineInfoList.end() - line_count)->mRect.mTop - mLineInfoList.back().mRect.mBottom; - } - else - { - lines_height = mLineInfoList.begin()->mRect.mTop - mLineInfoList.back().mRect.mBottom; - } - - int height = mVPad * 2 + lines_height; - - LLRect doc_rect = getRect(); - doc_rect.setOriginAndSize(doc_rect.mLeft, doc_rect.mBottom, doc_rect.getWidth(), height); - setShape(doc_rect); - - mPrevLinesCount = getLineCount(); - - if (mTextExpandedSignal) - { - (*mTextExpandedSignal)(this, LLSD() ); - } - - needsReflow(); - } -} - -// line history support -void LLChatEntry::updateHistory() -{ - // On history enabled, remember committed line and - // reset current history line number. - // Be sure only to remember lines that are not empty and that are - // different from the last on the list. - if (mHasHistory && getLength()) - { - // Add text to history, ignoring duplicates - if (mLineHistory.empty() || getText() != mLineHistory.back()) - { - mLineHistory.push_back(getText()); - } - - mCurrentHistoryLine = mLineHistory.end(); - } -} - -void LLChatEntry::beforeValueChange() -{ - if(this->getLength() == 0 && !mLabel.empty()) - { - this->clearSegments(); - } -} - -void LLChatEntry::onValueChange(S32 start, S32 end) -{ - //Internally resetLabel() must meet a condition before it can reset the label - resetLabel(); -} - -bool LLChatEntry::useLabel() const -{ - return !getLength() && !mLabel.empty(); -} - -void LLChatEntry::onFocusReceived() -{ - LLUICtrl::onFocusReceived(); - updateAllowingLanguageInput(); -} - -void LLChatEntry::onFocusLost() -{ - LLTextEditor::focusLostHelper(); - LLUICtrl::onFocusLost(); -} - -bool LLChatEntry::handleSpecialKey(const KEY key, const MASK mask) -{ - bool handled = false; - - LLTextEditor::handleSpecialKey(key, mask); - - switch(key) - { - case KEY_RETURN: - if (MASK_NONE == mask) - { - needsReflow(); - } - break; - - case KEY_UP: - if (mHasHistory && MASK_CONTROL == mask) - { - if (!mLineHistory.empty() && mCurrentHistoryLine > mLineHistory.begin()) - { - setText(*(--mCurrentHistoryLine)); - endOfDoc(); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - handled = true; - } - break; - - case KEY_DOWN: - if (mHasHistory && MASK_CONTROL == mask) - { - if (!mLineHistory.empty() && mCurrentHistoryLine < (mLineHistory.end() - 1) ) - { - setText(*(++mCurrentHistoryLine)); - endOfDoc(); - } - else if (!mLineHistory.empty() && mCurrentHistoryLine == (mLineHistory.end() - 1) ) - { - mCurrentHistoryLine++; - std::string empty(""); - setText(empty); - needsReflow(); - endOfDoc(); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - handled = true; - } - break; - - default: - break; - } - - return handled; -} - -void LLChatEntry::enableSingleLineMode(bool single_line_mode) -{ - if (mScroller) - { - mScroller->setSize(single_line_mode ? 0 : -1); - } - - mSingleLineMode = single_line_mode; - mPrevLinesCount = -1; - setWordWrap(!single_line_mode); -} +/** + * @file llchatentry.cpp + * @brief LLChatEntry implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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 "llscrollcontainer.h" + +#include "llchatentry.h" + +static LLDefaultChildRegistry::Register r("chat_editor"); + +LLChatEntry::Params::Params() +: has_history("has_history", true), + is_expandable("is_expandable", false), + expand_lines_count("expand_lines_count", 1) +{} + +LLChatEntry::LLChatEntry(const Params& p) +: LLTextEditor(p), + mTextExpandedSignal(NULL), + mHasHistory(p.has_history), + mIsExpandable(p.is_expandable), + mExpandLinesCount(p.expand_lines_count), + mPrevLinesCount(0), + mSingleLineMode(false), + mPrevExpandedLineCount(S32_MAX) +{ + // Initialize current history line iterator + mCurrentHistoryLine = mLineHistory.begin(); + + mAutoIndent = false; + keepSelectionOnReturn(true); +} + +LLChatEntry::~LLChatEntry() +{ + delete mTextExpandedSignal; +} + +void LLChatEntry::draw() +{ + if(mIsExpandable) + { + reflow(); + expandText(); + } + LLTextEditor::draw(); +} + +void LLChatEntry::onCommit() +{ + updateHistory(); + LLTextEditor::onCommit(); +} + +boost::signals2::connection LLChatEntry::setTextExpandedCallback(const commit_signal_t::slot_type& cb) +{ + if (!mTextExpandedSignal) + { + mTextExpandedSignal = new commit_signal_t(); + } + return mTextExpandedSignal->connect(cb); +} + +void LLChatEntry::expandText() +{ + S32 line_count = mSingleLineMode ? 1 : mExpandLinesCount; + + int visible_lines_count = llabs(getVisibleLines(true).first - getVisibleLines(true).second); + bool can_changed = getLineCount() <= line_count || line_count < mPrevExpandedLineCount ; + mPrevExpandedLineCount = line_count; + + // true if pasted text has more lines than expand height limit and expand limit is not reached yet + bool text_pasted = (getLineCount() > line_count) && (visible_lines_count < line_count); + + if (mIsExpandable && (can_changed || text_pasted || mSingleLineMode) && getLineCount() != mPrevLinesCount) + { + int lines_height = 0; + if (text_pasted) + { + // text is pasted and now mLineInfoList.size() > mExpandLineCounts and mLineInfoList is not empty, + // so lines_height is the sum of the last 'expanded_line_count' lines height + lines_height = (mLineInfoList.end() - line_count)->mRect.mTop - mLineInfoList.back().mRect.mBottom; + } + else + { + lines_height = mLineInfoList.begin()->mRect.mTop - mLineInfoList.back().mRect.mBottom; + } + + int height = mVPad * 2 + lines_height; + + LLRect doc_rect = getRect(); + doc_rect.setOriginAndSize(doc_rect.mLeft, doc_rect.mBottom, doc_rect.getWidth(), height); + setShape(doc_rect); + + mPrevLinesCount = getLineCount(); + + if (mTextExpandedSignal) + { + (*mTextExpandedSignal)(this, LLSD() ); + } + + needsReflow(); + } +} + +// line history support +void LLChatEntry::updateHistory() +{ + // On history enabled, remember committed line and + // reset current history line number. + // Be sure only to remember lines that are not empty and that are + // different from the last on the list. + if (mHasHistory && getLength()) + { + // Add text to history, ignoring duplicates + if (mLineHistory.empty() || getText() != mLineHistory.back()) + { + mLineHistory.push_back(getText()); + } + + mCurrentHistoryLine = mLineHistory.end(); + } +} + +void LLChatEntry::beforeValueChange() +{ + if(this->getLength() == 0 && !mLabel.empty()) + { + this->clearSegments(); + } +} + +void LLChatEntry::onValueChange(S32 start, S32 end) +{ + //Internally resetLabel() must meet a condition before it can reset the label + resetLabel(); +} + +bool LLChatEntry::useLabel() const +{ + return !getLength() && !mLabel.empty(); +} + +void LLChatEntry::onFocusReceived() +{ + LLUICtrl::onFocusReceived(); + updateAllowingLanguageInput(); +} + +void LLChatEntry::onFocusLost() +{ + LLTextEditor::focusLostHelper(); + LLUICtrl::onFocusLost(); +} + +bool LLChatEntry::handleSpecialKey(const KEY key, const MASK mask) +{ + bool handled = false; + + LLTextEditor::handleSpecialKey(key, mask); + + switch(key) + { + case KEY_RETURN: + if (MASK_NONE == mask) + { + needsReflow(); + } + break; + + case KEY_UP: + if (mHasHistory && MASK_CONTROL == mask) + { + if (!mLineHistory.empty() && mCurrentHistoryLine > mLineHistory.begin()) + { + setText(*(--mCurrentHistoryLine)); + endOfDoc(); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + handled = true; + } + break; + + case KEY_DOWN: + if (mHasHistory && MASK_CONTROL == mask) + { + if (!mLineHistory.empty() && mCurrentHistoryLine < (mLineHistory.end() - 1) ) + { + setText(*(++mCurrentHistoryLine)); + endOfDoc(); + } + else if (!mLineHistory.empty() && mCurrentHistoryLine == (mLineHistory.end() - 1) ) + { + mCurrentHistoryLine++; + std::string empty(""); + setText(empty); + needsReflow(); + endOfDoc(); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + handled = true; + } + break; + + default: + break; + } + + return handled; +} + +void LLChatEntry::enableSingleLineMode(bool single_line_mode) +{ + if (mScroller) + { + mScroller->setSize(single_line_mode ? 0 : -1); + } + + mSingleLineMode = single_line_mode; + mPrevLinesCount = -1; + setWordWrap(!single_line_mode); +} diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h index 5fc336c8cc..5621ede1e7 100644 --- a/indra/llui/llchatentry.h +++ b/indra/llui/llchatentry.h @@ -1,106 +1,106 @@ -/** - * @file llchatentry.h - * @author Paul Guslisty - * @brief Text editor widget which is used for user input - * - * Features: - * Optional line history so previous entries can be recalled by CTRL UP/DOWN - * Optional auto-resize behavior on input chat field - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, 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$ - */ - -#ifndef LLCHATENTRY_H_ -#define LLCHATENTRY_H_ - -#include "lltexteditor.h" - -class LLChatEntry : public LLTextEditor -{ -public: - - struct Params : public LLInitParam::Block - { - Optional has_history, - is_expandable; - - Optional expand_lines_count; - - Params(); - }; - - virtual ~LLChatEntry(); - -protected: - - friend class LLUICtrlFactory; - LLChatEntry(const Params& p); - /*virtual*/ void beforeValueChange(); - /*virtual*/ void onValueChange(S32 start, S32 end); - /*virtual*/ bool useLabel() const; - -public: - - virtual void draw(); - virtual void onCommit(); - /*virtual*/ void onFocusReceived(); - /*virtual*/ void onFocusLost(); - - void enableSingleLineMode(bool single_line_mode); - boost::signals2::connection setTextExpandedCallback(const commit_signal_t::slot_type& cb); - -private: - - /** - * Implements auto-resize behavior. - * When user's typing reaches the right edge of the chat field - * the chat field expands vertically by one line. The bottom of - * the chat field remains bottom-justified. The chat field does - * not expand beyond mExpandLinesCount. - */ - void expandText(); - - /** - * Implements line history so previous entries can be recalled by CTRL UP/DOWN - */ - void updateHistory(); - - bool handleSpecialKey(const KEY key, const MASK mask); - - - // Fired when text height expanded to mExpandLinesCount - commit_signal_t* mTextExpandedSignal; - - // line history support: - typedef std::vector line_history_t; - line_history_t::iterator mCurrentHistoryLine; // currently browsed history line - line_history_t mLineHistory; // line history storage - bool mHasHistory; // flag for enabled/disabled line history - bool mIsExpandable; - bool mSingleLineMode; - - S32 mExpandLinesCount; - S32 mPrevLinesCount; - S32 mPrevExpandedLineCount; -}; - -#endif /* LLCHATENTRY_H_ */ +/** + * @file llchatentry.h + * @author Paul Guslisty + * @brief Text editor widget which is used for user input + * + * Features: + * Optional line history so previous entries can be recalled by CTRL UP/DOWN + * Optional auto-resize behavior on input chat field + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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$ + */ + +#ifndef LLCHATENTRY_H_ +#define LLCHATENTRY_H_ + +#include "lltexteditor.h" + +class LLChatEntry : public LLTextEditor +{ +public: + + struct Params : public LLInitParam::Block + { + Optional has_history, + is_expandable; + + Optional expand_lines_count; + + Params(); + }; + + virtual ~LLChatEntry(); + +protected: + + friend class LLUICtrlFactory; + LLChatEntry(const Params& p); + /*virtual*/ void beforeValueChange(); + /*virtual*/ void onValueChange(S32 start, S32 end); + /*virtual*/ bool useLabel() const; + +public: + + virtual void draw(); + virtual void onCommit(); + /*virtual*/ void onFocusReceived(); + /*virtual*/ void onFocusLost(); + + void enableSingleLineMode(bool single_line_mode); + boost::signals2::connection setTextExpandedCallback(const commit_signal_t::slot_type& cb); + +private: + + /** + * Implements auto-resize behavior. + * When user's typing reaches the right edge of the chat field + * the chat field expands vertically by one line. The bottom of + * the chat field remains bottom-justified. The chat field does + * not expand beyond mExpandLinesCount. + */ + void expandText(); + + /** + * Implements line history so previous entries can be recalled by CTRL UP/DOWN + */ + void updateHistory(); + + bool handleSpecialKey(const KEY key, const MASK mask); + + + // Fired when text height expanded to mExpandLinesCount + commit_signal_t* mTextExpandedSignal; + + // line history support: + typedef std::vector line_history_t; + line_history_t::iterator mCurrentHistoryLine; // currently browsed history line + line_history_t mLineHistory; // line history storage + bool mHasHistory; // flag for enabled/disabled line history + bool mIsExpandable; + bool mSingleLineMode; + + S32 mExpandLinesCount; + S32 mPrevLinesCount; + S32 mPrevExpandedLineCount; +}; + +#endif /* LLCHATENTRY_H_ */ diff --git a/indra/llui/llcheckboxctrl.cpp b/indra/llui/llcheckboxctrl.cpp index 8578059fab..3bcf0a6517 100644 --- a/indra/llui/llcheckboxctrl.cpp +++ b/indra/llui/llcheckboxctrl.cpp @@ -1,304 +1,304 @@ -/** - * @file llcheckboxctrl.cpp - * @brief LLCheckBoxCtrl base 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$ - */ - -// The mutants are coming! -#include "linden_common.h" - -#define LLCHECKBOXCTRL_CPP -#include "llcheckboxctrl.h" - -#include "llgl.h" -#include "llui.h" -#include "lluiconstants.h" -#include "lluictrlfactory.h" -#include "llcontrol.h" - -#include "llstring.h" -#include "llfontgl.h" -#include "lltextbox.h" -#include "llkeyboard.h" - -static LLDefaultChildRegistry::Register r("check_box"); - -// Compiler optimization, generate extern template -template class LLCheckBoxCtrl* LLView::getChild( - const std::string& name, bool recurse) const; - -void LLCheckBoxCtrl::WordWrap::declareValues() -{ - declare("none", EWordWrap::WRAP_NONE); - declare("down", EWordWrap::WRAP_DOWN); - declare("up", EWordWrap::WRAP_UP); -} - -LLCheckBoxCtrl::Params::Params() -: initial_value("initial_value", false), - label_text("label_text"), - check_button("check_button"), - word_wrap("word_wrap", EWordWrap::WRAP_NONE), - radio_style("radio_style") -{} - - -LLCheckBoxCtrl::LLCheckBoxCtrl(const LLCheckBoxCtrl::Params& p) -: LLUICtrl(p), - mTextEnabledColor(p.label_text.text_color()), - mTextDisabledColor(p.label_text.text_readonly_color()), - mFont(p.font()), - mWordWrap(p.word_wrap) -{ - mViewModel->setValue(LLSD(p.initial_value)); - mViewModel->resetDirty(); - static LLUICachedControl llcheckboxctrl_spacing ("UICheckboxctrlSpacing", 0); - static LLUICachedControl llcheckboxctrl_hpad ("UICheckboxctrlHPad", 0); - static LLUICachedControl llcheckboxctrl_vpad ("UICheckboxctrlVPad", 0); - - // must be big enough to hold all children - setUseBoundingRect(true); - - // *HACK Get rid of this with SL-55508... - // this allows blank check boxes and radio boxes for now - std::string local_label = p.label; - if(local_label.empty()) - { - local_label = " "; - } - - LLTextBox::Params tbparams = p.label_text; - tbparams.initial_value(local_label); - if (p.font.isProvided()) - { - tbparams.font(p.font); - } - - mLabel = LLUICtrlFactory::create(tbparams); - if (mWordWrap != WRAP_NONE) - { - // Not setWordWrap(mWordWrap != WRAP_NONE) because there might be some old lurking code that sets it manually - mLabel->setWordWrap(true); - S32 new_width = getRect().getWidth() - p.check_button.rect().getWidth() - llcheckboxctrl_hpad; - LLRect label_rect = mLabel->getRect(); - label_rect.mRight = label_rect.mLeft + new_width; - mLabel->setRect(label_rect); - } - mLabel->reshapeToFitText(); - - LLRect label_rect = mLabel->getRect(); - if (mLabel->getLineCount() > 1) - { - if (mWordWrap == WRAP_DOWN) - { - // reshapeToFitText uses LLView::reshape() which always reshapes - // from bottom to top, but we want to extend the bottom - // Note: might be better idea to use getRect().mTop of LLCheckBoxCtrl (+pad) as top point of new rect - S32 delta = ll_round((F32)mLabel->getFont()->getLineHeight() * mLabel->getLineSpacingMult()) - label_rect.getHeight(); - label_rect.translate(0, delta); - mLabel->setRect(label_rect); - } - // else - // WRAP_UP is essentially done by reshapeToFitText() (extends from bottom to top) - // howhever it doesn't respect rect of checkbox - // todo: this should be fixed, but there are at least couple checkboxes that use this feature as is. - } - - addChild(mLabel); - - // Button - // Note: button cover the label by extending all the way to the right and down. - LLRect btn_rect = p.check_button.rect(); - btn_rect.setOriginAndSize( - btn_rect.mLeft, - llmin(btn_rect.mBottom, label_rect.mBottom), - llmax(btn_rect.mRight, label_rect.mRight - btn_rect.mLeft), - llmax(label_rect.getHeight(), btn_rect.mTop)); - std::string active_true_id, active_false_id; - std::string inactive_true_id, inactive_false_id; - - LLButton::Params params = p.check_button; - params.rect(btn_rect); - //params.control_name(p.control_name); - params.click_callback.function(boost::bind(&LLCheckBoxCtrl::onCommit, this)); - params.commit_on_return(false); - // Checkboxes only allow boolean initial values, but buttons can - // take any LLSD. - params.initial_value(LLSD(p.initial_value)); - params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); - - mButton = LLUICtrlFactory::create(params); - addChild(mButton); -} - -LLCheckBoxCtrl::~LLCheckBoxCtrl() -{ - // Children all cleaned up by default view destructor. -} - -void LLCheckBoxCtrl::onCommit() -{ - if( getEnabled() ) - { - setTentative(false); - setControlValue(getValue()); - LLUICtrl::onCommit(); - } -} - -void LLCheckBoxCtrl::setEnabled(bool b) -{ - LLView::setEnabled(b); - - if (b) - { - mLabel->setColor( mTextEnabledColor.get() ); - } - else - { - mLabel->setColor( mTextDisabledColor.get() ); - } -} - -void LLCheckBoxCtrl::clear() -{ - setValue( false ); -} - -void LLCheckBoxCtrl::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLRect rect = getRect(); - S32 delta_width = width - rect.getWidth(); - S32 delta_height = height - rect.getHeight(); - - if (delta_width || delta_height) - { - // adjust our rectangle - rect.mRight = getRect().mLeft + width; - rect.mTop = getRect().mBottom + height; - setRect(rect); - } - - // reshapeToFitText reshapes label to minimal size according to last bounding box - // it will work fine in case of decrease of space, but if we get more space or text - // becomes longer, label will fail to grow so reinit label's dimentions. - - LLRect label_rect = mLabel->getRect(); - S32 new_width = rect.getWidth() - label_rect.mLeft; - mLabel->reshape(new_width, label_rect.getHeight(), true); - - S32 label_top = label_rect.mTop; - mLabel->reshapeToFitText(true); - - label_rect = mLabel->getRect(); - if (label_top != label_rect.mTop && mWordWrap == WRAP_DOWN) - { - // reshapeToFitText uses LLView::reshape() which always reshapes - // from bottom to top, but we want to extend the bottom so - // reposition control - S32 delta = label_top - label_rect.mTop; - label_rect.translate(0, delta); - mLabel->setRect(label_rect); - } - - // Button - // Note: button cover the label by extending all the way to the right and down. - LLRect btn_rect = mButton->getRect(); - btn_rect.setOriginAndSize( - btn_rect.mLeft, - llmin(btn_rect.mBottom, label_rect.mBottom), - llmax(btn_rect.getWidth(), label_rect.mRight - btn_rect.mLeft), - llmax(label_rect.mTop - btn_rect.mBottom, btn_rect.getHeight())); - mButton->setShape(btn_rect); - - updateBoundingRect(); -} - -//virtual -void LLCheckBoxCtrl::setValue(const LLSD& value ) -{ - mButton->setValue( value ); -} - -//virtual -LLSD LLCheckBoxCtrl::getValue() const -{ - return mButton->getValue(); -} - -//virtual -void LLCheckBoxCtrl::setTentative(bool b) -{ - mButton->setTentative(b); -} - -//virtual -bool LLCheckBoxCtrl::getTentative() const -{ - return mButton->getTentative(); -} - -void LLCheckBoxCtrl::setLabel( const LLStringExplicit& label ) -{ - mLabel->setText( label ); - reshape(getRect().getWidth(), getRect().getHeight(), false); -} - -std::string LLCheckBoxCtrl::getLabel() const -{ - return mLabel->getText(); -} - -bool LLCheckBoxCtrl::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - bool res = mLabel->setTextArg(key, text); - reshape(getRect().getWidth(), getRect().getHeight(), false); - return res; -} - -// virtual -void LLCheckBoxCtrl::setControlName(const std::string& control_name, LLView* context) -{ - mButton->setControlName(control_name, context); -} - - -// virtual Returns true if the user has modified this control. -bool LLCheckBoxCtrl::isDirty() const -{ - if ( mButton ) - { - return mButton->isDirty(); - } - return false; // Shouldn't get here -} - - -// virtual Clear dirty state -void LLCheckBoxCtrl::resetDirty() -{ - if ( mButton ) - { - mButton->resetDirty(); - } -} +/** + * @file llcheckboxctrl.cpp + * @brief LLCheckBoxCtrl base 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$ + */ + +// The mutants are coming! +#include "linden_common.h" + +#define LLCHECKBOXCTRL_CPP +#include "llcheckboxctrl.h" + +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" +#include "lluictrlfactory.h" +#include "llcontrol.h" + +#include "llstring.h" +#include "llfontgl.h" +#include "lltextbox.h" +#include "llkeyboard.h" + +static LLDefaultChildRegistry::Register r("check_box"); + +// Compiler optimization, generate extern template +template class LLCheckBoxCtrl* LLView::getChild( + const std::string& name, bool recurse) const; + +void LLCheckBoxCtrl::WordWrap::declareValues() +{ + declare("none", EWordWrap::WRAP_NONE); + declare("down", EWordWrap::WRAP_DOWN); + declare("up", EWordWrap::WRAP_UP); +} + +LLCheckBoxCtrl::Params::Params() +: initial_value("initial_value", false), + label_text("label_text"), + check_button("check_button"), + word_wrap("word_wrap", EWordWrap::WRAP_NONE), + radio_style("radio_style") +{} + + +LLCheckBoxCtrl::LLCheckBoxCtrl(const LLCheckBoxCtrl::Params& p) +: LLUICtrl(p), + mTextEnabledColor(p.label_text.text_color()), + mTextDisabledColor(p.label_text.text_readonly_color()), + mFont(p.font()), + mWordWrap(p.word_wrap) +{ + mViewModel->setValue(LLSD(p.initial_value)); + mViewModel->resetDirty(); + static LLUICachedControl llcheckboxctrl_spacing ("UICheckboxctrlSpacing", 0); + static LLUICachedControl llcheckboxctrl_hpad ("UICheckboxctrlHPad", 0); + static LLUICachedControl llcheckboxctrl_vpad ("UICheckboxctrlVPad", 0); + + // must be big enough to hold all children + setUseBoundingRect(true); + + // *HACK Get rid of this with SL-55508... + // this allows blank check boxes and radio boxes for now + std::string local_label = p.label; + if(local_label.empty()) + { + local_label = " "; + } + + LLTextBox::Params tbparams = p.label_text; + tbparams.initial_value(local_label); + if (p.font.isProvided()) + { + tbparams.font(p.font); + } + + mLabel = LLUICtrlFactory::create(tbparams); + if (mWordWrap != WRAP_NONE) + { + // Not setWordWrap(mWordWrap != WRAP_NONE) because there might be some old lurking code that sets it manually + mLabel->setWordWrap(true); + S32 new_width = getRect().getWidth() - p.check_button.rect().getWidth() - llcheckboxctrl_hpad; + LLRect label_rect = mLabel->getRect(); + label_rect.mRight = label_rect.mLeft + new_width; + mLabel->setRect(label_rect); + } + mLabel->reshapeToFitText(); + + LLRect label_rect = mLabel->getRect(); + if (mLabel->getLineCount() > 1) + { + if (mWordWrap == WRAP_DOWN) + { + // reshapeToFitText uses LLView::reshape() which always reshapes + // from bottom to top, but we want to extend the bottom + // Note: might be better idea to use getRect().mTop of LLCheckBoxCtrl (+pad) as top point of new rect + S32 delta = ll_round((F32)mLabel->getFont()->getLineHeight() * mLabel->getLineSpacingMult()) - label_rect.getHeight(); + label_rect.translate(0, delta); + mLabel->setRect(label_rect); + } + // else + // WRAP_UP is essentially done by reshapeToFitText() (extends from bottom to top) + // howhever it doesn't respect rect of checkbox + // todo: this should be fixed, but there are at least couple checkboxes that use this feature as is. + } + + addChild(mLabel); + + // Button + // Note: button cover the label by extending all the way to the right and down. + LLRect btn_rect = p.check_button.rect(); + btn_rect.setOriginAndSize( + btn_rect.mLeft, + llmin(btn_rect.mBottom, label_rect.mBottom), + llmax(btn_rect.mRight, label_rect.mRight - btn_rect.mLeft), + llmax(label_rect.getHeight(), btn_rect.mTop)); + std::string active_true_id, active_false_id; + std::string inactive_true_id, inactive_false_id; + + LLButton::Params params = p.check_button; + params.rect(btn_rect); + //params.control_name(p.control_name); + params.click_callback.function(boost::bind(&LLCheckBoxCtrl::onCommit, this)); + params.commit_on_return(false); + // Checkboxes only allow boolean initial values, but buttons can + // take any LLSD. + params.initial_value(LLSD(p.initial_value)); + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + + mButton = LLUICtrlFactory::create(params); + addChild(mButton); +} + +LLCheckBoxCtrl::~LLCheckBoxCtrl() +{ + // Children all cleaned up by default view destructor. +} + +void LLCheckBoxCtrl::onCommit() +{ + if( getEnabled() ) + { + setTentative(false); + setControlValue(getValue()); + LLUICtrl::onCommit(); + } +} + +void LLCheckBoxCtrl::setEnabled(bool b) +{ + LLView::setEnabled(b); + + if (b) + { + mLabel->setColor( mTextEnabledColor.get() ); + } + else + { + mLabel->setColor( mTextDisabledColor.get() ); + } +} + +void LLCheckBoxCtrl::clear() +{ + setValue( false ); +} + +void LLCheckBoxCtrl::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLRect rect = getRect(); + S32 delta_width = width - rect.getWidth(); + S32 delta_height = height - rect.getHeight(); + + if (delta_width || delta_height) + { + // adjust our rectangle + rect.mRight = getRect().mLeft + width; + rect.mTop = getRect().mBottom + height; + setRect(rect); + } + + // reshapeToFitText reshapes label to minimal size according to last bounding box + // it will work fine in case of decrease of space, but if we get more space or text + // becomes longer, label will fail to grow so reinit label's dimentions. + + LLRect label_rect = mLabel->getRect(); + S32 new_width = rect.getWidth() - label_rect.mLeft; + mLabel->reshape(new_width, label_rect.getHeight(), true); + + S32 label_top = label_rect.mTop; + mLabel->reshapeToFitText(true); + + label_rect = mLabel->getRect(); + if (label_top != label_rect.mTop && mWordWrap == WRAP_DOWN) + { + // reshapeToFitText uses LLView::reshape() which always reshapes + // from bottom to top, but we want to extend the bottom so + // reposition control + S32 delta = label_top - label_rect.mTop; + label_rect.translate(0, delta); + mLabel->setRect(label_rect); + } + + // Button + // Note: button cover the label by extending all the way to the right and down. + LLRect btn_rect = mButton->getRect(); + btn_rect.setOriginAndSize( + btn_rect.mLeft, + llmin(btn_rect.mBottom, label_rect.mBottom), + llmax(btn_rect.getWidth(), label_rect.mRight - btn_rect.mLeft), + llmax(label_rect.mTop - btn_rect.mBottom, btn_rect.getHeight())); + mButton->setShape(btn_rect); + + updateBoundingRect(); +} + +//virtual +void LLCheckBoxCtrl::setValue(const LLSD& value ) +{ + mButton->setValue( value ); +} + +//virtual +LLSD LLCheckBoxCtrl::getValue() const +{ + return mButton->getValue(); +} + +//virtual +void LLCheckBoxCtrl::setTentative(bool b) +{ + mButton->setTentative(b); +} + +//virtual +bool LLCheckBoxCtrl::getTentative() const +{ + return mButton->getTentative(); +} + +void LLCheckBoxCtrl::setLabel( const LLStringExplicit& label ) +{ + mLabel->setText( label ); + reshape(getRect().getWidth(), getRect().getHeight(), false); +} + +std::string LLCheckBoxCtrl::getLabel() const +{ + return mLabel->getText(); +} + +bool LLCheckBoxCtrl::setLabelArg( const std::string& key, const LLStringExplicit& text ) +{ + bool res = mLabel->setTextArg(key, text); + reshape(getRect().getWidth(), getRect().getHeight(), false); + return res; +} + +// virtual +void LLCheckBoxCtrl::setControlName(const std::string& control_name, LLView* context) +{ + mButton->setControlName(control_name, context); +} + + +// virtual Returns true if the user has modified this control. +bool LLCheckBoxCtrl::isDirty() const +{ + if ( mButton ) + { + return mButton->isDirty(); + } + return false; // Shouldn't get here +} + + +// virtual Clear dirty state +void LLCheckBoxCtrl::resetDirty() +{ + if ( mButton ) + { + mButton->resetDirty(); + } +} diff --git a/indra/llui/llcheckboxctrl.h b/indra/llui/llcheckboxctrl.h index 0312e57511..3058e946c3 100644 --- a/indra/llui/llcheckboxctrl.h +++ b/indra/llui/llcheckboxctrl.h @@ -1,157 +1,157 @@ -/** - * @file llcheckboxctrl.h - * @brief LLCheckBoxCtrl base 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$ - */ - -#ifndef LL_LLCHECKBOXCTRL_H -#define LL_LLCHECKBOXCTRL_H - -#include "lluictrl.h" -#include "llbutton.h" -#include "lltextbox.h" -#include "v4color.h" - -// -// Constants -// - -const bool RADIO_STYLE = true; -const bool CHECK_STYLE = false; - -// -// Classes -// -class LLFontGL; -class LLViewBorder; - -class LLCheckBoxCtrl -: public LLUICtrl -, public ll::ui::SearchableControl -{ -public: - - enum EWordWrap - { - WRAP_NONE, - WRAP_UP, - WRAP_DOWN - }; - - struct WordWrap : public LLInitParam::TypeValuesHelper - { - static void declareValues(); - }; - - struct Params - : public LLInitParam::Block - { - Optional initial_value; // override LLUICtrl initial_value - - Optional label_text; - Optional check_button; - - Optional word_wrap; - - Ignored radio_style; - - Params(); - }; - - virtual ~LLCheckBoxCtrl(); - -protected: - LLCheckBoxCtrl(const Params&); - friend class LLUICtrlFactory; - -public: - // LLView interface - - virtual void setEnabled( bool b ); - - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - - // LLUICtrl interface - virtual void setValue(const LLSD& value ); - virtual LLSD getValue() const; - bool get() { return (bool)getValue().asBoolean(); } - void set(bool value) { setValue(value); } - - virtual void setTentative(bool b); - virtual bool getTentative() const; - - virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); - - virtual void clear(); - virtual void onCommit(); - - // LLCheckBoxCtrl interface - virtual bool toggle() { return mButton->toggleState(); } // returns new state - - void setBtnFocus() { mButton->setFocus(true); } - - void setEnabledColor( const LLColor4 &color ) { mTextEnabledColor = color; } - void setDisabledColor( const LLColor4 &color ) { mTextDisabledColor = color; } - - void setLabel( const LLStringExplicit& label ); - std::string getLabel() const; - - void setFont( const LLFontGL* font ) { mFont = font; } - const LLFontGL* getFont() const { return mFont; } - - virtual void setControlName(const std::string& control_name, LLView* context); - - virtual bool isDirty() const; // Returns true if the user has modified this control. - virtual void resetDirty(); // Clear dirty state - -protected: - virtual std::string _getSearchText() const - { - return getLabel() + getToolTip(); - } - - virtual void onSetHighlight() const // When highlight, really do highlight the label - { - if( mLabel ) - mLabel->ll::ui::SearchableControl::setHighlighted( ll::ui::SearchableControl::getHighlighted() ); - } - -protected: - // note: value is stored in toggle state of button - LLButton* mButton; - LLTextBox* mLabel; - const LLFontGL* mFont; - - LLUIColor mTextEnabledColor; - LLUIColor mTextDisabledColor; - - EWordWrap mWordWrap; // off, shifts text up, shifts text down -}; - -// Build time optimization, generate once in .cpp file -#ifndef LLCHECKBOXCTRL_CPP -extern template class LLCheckBoxCtrl* LLView::getChild( - const std::string& name, bool recurse) const; -#endif - -#endif // LL_LLCHECKBOXCTRL_H +/** + * @file llcheckboxctrl.h + * @brief LLCheckBoxCtrl base 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$ + */ + +#ifndef LL_LLCHECKBOXCTRL_H +#define LL_LLCHECKBOXCTRL_H + +#include "lluictrl.h" +#include "llbutton.h" +#include "lltextbox.h" +#include "v4color.h" + +// +// Constants +// + +const bool RADIO_STYLE = true; +const bool CHECK_STYLE = false; + +// +// Classes +// +class LLFontGL; +class LLViewBorder; + +class LLCheckBoxCtrl +: public LLUICtrl +, public ll::ui::SearchableControl +{ +public: + + enum EWordWrap + { + WRAP_NONE, + WRAP_UP, + WRAP_DOWN + }; + + struct WordWrap : public LLInitParam::TypeValuesHelper + { + static void declareValues(); + }; + + struct Params + : public LLInitParam::Block + { + Optional initial_value; // override LLUICtrl initial_value + + Optional label_text; + Optional check_button; + + Optional word_wrap; + + Ignored radio_style; + + Params(); + }; + + virtual ~LLCheckBoxCtrl(); + +protected: + LLCheckBoxCtrl(const Params&); + friend class LLUICtrlFactory; + +public: + // LLView interface + + virtual void setEnabled( bool b ); + + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + + // LLUICtrl interface + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + bool get() { return (bool)getValue().asBoolean(); } + void set(bool value) { setValue(value); } + + virtual void setTentative(bool b); + virtual bool getTentative() const; + + virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); + + virtual void clear(); + virtual void onCommit(); + + // LLCheckBoxCtrl interface + virtual bool toggle() { return mButton->toggleState(); } // returns new state + + void setBtnFocus() { mButton->setFocus(true); } + + void setEnabledColor( const LLColor4 &color ) { mTextEnabledColor = color; } + void setDisabledColor( const LLColor4 &color ) { mTextDisabledColor = color; } + + void setLabel( const LLStringExplicit& label ); + std::string getLabel() const; + + void setFont( const LLFontGL* font ) { mFont = font; } + const LLFontGL* getFont() const { return mFont; } + + virtual void setControlName(const std::string& control_name, LLView* context); + + virtual bool isDirty() const; // Returns true if the user has modified this control. + virtual void resetDirty(); // Clear dirty state + +protected: + virtual std::string _getSearchText() const + { + return getLabel() + getToolTip(); + } + + virtual void onSetHighlight() const // When highlight, really do highlight the label + { + if( mLabel ) + mLabel->ll::ui::SearchableControl::setHighlighted( ll::ui::SearchableControl::getHighlighted() ); + } + +protected: + // note: value is stored in toggle state of button + LLButton* mButton; + LLTextBox* mLabel; + const LLFontGL* mFont; + + LLUIColor mTextEnabledColor; + LLUIColor mTextDisabledColor; + + EWordWrap mWordWrap; // off, shifts text up, shifts text down +}; + +// Build time optimization, generate once in .cpp file +#ifndef LLCHECKBOXCTRL_CPP +extern template class LLCheckBoxCtrl* LLView::getChild( + const std::string& name, bool recurse) const; +#endif + +#endif // LL_LLCHECKBOXCTRL_H diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index 305263e0cc..0d82f29dfb 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -1,1231 +1,1231 @@ -/** - * @file llcombobox.cpp - * @brief LLComboBox base 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$ - */ - -// A control that displays the name of the chosen item, which when -// clicked shows a scrolling box of options. - -#include "linden_common.h" - -// file includes -#include "llcombobox.h" - -// common includes -#include "llstring.h" - -// newview includes -#include "llbutton.h" -#include "llkeyboard.h" -#include "llscrolllistctrl.h" -#include "llwindow.h" -#include "llfloater.h" -#include "llscrollbar.h" -#include "llscrolllistcell.h" -#include "llscrolllistitem.h" -#include "llcontrol.h" -#include "llfocusmgr.h" -#include "lllineeditor.h" -#include "v2math.h" -#include "lluictrlfactory.h" -#include "lltooltip.h" - -// Globals -S32 MAX_COMBO_WIDTH = 500; - -static LLDefaultChildRegistry::Register register_combo_box("combo_box"); - -void LLComboBox::PreferredPositionValues::declareValues() -{ - declare("above", ABOVE); - declare("below", BELOW); -} - -LLComboBox::ItemParams::ItemParams() -: label("label") -{ -} - - -LLComboBox::Params::Params() -: allow_text_entry("allow_text_entry", false), - allow_new_values("allow_new_values", false), - show_text_as_tentative("show_text_as_tentative", true), - max_chars("max_chars", 20), - list_position("list_position", BELOW), - items("item"), - combo_button("combo_button"), - combo_list("combo_list"), - combo_editor("combo_editor"), - drop_down_button("drop_down_button") -{ - addSynonym(items, "combo_item"); -} - - -LLComboBox::LLComboBox(const LLComboBox::Params& p) -: LLUICtrl(p), - mTextEntry(NULL), - mTextEntryTentative(p.show_text_as_tentative), - mHasAutocompletedText(false), - mAllowTextEntry(p.allow_text_entry), - mAllowNewValues(p.allow_new_values), - mMaxChars(p.max_chars), - mPrearrangeCallback(p.prearrange_callback()), - mTextEntryCallback(p.text_entry_callback()), - mTextChangedCallback(p.text_changed_callback()), - mListPosition(p.list_position), - mLastSelectedIndex(-1), - mLabel(p.label) -{ - // Text label button - - LLButton::Params button_params = (mAllowTextEntry ? p.combo_button : p.drop_down_button); - button_params.mouse_down_callback.function( - boost::bind(&LLComboBox::onButtonMouseDown, this)); - button_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_BOTTOM|FOLLOWS_RIGHT); - button_params.rect(p.rect); - - if(mAllowTextEntry) - { - button_params.pad_right(2); - } - - mArrowImage = button_params.image_unselected; - if (mArrowImage.notNull()) - { - mImageLoadedConnection = mArrowImage->addLoadedCallback(boost::bind(&LLComboBox::imageLoaded, this)); - } - - mButton = LLUICtrlFactory::create(button_params); - - - if(mAllowTextEntry) - { - //redo to compensate for button hack that leaves space for a character - //unless it is a "minimal combobox"(drop down) - mButton->setRightHPad(2); - } - addChild(mButton); - - LLScrollListCtrl::Params params = p.combo_list; - params.name("ComboBox"); - params.commit_callback.function(boost::bind(&LLComboBox::onItemSelected, this, _2)); - params.visible(false); - params.commit_on_keyboard_movement(false); - - mList = LLUICtrlFactory::create(params); - addChild(mList); - - // Mouse-down on button will transfer mouse focus to the list - // Grab the mouse-up event and make sure the button state is correct - mList->setMouseUpCallback(boost::bind(&LLComboBox::onListMouseUp, this)); - - for (LLInitParam::ParamIterator::const_iterator it = p.items.begin(); - it != p.items.end(); - ++it) - { - LLScrollListItem::Params item_params = *it; - if (it->label.isProvided()) - { - item_params.columns.add().value(it->label()); - } - - mList->addRow(item_params); - } - - createLineEditor(p); - - mTopLostSignalConnection = setTopLostCallback(boost::bind(&LLComboBox::hideList, this)); -} - -void LLComboBox::initFromParams(const LLComboBox::Params& p) -{ - LLUICtrl::initFromParams(p); - - if (!acceptsTextInput() && mLabel.empty()) - { - selectFirstItem(); - } -} - -// virtual -bool LLComboBox::postBuild() -{ - if (mControlVariable) - { - setValue(mControlVariable->getValue()); // selects the appropriate item - } - return true; -} - - -LLComboBox::~LLComboBox() -{ - // children automatically deleted, including mMenu, mButton - - // explicitly disconect this signal, since base class destructor might fire top lost - mTopLostSignalConnection.disconnect(); - mImageLoadedConnection.disconnect(); - - LLUI::getInstance()->removePopup(this); -} - - -void LLComboBox::clear() -{ - if (mTextEntry) - { - mTextEntry->setText(LLStringUtil::null); - } - mButton->setLabelSelected(LLStringUtil::null); - mButton->setLabelUnselected(LLStringUtil::null); - mList->deselectAllItems(); - mLastSelectedIndex = -1; -} - -void LLComboBox::onCommit() -{ - if (mAllowTextEntry && getCurrentIndex() != -1) - { - // we have selected an existing item, blitz the manual text entry with - // the properly capitalized item - mTextEntry->setValue(getSimple()); - mTextEntry->setTentative(false); - } - setControlValue(getValue()); - LLUICtrl::onCommit(); -} - -// virtual -bool LLComboBox::isDirty() const -{ - bool grubby = false; - if ( mList ) - { - grubby = mList->isDirty(); - } - return grubby; -} - -// virtual Clear dirty state -void LLComboBox::resetDirty() -{ - if ( mList ) - { - mList->resetDirty(); - } -} - -bool LLComboBox::itemExists(const std::string& name) -{ - return mList->getItemByLabel(name); -} - -// add item "name" to menu -LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, bool enabled) -{ - LLScrollListItem* item = mList->addSimpleElement(name, pos); - item->setEnabled(enabled); - if (!mAllowTextEntry && mLabel.empty()) - { - if (mControlVariable) - { - setValue(mControlVariable->getValue()); // selects the appropriate item - } - else - { - selectFirstItem(); - } - } - return item; -} - -// add item "name" with a unique id to menu -LLScrollListItem* LLComboBox::add(const std::string& name, const LLUUID& id, EAddPosition pos, bool enabled ) -{ - LLScrollListItem* item = mList->addSimpleElement(name, pos, id); - item->setEnabled(enabled); - if (!mAllowTextEntry && mLabel.empty()) - { - if (mControlVariable) - { - setValue(mControlVariable->getValue()); // selects the appropriate item - } - else - { - selectFirstItem(); - } - } - return item; -} - -// add item "name" with attached userdata -LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata, EAddPosition pos, bool enabled ) -{ - LLScrollListItem* item = mList->addSimpleElement(name, pos); - item->setEnabled(enabled); - item->setUserdata( userdata ); - if (!mAllowTextEntry && mLabel.empty()) - { - if (mControlVariable) - { - setValue(mControlVariable->getValue()); // selects the appropriate item - } - else - { - selectFirstItem(); - } - } - return item; -} - -// add item "name" with attached generic data -LLScrollListItem* LLComboBox::add(const std::string& name, LLSD value, EAddPosition pos, bool enabled ) -{ - LLScrollListItem* item = mList->addSimpleElement(name, pos, value); - item->setEnabled(enabled); - if (!mAllowTextEntry && mLabel.empty()) - { - if (mControlVariable) - { - setValue(mControlVariable->getValue()); // selects the appropriate item - } - else - { - selectFirstItem(); - } - } - return item; -} - -LLScrollListItem* LLComboBox::addSeparator(EAddPosition pos) -{ - return mList->addSeparator(pos); -} - -void LLComboBox::sortByName(bool ascending) -{ - mList->sortOnce(0, ascending); -} - - -// Choose an item with a given name in the menu. -// Returns true if the item was found. -bool LLComboBox::setSimple(const LLStringExplicit& name) -{ - bool found = mList->selectItemByLabel(name, false); - - if (found) - { - setLabel(name); - mLastSelectedIndex = mList->getFirstSelectedIndex(); - } - - return found; -} - -// virtual -void LLComboBox::setValue(const LLSD& value) -{ - bool found = mList->selectByValue(value); - if (found) - { - LLScrollListItem* item = mList->getFirstSelected(); - if (item) - { - updateLabel(); - } - mLastSelectedIndex = mList->getFirstSelectedIndex(); - } - else - { - mLastSelectedIndex = -1; - } -} - -const std::string LLComboBox::getSimple() const -{ - const std::string res = getSelectedItemLabel(); - if (res.empty() && mAllowTextEntry) - { - return mTextEntry->getText(); - } - else - { - return res; - } -} - -const std::string LLComboBox::getSelectedItemLabel(S32 column) const -{ - return mList->getSelectedItemLabel(column); -} - -// virtual -LLSD LLComboBox::getValue() const -{ - LLScrollListItem* item = mList->getFirstSelected(); - if( item ) - { - return item->getValue(); - } - else if (mAllowTextEntry) - { - return mTextEntry->getValue(); - } - else - { - return LLSD(); - } -} - -void LLComboBox::setLabel(const LLStringExplicit& name) -{ - if ( mTextEntry ) - { - mTextEntry->setText(name); - if (mList->selectItemByLabel(name, false)) - { - mTextEntry->setTentative(false); - mLastSelectedIndex = mList->getFirstSelectedIndex(); - } - else - { - mTextEntry->setTentative(mTextEntryTentative); - } - } - - if (!mAllowTextEntry) - { - mButton->setLabel(name); - } -} - -void LLComboBox::updateLabel() -{ - // Update the combo editor with the selected - // item label. - if (mTextEntry) - { - mTextEntry->setText(getSelectedItemLabel()); - mTextEntry->setTentative(false); - } - - // If combo box doesn't allow text entry update - // the combo button label. - if (!mAllowTextEntry) - { - mButton->setLabel(getSelectedItemLabel()); - } -} - -bool LLComboBox::remove(const std::string& name) -{ - bool found = mList->selectItemByLabel(name); - - if (found) - { - LLScrollListItem* item = mList->getFirstSelected(); - if (item) - { - mList->deleteSingleItem(mList->getItemIndex(item)); - } - mLastSelectedIndex = mList->getFirstSelectedIndex(); - } - - return found; -} - -bool LLComboBox::remove(S32 index) -{ - if (index < mList->getItemCount()) - { - mList->deleteSingleItem(index); - setLabel(getSelectedItemLabel()); - return true; - } - return false; -} - -// Keyboard focus lost. -void LLComboBox::onFocusLost() -{ - hideList(); - // if valid selection - if (mAllowTextEntry && getCurrentIndex() != -1) - { - mTextEntry->selectAll(); - } - mButton->setForcePressedState(false); - LLUICtrl::onFocusLost(); -} - -void LLComboBox::setButtonVisible(bool visible) -{ - mButton->setVisible(visible); - if (mTextEntry) - { - LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); - if (visible) - { - S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; - text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * BTN_DROP_SHADOW; - } - //mTextEntry->setRect(text_entry_rect); - mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), true); - } -} - -bool LLComboBox::setCurrentByIndex( S32 index ) -{ - bool found = mList->selectNthItem( index ); - if (found) - { - setLabel(getSelectedItemLabel()); - mLastSelectedIndex = index; - } - return found; -} - -S32 LLComboBox::getCurrentIndex() const -{ - LLScrollListItem* item = mList->getFirstSelected(); - if( item ) - { - return mList->getItemIndex( item ); - } - return -1; -} - -void LLComboBox::setEnabledByValue(const LLSD& value, bool enabled) -{ - LLScrollListItem *found = mList->getItem(value); - if (found) - { - found->setEnabled(enabled); - } -} - -void LLComboBox::createLineEditor(const LLComboBox::Params& p) -{ - LLRect rect = getLocalRect(); - if (mAllowTextEntry) - { - S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; - S32 shadow_size = BTN_DROP_SHADOW; - mButton->setRect(LLRect( getRect().getWidth() - llmax(8,arrow_width) - 2 * shadow_size, - rect.mTop, rect.mRight, rect.mBottom)); - mButton->setTabStop(false); - mButton->setHAlign(LLFontGL::HCENTER); - - LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); - text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * BTN_DROP_SHADOW; - // clear label on button - std::string cur_label = mButton->getLabelSelected(); - LLLineEditor::Params params = p.combo_editor; - params.rect(text_entry_rect); - params.default_text(LLStringUtil::null); - params.max_length.bytes(mMaxChars); - params.commit_callback.function(boost::bind(&LLComboBox::onTextCommit, this, _2)); - params.keystroke_callback(boost::bind(&LLComboBox::onTextEntry, this, _1)); - params.commit_on_focus_lost(false); - params.follows.flags(FOLLOWS_ALL); - params.label(mLabel); - mTextEntry = LLUICtrlFactory::create (params); - mTextEntry->setText(cur_label); - mTextEntry->setIgnoreTab(true); - addChild(mTextEntry); - - // clear label on button - setLabel(LLStringUtil::null); - - mButton->setFollows(FOLLOWS_BOTTOM | FOLLOWS_TOP | FOLLOWS_RIGHT); - } - else - { - mButton->setRect(rect); - mButton->setLabel(mLabel.getString()); - - if (mTextEntry) - { - mTextEntry->setVisible(false); - } - } -} - -void LLComboBox::setLeftTextPadding(S32 pad) -{ - S32 left_pad, right_pad; - mTextEntry->getTextPadding(&left_pad, &right_pad); - mTextEntry->setTextPadding(pad, right_pad); -} - -void* LLComboBox::getCurrentUserdata() -{ - LLScrollListItem* item = mList->getFirstSelected(); - if( item ) - { - return item->getUserdata(); - } - return NULL; -} - - -void LLComboBox::showList() -{ - // Make sure we don't go off top of screen. - LLCoordWindow window_size; - getWindow()->getSize(&window_size); - //HACK: shouldn't have to know about scale here - mList->fitContents( 192, llfloor((F32)window_size.mY / LLUI::getScaleFactor().mV[VY]) - 50 ); - - // Make sure that we can see the whole list - LLRect root_view_local; - LLView* root_view = getRootView(); - root_view->localRectToOtherView(root_view->getLocalRect(), &root_view_local, this); - - LLRect rect = mList->getRect(); - - S32 min_width = getRect().getWidth(); - S32 max_width = llmax(min_width, MAX_COMBO_WIDTH); - // make sure we have up to date content width metrics - S32 list_width = llclamp(mList->calcMaxContentWidth(), min_width, max_width); - - if (mListPosition == BELOW) - { - if (rect.getHeight() <= -root_view_local.mBottom) - { - // Move rect so it hangs off the bottom of this view - rect.setLeftTopAndSize(0, 0, list_width, rect.getHeight() ); - } - else - { - // stack on top or bottom, depending on which has more room - if (-root_view_local.mBottom > root_view_local.mTop - getRect().getHeight()) - { - // Move rect so it hangs off the bottom of this view - rect.setLeftTopAndSize(0, 0, list_width, llmin(-root_view_local.mBottom, rect.getHeight())); - } - else - { - // move rect so it stacks on top of this view (clipped to size of screen) - rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight())); - } - } - } - else // ABOVE - { - if (rect.getHeight() <= root_view_local.mTop - getRect().getHeight()) - { - // move rect so it stacks on top of this view (clipped to size of screen) - rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight())); - } - else - { - // stack on top or bottom, depending on which has more room - if (-root_view_local.mBottom > root_view_local.mTop - getRect().getHeight()) - { - // Move rect so it hangs off the bottom of this view - rect.setLeftTopAndSize(0, 0, list_width, llmin(-root_view_local.mBottom, rect.getHeight())); - } - else - { - // move rect so it stacks on top of this view (clipped to size of screen) - rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight())); - } - } - - } - mList->setOrigin(rect.mLeft, rect.mBottom); - mList->reshape(rect.getWidth(), rect.getHeight()); - mList->translateIntoRect(root_view_local); - - // Make sure we didn't go off bottom of screen - S32 x, y; - mList->localPointToScreen(0, 0, &x, &y); - - if (y < 0) - { - mList->translate(0, -y); - } - - // NB: this call will trigger the focuslost callback which will hide the list, so do it first - // before finally showing the list - - mList->setFocus(true); - - // Show the list and push the button down - mButton->setToggleState(true); - mList->setVisible(true); - - LLUI::getInstance()->addPopup(this); - - setUseBoundingRect(true); -// updateBoundingRect(); -} - -void LLComboBox::hideList() -{ - if (mList->getVisible()) - { - // assert selection in list - if(mAllowNewValues) - { - // mLastSelectedIndex = -1 means that we entered a new value, don't select - // any of existing items in this case. - if(mLastSelectedIndex >= 0) - mList->selectNthItem(mLastSelectedIndex); - } - else if(mLastSelectedIndex >= 0) - mList->selectNthItem(mLastSelectedIndex); - - mButton->setToggleState(false); - mList->setVisible(false); - mList->mouseOverHighlightNthItem(-1); - - setUseBoundingRect(false); - LLUI::getInstance()->removePopup(this); -// updateBoundingRect(); - } -} - -void LLComboBox::onButtonMouseDown() -{ - if (!mList->getVisible()) - { - // this might change selection, so do it first - prearrangeList(); - - // highlight the last selected item from the original selection before potentially selecting a new item - // as visual cue to original value of combo box - LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); - if (last_selected_item) - { - mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); - } - - if (mList->getItemCount() != 0) - { - showList(); - } - - setFocus( true ); - - // pass mouse capture on to list if button is depressed - if (mButton->hasMouseCapture()) - { - gFocusMgr.setMouseCapture(mList); - - // But keep the "pressed" look, which buttons normally lose when they - // lose focus - mButton->setForcePressedState(true); - } - } - else - { - hideList(); - } - -} - -void LLComboBox::onListMouseUp() -{ - // In some cases this is the termination of a mouse click that started on - // the button, so clear its pressed state - mButton->setForcePressedState(false); -} - -//------------------------------------------------------------------ -// static functions -//------------------------------------------------------------------ - -void LLComboBox::onItemSelected(const LLSD& data) -{ - mLastSelectedIndex = getCurrentIndex(); - if (mLastSelectedIndex != -1) - { - updateLabel(); - - if (mAllowTextEntry) - { - gFocusMgr.setKeyboardFocus(mTextEntry); - mTextEntry->selectAll(); - } - } - // hiding the list reasserts the old value stored in the text editor/dropdown button - hideList(); - - // commit does the reverse, asserting the value in the list - onCommit(); -} - -bool LLComboBox::handleToolTip(S32 x, S32 y, MASK mask) -{ - std::string tool_tip; - - if(LLUICtrl::handleToolTip(x, y, mask)) - { - return true; - } - - tool_tip = getToolTip(); - if (tool_tip.empty()) - { - tool_tip = getSelectedItemLabel(); - } - - if( !tool_tip.empty() ) - { - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(tool_tip) - .sticky_rect(calcScreenRect())); - } - return true; -} - -bool LLComboBox::handleKeyHere(KEY key, MASK mask) -{ - bool result = false; - if (hasFocus()) - { - if (mList->getVisible() - && key == KEY_ESCAPE && mask == MASK_NONE) - { - hideList(); - return true; - } - //give list a chance to pop up and handle key - LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); - if (last_selected_item) - { - // highlight the original selection before potentially selecting a new item - mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); - } - result = mList->handleKeyHere(key, mask); - - // will only see return key if it is originating from line editor - // since the dropdown button eats the key - if (key == KEY_RETURN) - { - if (mask == MASK_NONE) - { - mOnReturnSignal(this, getValue()); - } - - // don't show list and don't eat key input when committing - // free-form text entry with RETURN since user already knows - // what they are trying to select - return false; - } - // if selection has changed, pop open list - else if (mList->getLastSelectedItem() != last_selected_item - || ((key == KEY_DOWN || key == KEY_UP) - && mList->getCanSelect() - && !mList->isEmpty())) - { - showList(); - } - } - return result; -} - -bool LLComboBox::handleUnicodeCharHere(llwchar uni_char) -{ - bool result = false; - if (gFocusMgr.childHasKeyboardFocus(this)) - { - // space bar just shows the list - if (' ' != uni_char ) - { - LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); - if (last_selected_item) - { - // highlight the original selection before potentially selecting a new item - mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); - } - result = mList->handleUnicodeCharHere(uni_char); - if (mList->getLastSelectedItem() != last_selected_item) - { - showList(); - } - } - } - return result; -} - -void LLComboBox::setTextEntry(const LLStringExplicit& text) -{ - if (mTextEntry) - { - mTextEntry->setText(text); - mHasAutocompletedText = false; - updateSelection(); - } -} - -void LLComboBox::setKeystrokeOnEsc(bool enable) -{ - if (mTextEntry) - { - mTextEntry->setKeystrokeOnEsc(enable); - } -} - -void LLComboBox::onTextEntry(LLLineEditor* line_editor) -{ - if (mTextEntryCallback != NULL) - { - (mTextEntryCallback)(line_editor, LLSD()); - } - - KEY key = gKeyboard->currentKey(); - if (key == KEY_BACKSPACE || - key == KEY_DELETE) - { - if (mList->selectItemByLabel(line_editor->getText(), false)) - { - line_editor->setTentative(false); - mLastSelectedIndex = mList->getFirstSelectedIndex(); - } - else - { - line_editor->setTentative(mTextEntryTentative); - mList->deselectAllItems(); - mLastSelectedIndex = -1; - } - if (mTextChangedCallback != NULL) - { - (mTextChangedCallback)(line_editor, LLSD()); - } - return; - } - - if (key == KEY_LEFT || - key == KEY_RIGHT) - { - return; - } - - if (key == KEY_DOWN) - { - setCurrentByIndex(llmin(getItemCount() - 1, getCurrentIndex() + 1)); - if (!mList->getVisible()) - { - prearrangeList(); - - if (mList->getItemCount() != 0) - { - showList(); - } - } - line_editor->selectAll(); - line_editor->setTentative(false); - } - else if (key == KEY_UP) - { - setCurrentByIndex(llmax(0, getCurrentIndex() - 1)); - if (!mList->getVisible()) - { - prearrangeList(); - - if (mList->getItemCount() != 0) - { - showList(); - } - } - line_editor->selectAll(); - line_editor->setTentative(false); - } - else - { - // RN: presumably text entry - updateSelection(); - } - if (mTextChangedCallback != NULL) - { - (mTextChangedCallback)(line_editor, LLSD()); - } -} - -void LLComboBox::updateSelection() -{ - LLWString left_wstring = mTextEntry->getWText().substr(0, mTextEntry->getCursor()); - // user-entered portion of string, based on assumption that any selected - // text was a result of auto-completion - LLWString user_wstring = mHasAutocompletedText ? left_wstring : mTextEntry->getWText(); - std::string full_string = mTextEntry->getText(); - - // go ahead and arrange drop down list on first typed character, even - // though we aren't showing it... some code relies on prearrange - // callback to populate content - if( mTextEntry->getWText().size() == 1 ) - { - prearrangeList(mTextEntry->getText()); - } - - if (mList->selectItemByLabel(full_string, false)) - { - mTextEntry->setTentative(false); - mLastSelectedIndex = mList->getFirstSelectedIndex(); - } - else if (mList->selectItemByPrefix(left_wstring, false)) - { - LLWString selected_item = utf8str_to_wstring(getSelectedItemLabel()); - LLWString wtext = left_wstring + selected_item.substr(left_wstring.size(), selected_item.size()); - mTextEntry->setText(wstring_to_utf8str(wtext)); - mTextEntry->setSelection(left_wstring.size(), mTextEntry->getWText().size()); - mTextEntry->endSelection(); - mTextEntry->setTentative(false); - mHasAutocompletedText = true; - mLastSelectedIndex = mList->getFirstSelectedIndex(); - } - else // no matching items found - { - mList->deselectAllItems(); - mTextEntry->setText(wstring_to_utf8str(user_wstring)); // removes text added by autocompletion - mTextEntry->setTentative(mTextEntryTentative); - mHasAutocompletedText = false; - mLastSelectedIndex = -1; - } -} - -void LLComboBox::onTextCommit(const LLSD& data) -{ - std::string text = mTextEntry->getText(); - setSimple(text); - onCommit(); - mTextEntry->selectAll(); -} - -void LLComboBox::setFocus(bool b) -{ - LLUICtrl::setFocus(b); - - if (b) - { - mList->clearSearchString(); - if (mList->getVisible()) - { - mList->setFocus(true); - } - } -} - -void LLComboBox::prearrangeList(std::string filter) -{ - if (mPrearrangeCallback) - { - mPrearrangeCallback(this, LLSD(filter)); - } -} - - -//============================================================================ -// ll::ui::SearchableControl functions - -//virtual -std::string LLComboBox::_getSearchText() const -{ - std::string res; - if (mList) - { - // getAllData returns a full copy of content, might be a - // better option to implement an mList->getSearchText(column) - std::vector data = mList->getAllData(); - std::vector::iterator iter = data.begin(); - while (iter != data.end()) - { - LLScrollListCell* cell = (*iter)->getColumn(0); - if (cell) - { - std::string whitelist_url = cell->getValue().asString(); - res += cell->getValue().asString(); - } - iter++; - } - } - return res + getToolTip(); -} - -//virtual -void LLComboBox::onSetHighlight() const -{ - if (mButton) - { - mButton->ll::ui::SearchableControl::setHighlighted(ll::ui::SearchableControl::getHighlighted()); - } -} - -void LLComboBox::imageLoaded() -{ - if (mAllowTextEntry) - { - LLRect rect = getLocalRect(); - S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; - S32 shadow_size = BTN_DROP_SHADOW; - mButton->setRect(LLRect(getRect().getWidth() - llmax(8, arrow_width) - 2 * shadow_size, - rect.mTop, rect.mRight, rect.mBottom)); - if (mButton->getVisible()) - { - // recalculate field size - if (mTextEntry) - { - LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); - text_entry_rect.mRight -= llmax(8, arrow_width) + 2 * BTN_DROP_SHADOW; - mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), true); - } - } - } -} - -//============================================================================ -// LLCtrlListInterface functions - -S32 LLComboBox::getItemCount() const -{ - return mList->getItemCount(); -} - -void LLComboBox::addColumn(const LLSD& column, EAddPosition pos) -{ - mList->clearColumns(); - mList->addColumn(column, pos); -} - -void LLComboBox::clearColumns() -{ - mList->clearColumns(); -} - -void LLComboBox::setColumnLabel(const std::string& column, const std::string& label) -{ - mList->setColumnLabel(column, label); -} - -LLScrollListItem* LLComboBox::addElement(const LLSD& value, EAddPosition pos, void* userdata) -{ - return mList->addElement(value, pos, userdata); -} - -LLScrollListItem* LLComboBox::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id) -{ - return mList->addSimpleElement(value, pos, id); -} - -void LLComboBox::clearRows() -{ - mList->clearRows(); -} - -void LLComboBox::sortByColumn(const std::string& name, bool ascending) -{ - mList->sortByColumn(name, ascending); -} - -//============================================================================ -//LLCtrlSelectionInterface functions - -bool LLComboBox::setCurrentByID(const LLUUID& id) -{ - bool found = mList->selectByID( id ); - - if (found) - { - setLabel(getSelectedItemLabel()); - mLastSelectedIndex = mList->getFirstSelectedIndex(); - } - - return found; -} - -LLUUID LLComboBox::getCurrentID() const -{ - return mList->getStringUUIDSelectedItem(); -} -bool LLComboBox::setSelectedByValue(const LLSD& value, bool selected) -{ - bool found = mList->setSelectedByValue(value, selected); - if (found) - { - setLabel(getSelectedItemLabel()); - } - return found; -} - -LLSD LLComboBox::getSelectedValue() -{ - return mList->getSelectedValue(); -} - -bool LLComboBox::isSelected(const LLSD& value) const -{ - return mList->isSelected(value); -} - -bool LLComboBox::operateOnSelection(EOperation op) -{ - if (op == OP_DELETE) - { - mList->deleteSelectedItems(); - return true; - } - return false; -} - -bool LLComboBox::operateOnAll(EOperation op) -{ - if (op == OP_DELETE) - { - clearRows(); - return true; - } - return false; -} - -bool LLComboBox::selectItemRange( S32 first, S32 last ) -{ - return mList->selectItemRange(first, last); -} - - -static LLDefaultChildRegistry::Register register_icons_combo_box("icons_combo_box"); - -LLIconsComboBox::Params::Params() -: icon_column("icon_column", ICON_COLUMN), - label_column("label_column", LABEL_COLUMN) -{} - -LLIconsComboBox::LLIconsComboBox(const LLIconsComboBox::Params& p) -: LLComboBox(p), - mIconColumnIndex(p.icon_column), - mLabelColumnIndex(p.label_column) -{} - -const std::string LLIconsComboBox::getSelectedItemLabel(S32 column) const -{ - mButton->setImageOverlay(LLComboBox::getSelectedItemLabel(mIconColumnIndex), mButton->getImageOverlayHAlign()); - - return LLComboBox::getSelectedItemLabel(mLabelColumnIndex); -} +/** + * @file llcombobox.cpp + * @brief LLComboBox base 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$ + */ + +// A control that displays the name of the chosen item, which when +// clicked shows a scrolling box of options. + +#include "linden_common.h" + +// file includes +#include "llcombobox.h" + +// common includes +#include "llstring.h" + +// newview includes +#include "llbutton.h" +#include "llkeyboard.h" +#include "llscrolllistctrl.h" +#include "llwindow.h" +#include "llfloater.h" +#include "llscrollbar.h" +#include "llscrolllistcell.h" +#include "llscrolllistitem.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "lllineeditor.h" +#include "v2math.h" +#include "lluictrlfactory.h" +#include "lltooltip.h" + +// Globals +S32 MAX_COMBO_WIDTH = 500; + +static LLDefaultChildRegistry::Register register_combo_box("combo_box"); + +void LLComboBox::PreferredPositionValues::declareValues() +{ + declare("above", ABOVE); + declare("below", BELOW); +} + +LLComboBox::ItemParams::ItemParams() +: label("label") +{ +} + + +LLComboBox::Params::Params() +: allow_text_entry("allow_text_entry", false), + allow_new_values("allow_new_values", false), + show_text_as_tentative("show_text_as_tentative", true), + max_chars("max_chars", 20), + list_position("list_position", BELOW), + items("item"), + combo_button("combo_button"), + combo_list("combo_list"), + combo_editor("combo_editor"), + drop_down_button("drop_down_button") +{ + addSynonym(items, "combo_item"); +} + + +LLComboBox::LLComboBox(const LLComboBox::Params& p) +: LLUICtrl(p), + mTextEntry(NULL), + mTextEntryTentative(p.show_text_as_tentative), + mHasAutocompletedText(false), + mAllowTextEntry(p.allow_text_entry), + mAllowNewValues(p.allow_new_values), + mMaxChars(p.max_chars), + mPrearrangeCallback(p.prearrange_callback()), + mTextEntryCallback(p.text_entry_callback()), + mTextChangedCallback(p.text_changed_callback()), + mListPosition(p.list_position), + mLastSelectedIndex(-1), + mLabel(p.label) +{ + // Text label button + + LLButton::Params button_params = (mAllowTextEntry ? p.combo_button : p.drop_down_button); + button_params.mouse_down_callback.function( + boost::bind(&LLComboBox::onButtonMouseDown, this)); + button_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_BOTTOM|FOLLOWS_RIGHT); + button_params.rect(p.rect); + + if(mAllowTextEntry) + { + button_params.pad_right(2); + } + + mArrowImage = button_params.image_unselected; + if (mArrowImage.notNull()) + { + mImageLoadedConnection = mArrowImage->addLoadedCallback(boost::bind(&LLComboBox::imageLoaded, this)); + } + + mButton = LLUICtrlFactory::create(button_params); + + + if(mAllowTextEntry) + { + //redo to compensate for button hack that leaves space for a character + //unless it is a "minimal combobox"(drop down) + mButton->setRightHPad(2); + } + addChild(mButton); + + LLScrollListCtrl::Params params = p.combo_list; + params.name("ComboBox"); + params.commit_callback.function(boost::bind(&LLComboBox::onItemSelected, this, _2)); + params.visible(false); + params.commit_on_keyboard_movement(false); + + mList = LLUICtrlFactory::create(params); + addChild(mList); + + // Mouse-down on button will transfer mouse focus to the list + // Grab the mouse-up event and make sure the button state is correct + mList->setMouseUpCallback(boost::bind(&LLComboBox::onListMouseUp, this)); + + for (LLInitParam::ParamIterator::const_iterator it = p.items.begin(); + it != p.items.end(); + ++it) + { + LLScrollListItem::Params item_params = *it; + if (it->label.isProvided()) + { + item_params.columns.add().value(it->label()); + } + + mList->addRow(item_params); + } + + createLineEditor(p); + + mTopLostSignalConnection = setTopLostCallback(boost::bind(&LLComboBox::hideList, this)); +} + +void LLComboBox::initFromParams(const LLComboBox::Params& p) +{ + LLUICtrl::initFromParams(p); + + if (!acceptsTextInput() && mLabel.empty()) + { + selectFirstItem(); + } +} + +// virtual +bool LLComboBox::postBuild() +{ + if (mControlVariable) + { + setValue(mControlVariable->getValue()); // selects the appropriate item + } + return true; +} + + +LLComboBox::~LLComboBox() +{ + // children automatically deleted, including mMenu, mButton + + // explicitly disconect this signal, since base class destructor might fire top lost + mTopLostSignalConnection.disconnect(); + mImageLoadedConnection.disconnect(); + + LLUI::getInstance()->removePopup(this); +} + + +void LLComboBox::clear() +{ + if (mTextEntry) + { + mTextEntry->setText(LLStringUtil::null); + } + mButton->setLabelSelected(LLStringUtil::null); + mButton->setLabelUnselected(LLStringUtil::null); + mList->deselectAllItems(); + mLastSelectedIndex = -1; +} + +void LLComboBox::onCommit() +{ + if (mAllowTextEntry && getCurrentIndex() != -1) + { + // we have selected an existing item, blitz the manual text entry with + // the properly capitalized item + mTextEntry->setValue(getSimple()); + mTextEntry->setTentative(false); + } + setControlValue(getValue()); + LLUICtrl::onCommit(); +} + +// virtual +bool LLComboBox::isDirty() const +{ + bool grubby = false; + if ( mList ) + { + grubby = mList->isDirty(); + } + return grubby; +} + +// virtual Clear dirty state +void LLComboBox::resetDirty() +{ + if ( mList ) + { + mList->resetDirty(); + } +} + +bool LLComboBox::itemExists(const std::string& name) +{ + return mList->getItemByLabel(name); +} + +// add item "name" to menu +LLScrollListItem* LLComboBox::add(const std::string& name, EAddPosition pos, bool enabled) +{ + LLScrollListItem* item = mList->addSimpleElement(name, pos); + item->setEnabled(enabled); + if (!mAllowTextEntry && mLabel.empty()) + { + if (mControlVariable) + { + setValue(mControlVariable->getValue()); // selects the appropriate item + } + else + { + selectFirstItem(); + } + } + return item; +} + +// add item "name" with a unique id to menu +LLScrollListItem* LLComboBox::add(const std::string& name, const LLUUID& id, EAddPosition pos, bool enabled ) +{ + LLScrollListItem* item = mList->addSimpleElement(name, pos, id); + item->setEnabled(enabled); + if (!mAllowTextEntry && mLabel.empty()) + { + if (mControlVariable) + { + setValue(mControlVariable->getValue()); // selects the appropriate item + } + else + { + selectFirstItem(); + } + } + return item; +} + +// add item "name" with attached userdata +LLScrollListItem* LLComboBox::add(const std::string& name, void* userdata, EAddPosition pos, bool enabled ) +{ + LLScrollListItem* item = mList->addSimpleElement(name, pos); + item->setEnabled(enabled); + item->setUserdata( userdata ); + if (!mAllowTextEntry && mLabel.empty()) + { + if (mControlVariable) + { + setValue(mControlVariable->getValue()); // selects the appropriate item + } + else + { + selectFirstItem(); + } + } + return item; +} + +// add item "name" with attached generic data +LLScrollListItem* LLComboBox::add(const std::string& name, LLSD value, EAddPosition pos, bool enabled ) +{ + LLScrollListItem* item = mList->addSimpleElement(name, pos, value); + item->setEnabled(enabled); + if (!mAllowTextEntry && mLabel.empty()) + { + if (mControlVariable) + { + setValue(mControlVariable->getValue()); // selects the appropriate item + } + else + { + selectFirstItem(); + } + } + return item; +} + +LLScrollListItem* LLComboBox::addSeparator(EAddPosition pos) +{ + return mList->addSeparator(pos); +} + +void LLComboBox::sortByName(bool ascending) +{ + mList->sortOnce(0, ascending); +} + + +// Choose an item with a given name in the menu. +// Returns true if the item was found. +bool LLComboBox::setSimple(const LLStringExplicit& name) +{ + bool found = mList->selectItemByLabel(name, false); + + if (found) + { + setLabel(name); + mLastSelectedIndex = mList->getFirstSelectedIndex(); + } + + return found; +} + +// virtual +void LLComboBox::setValue(const LLSD& value) +{ + bool found = mList->selectByValue(value); + if (found) + { + LLScrollListItem* item = mList->getFirstSelected(); + if (item) + { + updateLabel(); + } + mLastSelectedIndex = mList->getFirstSelectedIndex(); + } + else + { + mLastSelectedIndex = -1; + } +} + +const std::string LLComboBox::getSimple() const +{ + const std::string res = getSelectedItemLabel(); + if (res.empty() && mAllowTextEntry) + { + return mTextEntry->getText(); + } + else + { + return res; + } +} + +const std::string LLComboBox::getSelectedItemLabel(S32 column) const +{ + return mList->getSelectedItemLabel(column); +} + +// virtual +LLSD LLComboBox::getValue() const +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return item->getValue(); + } + else if (mAllowTextEntry) + { + return mTextEntry->getValue(); + } + else + { + return LLSD(); + } +} + +void LLComboBox::setLabel(const LLStringExplicit& name) +{ + if ( mTextEntry ) + { + mTextEntry->setText(name); + if (mList->selectItemByLabel(name, false)) + { + mTextEntry->setTentative(false); + mLastSelectedIndex = mList->getFirstSelectedIndex(); + } + else + { + mTextEntry->setTentative(mTextEntryTentative); + } + } + + if (!mAllowTextEntry) + { + mButton->setLabel(name); + } +} + +void LLComboBox::updateLabel() +{ + // Update the combo editor with the selected + // item label. + if (mTextEntry) + { + mTextEntry->setText(getSelectedItemLabel()); + mTextEntry->setTentative(false); + } + + // If combo box doesn't allow text entry update + // the combo button label. + if (!mAllowTextEntry) + { + mButton->setLabel(getSelectedItemLabel()); + } +} + +bool LLComboBox::remove(const std::string& name) +{ + bool found = mList->selectItemByLabel(name); + + if (found) + { + LLScrollListItem* item = mList->getFirstSelected(); + if (item) + { + mList->deleteSingleItem(mList->getItemIndex(item)); + } + mLastSelectedIndex = mList->getFirstSelectedIndex(); + } + + return found; +} + +bool LLComboBox::remove(S32 index) +{ + if (index < mList->getItemCount()) + { + mList->deleteSingleItem(index); + setLabel(getSelectedItemLabel()); + return true; + } + return false; +} + +// Keyboard focus lost. +void LLComboBox::onFocusLost() +{ + hideList(); + // if valid selection + if (mAllowTextEntry && getCurrentIndex() != -1) + { + mTextEntry->selectAll(); + } + mButton->setForcePressedState(false); + LLUICtrl::onFocusLost(); +} + +void LLComboBox::setButtonVisible(bool visible) +{ + mButton->setVisible(visible); + if (mTextEntry) + { + LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); + if (visible) + { + S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; + text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * BTN_DROP_SHADOW; + } + //mTextEntry->setRect(text_entry_rect); + mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), true); + } +} + +bool LLComboBox::setCurrentByIndex( S32 index ) +{ + bool found = mList->selectNthItem( index ); + if (found) + { + setLabel(getSelectedItemLabel()); + mLastSelectedIndex = index; + } + return found; +} + +S32 LLComboBox::getCurrentIndex() const +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return mList->getItemIndex( item ); + } + return -1; +} + +void LLComboBox::setEnabledByValue(const LLSD& value, bool enabled) +{ + LLScrollListItem *found = mList->getItem(value); + if (found) + { + found->setEnabled(enabled); + } +} + +void LLComboBox::createLineEditor(const LLComboBox::Params& p) +{ + LLRect rect = getLocalRect(); + if (mAllowTextEntry) + { + S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; + S32 shadow_size = BTN_DROP_SHADOW; + mButton->setRect(LLRect( getRect().getWidth() - llmax(8,arrow_width) - 2 * shadow_size, + rect.mTop, rect.mRight, rect.mBottom)); + mButton->setTabStop(false); + mButton->setHAlign(LLFontGL::HCENTER); + + LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); + text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * BTN_DROP_SHADOW; + // clear label on button + std::string cur_label = mButton->getLabelSelected(); + LLLineEditor::Params params = p.combo_editor; + params.rect(text_entry_rect); + params.default_text(LLStringUtil::null); + params.max_length.bytes(mMaxChars); + params.commit_callback.function(boost::bind(&LLComboBox::onTextCommit, this, _2)); + params.keystroke_callback(boost::bind(&LLComboBox::onTextEntry, this, _1)); + params.commit_on_focus_lost(false); + params.follows.flags(FOLLOWS_ALL); + params.label(mLabel); + mTextEntry = LLUICtrlFactory::create (params); + mTextEntry->setText(cur_label); + mTextEntry->setIgnoreTab(true); + addChild(mTextEntry); + + // clear label on button + setLabel(LLStringUtil::null); + + mButton->setFollows(FOLLOWS_BOTTOM | FOLLOWS_TOP | FOLLOWS_RIGHT); + } + else + { + mButton->setRect(rect); + mButton->setLabel(mLabel.getString()); + + if (mTextEntry) + { + mTextEntry->setVisible(false); + } + } +} + +void LLComboBox::setLeftTextPadding(S32 pad) +{ + S32 left_pad, right_pad; + mTextEntry->getTextPadding(&left_pad, &right_pad); + mTextEntry->setTextPadding(pad, right_pad); +} + +void* LLComboBox::getCurrentUserdata() +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return item->getUserdata(); + } + return NULL; +} + + +void LLComboBox::showList() +{ + // Make sure we don't go off top of screen. + LLCoordWindow window_size; + getWindow()->getSize(&window_size); + //HACK: shouldn't have to know about scale here + mList->fitContents( 192, llfloor((F32)window_size.mY / LLUI::getScaleFactor().mV[VY]) - 50 ); + + // Make sure that we can see the whole list + LLRect root_view_local; + LLView* root_view = getRootView(); + root_view->localRectToOtherView(root_view->getLocalRect(), &root_view_local, this); + + LLRect rect = mList->getRect(); + + S32 min_width = getRect().getWidth(); + S32 max_width = llmax(min_width, MAX_COMBO_WIDTH); + // make sure we have up to date content width metrics + S32 list_width = llclamp(mList->calcMaxContentWidth(), min_width, max_width); + + if (mListPosition == BELOW) + { + if (rect.getHeight() <= -root_view_local.mBottom) + { + // Move rect so it hangs off the bottom of this view + rect.setLeftTopAndSize(0, 0, list_width, rect.getHeight() ); + } + else + { + // stack on top or bottom, depending on which has more room + if (-root_view_local.mBottom > root_view_local.mTop - getRect().getHeight()) + { + // Move rect so it hangs off the bottom of this view + rect.setLeftTopAndSize(0, 0, list_width, llmin(-root_view_local.mBottom, rect.getHeight())); + } + else + { + // move rect so it stacks on top of this view (clipped to size of screen) + rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight())); + } + } + } + else // ABOVE + { + if (rect.getHeight() <= root_view_local.mTop - getRect().getHeight()) + { + // move rect so it stacks on top of this view (clipped to size of screen) + rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight())); + } + else + { + // stack on top or bottom, depending on which has more room + if (-root_view_local.mBottom > root_view_local.mTop - getRect().getHeight()) + { + // Move rect so it hangs off the bottom of this view + rect.setLeftTopAndSize(0, 0, list_width, llmin(-root_view_local.mBottom, rect.getHeight())); + } + else + { + // move rect so it stacks on top of this view (clipped to size of screen) + rect.setOriginAndSize(0, getRect().getHeight(), list_width, llmin(root_view_local.mTop - getRect().getHeight(), rect.getHeight())); + } + } + + } + mList->setOrigin(rect.mLeft, rect.mBottom); + mList->reshape(rect.getWidth(), rect.getHeight()); + mList->translateIntoRect(root_view_local); + + // Make sure we didn't go off bottom of screen + S32 x, y; + mList->localPointToScreen(0, 0, &x, &y); + + if (y < 0) + { + mList->translate(0, -y); + } + + // NB: this call will trigger the focuslost callback which will hide the list, so do it first + // before finally showing the list + + mList->setFocus(true); + + // Show the list and push the button down + mButton->setToggleState(true); + mList->setVisible(true); + + LLUI::getInstance()->addPopup(this); + + setUseBoundingRect(true); +// updateBoundingRect(); +} + +void LLComboBox::hideList() +{ + if (mList->getVisible()) + { + // assert selection in list + if(mAllowNewValues) + { + // mLastSelectedIndex = -1 means that we entered a new value, don't select + // any of existing items in this case. + if(mLastSelectedIndex >= 0) + mList->selectNthItem(mLastSelectedIndex); + } + else if(mLastSelectedIndex >= 0) + mList->selectNthItem(mLastSelectedIndex); + + mButton->setToggleState(false); + mList->setVisible(false); + mList->mouseOverHighlightNthItem(-1); + + setUseBoundingRect(false); + LLUI::getInstance()->removePopup(this); +// updateBoundingRect(); + } +} + +void LLComboBox::onButtonMouseDown() +{ + if (!mList->getVisible()) + { + // this might change selection, so do it first + prearrangeList(); + + // highlight the last selected item from the original selection before potentially selecting a new item + // as visual cue to original value of combo box + LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); + if (last_selected_item) + { + mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); + } + + if (mList->getItemCount() != 0) + { + showList(); + } + + setFocus( true ); + + // pass mouse capture on to list if button is depressed + if (mButton->hasMouseCapture()) + { + gFocusMgr.setMouseCapture(mList); + + // But keep the "pressed" look, which buttons normally lose when they + // lose focus + mButton->setForcePressedState(true); + } + } + else + { + hideList(); + } + +} + +void LLComboBox::onListMouseUp() +{ + // In some cases this is the termination of a mouse click that started on + // the button, so clear its pressed state + mButton->setForcePressedState(false); +} + +//------------------------------------------------------------------ +// static functions +//------------------------------------------------------------------ + +void LLComboBox::onItemSelected(const LLSD& data) +{ + mLastSelectedIndex = getCurrentIndex(); + if (mLastSelectedIndex != -1) + { + updateLabel(); + + if (mAllowTextEntry) + { + gFocusMgr.setKeyboardFocus(mTextEntry); + mTextEntry->selectAll(); + } + } + // hiding the list reasserts the old value stored in the text editor/dropdown button + hideList(); + + // commit does the reverse, asserting the value in the list + onCommit(); +} + +bool LLComboBox::handleToolTip(S32 x, S32 y, MASK mask) +{ + std::string tool_tip; + + if(LLUICtrl::handleToolTip(x, y, mask)) + { + return true; + } + + tool_tip = getToolTip(); + if (tool_tip.empty()) + { + tool_tip = getSelectedItemLabel(); + } + + if( !tool_tip.empty() ) + { + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(tool_tip) + .sticky_rect(calcScreenRect())); + } + return true; +} + +bool LLComboBox::handleKeyHere(KEY key, MASK mask) +{ + bool result = false; + if (hasFocus()) + { + if (mList->getVisible() + && key == KEY_ESCAPE && mask == MASK_NONE) + { + hideList(); + return true; + } + //give list a chance to pop up and handle key + LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); + if (last_selected_item) + { + // highlight the original selection before potentially selecting a new item + mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); + } + result = mList->handleKeyHere(key, mask); + + // will only see return key if it is originating from line editor + // since the dropdown button eats the key + if (key == KEY_RETURN) + { + if (mask == MASK_NONE) + { + mOnReturnSignal(this, getValue()); + } + + // don't show list and don't eat key input when committing + // free-form text entry with RETURN since user already knows + // what they are trying to select + return false; + } + // if selection has changed, pop open list + else if (mList->getLastSelectedItem() != last_selected_item + || ((key == KEY_DOWN || key == KEY_UP) + && mList->getCanSelect() + && !mList->isEmpty())) + { + showList(); + } + } + return result; +} + +bool LLComboBox::handleUnicodeCharHere(llwchar uni_char) +{ + bool result = false; + if (gFocusMgr.childHasKeyboardFocus(this)) + { + // space bar just shows the list + if (' ' != uni_char ) + { + LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); + if (last_selected_item) + { + // highlight the original selection before potentially selecting a new item + mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); + } + result = mList->handleUnicodeCharHere(uni_char); + if (mList->getLastSelectedItem() != last_selected_item) + { + showList(); + } + } + } + return result; +} + +void LLComboBox::setTextEntry(const LLStringExplicit& text) +{ + if (mTextEntry) + { + mTextEntry->setText(text); + mHasAutocompletedText = false; + updateSelection(); + } +} + +void LLComboBox::setKeystrokeOnEsc(bool enable) +{ + if (mTextEntry) + { + mTextEntry->setKeystrokeOnEsc(enable); + } +} + +void LLComboBox::onTextEntry(LLLineEditor* line_editor) +{ + if (mTextEntryCallback != NULL) + { + (mTextEntryCallback)(line_editor, LLSD()); + } + + KEY key = gKeyboard->currentKey(); + if (key == KEY_BACKSPACE || + key == KEY_DELETE) + { + if (mList->selectItemByLabel(line_editor->getText(), false)) + { + line_editor->setTentative(false); + mLastSelectedIndex = mList->getFirstSelectedIndex(); + } + else + { + line_editor->setTentative(mTextEntryTentative); + mList->deselectAllItems(); + mLastSelectedIndex = -1; + } + if (mTextChangedCallback != NULL) + { + (mTextChangedCallback)(line_editor, LLSD()); + } + return; + } + + if (key == KEY_LEFT || + key == KEY_RIGHT) + { + return; + } + + if (key == KEY_DOWN) + { + setCurrentByIndex(llmin(getItemCount() - 1, getCurrentIndex() + 1)); + if (!mList->getVisible()) + { + prearrangeList(); + + if (mList->getItemCount() != 0) + { + showList(); + } + } + line_editor->selectAll(); + line_editor->setTentative(false); + } + else if (key == KEY_UP) + { + setCurrentByIndex(llmax(0, getCurrentIndex() - 1)); + if (!mList->getVisible()) + { + prearrangeList(); + + if (mList->getItemCount() != 0) + { + showList(); + } + } + line_editor->selectAll(); + line_editor->setTentative(false); + } + else + { + // RN: presumably text entry + updateSelection(); + } + if (mTextChangedCallback != NULL) + { + (mTextChangedCallback)(line_editor, LLSD()); + } +} + +void LLComboBox::updateSelection() +{ + LLWString left_wstring = mTextEntry->getWText().substr(0, mTextEntry->getCursor()); + // user-entered portion of string, based on assumption that any selected + // text was a result of auto-completion + LLWString user_wstring = mHasAutocompletedText ? left_wstring : mTextEntry->getWText(); + std::string full_string = mTextEntry->getText(); + + // go ahead and arrange drop down list on first typed character, even + // though we aren't showing it... some code relies on prearrange + // callback to populate content + if( mTextEntry->getWText().size() == 1 ) + { + prearrangeList(mTextEntry->getText()); + } + + if (mList->selectItemByLabel(full_string, false)) + { + mTextEntry->setTentative(false); + mLastSelectedIndex = mList->getFirstSelectedIndex(); + } + else if (mList->selectItemByPrefix(left_wstring, false)) + { + LLWString selected_item = utf8str_to_wstring(getSelectedItemLabel()); + LLWString wtext = left_wstring + selected_item.substr(left_wstring.size(), selected_item.size()); + mTextEntry->setText(wstring_to_utf8str(wtext)); + mTextEntry->setSelection(left_wstring.size(), mTextEntry->getWText().size()); + mTextEntry->endSelection(); + mTextEntry->setTentative(false); + mHasAutocompletedText = true; + mLastSelectedIndex = mList->getFirstSelectedIndex(); + } + else // no matching items found + { + mList->deselectAllItems(); + mTextEntry->setText(wstring_to_utf8str(user_wstring)); // removes text added by autocompletion + mTextEntry->setTentative(mTextEntryTentative); + mHasAutocompletedText = false; + mLastSelectedIndex = -1; + } +} + +void LLComboBox::onTextCommit(const LLSD& data) +{ + std::string text = mTextEntry->getText(); + setSimple(text); + onCommit(); + mTextEntry->selectAll(); +} + +void LLComboBox::setFocus(bool b) +{ + LLUICtrl::setFocus(b); + + if (b) + { + mList->clearSearchString(); + if (mList->getVisible()) + { + mList->setFocus(true); + } + } +} + +void LLComboBox::prearrangeList(std::string filter) +{ + if (mPrearrangeCallback) + { + mPrearrangeCallback(this, LLSD(filter)); + } +} + + +//============================================================================ +// ll::ui::SearchableControl functions + +//virtual +std::string LLComboBox::_getSearchText() const +{ + std::string res; + if (mList) + { + // getAllData returns a full copy of content, might be a + // better option to implement an mList->getSearchText(column) + std::vector data = mList->getAllData(); + std::vector::iterator iter = data.begin(); + while (iter != data.end()) + { + LLScrollListCell* cell = (*iter)->getColumn(0); + if (cell) + { + std::string whitelist_url = cell->getValue().asString(); + res += cell->getValue().asString(); + } + iter++; + } + } + return res + getToolTip(); +} + +//virtual +void LLComboBox::onSetHighlight() const +{ + if (mButton) + { + mButton->ll::ui::SearchableControl::setHighlighted(ll::ui::SearchableControl::getHighlighted()); + } +} + +void LLComboBox::imageLoaded() +{ + if (mAllowTextEntry) + { + LLRect rect = getLocalRect(); + S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; + S32 shadow_size = BTN_DROP_SHADOW; + mButton->setRect(LLRect(getRect().getWidth() - llmax(8, arrow_width) - 2 * shadow_size, + rect.mTop, rect.mRight, rect.mBottom)); + if (mButton->getVisible()) + { + // recalculate field size + if (mTextEntry) + { + LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); + text_entry_rect.mRight -= llmax(8, arrow_width) + 2 * BTN_DROP_SHADOW; + mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), true); + } + } + } +} + +//============================================================================ +// LLCtrlListInterface functions + +S32 LLComboBox::getItemCount() const +{ + return mList->getItemCount(); +} + +void LLComboBox::addColumn(const LLSD& column, EAddPosition pos) +{ + mList->clearColumns(); + mList->addColumn(column, pos); +} + +void LLComboBox::clearColumns() +{ + mList->clearColumns(); +} + +void LLComboBox::setColumnLabel(const std::string& column, const std::string& label) +{ + mList->setColumnLabel(column, label); +} + +LLScrollListItem* LLComboBox::addElement(const LLSD& value, EAddPosition pos, void* userdata) +{ + return mList->addElement(value, pos, userdata); +} + +LLScrollListItem* LLComboBox::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id) +{ + return mList->addSimpleElement(value, pos, id); +} + +void LLComboBox::clearRows() +{ + mList->clearRows(); +} + +void LLComboBox::sortByColumn(const std::string& name, bool ascending) +{ + mList->sortByColumn(name, ascending); +} + +//============================================================================ +//LLCtrlSelectionInterface functions + +bool LLComboBox::setCurrentByID(const LLUUID& id) +{ + bool found = mList->selectByID( id ); + + if (found) + { + setLabel(getSelectedItemLabel()); + mLastSelectedIndex = mList->getFirstSelectedIndex(); + } + + return found; +} + +LLUUID LLComboBox::getCurrentID() const +{ + return mList->getStringUUIDSelectedItem(); +} +bool LLComboBox::setSelectedByValue(const LLSD& value, bool selected) +{ + bool found = mList->setSelectedByValue(value, selected); + if (found) + { + setLabel(getSelectedItemLabel()); + } + return found; +} + +LLSD LLComboBox::getSelectedValue() +{ + return mList->getSelectedValue(); +} + +bool LLComboBox::isSelected(const LLSD& value) const +{ + return mList->isSelected(value); +} + +bool LLComboBox::operateOnSelection(EOperation op) +{ + if (op == OP_DELETE) + { + mList->deleteSelectedItems(); + return true; + } + return false; +} + +bool LLComboBox::operateOnAll(EOperation op) +{ + if (op == OP_DELETE) + { + clearRows(); + return true; + } + return false; +} + +bool LLComboBox::selectItemRange( S32 first, S32 last ) +{ + return mList->selectItemRange(first, last); +} + + +static LLDefaultChildRegistry::Register register_icons_combo_box("icons_combo_box"); + +LLIconsComboBox::Params::Params() +: icon_column("icon_column", ICON_COLUMN), + label_column("label_column", LABEL_COLUMN) +{} + +LLIconsComboBox::LLIconsComboBox(const LLIconsComboBox::Params& p) +: LLComboBox(p), + mIconColumnIndex(p.icon_column), + mLabelColumnIndex(p.label_column) +{} + +const std::string LLIconsComboBox::getSelectedItemLabel(S32 column) const +{ + mButton->setImageOverlay(LLComboBox::getSelectedItemLabel(mIconColumnIndex), mButton->getImageOverlayHAlign()); + + return LLComboBox::getSelectedItemLabel(mLabelColumnIndex); +} diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h index dd827fb548..cc1c2885fc 100644 --- a/indra/llui/llcombobox.h +++ b/indra/llui/llcombobox.h @@ -1,284 +1,284 @@ -/** - * @file llcombobox.h - * @brief LLComboBox base 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$ - */ - -// A control that displays the name of the chosen item, which when clicked -// shows a scrolling box of choices. - -#ifndef LL_LLCOMBOBOX_H -#define LL_LLCOMBOBOX_H - -#include "llbutton.h" -#include "lluictrl.h" -#include "llctrlselectioninterface.h" -#include "llrect.h" -#include "llscrolllistctrl.h" -#include "lllineeditor.h" -#include - -// Classes - -class LLFontGL; -class LLViewBorder; - -class LLComboBox -: public LLUICtrl -, public LLCtrlListInterface -, public ll::ui::SearchableControl -{ -public: - typedef enum e_preferred_position - { - ABOVE, - BELOW - } EPreferredPosition; - - struct PreferredPositionValues : public LLInitParam::TypeValuesHelper - { - static void declareValues(); - }; - - - struct ItemParams : public LLInitParam::Block - { - Optional label; - ItemParams(); - }; - - struct Params - : public LLInitParam::Block - { - Optional allow_text_entry, - show_text_as_tentative, - allow_new_values; - Optional max_chars; - Optional prearrange_callback, - text_entry_callback, - text_changed_callback; - - Optional list_position; - - // components - Optional combo_button; - Optional combo_list; - Optional combo_editor; - - Optional drop_down_button; - - Multiple items; - - Params(); - }; - - - virtual ~LLComboBox(); - /*virtual*/ bool postBuild(); - -protected: - friend class LLUICtrlFactory; - LLComboBox(const Params&); - void initFromParams(const Params&); - void prearrangeList(std::string filter = ""); - - virtual std::string _getSearchText() const; - virtual void onSetHighlight() const; - - void imageLoaded(); - -public: - // LLView interface - virtual void onFocusLost(); - - virtual bool handleToolTip(S32 x, S32 y, MASK mask); - virtual bool handleKeyHere(KEY key, MASK mask); - virtual bool handleUnicodeCharHere(llwchar uni_char); - - // LLUICtrl interface - virtual void clear(); // select nothing - virtual void onCommit(); - virtual bool acceptsTextInput() const { return mAllowTextEntry; } - virtual bool isDirty() const; // Returns true if the user has modified this control. - virtual void resetDirty(); // Clear dirty state - - virtual void setFocus(bool b); - - // Selects item by underlying LLSD value, using LLSD::asString() matching. - // For simple items, this is just the name of the label. - virtual void setValue(const LLSD& value ); - - // Gets underlying LLSD value for currently selected items. For simple - // items, this is just the label. - virtual LLSD getValue() const; - - void setTextEntry(const LLStringExplicit& text); - void setKeystrokeOnEsc(bool enable); - - LLScrollListItem* add(const std::string& name, EAddPosition pos = ADD_BOTTOM, bool enabled = true); // add item "name" to menu - LLScrollListItem* add(const std::string& name, const LLUUID& id, EAddPosition pos = ADD_BOTTOM, bool enabled = true); - LLScrollListItem* add(const std::string& name, void* userdata, EAddPosition pos = ADD_BOTTOM, bool enabled = true); - LLScrollListItem* add(const std::string& name, LLSD value, EAddPosition pos = ADD_BOTTOM, bool enabled = true); - LLScrollListItem* addSeparator(EAddPosition pos = ADD_BOTTOM); - bool remove( S32 index ); // remove item by index, return true if found and removed - void removeall() { clearRows(); } - bool itemExists(const std::string& name); - - void sortByName(bool ascending = true); // Sort the entries in the combobox by name - - // Select current item by name using selectItemByLabel. Returns false if not found. - bool setSimple(const LLStringExplicit& name); - // Get name of current item. Returns an empty string if not found. - const std::string getSimple() const; - // Get contents of column x of selected row - virtual const std::string getSelectedItemLabel(S32 column = 0) const; - - // Sets the label, which doesn't have to exist in the label. - // This is probably a UI abuse. - void setLabel(const LLStringExplicit& name); - - // Updates the combobox label to match the selected list item. - void updateLabel(); - - bool remove(const std::string& name); // remove item "name", return true if found and removed - - bool setCurrentByIndex( S32 index ); - S32 getCurrentIndex() const; - - void setEnabledByValue(const LLSD& value, bool enabled); - - void createLineEditor(const Params&); - - //======================================================================== - LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; }; - LLCtrlListInterface* getListInterface() { return (LLCtrlListInterface*)this; }; - - // LLCtrlListInterface functions - // See llscrolllistctrl.h - virtual S32 getItemCount() const; - // Overwrites the default column (See LLScrollListCtrl for format) - virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM); - virtual void clearColumns(); - virtual void setColumnLabel(const std::string& column, const std::string& label); - virtual LLScrollListItem* addElement(const LLSD& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); - virtual LLScrollListItem* addSimpleElement(const std::string& value, EAddPosition pos = ADD_BOTTOM, const LLSD& id = LLSD()); - virtual void clearRows(); - virtual void sortByColumn(const std::string& name, bool ascending); - - // LLCtrlSelectionInterface functions - virtual bool getCanSelect() const { return true; } - virtual bool selectFirstItem() { return setCurrentByIndex(0); } - virtual bool selectNthItem( S32 index ) { return setCurrentByIndex(index); } - virtual bool selectItemRange( S32 first, S32 last ); - virtual S32 getFirstSelectedIndex() const { return getCurrentIndex(); } - virtual bool setCurrentByID( const LLUUID& id ); - virtual LLUUID getCurrentID() const; // LLUUID::null if no items in menu - virtual bool setSelectedByValue(const LLSD& value, bool selected); - virtual LLSD getSelectedValue(); - virtual bool isSelected(const LLSD& value) const; - virtual bool operateOnSelection(EOperation op); - virtual bool operateOnAll(EOperation op); - - //======================================================================== - - void setLeftTextPadding(S32 pad); - - void* getCurrentUserdata(); - - void setPrearrangeCallback( commit_callback_t cb ) { mPrearrangeCallback = cb; } - void setTextEntryCallback( commit_callback_t cb ) { mTextEntryCallback = cb; } - void setTextChangedCallback( commit_callback_t cb ) { mTextChangedCallback = cb; } - - /** - * Connects callback to signal called when Return key is pressed. - */ - boost::signals2::connection setReturnCallback( const commit_signal_t::slot_type& cb ) { return mOnReturnSignal.connect(cb); } - - void setButtonVisible(bool visible); - - void onButtonMouseDown(); - void onListMouseUp(); - void onItemSelected(const LLSD& data); - void onTextCommit(const LLSD& data); - - void updateSelection(); - virtual void showList(); - virtual void hideList(); - - virtual void onTextEntry(LLLineEditor* line_editor); - -protected: - LLButton* mButton; - LLLineEditor* mTextEntry; - LLScrollListCtrl* mList; - EPreferredPosition mListPosition; - LLPointer mArrowImage; - LLUIString mLabel; - bool mHasAutocompletedText; - -private: - bool mAllowTextEntry; - bool mAllowNewValues; - S32 mMaxChars; - bool mTextEntryTentative; - commit_callback_t mPrearrangeCallback; - commit_callback_t mTextEntryCallback; - commit_callback_t mTextChangedCallback; - commit_callback_t mSelectionCallback; - boost::signals2::connection mTopLostSignalConnection; - boost::signals2::connection mImageLoadedConnection; - commit_signal_t mOnReturnSignal; - S32 mLastSelectedIndex; -}; - -// A combo box with icons for the list of items. -class LLIconsComboBox -: public LLComboBox -{ -public: - struct Params - : public LLInitParam::Block - { - Optional icon_column, - label_column; - Params(); - }; - - /*virtual*/ const std::string getSelectedItemLabel(S32 column = 0) const; - -private: - enum EColumnIndex - { - ICON_COLUMN = 0, - LABEL_COLUMN - }; - - friend class LLUICtrlFactory; - LLIconsComboBox(const Params&); - virtual ~LLIconsComboBox() {}; - - S32 mIconColumnIndex; - S32 mLabelColumnIndex; -}; - -#endif +/** + * @file llcombobox.h + * @brief LLComboBox base 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$ + */ + +// A control that displays the name of the chosen item, which when clicked +// shows a scrolling box of choices. + +#ifndef LL_LLCOMBOBOX_H +#define LL_LLCOMBOBOX_H + +#include "llbutton.h" +#include "lluictrl.h" +#include "llctrlselectioninterface.h" +#include "llrect.h" +#include "llscrolllistctrl.h" +#include "lllineeditor.h" +#include + +// Classes + +class LLFontGL; +class LLViewBorder; + +class LLComboBox +: public LLUICtrl +, public LLCtrlListInterface +, public ll::ui::SearchableControl +{ +public: + typedef enum e_preferred_position + { + ABOVE, + BELOW + } EPreferredPosition; + + struct PreferredPositionValues : public LLInitParam::TypeValuesHelper + { + static void declareValues(); + }; + + + struct ItemParams : public LLInitParam::Block + { + Optional label; + ItemParams(); + }; + + struct Params + : public LLInitParam::Block + { + Optional allow_text_entry, + show_text_as_tentative, + allow_new_values; + Optional max_chars; + Optional prearrange_callback, + text_entry_callback, + text_changed_callback; + + Optional list_position; + + // components + Optional combo_button; + Optional combo_list; + Optional combo_editor; + + Optional drop_down_button; + + Multiple items; + + Params(); + }; + + + virtual ~LLComboBox(); + /*virtual*/ bool postBuild(); + +protected: + friend class LLUICtrlFactory; + LLComboBox(const Params&); + void initFromParams(const Params&); + void prearrangeList(std::string filter = ""); + + virtual std::string _getSearchText() const; + virtual void onSetHighlight() const; + + void imageLoaded(); + +public: + // LLView interface + virtual void onFocusLost(); + + virtual bool handleToolTip(S32 x, S32 y, MASK mask); + virtual bool handleKeyHere(KEY key, MASK mask); + virtual bool handleUnicodeCharHere(llwchar uni_char); + + // LLUICtrl interface + virtual void clear(); // select nothing + virtual void onCommit(); + virtual bool acceptsTextInput() const { return mAllowTextEntry; } + virtual bool isDirty() const; // Returns true if the user has modified this control. + virtual void resetDirty(); // Clear dirty state + + virtual void setFocus(bool b); + + // Selects item by underlying LLSD value, using LLSD::asString() matching. + // For simple items, this is just the name of the label. + virtual void setValue(const LLSD& value ); + + // Gets underlying LLSD value for currently selected items. For simple + // items, this is just the label. + virtual LLSD getValue() const; + + void setTextEntry(const LLStringExplicit& text); + void setKeystrokeOnEsc(bool enable); + + LLScrollListItem* add(const std::string& name, EAddPosition pos = ADD_BOTTOM, bool enabled = true); // add item "name" to menu + LLScrollListItem* add(const std::string& name, const LLUUID& id, EAddPosition pos = ADD_BOTTOM, bool enabled = true); + LLScrollListItem* add(const std::string& name, void* userdata, EAddPosition pos = ADD_BOTTOM, bool enabled = true); + LLScrollListItem* add(const std::string& name, LLSD value, EAddPosition pos = ADD_BOTTOM, bool enabled = true); + LLScrollListItem* addSeparator(EAddPosition pos = ADD_BOTTOM); + bool remove( S32 index ); // remove item by index, return true if found and removed + void removeall() { clearRows(); } + bool itemExists(const std::string& name); + + void sortByName(bool ascending = true); // Sort the entries in the combobox by name + + // Select current item by name using selectItemByLabel. Returns false if not found. + bool setSimple(const LLStringExplicit& name); + // Get name of current item. Returns an empty string if not found. + const std::string getSimple() const; + // Get contents of column x of selected row + virtual const std::string getSelectedItemLabel(S32 column = 0) const; + + // Sets the label, which doesn't have to exist in the label. + // This is probably a UI abuse. + void setLabel(const LLStringExplicit& name); + + // Updates the combobox label to match the selected list item. + void updateLabel(); + + bool remove(const std::string& name); // remove item "name", return true if found and removed + + bool setCurrentByIndex( S32 index ); + S32 getCurrentIndex() const; + + void setEnabledByValue(const LLSD& value, bool enabled); + + void createLineEditor(const Params&); + + //======================================================================== + LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; }; + LLCtrlListInterface* getListInterface() { return (LLCtrlListInterface*)this; }; + + // LLCtrlListInterface functions + // See llscrolllistctrl.h + virtual S32 getItemCount() const; + // Overwrites the default column (See LLScrollListCtrl for format) + virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM); + virtual void clearColumns(); + virtual void setColumnLabel(const std::string& column, const std::string& label); + virtual LLScrollListItem* addElement(const LLSD& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); + virtual LLScrollListItem* addSimpleElement(const std::string& value, EAddPosition pos = ADD_BOTTOM, const LLSD& id = LLSD()); + virtual void clearRows(); + virtual void sortByColumn(const std::string& name, bool ascending); + + // LLCtrlSelectionInterface functions + virtual bool getCanSelect() const { return true; } + virtual bool selectFirstItem() { return setCurrentByIndex(0); } + virtual bool selectNthItem( S32 index ) { return setCurrentByIndex(index); } + virtual bool selectItemRange( S32 first, S32 last ); + virtual S32 getFirstSelectedIndex() const { return getCurrentIndex(); } + virtual bool setCurrentByID( const LLUUID& id ); + virtual LLUUID getCurrentID() const; // LLUUID::null if no items in menu + virtual bool setSelectedByValue(const LLSD& value, bool selected); + virtual LLSD getSelectedValue(); + virtual bool isSelected(const LLSD& value) const; + virtual bool operateOnSelection(EOperation op); + virtual bool operateOnAll(EOperation op); + + //======================================================================== + + void setLeftTextPadding(S32 pad); + + void* getCurrentUserdata(); + + void setPrearrangeCallback( commit_callback_t cb ) { mPrearrangeCallback = cb; } + void setTextEntryCallback( commit_callback_t cb ) { mTextEntryCallback = cb; } + void setTextChangedCallback( commit_callback_t cb ) { mTextChangedCallback = cb; } + + /** + * Connects callback to signal called when Return key is pressed. + */ + boost::signals2::connection setReturnCallback( const commit_signal_t::slot_type& cb ) { return mOnReturnSignal.connect(cb); } + + void setButtonVisible(bool visible); + + void onButtonMouseDown(); + void onListMouseUp(); + void onItemSelected(const LLSD& data); + void onTextCommit(const LLSD& data); + + void updateSelection(); + virtual void showList(); + virtual void hideList(); + + virtual void onTextEntry(LLLineEditor* line_editor); + +protected: + LLButton* mButton; + LLLineEditor* mTextEntry; + LLScrollListCtrl* mList; + EPreferredPosition mListPosition; + LLPointer mArrowImage; + LLUIString mLabel; + bool mHasAutocompletedText; + +private: + bool mAllowTextEntry; + bool mAllowNewValues; + S32 mMaxChars; + bool mTextEntryTentative; + commit_callback_t mPrearrangeCallback; + commit_callback_t mTextEntryCallback; + commit_callback_t mTextChangedCallback; + commit_callback_t mSelectionCallback; + boost::signals2::connection mTopLostSignalConnection; + boost::signals2::connection mImageLoadedConnection; + commit_signal_t mOnReturnSignal; + S32 mLastSelectedIndex; +}; + +// A combo box with icons for the list of items. +class LLIconsComboBox +: public LLComboBox +{ +public: + struct Params + : public LLInitParam::Block + { + Optional icon_column, + label_column; + Params(); + }; + + /*virtual*/ const std::string getSelectedItemLabel(S32 column = 0) const; + +private: + enum EColumnIndex + { + ICON_COLUMN = 0, + LABEL_COLUMN + }; + + friend class LLUICtrlFactory; + LLIconsComboBox(const Params&); + virtual ~LLIconsComboBox() {}; + + S32 mIconColumnIndex; + S32 mLabelColumnIndex; +}; + +#endif diff --git a/indra/llui/llconsole.cpp b/indra/llui/llconsole.cpp index 4c163b10b0..be100a6fd2 100644 --- a/indra/llui/llconsole.cpp +++ b/indra/llui/llconsole.cpp @@ -1,403 +1,403 @@ -/** - * @file llconsole.cpp - * @brief a scrolling console output device - * - * $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 "llviewerprecompiledheaders.h" -#include "linden_common.h" - -#include "llconsole.h" - -// linden library includes -#include "llmath.h" -//#include "llviewercontrol.h" -#include "llcriticaldamp.h" -#include "llfontgl.h" -#include "llgl.h" -#include "llui.h" -#include "lluiimage.h" -//#include "llviewerimage.h" -//#include "llviewerimagelist.h" -//#include "llviewerwindow.h" -#include "llsd.h" -#include "llfontgl.h" -#include "llmath.h" - -//#include "llstartup.h" - -// Used for LCD display -extern void AddNewDebugConsoleToLCD(const LLWString &newLine); - -LLConsole* gConsole = NULL; // Created and destroyed in LLViewerWindow. - -const F32 FADE_DURATION = 2.f; - -static LLDefaultChildRegistry::Register r("console"); - -LLConsole::LLConsole(const LLConsole::Params& p) -: LLUICtrl(p), - LLFixedBuffer(p.max_lines), - mLinePersistTime(p.persist_time), // seconds - mFont(p.font), - mConsoleWidth(0), - mConsoleHeight(0) -{ - if (p.font_size_index.isProvided()) - { - setFontSize(p.font_size_index); - } - mFadeTime = mLinePersistTime - FADE_DURATION; - setMaxLines(LLUI::getInstance()->mSettingGroups["config"]->getS32("ConsoleMaxLines")); -} - -void LLConsole::setLinePersistTime(F32 seconds) -{ - mLinePersistTime = seconds; - mFadeTime = mLinePersistTime - FADE_DURATION; -} - -void LLConsole::reshape(S32 width, S32 height, bool called_from_parent) -{ - S32 new_width = llmax(50, llmin(getRect().getWidth(), width)); - S32 new_height = llmax(llfloor(mFont->getLineHeight()) + 15, llmin(getRect().getHeight(), height)); - - if ( mConsoleWidth == new_width - && mConsoleHeight == new_height ) - { - return; - } - - mConsoleWidth = new_width; - mConsoleHeight= new_height; - - LLUICtrl::reshape(new_width, new_height, called_from_parent); - - for(paragraph_t::iterator paragraph_it = mParagraphs.begin(); paragraph_it != mParagraphs.end(); paragraph_it++) - { - (*paragraph_it).updateLines((F32)getRect().getWidth(), mFont, true); - } -} - -void LLConsole::setFontSize(S32 size_index) -{ - if (-1 == size_index) - { - mFont = LLFontGL::getFontMonospace(); - } - else if (0 == size_index) - { - mFont = LLFontGL::getFontSansSerif(); - } - else if (1 == size_index) - { - mFont = LLFontGL::getFontSansSerifBig(); - } - else - { - mFont = LLFontGL::getFontSansSerifHuge(); - } - // Make sure the font exists - if (mFont == NULL) - { - mFont = LLFontGL::getFontDefault(); - } - - for(paragraph_t::iterator paragraph_it = mParagraphs.begin(); paragraph_it != mParagraphs.end(); paragraph_it++) - { - (*paragraph_it).updateLines((F32)getRect().getWidth(), mFont, true); - } -} - -void LLConsole::draw() -{ - // Units in pixels - static const F32 padding_horizontal = 10; - static const F32 padding_vertical = 3; - LLGLSUIDefault gls_ui; - - // skip lines added more than mLinePersistTime ago - F32 cur_time = mTimer.getElapsedTimeF32(); - - F32 skip_time = cur_time - mLinePersistTime; - F32 fade_time = cur_time - mFadeTime; - - if (mParagraphs.empty()) //No text to draw. - { - return; - } - - U32 num_lines=0; - - paragraph_t::reverse_iterator paragraph_it; - paragraph_it = mParagraphs.rbegin(); - U32 paragraph_num=mParagraphs.size(); - - while (!mParagraphs.empty() && paragraph_it != mParagraphs.rend()) - { - num_lines += (*paragraph_it).mLines.size(); - if(num_lines > mMaxLines - || ( (mLinePersistTime > (F32)0.f) && ((*paragraph_it).mAddTime - skip_time)/(mLinePersistTime - mFadeTime) <= (F32)0.f)) - { //All lines above here are done. Lose them. - for (U32 i=0;i console_bg_opacity(*LLUI::getInstance()->mSettingGroups["config"], "ConsoleBackgroundOpacity", 0.7f); - F32 console_opacity = llclamp(console_bg_opacity(), 0.f, 1.f); - - LLColor4 color = LLUIColorTable::instance().getColor("ConsoleBackground"); - color.mV[VALPHA] *= console_opacity; - - F32 line_height = mFont->getLineHeight(); - - for(paragraph_it = mParagraphs.rbegin(); paragraph_it != mParagraphs.rend(); paragraph_it++) - { - S32 target_height = llfloor( (*paragraph_it).mLines.size() * line_height + padding_vertical); - S32 target_width = llfloor( (*paragraph_it).mMaxWidth + padding_horizontal); - - y_pos += ((*paragraph_it).mLines.size()) * line_height; - imagep->drawSolid(-14, (S32)(y_pos + line_height - target_height), target_width, target_height, color); - - F32 y_off=0; - - F32 alpha; - - if ((mLinePersistTime > 0.f) && ((*paragraph_it).mAddTime < fade_time)) - { - alpha = ((*paragraph_it).mAddTime - skip_time)/(mLinePersistTime - mFadeTime); - } - else - { - alpha = 1.0f; - } - - if( alpha > 0.f ) - { - for (lines_t::iterator line_it=(*paragraph_it).mLines.begin(); - line_it != (*paragraph_it).mLines.end(); - line_it ++) - { - for (line_color_segments_t::iterator seg_it = (*line_it).mLineColorSegments.begin(); - seg_it != (*line_it).mLineColorSegments.end(); - seg_it++) - { - mFont->render((*seg_it).mText, 0, (*seg_it).mXPosition - 8, y_pos - y_off, - LLColor4( - (*seg_it).mColor.mV[VRED], - (*seg_it).mColor.mV[VGREEN], - (*seg_it).mColor.mV[VBLUE], - (*seg_it).mColor.mV[VALPHA]*alpha), - LLFontGL::LEFT, - LLFontGL::BASELINE, - LLFontGL::NORMAL, - LLFontGL::DROP_SHADOW, - S32_MAX, - target_width - ); - } - y_off += line_height; - } - } - y_pos += padding_vertical; - } -} - -//Generate highlight color segments for this paragraph. Pass in default color of paragraph. -void LLConsole::Paragraph::makeParagraphColorSegments (const LLColor4 &color) -{ - LLSD paragraph_color_segments; - paragraph_color_segments[0]["text"] =wstring_to_utf8str(mParagraphText); - LLSD color_sd = color.getValue(); - paragraph_color_segments[0]["color"]=color_sd; - - for(LLSD::array_const_iterator color_segment_it = paragraph_color_segments.beginArray(); - color_segment_it != paragraph_color_segments.endArray(); - ++color_segment_it) - { - LLSD color_llsd = (*color_segment_it)["color"]; - std::string color_str = (*color_segment_it)["text"].asString(); - - ParagraphColorSegment color_segment; - - color_segment.mColor.setValue(color_llsd); - color_segment.mNumChars = color_str.length(); - - mParagraphColorSegments.push_back(color_segment); - } -} - -//Called when a paragraph is added to the console or window is resized. -void LLConsole::Paragraph::updateLines(F32 screen_width, const LLFontGL* font, bool force_resize) -{ - if ( !force_resize ) - { - if ( mMaxWidth >= 0.0f - && mMaxWidth < screen_width ) - { - return; //No resize required. - } - } - - screen_width = screen_width - 30; //Margin for small windows. - - if ( mParagraphText.empty() - || mParagraphColorSegments.empty() - || font == NULL) - { - return; //Not enough info to complete. - } - - mLines.clear(); //Chuck everything. - mMaxWidth = 0.0f; - - paragraph_color_segments_t::iterator current_color = mParagraphColorSegments.begin(); - U32 current_color_length = (*current_color).mNumChars; - - S32 paragraph_offset = 0; //Offset into the paragraph text. - - // Wrap lines that are longer than the view is wide. - while( paragraph_offset < (S32)mParagraphText.length() && - mParagraphText[paragraph_offset] != 0) - { - S32 skip_chars; // skip '\n' - // Figure out if a word-wrapped line fits here. - LLWString::size_type line_end = mParagraphText.find_first_of(llwchar('\n'), paragraph_offset); - if (line_end != LLWString::npos) - { - skip_chars = 1; // skip '\n' - } - else - { - line_end = mParagraphText.size(); - skip_chars = 0; - } - - U32 drawable = font->maxDrawableChars(mParagraphText.c_str()+paragraph_offset, screen_width, line_end - paragraph_offset, LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); - - if (drawable != 0) - { - F32 x_position = 0; //Screen X position of text. - - mMaxWidth = llmax( mMaxWidth, (F32)font->getWidth( mParagraphText.substr( paragraph_offset, drawable ).c_str() ) ); - Line line; - - U32 left_to_draw = drawable; - U32 drawn = 0; - - while (left_to_draw >= current_color_length - && current_color != mParagraphColorSegments.end() ) - { - LLWString color_text = mParagraphText.substr( paragraph_offset + drawn, current_color_length ); - line.mLineColorSegments.push_back( LineColorSegment( color_text, //Append segment to line. - (*current_color).mColor, - x_position ) ); - - x_position += font->getWidth( color_text.c_str() ); //Set up next screen position. - - drawn += current_color_length; - left_to_draw -= current_color_length; - - current_color++; //Goto next paragraph color record. - - if (current_color != mParagraphColorSegments.end()) - { - current_color_length = (*current_color).mNumChars; - } - } - - if (left_to_draw > 0 && current_color != mParagraphColorSegments.end() ) - { - LLWString color_text = mParagraphText.substr( paragraph_offset + drawn, left_to_draw ); - - line.mLineColorSegments.push_back( LineColorSegment( color_text, //Append segment to line. - (*current_color).mColor, - x_position ) ); - - current_color_length -= left_to_draw; - } - mLines.push_back(line); //Append line to paragraph line list. - } - paragraph_offset += (drawable + skip_chars); - } -} - -//Pass in the string and the default color for this block of text. -LLConsole::Paragraph::Paragraph (LLWString str, const LLColor4 &color, F32 add_time, const LLFontGL* font, F32 screen_width) -: mParagraphText(str), mAddTime(add_time), mMaxWidth(-1) -{ - makeParagraphColorSegments(color); - updateLines( screen_width, font ); -} - -// called once per frame regardless of console visibility -// static -void LLConsole::updateClass() -{ - for (auto& con : instance_snapshot()) - { - con.update(); - } -} - -void LLConsole::update() -{ - { - LLMutexLock lock(&mMutex); - - while (!mLines.empty()) - { - mParagraphs.push_back( - Paragraph( mLines.front(), - LLColor4::white, - mTimer.getElapsedTimeF32(), - mFont, - (F32)getRect().getWidth())); - mLines.pop_front(); - } - } - - // remove old paragraphs which can't possibly be visible any more. ::draw() will do something similar but more conservative - we do this here because ::draw() isn't guaranteed to ever be called! (i.e. the console isn't visible) - while ((S32)mParagraphs.size() > llmax((S32)0, (S32)(mMaxLines))) - { - mParagraphs.pop_front(); - } -} - +/** + * @file llconsole.cpp + * @brief a scrolling console output device + * + * $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 "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "llconsole.h" + +// linden library includes +#include "llmath.h" +//#include "llviewercontrol.h" +#include "llcriticaldamp.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llui.h" +#include "lluiimage.h" +//#include "llviewerimage.h" +//#include "llviewerimagelist.h" +//#include "llviewerwindow.h" +#include "llsd.h" +#include "llfontgl.h" +#include "llmath.h" + +//#include "llstartup.h" + +// Used for LCD display +extern void AddNewDebugConsoleToLCD(const LLWString &newLine); + +LLConsole* gConsole = NULL; // Created and destroyed in LLViewerWindow. + +const F32 FADE_DURATION = 2.f; + +static LLDefaultChildRegistry::Register r("console"); + +LLConsole::LLConsole(const LLConsole::Params& p) +: LLUICtrl(p), + LLFixedBuffer(p.max_lines), + mLinePersistTime(p.persist_time), // seconds + mFont(p.font), + mConsoleWidth(0), + mConsoleHeight(0) +{ + if (p.font_size_index.isProvided()) + { + setFontSize(p.font_size_index); + } + mFadeTime = mLinePersistTime - FADE_DURATION; + setMaxLines(LLUI::getInstance()->mSettingGroups["config"]->getS32("ConsoleMaxLines")); +} + +void LLConsole::setLinePersistTime(F32 seconds) +{ + mLinePersistTime = seconds; + mFadeTime = mLinePersistTime - FADE_DURATION; +} + +void LLConsole::reshape(S32 width, S32 height, bool called_from_parent) +{ + S32 new_width = llmax(50, llmin(getRect().getWidth(), width)); + S32 new_height = llmax(llfloor(mFont->getLineHeight()) + 15, llmin(getRect().getHeight(), height)); + + if ( mConsoleWidth == new_width + && mConsoleHeight == new_height ) + { + return; + } + + mConsoleWidth = new_width; + mConsoleHeight= new_height; + + LLUICtrl::reshape(new_width, new_height, called_from_parent); + + for(paragraph_t::iterator paragraph_it = mParagraphs.begin(); paragraph_it != mParagraphs.end(); paragraph_it++) + { + (*paragraph_it).updateLines((F32)getRect().getWidth(), mFont, true); + } +} + +void LLConsole::setFontSize(S32 size_index) +{ + if (-1 == size_index) + { + mFont = LLFontGL::getFontMonospace(); + } + else if (0 == size_index) + { + mFont = LLFontGL::getFontSansSerif(); + } + else if (1 == size_index) + { + mFont = LLFontGL::getFontSansSerifBig(); + } + else + { + mFont = LLFontGL::getFontSansSerifHuge(); + } + // Make sure the font exists + if (mFont == NULL) + { + mFont = LLFontGL::getFontDefault(); + } + + for(paragraph_t::iterator paragraph_it = mParagraphs.begin(); paragraph_it != mParagraphs.end(); paragraph_it++) + { + (*paragraph_it).updateLines((F32)getRect().getWidth(), mFont, true); + } +} + +void LLConsole::draw() +{ + // Units in pixels + static const F32 padding_horizontal = 10; + static const F32 padding_vertical = 3; + LLGLSUIDefault gls_ui; + + // skip lines added more than mLinePersistTime ago + F32 cur_time = mTimer.getElapsedTimeF32(); + + F32 skip_time = cur_time - mLinePersistTime; + F32 fade_time = cur_time - mFadeTime; + + if (mParagraphs.empty()) //No text to draw. + { + return; + } + + U32 num_lines=0; + + paragraph_t::reverse_iterator paragraph_it; + paragraph_it = mParagraphs.rbegin(); + U32 paragraph_num=mParagraphs.size(); + + while (!mParagraphs.empty() && paragraph_it != mParagraphs.rend()) + { + num_lines += (*paragraph_it).mLines.size(); + if(num_lines > mMaxLines + || ( (mLinePersistTime > (F32)0.f) && ((*paragraph_it).mAddTime - skip_time)/(mLinePersistTime - mFadeTime) <= (F32)0.f)) + { //All lines above here are done. Lose them. + for (U32 i=0;i console_bg_opacity(*LLUI::getInstance()->mSettingGroups["config"], "ConsoleBackgroundOpacity", 0.7f); + F32 console_opacity = llclamp(console_bg_opacity(), 0.f, 1.f); + + LLColor4 color = LLUIColorTable::instance().getColor("ConsoleBackground"); + color.mV[VALPHA] *= console_opacity; + + F32 line_height = mFont->getLineHeight(); + + for(paragraph_it = mParagraphs.rbegin(); paragraph_it != mParagraphs.rend(); paragraph_it++) + { + S32 target_height = llfloor( (*paragraph_it).mLines.size() * line_height + padding_vertical); + S32 target_width = llfloor( (*paragraph_it).mMaxWidth + padding_horizontal); + + y_pos += ((*paragraph_it).mLines.size()) * line_height; + imagep->drawSolid(-14, (S32)(y_pos + line_height - target_height), target_width, target_height, color); + + F32 y_off=0; + + F32 alpha; + + if ((mLinePersistTime > 0.f) && ((*paragraph_it).mAddTime < fade_time)) + { + alpha = ((*paragraph_it).mAddTime - skip_time)/(mLinePersistTime - mFadeTime); + } + else + { + alpha = 1.0f; + } + + if( alpha > 0.f ) + { + for (lines_t::iterator line_it=(*paragraph_it).mLines.begin(); + line_it != (*paragraph_it).mLines.end(); + line_it ++) + { + for (line_color_segments_t::iterator seg_it = (*line_it).mLineColorSegments.begin(); + seg_it != (*line_it).mLineColorSegments.end(); + seg_it++) + { + mFont->render((*seg_it).mText, 0, (*seg_it).mXPosition - 8, y_pos - y_off, + LLColor4( + (*seg_it).mColor.mV[VRED], + (*seg_it).mColor.mV[VGREEN], + (*seg_it).mColor.mV[VBLUE], + (*seg_it).mColor.mV[VALPHA]*alpha), + LLFontGL::LEFT, + LLFontGL::BASELINE, + LLFontGL::NORMAL, + LLFontGL::DROP_SHADOW, + S32_MAX, + target_width + ); + } + y_off += line_height; + } + } + y_pos += padding_vertical; + } +} + +//Generate highlight color segments for this paragraph. Pass in default color of paragraph. +void LLConsole::Paragraph::makeParagraphColorSegments (const LLColor4 &color) +{ + LLSD paragraph_color_segments; + paragraph_color_segments[0]["text"] =wstring_to_utf8str(mParagraphText); + LLSD color_sd = color.getValue(); + paragraph_color_segments[0]["color"]=color_sd; + + for(LLSD::array_const_iterator color_segment_it = paragraph_color_segments.beginArray(); + color_segment_it != paragraph_color_segments.endArray(); + ++color_segment_it) + { + LLSD color_llsd = (*color_segment_it)["color"]; + std::string color_str = (*color_segment_it)["text"].asString(); + + ParagraphColorSegment color_segment; + + color_segment.mColor.setValue(color_llsd); + color_segment.mNumChars = color_str.length(); + + mParagraphColorSegments.push_back(color_segment); + } +} + +//Called when a paragraph is added to the console or window is resized. +void LLConsole::Paragraph::updateLines(F32 screen_width, const LLFontGL* font, bool force_resize) +{ + if ( !force_resize ) + { + if ( mMaxWidth >= 0.0f + && mMaxWidth < screen_width ) + { + return; //No resize required. + } + } + + screen_width = screen_width - 30; //Margin for small windows. + + if ( mParagraphText.empty() + || mParagraphColorSegments.empty() + || font == NULL) + { + return; //Not enough info to complete. + } + + mLines.clear(); //Chuck everything. + mMaxWidth = 0.0f; + + paragraph_color_segments_t::iterator current_color = mParagraphColorSegments.begin(); + U32 current_color_length = (*current_color).mNumChars; + + S32 paragraph_offset = 0; //Offset into the paragraph text. + + // Wrap lines that are longer than the view is wide. + while( paragraph_offset < (S32)mParagraphText.length() && + mParagraphText[paragraph_offset] != 0) + { + S32 skip_chars; // skip '\n' + // Figure out if a word-wrapped line fits here. + LLWString::size_type line_end = mParagraphText.find_first_of(llwchar('\n'), paragraph_offset); + if (line_end != LLWString::npos) + { + skip_chars = 1; // skip '\n' + } + else + { + line_end = mParagraphText.size(); + skip_chars = 0; + } + + U32 drawable = font->maxDrawableChars(mParagraphText.c_str()+paragraph_offset, screen_width, line_end - paragraph_offset, LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); + + if (drawable != 0) + { + F32 x_position = 0; //Screen X position of text. + + mMaxWidth = llmax( mMaxWidth, (F32)font->getWidth( mParagraphText.substr( paragraph_offset, drawable ).c_str() ) ); + Line line; + + U32 left_to_draw = drawable; + U32 drawn = 0; + + while (left_to_draw >= current_color_length + && current_color != mParagraphColorSegments.end() ) + { + LLWString color_text = mParagraphText.substr( paragraph_offset + drawn, current_color_length ); + line.mLineColorSegments.push_back( LineColorSegment( color_text, //Append segment to line. + (*current_color).mColor, + x_position ) ); + + x_position += font->getWidth( color_text.c_str() ); //Set up next screen position. + + drawn += current_color_length; + left_to_draw -= current_color_length; + + current_color++; //Goto next paragraph color record. + + if (current_color != mParagraphColorSegments.end()) + { + current_color_length = (*current_color).mNumChars; + } + } + + if (left_to_draw > 0 && current_color != mParagraphColorSegments.end() ) + { + LLWString color_text = mParagraphText.substr( paragraph_offset + drawn, left_to_draw ); + + line.mLineColorSegments.push_back( LineColorSegment( color_text, //Append segment to line. + (*current_color).mColor, + x_position ) ); + + current_color_length -= left_to_draw; + } + mLines.push_back(line); //Append line to paragraph line list. + } + paragraph_offset += (drawable + skip_chars); + } +} + +//Pass in the string and the default color for this block of text. +LLConsole::Paragraph::Paragraph (LLWString str, const LLColor4 &color, F32 add_time, const LLFontGL* font, F32 screen_width) +: mParagraphText(str), mAddTime(add_time), mMaxWidth(-1) +{ + makeParagraphColorSegments(color); + updateLines( screen_width, font ); +} + +// called once per frame regardless of console visibility +// static +void LLConsole::updateClass() +{ + for (auto& con : instance_snapshot()) + { + con.update(); + } +} + +void LLConsole::update() +{ + { + LLMutexLock lock(&mMutex); + + while (!mLines.empty()) + { + mParagraphs.push_back( + Paragraph( mLines.front(), + LLColor4::white, + mTimer.getElapsedTimeF32(), + mFont, + (F32)getRect().getWidth())); + mLines.pop_front(); + } + } + + // remove old paragraphs which can't possibly be visible any more. ::draw() will do something similar but more conservative - we do this here because ::draw() isn't guaranteed to ever be called! (i.e. the console isn't visible) + while ((S32)mParagraphs.size() > llmax((S32)0, (S32)(mMaxLines))) + { + mParagraphs.pop_front(); + } +} + diff --git a/indra/llui/llconsole.h b/indra/llui/llconsole.h index 86ad1cc2cb..cc9fd71d84 100644 --- a/indra/llui/llconsole.h +++ b/indra/llui/llconsole.h @@ -1,156 +1,156 @@ -/** - * @file llconsole.h - * @brief a simple console-style output device - * - * $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$ - */ - -#ifndef LL_LLCONSOLE_H -#define LL_LLCONSOLE_H - -#include "llfixedbuffer.h" -#include "lluictrl.h" -#include "v4color.h" -#include - -class LLSD; - -class LLConsole : public LLFixedBuffer, public LLUICtrl, public LLInstanceTracker -{ -public: - - typedef enum e_font_size - { - MONOSPACE = -1, - SMALL = 0, - BIG = 1 - } EFontSize; - - struct Params : public LLInitParam::Block - { - Optional max_lines; - Optional persist_time; - Optional font_size_index; - Params() - : max_lines("max_lines", LLUI::getInstance()->mSettingGroups["config"]->getS32("ConsoleMaxLines")), - persist_time("persist_time", 0.f), // forever - font_size_index("font_size_index") - { - changeDefault(mouse_opaque, false); - } - }; -protected: - LLConsole(const Params&); - friend class LLUICtrlFactory; - -public: - // call once per frame to pull data out of LLFixedBuffer - static void updateClass(); - - //A paragraph color segment defines the color of text in a line - //of text that was received for console display. It has no - //notion of line wraps, screen position, or the text it contains. - //It is only the number of characters that are a color and the - //color. - struct ParagraphColorSegment - { - S32 mNumChars; - LLColor4 mColor; - }; - - //A line color segment is a chunk of text, the color associated - //with it, and the X Position it was calculated to begin at - //on the screen. X Positions are re-calculated if the - //screen changes size. - class LineColorSegment - { - public: - LineColorSegment(LLWString text, LLColor4 color, F32 xpos) : mText(text), mColor(color), mXPosition(xpos) {} - public: - LLWString mText; - LLColor4 mColor; - F32 mXPosition; - }; - - typedef std::list line_color_segments_t; - - //A line is composed of one or more color segments. - class Line - { - public: - line_color_segments_t mLineColorSegments; - }; - - typedef std::list lines_t; - typedef std::list paragraph_color_segments_t; - - //A paragraph is a processed element containing the entire text of the - //message (used for recalculating positions on screen resize) - //The time this message was added to the console output - //The visual screen width of the longest line in this block - //And a list of one or more lines which are used to display this message. - class Paragraph - { - public: - Paragraph (LLWString str, const LLColor4 &color, F32 add_time, const LLFontGL* font, F32 screen_width); - void makeParagraphColorSegments ( const LLColor4 &color); - void updateLines ( F32 screen_width, const LLFontGL* font, bool force_resize=false ); - public: - LLWString mParagraphText; //The entire text of the paragraph - paragraph_color_segments_t mParagraphColorSegments; - F32 mAddTime; //Time this paragraph was added to the display. - F32 mMaxWidth; //Width of the widest line of text in this paragraph. - lines_t mLines; - - }; - - //The console contains a deque of paragraphs which represent the individual messages. - typedef std::deque paragraph_t; - paragraph_t mParagraphs; - - ~LLConsole(){}; - - // each line lasts this long after being added - void setLinePersistTime(F32 seconds); - - void reshape(S32 width, S32 height, bool called_from_parent = true); - - // -1 = monospace, 0 means small, font size = 1 means big - void setFontSize(S32 size_index); - - - // Overrides - /*virtual*/ void draw(); -private: - void update(); - - F32 mLinePersistTime; // Age at which to stop drawing. - F32 mFadeTime; // Age at which to start fading - const LLFontGL* mFont; - S32 mConsoleWidth; - S32 mConsoleHeight; - -}; - -extern LLConsole* gConsole; - -#endif +/** + * @file llconsole.h + * @brief a simple console-style output device + * + * $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$ + */ + +#ifndef LL_LLCONSOLE_H +#define LL_LLCONSOLE_H + +#include "llfixedbuffer.h" +#include "lluictrl.h" +#include "v4color.h" +#include + +class LLSD; + +class LLConsole : public LLFixedBuffer, public LLUICtrl, public LLInstanceTracker +{ +public: + + typedef enum e_font_size + { + MONOSPACE = -1, + SMALL = 0, + BIG = 1 + } EFontSize; + + struct Params : public LLInitParam::Block + { + Optional max_lines; + Optional persist_time; + Optional font_size_index; + Params() + : max_lines("max_lines", LLUI::getInstance()->mSettingGroups["config"]->getS32("ConsoleMaxLines")), + persist_time("persist_time", 0.f), // forever + font_size_index("font_size_index") + { + changeDefault(mouse_opaque, false); + } + }; +protected: + LLConsole(const Params&); + friend class LLUICtrlFactory; + +public: + // call once per frame to pull data out of LLFixedBuffer + static void updateClass(); + + //A paragraph color segment defines the color of text in a line + //of text that was received for console display. It has no + //notion of line wraps, screen position, or the text it contains. + //It is only the number of characters that are a color and the + //color. + struct ParagraphColorSegment + { + S32 mNumChars; + LLColor4 mColor; + }; + + //A line color segment is a chunk of text, the color associated + //with it, and the X Position it was calculated to begin at + //on the screen. X Positions are re-calculated if the + //screen changes size. + class LineColorSegment + { + public: + LineColorSegment(LLWString text, LLColor4 color, F32 xpos) : mText(text), mColor(color), mXPosition(xpos) {} + public: + LLWString mText; + LLColor4 mColor; + F32 mXPosition; + }; + + typedef std::list line_color_segments_t; + + //A line is composed of one or more color segments. + class Line + { + public: + line_color_segments_t mLineColorSegments; + }; + + typedef std::list lines_t; + typedef std::list paragraph_color_segments_t; + + //A paragraph is a processed element containing the entire text of the + //message (used for recalculating positions on screen resize) + //The time this message was added to the console output + //The visual screen width of the longest line in this block + //And a list of one or more lines which are used to display this message. + class Paragraph + { + public: + Paragraph (LLWString str, const LLColor4 &color, F32 add_time, const LLFontGL* font, F32 screen_width); + void makeParagraphColorSegments ( const LLColor4 &color); + void updateLines ( F32 screen_width, const LLFontGL* font, bool force_resize=false ); + public: + LLWString mParagraphText; //The entire text of the paragraph + paragraph_color_segments_t mParagraphColorSegments; + F32 mAddTime; //Time this paragraph was added to the display. + F32 mMaxWidth; //Width of the widest line of text in this paragraph. + lines_t mLines; + + }; + + //The console contains a deque of paragraphs which represent the individual messages. + typedef std::deque paragraph_t; + paragraph_t mParagraphs; + + ~LLConsole(){}; + + // each line lasts this long after being added + void setLinePersistTime(F32 seconds); + + void reshape(S32 width, S32 height, bool called_from_parent = true); + + // -1 = monospace, 0 means small, font size = 1 means big + void setFontSize(S32 size_index); + + + // Overrides + /*virtual*/ void draw(); +private: + void update(); + + F32 mLinePersistTime; // Age at which to stop drawing. + F32 mFadeTime; // Age at which to start fading + const LLFontGL* mFont; + S32 mConsoleWidth; + S32 mConsoleHeight; + +}; + +extern LLConsole* gConsole; + +#endif diff --git a/indra/llui/llcontainerview.cpp b/indra/llui/llcontainerview.cpp index a47218c4e2..4c2912cde6 100644 --- a/indra/llui/llcontainerview.cpp +++ b/indra/llui/llcontainerview.cpp @@ -1,300 +1,300 @@ -/** - * @file llcontainerview.cpp - * @brief Container for all statistics info - * - * $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 "llcontainerview.h" - -#include "llerror.h" -#include "llfontgl.h" -#include "llgl.h" -#include "llui.h" -#include "llstring.h" -#include "llscrollcontainer.h" -#include "lluictrlfactory.h" - -static LLDefaultChildRegistry::Register r1("container_view"); - -#include "llpanel.h" -#include "llstatview.h" -static ContainerViewRegistry::Register r2("stat_view"); -static ContainerViewRegistry::Register r3("panel", &LLPanel::fromXML); - -LLContainerView::LLContainerView(const LLContainerView::Params& p) -: LLView(p), - mShowLabel(p.show_label), - mLabel(p.label), - mDisplayChildren(p.display_children) -{ - mScrollContainer = NULL; -} - -LLContainerView::~LLContainerView() -{ - // Children all cleaned up by default view destructor. -} - -bool LLContainerView::postBuild() -{ - setDisplayChildren(mDisplayChildren); - reshape(getRect().getWidth(), getRect().getHeight(), false); - return true; -} - -bool LLContainerView::addChild(LLView* child, S32 tab_group) -{ - bool res = LLView::addChild(child, tab_group); - if (res) - { - sendChildToBack(child); - } - return res; -} - -bool LLContainerView::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - return handleMouseDown(x, y, mask); -} - -bool LLContainerView::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - if (mDisplayChildren) - { - handled = (LLView::childrenHandleMouseDown(x, y, mask) != NULL); - } - if (!handled) - { - if( mShowLabel && (y >= getRect().getHeight() - 10) ) - { - setDisplayChildren(!mDisplayChildren); - reshape(getRect().getWidth(), getRect().getHeight(), false); - handled = true; - } - } - return handled; -} - -bool LLContainerView::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - if (mDisplayChildren) - { - handled = (LLView::childrenHandleMouseUp(x, y, mask) != NULL); - } - return handled; -} - - -void LLContainerView::draw() -{ - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4(0.f, 0.f, 0.f, 0.25f)); - } - - // Draw the label - if (mShowLabel) - { - LLFontGL::getFontMonospace()->renderUTF8( - mLabel, 0, 2, getRect().getHeight() - 2, LLColor4(1,1,1,1), LLFontGL::LEFT, LLFontGL::TOP); - } - - LLView::draw(); -} - - -void LLContainerView::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLRect scroller_rect; - scroller_rect.setOriginAndSize(0, 0, width, height); - - if (mScrollContainer) - { - scroller_rect = mScrollContainer->getContentWindowRect(); - } - else - { - // if we're uncontained - make height as small as possible - scroller_rect.mTop = 0; - } - - arrange(scroller_rect.getWidth(), scroller_rect.getHeight(), called_from_parent); - - // sometimes, after layout, our container will change size (scrollbars popping in and out) - // if so, attempt another layout - if (mScrollContainer) - { - LLRect new_container_rect = mScrollContainer->getContentWindowRect(); - - if ((new_container_rect.getWidth() != scroller_rect.getWidth()) || - (new_container_rect.getHeight() != scroller_rect.getHeight())) // the container size has changed, attempt to arrange again - { - arrange(new_container_rect.getWidth(), new_container_rect.getHeight(), called_from_parent); - } - } -} - -void LLContainerView::arrange(S32 width, S32 height, bool called_from_parent) -{ - // Determine the sizes and locations of all contained views - S32 total_height = 0; - S32 top, left, right, bottom; - //LLView *childp; - - // These will be used for the children - left = 10; - top = getRect().getHeight() - 4; - right = width - 2; - bottom = top; - - // Leave some space for the top label/grab handle - if (mShowLabel) - { - total_height += 20; - } - - if (mDisplayChildren) - { - // Determine total height - U32 child_height = 0; - for (child_list_const_iter_t child_iter = getChildList()->begin(); - child_iter != getChildList()->end(); ++child_iter) - { - LLView *childp = *child_iter; - if (!childp->getVisible()) - { - LL_WARNS() << "Incorrect visibility!" << LL_ENDL; - } - LLRect child_rect = childp->getRequiredRect(); - child_height += child_rect.getHeight(); - child_height += 2; - } - total_height += child_height; - } - - if (total_height < height) - total_height = height; - - LLRect my_rect = getRect(); - if (followsTop()) - { - my_rect.mBottom = my_rect.mTop - total_height; - } - else - { - my_rect.mTop = my_rect.mBottom + total_height; - } - - my_rect.mRight = my_rect.mLeft + width; - setRect(my_rect); - - top = total_height; - if (mShowLabel) - { - top -= 20; - } - - bottom = top; - - if (mDisplayChildren) - { - // Iterate through all children, and put in container from top down. - for (child_list_const_iter_t child_iter = getChildList()->begin(); - child_iter != getChildList()->end(); ++child_iter) - { - LLView *childp = *child_iter; - LLRect child_rect = childp->getRequiredRect(); - bottom -= child_rect.getHeight(); - LLRect r(left, bottom + child_rect.getHeight(), right, bottom); - childp->setRect(r); - childp->reshape(right - left, top - bottom); - top = bottom - 2; - bottom = top; - } - } - - if (!called_from_parent) - { - if (getParent()) - { - getParent()->reshape(getParent()->getRect().getWidth(), getParent()->getRect().getHeight(), false); - } - } - -} - -LLRect LLContainerView::getRequiredRect() -{ - LLRect req_rect; - //LLView *childp; - U32 total_height = 0; - - // Determine the sizes and locations of all contained views - - // Leave some space for the top label/grab handle - - if (mShowLabel) - { - total_height = 20; - } - - - if (mDisplayChildren) - { - // Determine total height - U32 child_height = 0; - for (child_list_const_iter_t child_iter = getChildList()->begin(); - child_iter != getChildList()->end(); ++child_iter) - { - LLView *childp = *child_iter; - LLRect child_rect = childp->getRequiredRect(); - child_height += child_rect.getHeight(); - child_height += 2; - } - - total_height += child_height; - } - req_rect.mTop = total_height; - return req_rect; -} - -void LLContainerView::setLabel(const std::string& label) -{ - mLabel = label; -} - -void LLContainerView::setDisplayChildren(bool displayChildren) -{ - mDisplayChildren = displayChildren; - for (child_list_const_iter_t child_iter = getChildList()->begin(); - child_iter != getChildList()->end(); ++child_iter) - { - LLView *childp = *child_iter; - childp->setVisible(mDisplayChildren); - } -} +/** + * @file llcontainerview.cpp + * @brief Container for all statistics info + * + * $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 "llcontainerview.h" + +#include "llerror.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llui.h" +#include "llstring.h" +#include "llscrollcontainer.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register r1("container_view"); + +#include "llpanel.h" +#include "llstatview.h" +static ContainerViewRegistry::Register r2("stat_view"); +static ContainerViewRegistry::Register r3("panel", &LLPanel::fromXML); + +LLContainerView::LLContainerView(const LLContainerView::Params& p) +: LLView(p), + mShowLabel(p.show_label), + mLabel(p.label), + mDisplayChildren(p.display_children) +{ + mScrollContainer = NULL; +} + +LLContainerView::~LLContainerView() +{ + // Children all cleaned up by default view destructor. +} + +bool LLContainerView::postBuild() +{ + setDisplayChildren(mDisplayChildren); + reshape(getRect().getWidth(), getRect().getHeight(), false); + return true; +} + +bool LLContainerView::addChild(LLView* child, S32 tab_group) +{ + bool res = LLView::addChild(child, tab_group); + if (res) + { + sendChildToBack(child); + } + return res; +} + +bool LLContainerView::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + return handleMouseDown(x, y, mask); +} + +bool LLContainerView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + if (mDisplayChildren) + { + handled = (LLView::childrenHandleMouseDown(x, y, mask) != NULL); + } + if (!handled) + { + if( mShowLabel && (y >= getRect().getHeight() - 10) ) + { + setDisplayChildren(!mDisplayChildren); + reshape(getRect().getWidth(), getRect().getHeight(), false); + handled = true; + } + } + return handled; +} + +bool LLContainerView::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + if (mDisplayChildren) + { + handled = (LLView::childrenHandleMouseUp(x, y, mask) != NULL); + } + return handled; +} + + +void LLContainerView::draw() +{ + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4(0.f, 0.f, 0.f, 0.25f)); + } + + // Draw the label + if (mShowLabel) + { + LLFontGL::getFontMonospace()->renderUTF8( + mLabel, 0, 2, getRect().getHeight() - 2, LLColor4(1,1,1,1), LLFontGL::LEFT, LLFontGL::TOP); + } + + LLView::draw(); +} + + +void LLContainerView::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLRect scroller_rect; + scroller_rect.setOriginAndSize(0, 0, width, height); + + if (mScrollContainer) + { + scroller_rect = mScrollContainer->getContentWindowRect(); + } + else + { + // if we're uncontained - make height as small as possible + scroller_rect.mTop = 0; + } + + arrange(scroller_rect.getWidth(), scroller_rect.getHeight(), called_from_parent); + + // sometimes, after layout, our container will change size (scrollbars popping in and out) + // if so, attempt another layout + if (mScrollContainer) + { + LLRect new_container_rect = mScrollContainer->getContentWindowRect(); + + if ((new_container_rect.getWidth() != scroller_rect.getWidth()) || + (new_container_rect.getHeight() != scroller_rect.getHeight())) // the container size has changed, attempt to arrange again + { + arrange(new_container_rect.getWidth(), new_container_rect.getHeight(), called_from_parent); + } + } +} + +void LLContainerView::arrange(S32 width, S32 height, bool called_from_parent) +{ + // Determine the sizes and locations of all contained views + S32 total_height = 0; + S32 top, left, right, bottom; + //LLView *childp; + + // These will be used for the children + left = 10; + top = getRect().getHeight() - 4; + right = width - 2; + bottom = top; + + // Leave some space for the top label/grab handle + if (mShowLabel) + { + total_height += 20; + } + + if (mDisplayChildren) + { + // Determine total height + U32 child_height = 0; + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *childp = *child_iter; + if (!childp->getVisible()) + { + LL_WARNS() << "Incorrect visibility!" << LL_ENDL; + } + LLRect child_rect = childp->getRequiredRect(); + child_height += child_rect.getHeight(); + child_height += 2; + } + total_height += child_height; + } + + if (total_height < height) + total_height = height; + + LLRect my_rect = getRect(); + if (followsTop()) + { + my_rect.mBottom = my_rect.mTop - total_height; + } + else + { + my_rect.mTop = my_rect.mBottom + total_height; + } + + my_rect.mRight = my_rect.mLeft + width; + setRect(my_rect); + + top = total_height; + if (mShowLabel) + { + top -= 20; + } + + bottom = top; + + if (mDisplayChildren) + { + // Iterate through all children, and put in container from top down. + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *childp = *child_iter; + LLRect child_rect = childp->getRequiredRect(); + bottom -= child_rect.getHeight(); + LLRect r(left, bottom + child_rect.getHeight(), right, bottom); + childp->setRect(r); + childp->reshape(right - left, top - bottom); + top = bottom - 2; + bottom = top; + } + } + + if (!called_from_parent) + { + if (getParent()) + { + getParent()->reshape(getParent()->getRect().getWidth(), getParent()->getRect().getHeight(), false); + } + } + +} + +LLRect LLContainerView::getRequiredRect() +{ + LLRect req_rect; + //LLView *childp; + U32 total_height = 0; + + // Determine the sizes and locations of all contained views + + // Leave some space for the top label/grab handle + + if (mShowLabel) + { + total_height = 20; + } + + + if (mDisplayChildren) + { + // Determine total height + U32 child_height = 0; + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *childp = *child_iter; + LLRect child_rect = childp->getRequiredRect(); + child_height += child_rect.getHeight(); + child_height += 2; + } + + total_height += child_height; + } + req_rect.mTop = total_height; + return req_rect; +} + +void LLContainerView::setLabel(const std::string& label) +{ + mLabel = label; +} + +void LLContainerView::setDisplayChildren(bool displayChildren) +{ + mDisplayChildren = displayChildren; + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *childp = *child_iter; + childp->setVisible(mDisplayChildren); + } +} diff --git a/indra/llui/llcontainerview.h b/indra/llui/llcontainerview.h index 82239eda4e..319fb7d5e9 100644 --- a/indra/llui/llcontainerview.h +++ b/indra/llui/llcontainerview.h @@ -1,94 +1,94 @@ -/** - * @file llcontainerview.h - * @brief Container for all statistics info. - * - * $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$ - */ - -#ifndef LL_LLCONTAINERVIEW_H -#define LL_LLCONTAINERVIEW_H - -#include "stdtypes.h" -#include "lltextbox.h" -#include "llstatbar.h" -#include "llview.h" - -class LLScrollContainer; - -struct ContainerViewRegistry : public LLChildRegistry -{ - LLSINGLETON_EMPTY_CTOR(ContainerViewRegistry); -}; - -class LLContainerView : public LLView -{ -public: - struct Params : public LLInitParam::Block - { - Optional label; - Optional show_label; - Optional display_children; - Params() - : label("label"), - show_label("show_label", false), - display_children("display_children", true) - { - changeDefault(mouse_opaque, false); - } - }; - - // my valid children are stored in this registry - typedef ContainerViewRegistry child_registry_t; - -protected: - LLContainerView(const Params& p); - friend class LLUICtrlFactory; -public: - ~LLContainerView(); - - /*virtual*/ bool postBuild(); - /*virtual*/ bool addChild(LLView* view, S32 tab_group = 0); - - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - - /*virtual*/ void draw(); - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - /*virtual*/ LLRect getRequiredRect(); // Return the height of this object, given the set options. - - void setLabel(const std::string& label); - void showLabel(bool show) { mShowLabel = show; } - void setDisplayChildren(bool displayChildren); - bool getDisplayChildren() { return mDisplayChildren; } - void setScrollContainer(LLScrollContainer* scroll) {mScrollContainer = scroll;} - - private: - LLScrollContainer* mScrollContainer; - void arrange(S32 width, S32 height, bool called_from_parent = true); - bool mShowLabel; - -protected: - bool mDisplayChildren; - std::string mLabel; -}; -#endif // LL_CONTAINERVIEW_ +/** + * @file llcontainerview.h + * @brief Container for all statistics info. + * + * $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$ + */ + +#ifndef LL_LLCONTAINERVIEW_H +#define LL_LLCONTAINERVIEW_H + +#include "stdtypes.h" +#include "lltextbox.h" +#include "llstatbar.h" +#include "llview.h" + +class LLScrollContainer; + +struct ContainerViewRegistry : public LLChildRegistry +{ + LLSINGLETON_EMPTY_CTOR(ContainerViewRegistry); +}; + +class LLContainerView : public LLView +{ +public: + struct Params : public LLInitParam::Block + { + Optional label; + Optional show_label; + Optional display_children; + Params() + : label("label"), + show_label("show_label", false), + display_children("display_children", true) + { + changeDefault(mouse_opaque, false); + } + }; + + // my valid children are stored in this registry + typedef ContainerViewRegistry child_registry_t; + +protected: + LLContainerView(const Params& p); + friend class LLUICtrlFactory; +public: + ~LLContainerView(); + + /*virtual*/ bool postBuild(); + /*virtual*/ bool addChild(LLView* view, S32 tab_group = 0); + + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + + /*virtual*/ void draw(); + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + /*virtual*/ LLRect getRequiredRect(); // Return the height of this object, given the set options. + + void setLabel(const std::string& label); + void showLabel(bool show) { mShowLabel = show; } + void setDisplayChildren(bool displayChildren); + bool getDisplayChildren() { return mDisplayChildren; } + void setScrollContainer(LLScrollContainer* scroll) {mScrollContainer = scroll;} + + private: + LLScrollContainer* mScrollContainer; + void arrange(S32 width, S32 height, bool called_from_parent = true); + bool mShowLabel; + +protected: + bool mDisplayChildren; + std::string mLabel; +}; +#endif // LL_CONTAINERVIEW_ diff --git a/indra/llui/llctrlselectioninterface.cpp b/indra/llui/llctrlselectioninterface.cpp index 55c901b54c..0a5512afa1 100644 --- a/indra/llui/llctrlselectioninterface.cpp +++ b/indra/llui/llctrlselectioninterface.cpp @@ -1,63 +1,63 @@ -/** - * @file llctrlselectioninterface.cpp - * @brief Programmatic selection of items in a list. - * - * $LicenseInfo:firstyear=2006&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 "llctrlselectioninterface.h" - -#include "llsd.h" - -// virtual -LLCtrlSelectionInterface::~LLCtrlSelectionInterface() -{ } - -bool LLCtrlSelectionInterface::selectByValue(LLSD value) -{ - return setSelectedByValue(value, true); -} - -bool LLCtrlSelectionInterface::deselectByValue(LLSD value) -{ - return setSelectedByValue(value, false); -} - - -// virtual -LLCtrlListInterface::~LLCtrlListInterface() -{ } - -LLScrollListItem* LLCtrlListInterface::addSimpleElement(const std::string& value) -{ - return addSimpleElement(value, ADD_BOTTOM, LLSD()); -} - -LLScrollListItem* LLCtrlListInterface::addSimpleElement(const std::string& value, EAddPosition pos) -{ - return addSimpleElement(value, pos, LLSD()); -} - -// virtual -LLCtrlScrollInterface::~LLCtrlScrollInterface() -{ } +/** + * @file llctrlselectioninterface.cpp + * @brief Programmatic selection of items in a list. + * + * $LicenseInfo:firstyear=2006&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 "llctrlselectioninterface.h" + +#include "llsd.h" + +// virtual +LLCtrlSelectionInterface::~LLCtrlSelectionInterface() +{ } + +bool LLCtrlSelectionInterface::selectByValue(LLSD value) +{ + return setSelectedByValue(value, true); +} + +bool LLCtrlSelectionInterface::deselectByValue(LLSD value) +{ + return setSelectedByValue(value, false); +} + + +// virtual +LLCtrlListInterface::~LLCtrlListInterface() +{ } + +LLScrollListItem* LLCtrlListInterface::addSimpleElement(const std::string& value) +{ + return addSimpleElement(value, ADD_BOTTOM, LLSD()); +} + +LLScrollListItem* LLCtrlListInterface::addSimpleElement(const std::string& value, EAddPosition pos) +{ + return addSimpleElement(value, pos, LLSD()); +} + +// virtual +LLCtrlScrollInterface::~LLCtrlScrollInterface() +{ } diff --git a/indra/llui/llctrlselectioninterface.h b/indra/llui/llctrlselectioninterface.h index 01ab9db670..c845f7027d 100644 --- a/indra/llui/llctrlselectioninterface.h +++ b/indra/llui/llctrlselectioninterface.h @@ -1,104 +1,104 @@ -/** - * @file llctrlselectioninterface.h - * @brief Programmatic selection of items in a list. - * - * $LicenseInfo:firstyear=2006&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$ - */ - -#ifndef LLCTRLSELECTIONINTERFACE_H -#define LLCTRLSELECTIONINTERFACE_H - -#include "stdtypes.h" -#include "llstring.h" -#include "llui.h" - -class LLSD; -class LLUUID; -class LLScrollListItem; - -class LLCtrlSelectionInterface -{ -public: - virtual ~LLCtrlSelectionInterface(); - - enum EOperation - { - OP_DELETE = 1, - OP_SELECT, - OP_DESELECT, - }; - - virtual bool getCanSelect() const = 0; - - virtual S32 getItemCount() const = 0; - - virtual bool selectFirstItem() = 0; - virtual bool selectNthItem( S32 index ) = 0; - virtual bool selectItemRange( S32 first, S32 last ) = 0; - - virtual S32 getFirstSelectedIndex() const = 0; - - // TomY TODO: Simply cast the UUIDs to LLSDs, using the selectByValue function - virtual bool setCurrentByID( const LLUUID& id ) = 0; - virtual LLUUID getCurrentID() const = 0; - - bool selectByValue(const LLSD value); - bool deselectByValue(const LLSD value); - virtual bool setSelectedByValue(const LLSD& value, bool selected) = 0; - virtual LLSD getSelectedValue() = 0; - - virtual bool isSelected(const LLSD& value) const = 0; - - virtual bool operateOnSelection(EOperation op) = 0; - virtual bool operateOnAll(EOperation op) = 0; -}; - -class LLCtrlListInterface : public LLCtrlSelectionInterface -{ -public: - virtual ~LLCtrlListInterface(); - - virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM) = 0; - virtual void clearColumns() = 0; - virtual void setColumnLabel(const std::string& column, const std::string& label) = 0; - // TomY TODO: Document this - virtual LLScrollListItem* addElement(const LLSD& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL) = 0; - - LLScrollListItem* addSimpleElement(const std::string& value); // defaults to bottom - LLScrollListItem* addSimpleElement(const std::string& value, EAddPosition pos); // defaults to no LLSD() id - virtual LLScrollListItem* addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id) = 0; - - virtual void clearRows() = 0; - virtual void sortByColumn(const std::string& name, bool ascending) = 0; -}; - -class LLCtrlScrollInterface -{ -public: - virtual ~LLCtrlScrollInterface(); - - virtual S32 getScrollPos() const = 0; - virtual void setScrollPos( S32 pos ) = 0; - virtual void scrollToShowSelected() = 0; -}; - -#endif +/** + * @file llctrlselectioninterface.h + * @brief Programmatic selection of items in a list. + * + * $LicenseInfo:firstyear=2006&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$ + */ + +#ifndef LLCTRLSELECTIONINTERFACE_H +#define LLCTRLSELECTIONINTERFACE_H + +#include "stdtypes.h" +#include "llstring.h" +#include "llui.h" + +class LLSD; +class LLUUID; +class LLScrollListItem; + +class LLCtrlSelectionInterface +{ +public: + virtual ~LLCtrlSelectionInterface(); + + enum EOperation + { + OP_DELETE = 1, + OP_SELECT, + OP_DESELECT, + }; + + virtual bool getCanSelect() const = 0; + + virtual S32 getItemCount() const = 0; + + virtual bool selectFirstItem() = 0; + virtual bool selectNthItem( S32 index ) = 0; + virtual bool selectItemRange( S32 first, S32 last ) = 0; + + virtual S32 getFirstSelectedIndex() const = 0; + + // TomY TODO: Simply cast the UUIDs to LLSDs, using the selectByValue function + virtual bool setCurrentByID( const LLUUID& id ) = 0; + virtual LLUUID getCurrentID() const = 0; + + bool selectByValue(const LLSD value); + bool deselectByValue(const LLSD value); + virtual bool setSelectedByValue(const LLSD& value, bool selected) = 0; + virtual LLSD getSelectedValue() = 0; + + virtual bool isSelected(const LLSD& value) const = 0; + + virtual bool operateOnSelection(EOperation op) = 0; + virtual bool operateOnAll(EOperation op) = 0; +}; + +class LLCtrlListInterface : public LLCtrlSelectionInterface +{ +public: + virtual ~LLCtrlListInterface(); + + virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM) = 0; + virtual void clearColumns() = 0; + virtual void setColumnLabel(const std::string& column, const std::string& label) = 0; + // TomY TODO: Document this + virtual LLScrollListItem* addElement(const LLSD& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL) = 0; + + LLScrollListItem* addSimpleElement(const std::string& value); // defaults to bottom + LLScrollListItem* addSimpleElement(const std::string& value, EAddPosition pos); // defaults to no LLSD() id + virtual LLScrollListItem* addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id) = 0; + + virtual void clearRows() = 0; + virtual void sortByColumn(const std::string& name, bool ascending) = 0; +}; + +class LLCtrlScrollInterface +{ +public: + virtual ~LLCtrlScrollInterface(); + + virtual S32 getScrollPos() const = 0; + virtual void setScrollPos( S32 pos ) = 0; + virtual void scrollToShowSelected() = 0; +}; + +#endif diff --git a/indra/llui/lldockablefloater.cpp b/indra/llui/lldockablefloater.cpp index ab61b4617a..19ae03cdf9 100644 --- a/indra/llui/lldockablefloater.cpp +++ b/indra/llui/lldockablefloater.cpp @@ -1,263 +1,263 @@ -/** - * @file lldockablefloater.cpp - * @brief Creates a panel of a specific kind for a toast - * - * $LicenseInfo:firstyear=2000&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 "lldockablefloater.h" -#include "llfloaterreg.h" - -//static -LLHandle LLDockableFloater::sInstanceHandle; - -//static -void LLDockableFloater::init(LLDockableFloater* thiz) -{ - thiz->setDocked(thiz->mDockControl.get() != NULL - && thiz->mDockControl.get()->isDockVisible()); - thiz->resetInstance(); - - // all dockable floaters should have close, dock and minimize buttons - thiz->setCanClose(true); - thiz->setCanDock(true); - thiz->setCanMinimize(true); - thiz->setOverlapsScreenChannel(false); - thiz->mForceDocking = false; -} - -LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, - const LLSD& key, const Params& params) : - LLFloater(key, params), mDockControl(dockControl), mUniqueDocking(true) -{ - init(this); - mUseTongue = true; -} - -LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, - const LLSD& key, const Params& params) : - LLFloater(key, params), mDockControl(dockControl), mUniqueDocking(uniqueDocking) -{ - init(this); - mUseTongue = true; -} - -LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, - bool useTongue, const LLSD& key, const Params& params) : - LLFloater(key, params), mDockControl(dockControl), mUseTongue(useTongue), mUniqueDocking(uniqueDocking) -{ - init(this); -} - -LLDockableFloater::~LLDockableFloater() -{ -} - -bool LLDockableFloater::postBuild() -{ - // Remember we should force docking when the floater is opened for the first time - if (mIsDockedStateForcedCallback != NULL && mIsDockedStateForcedCallback()) - { - mForceDocking = true; - } - - mDockTongue = LLUI::getUIImage("Flyout_Pointer"); - LLFloater::setDocked(true); - return LLView::postBuild(); -} - -//static -void LLDockableFloater::toggleInstance(const LLSD& sdname) -{ - LLSD key; - std::string name = sdname.asString(); - - LLDockableFloater* instance = - dynamic_cast (LLFloaterReg::findInstance(name)); - // if floater closed or docked - if (instance == NULL || (instance && instance->isDocked())) - { - LLFloaterReg::toggleInstance(name, key); - // restore button toggle state - if (instance != NULL) - { - instance->storeVisibilityControl(); - } - } - // if floater undocked - else if (instance != NULL) - { - instance->setMinimized(false); - if (instance->getVisible()) - { - instance->setVisible(false); - } - else - { - instance->setVisible(true); - gFloaterView->bringToFront(instance); - } - } -} - -void LLDockableFloater::resetInstance() -{ - if (mUniqueDocking && sInstanceHandle.get() != this) - { - if (sInstanceHandle.get() != NULL && sInstanceHandle.get()->isDocked()) - { - sInstanceHandle.get()->setVisible(false); - } - sInstanceHandle = getHandle(); - } -} - -void LLDockableFloater::setVisible(bool visible) -{ - // Force docking if requested - if (visible && mForceDocking) - { - setCanDock(true); - setDocked(true); - mForceDocking = false; - } - - if(visible && isDocked()) - { - resetInstance(); - } - - if (visible && mDockControl.get() != NULL) - { - mDockControl.get()->repositionDockable(); - } - - if (visible && !isMinimized()) - { - LLFloater::setFrontmost(getAutoFocus()); - } - LLFloater::setVisible(visible); -} - -void LLDockableFloater::setMinimized(bool minimize) -{ - if(minimize && isDocked()) - { - // minimizing a docked floater just hides it - setVisible(false); - } - else - { - LLFloater::setMinimized(minimize); - } -} - -LLView * LLDockableFloater::getDockWidget() -{ - LLView * res = NULL; - if (getDockControl() != NULL) { - res = getDockControl()->getDock(); - } - - return res; -} - -void LLDockableFloater::onDockHidden() -{ - setCanDock(false); -} - -void LLDockableFloater::onDockShown() -{ - if (!isMinimized()) - { - setCanDock(true); - } -} - -void LLDockableFloater::setDocked(bool docked, bool pop_on_undock) -{ - if (mDockControl.get() != NULL && mDockControl.get()->isDockVisible()) - { - if (docked) - { - resetInstance(); - mDockControl.get()->on(); - } - else - { - mDockControl.get()->off(); - } - - if (!docked && pop_on_undock) - { - // visually pop up a little bit to emphasize the undocking - translate(0, UNDOCK_LEAP_HEIGHT); - } - } - - LLFloater::setDocked(docked, pop_on_undock); -} - -void LLDockableFloater::draw() -{ - if (mDockControl.get() != NULL) - { - mDockControl.get()->repositionDockable(); - if (isDocked()) - { - mDockControl.get()->drawToungue(); - } - } - LLFloater::draw(); -} - -void LLDockableFloater::setDockControl(LLDockControl* dockControl) -{ - mDockControl.reset(dockControl); - setDocked(isDocked()); -} - -const LLUIImagePtr& LLDockableFloater::getDockTongue(LLDockControl::DocAt dock_side) -{ - switch(dock_side) - { - case LLDockControl::LEFT: - mDockTongue = LLUI::getUIImage("Flyout_Left"); - break; - case LLDockControl::RIGHT: - mDockTongue = LLUI::getUIImage("Flyout_Right"); - break; - default: - mDockTongue = LLUI::getUIImage("Flyout_Pointer"); - break; - } - - return mDockTongue; -} - -LLDockControl* LLDockableFloater::getDockControl() -{ - return mDockControl.get(); -} +/** + * @file lldockablefloater.cpp + * @brief Creates a panel of a specific kind for a toast + * + * $LicenseInfo:firstyear=2000&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 "lldockablefloater.h" +#include "llfloaterreg.h" + +//static +LLHandle LLDockableFloater::sInstanceHandle; + +//static +void LLDockableFloater::init(LLDockableFloater* thiz) +{ + thiz->setDocked(thiz->mDockControl.get() != NULL + && thiz->mDockControl.get()->isDockVisible()); + thiz->resetInstance(); + + // all dockable floaters should have close, dock and minimize buttons + thiz->setCanClose(true); + thiz->setCanDock(true); + thiz->setCanMinimize(true); + thiz->setOverlapsScreenChannel(false); + thiz->mForceDocking = false; +} + +LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, + const LLSD& key, const Params& params) : + LLFloater(key, params), mDockControl(dockControl), mUniqueDocking(true) +{ + init(this); + mUseTongue = true; +} + +LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, + const LLSD& key, const Params& params) : + LLFloater(key, params), mDockControl(dockControl), mUniqueDocking(uniqueDocking) +{ + init(this); + mUseTongue = true; +} + +LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, + bool useTongue, const LLSD& key, const Params& params) : + LLFloater(key, params), mDockControl(dockControl), mUseTongue(useTongue), mUniqueDocking(uniqueDocking) +{ + init(this); +} + +LLDockableFloater::~LLDockableFloater() +{ +} + +bool LLDockableFloater::postBuild() +{ + // Remember we should force docking when the floater is opened for the first time + if (mIsDockedStateForcedCallback != NULL && mIsDockedStateForcedCallback()) + { + mForceDocking = true; + } + + mDockTongue = LLUI::getUIImage("Flyout_Pointer"); + LLFloater::setDocked(true); + return LLView::postBuild(); +} + +//static +void LLDockableFloater::toggleInstance(const LLSD& sdname) +{ + LLSD key; + std::string name = sdname.asString(); + + LLDockableFloater* instance = + dynamic_cast (LLFloaterReg::findInstance(name)); + // if floater closed or docked + if (instance == NULL || (instance && instance->isDocked())) + { + LLFloaterReg::toggleInstance(name, key); + // restore button toggle state + if (instance != NULL) + { + instance->storeVisibilityControl(); + } + } + // if floater undocked + else if (instance != NULL) + { + instance->setMinimized(false); + if (instance->getVisible()) + { + instance->setVisible(false); + } + else + { + instance->setVisible(true); + gFloaterView->bringToFront(instance); + } + } +} + +void LLDockableFloater::resetInstance() +{ + if (mUniqueDocking && sInstanceHandle.get() != this) + { + if (sInstanceHandle.get() != NULL && sInstanceHandle.get()->isDocked()) + { + sInstanceHandle.get()->setVisible(false); + } + sInstanceHandle = getHandle(); + } +} + +void LLDockableFloater::setVisible(bool visible) +{ + // Force docking if requested + if (visible && mForceDocking) + { + setCanDock(true); + setDocked(true); + mForceDocking = false; + } + + if(visible && isDocked()) + { + resetInstance(); + } + + if (visible && mDockControl.get() != NULL) + { + mDockControl.get()->repositionDockable(); + } + + if (visible && !isMinimized()) + { + LLFloater::setFrontmost(getAutoFocus()); + } + LLFloater::setVisible(visible); +} + +void LLDockableFloater::setMinimized(bool minimize) +{ + if(minimize && isDocked()) + { + // minimizing a docked floater just hides it + setVisible(false); + } + else + { + LLFloater::setMinimized(minimize); + } +} + +LLView * LLDockableFloater::getDockWidget() +{ + LLView * res = NULL; + if (getDockControl() != NULL) { + res = getDockControl()->getDock(); + } + + return res; +} + +void LLDockableFloater::onDockHidden() +{ + setCanDock(false); +} + +void LLDockableFloater::onDockShown() +{ + if (!isMinimized()) + { + setCanDock(true); + } +} + +void LLDockableFloater::setDocked(bool docked, bool pop_on_undock) +{ + if (mDockControl.get() != NULL && mDockControl.get()->isDockVisible()) + { + if (docked) + { + resetInstance(); + mDockControl.get()->on(); + } + else + { + mDockControl.get()->off(); + } + + if (!docked && pop_on_undock) + { + // visually pop up a little bit to emphasize the undocking + translate(0, UNDOCK_LEAP_HEIGHT); + } + } + + LLFloater::setDocked(docked, pop_on_undock); +} + +void LLDockableFloater::draw() +{ + if (mDockControl.get() != NULL) + { + mDockControl.get()->repositionDockable(); + if (isDocked()) + { + mDockControl.get()->drawToungue(); + } + } + LLFloater::draw(); +} + +void LLDockableFloater::setDockControl(LLDockControl* dockControl) +{ + mDockControl.reset(dockControl); + setDocked(isDocked()); +} + +const LLUIImagePtr& LLDockableFloater::getDockTongue(LLDockControl::DocAt dock_side) +{ + switch(dock_side) + { + case LLDockControl::LEFT: + mDockTongue = LLUI::getUIImage("Flyout_Left"); + break; + case LLDockControl::RIGHT: + mDockTongue = LLUI::getUIImage("Flyout_Right"); + break; + default: + mDockTongue = LLUI::getUIImage("Flyout_Pointer"); + break; + } + + return mDockTongue; +} + +LLDockControl* LLDockableFloater::getDockControl() +{ + return mDockControl.get(); +} diff --git a/indra/llui/lldockablefloater.h b/indra/llui/lldockablefloater.h index fd23877e09..3effc977db 100644 --- a/indra/llui/lldockablefloater.h +++ b/indra/llui/lldockablefloater.h @@ -1,152 +1,152 @@ -/** - * @file lldockablefloater.h - * @brief Creates a panel of a specific kind for a toast. - * - * $LicenseInfo:firstyear=2003&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$ - */ - -#ifndef LL_DOCKABLEFLOATER_H -#define LL_DOCKABLEFLOATER_H - -#include "llerror.h" -#include "llfloater.h" -#include "lldockcontrol.h" -#include - -/** - * Represents floater that can dock. - * In case impossibility deriving from LLDockableFloater use LLDockControl. - */ -class LLDockableFloater : public LLFloater -{ - static const U32 UNDOCK_LEAP_HEIGHT = 12; - - static void init(LLDockableFloater* thiz); -public: - LOG_CLASS(LLDockableFloater); - LLDockableFloater(LLDockControl* dockControl, const LLSD& key, - const Params& params = getDefaultParams()); - - /** - * Constructor. - * @param dockControl a pointer to the doc control instance - * @param uniqueDocking - a flag defines is docking should work as tab(at one - * moment only one docked floater can be shown), also this flag defines is dock - * tongue should be used. - * @params key a floater key. - * @params params a floater parameters - */ - LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, - const LLSD& key, const Params& params = getDefaultParams()); - - /** - * Constructor. - * @param dockControl a pointer to the doc control instance - * @param uniqueDocking - a flag defines is docking should work as tab(at one - * moment only one docked floater can be shown). - * @praram useTongue - a flag defines is dock tongue should be used. - * @params key a floater key. - * @params params a floater parameters - */ - LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, - bool useTongue, const LLSD& key, - const Params& params = getDefaultParams()); - - virtual ~LLDockableFloater(); - - static LLHandle getInstanceHandle() { return sInstanceHandle; } - - static void toggleInstance(const LLSD& sdname); - - /** - * If descendant class overrides postBuild() in order to perform specific - * construction then it must still invoke its superclass' implementation. - */ - /* virtula */bool postBuild(); - /* virtual */void setDocked(bool docked, bool pop_on_undock = true); - /* virtual */void draw(); - - /** - * If descendant class overrides setVisible() then it must still invoke its - * superclass' implementation. - */ - /*virtual*/ void setVisible(bool visible); - - /** - * If descendant class overrides setMinimized() then it must still invoke its - * superclass' implementation. - */ - /*virtual*/ void setMinimized(bool minimize); - - LLView * getDockWidget(); - - virtual void onDockHidden(); - virtual void onDockShown(); - - LLDockControl* getDockControl(); - - /** - * Returns true if screen channel should consider floater's size when drawing toasts. - * - * By default returns false. - */ - virtual bool overlapsScreenChannel() { return mOverlapsScreenChannel && getVisible() && isDocked(); } - virtual void setOverlapsScreenChannel(bool overlaps) { mOverlapsScreenChannel = overlaps; } - - bool getUniqueDocking() { return mUniqueDocking; } - bool getUseTongue() { return mUseTongue; } - - void setUseTongue(bool use_tongue) { mUseTongue = use_tongue;} -private: - /** - * Provides unique of dockable floater. - * If dockable floater already exists it should be closed. - */ - void resetInstance(); - -protected: - void setDockControl(LLDockControl* dockControl); - const LLUIImagePtr& getDockTongue(LLDockControl::DocAt dock_side = LLDockControl::TOP); - - // Checks if docking should be forced. - // It may be useful e.g. if floater created in mouselook mode (see EXT-5609) - boost::function mIsDockedStateForcedCallback; - -private: - std::unique_ptr mDockControl; - LLUIImagePtr mDockTongue; - static LLHandle sInstanceHandle; - /** - * Provides possibility to define that dockable floaters can be docked - * non exclusively. - */ - bool mUniqueDocking; - - bool mUseTongue; - - bool mOverlapsScreenChannel; - - // Force docking when the floater is being shown for the first time. - bool mForceDocking; -}; - -#endif /* LL_DOCKABLEFLOATER_H */ +/** + * @file lldockablefloater.h + * @brief Creates a panel of a specific kind for a toast. + * + * $LicenseInfo:firstyear=2003&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$ + */ + +#ifndef LL_DOCKABLEFLOATER_H +#define LL_DOCKABLEFLOATER_H + +#include "llerror.h" +#include "llfloater.h" +#include "lldockcontrol.h" +#include + +/** + * Represents floater that can dock. + * In case impossibility deriving from LLDockableFloater use LLDockControl. + */ +class LLDockableFloater : public LLFloater +{ + static const U32 UNDOCK_LEAP_HEIGHT = 12; + + static void init(LLDockableFloater* thiz); +public: + LOG_CLASS(LLDockableFloater); + LLDockableFloater(LLDockControl* dockControl, const LLSD& key, + const Params& params = getDefaultParams()); + + /** + * Constructor. + * @param dockControl a pointer to the doc control instance + * @param uniqueDocking - a flag defines is docking should work as tab(at one + * moment only one docked floater can be shown), also this flag defines is dock + * tongue should be used. + * @params key a floater key. + * @params params a floater parameters + */ + LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, + const LLSD& key, const Params& params = getDefaultParams()); + + /** + * Constructor. + * @param dockControl a pointer to the doc control instance + * @param uniqueDocking - a flag defines is docking should work as tab(at one + * moment only one docked floater can be shown). + * @praram useTongue - a flag defines is dock tongue should be used. + * @params key a floater key. + * @params params a floater parameters + */ + LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, + bool useTongue, const LLSD& key, + const Params& params = getDefaultParams()); + + virtual ~LLDockableFloater(); + + static LLHandle getInstanceHandle() { return sInstanceHandle; } + + static void toggleInstance(const LLSD& sdname); + + /** + * If descendant class overrides postBuild() in order to perform specific + * construction then it must still invoke its superclass' implementation. + */ + /* virtula */bool postBuild(); + /* virtual */void setDocked(bool docked, bool pop_on_undock = true); + /* virtual */void draw(); + + /** + * If descendant class overrides setVisible() then it must still invoke its + * superclass' implementation. + */ + /*virtual*/ void setVisible(bool visible); + + /** + * If descendant class overrides setMinimized() then it must still invoke its + * superclass' implementation. + */ + /*virtual*/ void setMinimized(bool minimize); + + LLView * getDockWidget(); + + virtual void onDockHidden(); + virtual void onDockShown(); + + LLDockControl* getDockControl(); + + /** + * Returns true if screen channel should consider floater's size when drawing toasts. + * + * By default returns false. + */ + virtual bool overlapsScreenChannel() { return mOverlapsScreenChannel && getVisible() && isDocked(); } + virtual void setOverlapsScreenChannel(bool overlaps) { mOverlapsScreenChannel = overlaps; } + + bool getUniqueDocking() { return mUniqueDocking; } + bool getUseTongue() { return mUseTongue; } + + void setUseTongue(bool use_tongue) { mUseTongue = use_tongue;} +private: + /** + * Provides unique of dockable floater. + * If dockable floater already exists it should be closed. + */ + void resetInstance(); + +protected: + void setDockControl(LLDockControl* dockControl); + const LLUIImagePtr& getDockTongue(LLDockControl::DocAt dock_side = LLDockControl::TOP); + + // Checks if docking should be forced. + // It may be useful e.g. if floater created in mouselook mode (see EXT-5609) + boost::function mIsDockedStateForcedCallback; + +private: + std::unique_ptr mDockControl; + LLUIImagePtr mDockTongue; + static LLHandle sInstanceHandle; + /** + * Provides possibility to define that dockable floaters can be docked + * non exclusively. + */ + bool mUniqueDocking; + + bool mUseTongue; + + bool mOverlapsScreenChannel; + + // Force docking when the floater is being shown for the first time. + bool mForceDocking; +}; + +#endif /* LL_DOCKABLEFLOATER_H */ diff --git a/indra/llui/lldraghandle.cpp b/indra/llui/lldraghandle.cpp index 78efdfc639..15536178ab 100644 --- a/indra/llui/lldraghandle.cpp +++ b/indra/llui/lldraghandle.cpp @@ -1,387 +1,387 @@ -/** - * @file lldraghandle.cpp - * @brief LLDragHandle base 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$ - */ - -// A widget for dragging a view around the screen using the mouse. - -#include "linden_common.h" - -#include "lldraghandle.h" - -#include "llmath.h" - -//#include "llviewerwindow.h" -#include "llui.h" -#include "llmenugl.h" -#include "lltextbox.h" -#include "llcontrol.h" -#include "llfontgl.h" -#include "llwindow.h" -#include "llfocusmgr.h" -#include "lluictrlfactory.h" - -const S32 LEADING_PAD = 5; -const S32 TITLE_HPAD = 8; -const S32 BORDER_PAD = 1; -const S32 LEFT_PAD = BORDER_PAD + TITLE_HPAD + LEADING_PAD; - -S32 LLDragHandle::sSnapMargin = 5; - -LLDragHandle::LLDragHandle(const LLDragHandle::Params& p) -: LLView(p), - mDragLastScreenX( 0 ), - mDragLastScreenY( 0 ), - mLastMouseScreenX( 0 ), - mLastMouseScreenY( 0 ), - mTitleBox( NULL ), - mMaxTitleWidth( 0 ), - mForeground( true ), - mDragHighlightColor(p.drag_highlight_color()), - mDragShadowColor(p.drag_shadow_color()) - -{ - static LLUICachedControl snap_margin ("SnapMargin", 0); - sSnapMargin = snap_margin; -} - -LLDragHandle::~LLDragHandle() -{ - gFocusMgr.removeKeyboardFocusWithoutCallback(this); - removeChild(mTitleBox); - delete mTitleBox; -} - -void LLDragHandle::initFromParams(const LLDragHandle::Params& p) -{ - LLView::initFromParams(p); - setTitle( p.label ); -} - -void LLDragHandle::setTitleVisible(bool visible) -{ - if(mTitleBox) - { - mTitleBox->setVisible(visible); - } -} - -void LLDragHandleTop::setTitle(const std::string& title) -{ - std::string trimmed_title = title; - LLStringUtil::trim(trimmed_title); - - if( mTitleBox ) - { - mTitleBox->setText(trimmed_title); - } - else - { - const LLFontGL* font = LLFontGL::getFontSansSerif(); - LLTextBox::Params params; - params.name("Drag Handle Title"); - params.rect(getRect()); - params.initial_value(trimmed_title); - params.font(font); - params.follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT | FOLLOWS_RIGHT); - params.font_shadow(LLFontGL::DROP_SHADOW_SOFT); - params.use_ellipses = true; - params.parse_urls = false; //cancel URL replacement in floater title - mTitleBox = LLUICtrlFactory::create (params); - addChild( mTitleBox ); - } - - reshapeTitleBox(); -} - - -std::string LLDragHandleTop::getTitle() const -{ - return mTitleBox == NULL ? LLStringUtil::null : mTitleBox->getText(); -} - - -void LLDragHandleLeft::setTitle(const std::string& ) -{ - if( mTitleBox ) - { - removeChild(mTitleBox); - delete mTitleBox; - mTitleBox = NULL; - } - /* no title on left edge */ -} - - -std::string LLDragHandleLeft::getTitle() const -{ - return LLStringUtil::null; -} - - -void LLDragHandleTop::draw() -{ - /* Disable lines. Can drag anywhere in most windows. JC - if( getVisible() && getEnabled() && mForeground) - { - const S32 BORDER_PAD = 2; - const S32 HPAD = 2; - const S32 VPAD = 2; - S32 left = BORDER_PAD + HPAD; - S32 top = getRect().getHeight() - 2 * VPAD; - S32 right = getRect().getWidth() - HPAD; -// S32 bottom = VPAD; - - // draw lines for drag areas - - const S32 LINE_SPACING = (DRAG_HANDLE_HEIGHT - 2 * VPAD) / 4; - S32 line = top - LINE_SPACING; - - LLRect title_rect = mTitleBox->getRect(); - S32 title_right = title_rect.mLeft + mTitleWidth; - bool show_right_side = title_right < getRect().getWidth(); - - for( S32 i=0; i<4; i++ ) - { - gl_line_2d(left, line+1, title_rect.mLeft - LEADING_PAD, line+1, mDragHighlightColor); - if( show_right_side ) - { - gl_line_2d(title_right, line+1, right, line+1, mDragHighlightColor); - } - - gl_line_2d(left, line, title_rect.mLeft - LEADING_PAD, line, mDragShadowColor); - if( show_right_side ) - { - gl_line_2d(title_right, line, right, line, mDragShadowColor); - } - line -= LINE_SPACING; - } - } - */ - - // Colorize the text to match the frontmost state - if (mTitleBox) - { - mTitleBox->setEnabled(getForeground()); - } - - LLView::draw(); -} - - -// assumes GL state is set for 2D -void LLDragHandleLeft::draw() -{ - /* Disable lines. Can drag anywhere in most windows. JC - if( getVisible() && getEnabled() && mForeground ) - { - const S32 BORDER_PAD = 2; -// const S32 HPAD = 2; - const S32 VPAD = 2; - const S32 LINE_SPACING = 3; - - S32 left = BORDER_PAD + LINE_SPACING; - S32 top = getRect().getHeight() - 2 * VPAD; -// S32 right = getRect().getWidth() - HPAD; - S32 bottom = VPAD; - - // draw lines for drag areas - - // no titles yet - //LLRect title_rect = mTitleBox->getRect(); - //S32 title_right = title_rect.mLeft + mTitleWidth; - //bool show_right_side = title_right < getRect().getWidth(); - - S32 line = left; - for( S32 i=0; i<4; i++ ) - { - gl_line_2d(line, top, line, bottom, mDragHighlightColor); - - gl_line_2d(line+1, top, line+1, bottom, mDragShadowColor); - - line += LINE_SPACING; - } - } - */ - - // Colorize the text to match the frontmost state - if (mTitleBox) - { - mTitleBox->setEnabled(getForeground()); - } - - LLView::draw(); -} - -void LLDragHandleTop::reshapeTitleBox() -{ - static LLUICachedControl title_vpad("UIFloaterTitleVPad", 0); - if( ! mTitleBox) - { - return; - } - const LLFontGL* font = LLFontGL::getFontSansSerif(); - S32 title_width = getRect().getWidth(); - title_width -= LEFT_PAD + 2 * BORDER_PAD + getButtonsRect().getWidth(); - S32 title_height = font->getLineHeight(); - LLRect title_rect; - title_rect.setLeftTopAndSize( - LEFT_PAD, - getRect().getHeight() - title_vpad, - title_width, - title_height); - - // calls reshape on mTitleBox - mTitleBox->setShape( title_rect ); -} - -void LLDragHandleTop::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLView::reshape(width, height, called_from_parent); - reshapeTitleBox(); -} - -void LLDragHandleLeft::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLView::reshape(width, height, called_from_parent); -} - -//------------------------------------------------------------- -// UI event handling -//------------------------------------------------------------- - -bool LLDragHandle::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // Route future Mouse messages here preemptively. (Release on mouse up.) - // No handler needed for focus lost since this clas has no state that depends on it. - gFocusMgr.setMouseCapture(this); - - localPointToScreen(x, y, &mDragLastScreenX, &mDragLastScreenY); - mLastMouseScreenX = mDragLastScreenX; - mLastMouseScreenY = mDragLastScreenY; - - // Note: don't pass on to children - return true; -} - - -bool LLDragHandle::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if( hasMouseCapture() ) - { - // Release the mouse - gFocusMgr.setMouseCapture( NULL ); - } - - // Note: don't pass on to children - return true; -} - - -bool LLDragHandle::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // We only handle the click if the click both started and ended within us - if( hasMouseCapture() ) - { - S32 screen_x; - S32 screen_y; - localPointToScreen(x, y, &screen_x, &screen_y); - - // Resize the parent - S32 delta_x = screen_x - mDragLastScreenX; - S32 delta_y = screen_y - mDragLastScreenY; - - // if dragging a docked floater we want to undock - LLFloater * parent = dynamic_cast(getParent()); - if (parent && parent->isDocked()) - { - const S32 SLOP = 12; - - if (delta_y <= -SLOP || - delta_y >= SLOP) - { - parent->setDocked(false, false); - return true; - } - else - { - return false; - } - } - - LLRect original_rect = getParent()->getRect(); - LLRect translated_rect = getParent()->getRect(); - translated_rect.translate(delta_x, delta_y); - // temporarily slam dragged window to new position - getParent()->setRect(translated_rect); - S32 pre_snap_x = getParent()->getRect().mLeft; - S32 pre_snap_y = getParent()->getRect().mBottom; - mDragLastScreenX = screen_x; - mDragLastScreenY = screen_y; - - LLRect new_rect; - LLCoordGL mouse_dir; - // use hysteresis on mouse motion to preserve user intent when mouse stops moving - mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; - mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; - mLastMouseDir = mouse_dir; - mLastMouseScreenX = screen_x; - mLastMouseScreenY = screen_y; - - LLView* snap_view = getParent()->findSnapRect(new_rect, mouse_dir, SNAP_PARENT_AND_SIBLINGS, sSnapMargin); - - getParent()->setSnappedTo(snap_view); - delta_x = new_rect.mLeft - pre_snap_x; - delta_y = new_rect.mBottom - pre_snap_y; - translated_rect.translate(delta_x, delta_y); - - // restore original rect so delta are detected, then call user reshape method to handle snapped floaters, etc - getParent()->setRect(original_rect); - getParent()->setShape(translated_rect, true); - - mDragLastScreenX += delta_x; - mDragLastScreenY += delta_y; - - getWindow()->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" <setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; - handled = true; - } - - // Note: don't pass on to children - - return handled; -} - -void LLDragHandle::setValue(const LLSD& value) -{ - setTitle(value.asString()); -} +/** + * @file lldraghandle.cpp + * @brief LLDragHandle base 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$ + */ + +// A widget for dragging a view around the screen using the mouse. + +#include "linden_common.h" + +#include "lldraghandle.h" + +#include "llmath.h" + +//#include "llviewerwindow.h" +#include "llui.h" +#include "llmenugl.h" +#include "lltextbox.h" +#include "llcontrol.h" +#include "llfontgl.h" +#include "llwindow.h" +#include "llfocusmgr.h" +#include "lluictrlfactory.h" + +const S32 LEADING_PAD = 5; +const S32 TITLE_HPAD = 8; +const S32 BORDER_PAD = 1; +const S32 LEFT_PAD = BORDER_PAD + TITLE_HPAD + LEADING_PAD; + +S32 LLDragHandle::sSnapMargin = 5; + +LLDragHandle::LLDragHandle(const LLDragHandle::Params& p) +: LLView(p), + mDragLastScreenX( 0 ), + mDragLastScreenY( 0 ), + mLastMouseScreenX( 0 ), + mLastMouseScreenY( 0 ), + mTitleBox( NULL ), + mMaxTitleWidth( 0 ), + mForeground( true ), + mDragHighlightColor(p.drag_highlight_color()), + mDragShadowColor(p.drag_shadow_color()) + +{ + static LLUICachedControl snap_margin ("SnapMargin", 0); + sSnapMargin = snap_margin; +} + +LLDragHandle::~LLDragHandle() +{ + gFocusMgr.removeKeyboardFocusWithoutCallback(this); + removeChild(mTitleBox); + delete mTitleBox; +} + +void LLDragHandle::initFromParams(const LLDragHandle::Params& p) +{ + LLView::initFromParams(p); + setTitle( p.label ); +} + +void LLDragHandle::setTitleVisible(bool visible) +{ + if(mTitleBox) + { + mTitleBox->setVisible(visible); + } +} + +void LLDragHandleTop::setTitle(const std::string& title) +{ + std::string trimmed_title = title; + LLStringUtil::trim(trimmed_title); + + if( mTitleBox ) + { + mTitleBox->setText(trimmed_title); + } + else + { + const LLFontGL* font = LLFontGL::getFontSansSerif(); + LLTextBox::Params params; + params.name("Drag Handle Title"); + params.rect(getRect()); + params.initial_value(trimmed_title); + params.font(font); + params.follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT | FOLLOWS_RIGHT); + params.font_shadow(LLFontGL::DROP_SHADOW_SOFT); + params.use_ellipses = true; + params.parse_urls = false; //cancel URL replacement in floater title + mTitleBox = LLUICtrlFactory::create (params); + addChild( mTitleBox ); + } + + reshapeTitleBox(); +} + + +std::string LLDragHandleTop::getTitle() const +{ + return mTitleBox == NULL ? LLStringUtil::null : mTitleBox->getText(); +} + + +void LLDragHandleLeft::setTitle(const std::string& ) +{ + if( mTitleBox ) + { + removeChild(mTitleBox); + delete mTitleBox; + mTitleBox = NULL; + } + /* no title on left edge */ +} + + +std::string LLDragHandleLeft::getTitle() const +{ + return LLStringUtil::null; +} + + +void LLDragHandleTop::draw() +{ + /* Disable lines. Can drag anywhere in most windows. JC + if( getVisible() && getEnabled() && mForeground) + { + const S32 BORDER_PAD = 2; + const S32 HPAD = 2; + const S32 VPAD = 2; + S32 left = BORDER_PAD + HPAD; + S32 top = getRect().getHeight() - 2 * VPAD; + S32 right = getRect().getWidth() - HPAD; +// S32 bottom = VPAD; + + // draw lines for drag areas + + const S32 LINE_SPACING = (DRAG_HANDLE_HEIGHT - 2 * VPAD) / 4; + S32 line = top - LINE_SPACING; + + LLRect title_rect = mTitleBox->getRect(); + S32 title_right = title_rect.mLeft + mTitleWidth; + bool show_right_side = title_right < getRect().getWidth(); + + for( S32 i=0; i<4; i++ ) + { + gl_line_2d(left, line+1, title_rect.mLeft - LEADING_PAD, line+1, mDragHighlightColor); + if( show_right_side ) + { + gl_line_2d(title_right, line+1, right, line+1, mDragHighlightColor); + } + + gl_line_2d(left, line, title_rect.mLeft - LEADING_PAD, line, mDragShadowColor); + if( show_right_side ) + { + gl_line_2d(title_right, line, right, line, mDragShadowColor); + } + line -= LINE_SPACING; + } + } + */ + + // Colorize the text to match the frontmost state + if (mTitleBox) + { + mTitleBox->setEnabled(getForeground()); + } + + LLView::draw(); +} + + +// assumes GL state is set for 2D +void LLDragHandleLeft::draw() +{ + /* Disable lines. Can drag anywhere in most windows. JC + if( getVisible() && getEnabled() && mForeground ) + { + const S32 BORDER_PAD = 2; +// const S32 HPAD = 2; + const S32 VPAD = 2; + const S32 LINE_SPACING = 3; + + S32 left = BORDER_PAD + LINE_SPACING; + S32 top = getRect().getHeight() - 2 * VPAD; +// S32 right = getRect().getWidth() - HPAD; + S32 bottom = VPAD; + + // draw lines for drag areas + + // no titles yet + //LLRect title_rect = mTitleBox->getRect(); + //S32 title_right = title_rect.mLeft + mTitleWidth; + //bool show_right_side = title_right < getRect().getWidth(); + + S32 line = left; + for( S32 i=0; i<4; i++ ) + { + gl_line_2d(line, top, line, bottom, mDragHighlightColor); + + gl_line_2d(line+1, top, line+1, bottom, mDragShadowColor); + + line += LINE_SPACING; + } + } + */ + + // Colorize the text to match the frontmost state + if (mTitleBox) + { + mTitleBox->setEnabled(getForeground()); + } + + LLView::draw(); +} + +void LLDragHandleTop::reshapeTitleBox() +{ + static LLUICachedControl title_vpad("UIFloaterTitleVPad", 0); + if( ! mTitleBox) + { + return; + } + const LLFontGL* font = LLFontGL::getFontSansSerif(); + S32 title_width = getRect().getWidth(); + title_width -= LEFT_PAD + 2 * BORDER_PAD + getButtonsRect().getWidth(); + S32 title_height = font->getLineHeight(); + LLRect title_rect; + title_rect.setLeftTopAndSize( + LEFT_PAD, + getRect().getHeight() - title_vpad, + title_width, + title_height); + + // calls reshape on mTitleBox + mTitleBox->setShape( title_rect ); +} + +void LLDragHandleTop::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLView::reshape(width, height, called_from_parent); + reshapeTitleBox(); +} + +void LLDragHandleLeft::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLView::reshape(width, height, called_from_parent); +} + +//------------------------------------------------------------- +// UI event handling +//------------------------------------------------------------- + +bool LLDragHandle::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Route future Mouse messages here preemptively. (Release on mouse up.) + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture(this); + + localPointToScreen(x, y, &mDragLastScreenX, &mDragLastScreenY); + mLastMouseScreenX = mDragLastScreenX; + mLastMouseScreenY = mDragLastScreenY; + + // Note: don't pass on to children + return true; +} + + +bool LLDragHandle::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if( hasMouseCapture() ) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL ); + } + + // Note: don't pass on to children + return true; +} + + +bool LLDragHandle::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // We only handle the click if the click both started and ended within us + if( hasMouseCapture() ) + { + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + + // Resize the parent + S32 delta_x = screen_x - mDragLastScreenX; + S32 delta_y = screen_y - mDragLastScreenY; + + // if dragging a docked floater we want to undock + LLFloater * parent = dynamic_cast(getParent()); + if (parent && parent->isDocked()) + { + const S32 SLOP = 12; + + if (delta_y <= -SLOP || + delta_y >= SLOP) + { + parent->setDocked(false, false); + return true; + } + else + { + return false; + } + } + + LLRect original_rect = getParent()->getRect(); + LLRect translated_rect = getParent()->getRect(); + translated_rect.translate(delta_x, delta_y); + // temporarily slam dragged window to new position + getParent()->setRect(translated_rect); + S32 pre_snap_x = getParent()->getRect().mLeft; + S32 pre_snap_y = getParent()->getRect().mBottom; + mDragLastScreenX = screen_x; + mDragLastScreenY = screen_y; + + LLRect new_rect; + LLCoordGL mouse_dir; + // use hysteresis on mouse motion to preserve user intent when mouse stops moving + mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; + mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; + mLastMouseDir = mouse_dir; + mLastMouseScreenX = screen_x; + mLastMouseScreenY = screen_y; + + LLView* snap_view = getParent()->findSnapRect(new_rect, mouse_dir, SNAP_PARENT_AND_SIBLINGS, sSnapMargin); + + getParent()->setSnappedTo(snap_view); + delta_x = new_rect.mLeft - pre_snap_x; + delta_y = new_rect.mBottom - pre_snap_y; + translated_rect.translate(delta_x, delta_y); + + // restore original rect so delta are detected, then call user reshape method to handle snapped floaters, etc + getParent()->setRect(original_rect); + getParent()->setShape(translated_rect, true); + + mDragLastScreenX += delta_x; + mDragLastScreenY += delta_y; + + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" <setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; + handled = true; + } + + // Note: don't pass on to children + + return handled; +} + +void LLDragHandle::setValue(const LLSD& value) +{ + setTitle(value.asString()); +} diff --git a/indra/llui/lldraghandle.h b/indra/llui/lldraghandle.h index e7e9469acb..a522e63243 100644 --- a/indra/llui/lldraghandle.h +++ b/indra/llui/lldraghandle.h @@ -1,139 +1,139 @@ -/** - * @file lldraghandle.h - * @brief LLDragHandle base 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$ - */ - -// A widget for dragging a view around the screen using the mouse. - -#ifndef LL_DRAGHANDLE_H -#define LL_DRAGHANDLE_H - -#include "llview.h" -#include "v4color.h" -#include "llrect.h" -#include "llcoord.h" - -class LLTextBox; - -class LLDragHandle : public LLView -{ -public: - struct Params - : public LLInitParam::Block - { - Optional label; - Optional drag_highlight_color; - Optional drag_shadow_color; - - Params() - : label("label"), - drag_highlight_color("drag_highlight_color", LLUIColorTable::instance().getColor("DefaultHighlightLight")), - drag_shadow_color("drag_shadow_color", LLUIColorTable::instance().getColor("DefaultShadowDark")) - { - changeDefault(mouse_opaque, true); - changeDefault(follows.flags, FOLLOWS_ALL); - } - }; - void initFromParams(const Params&); - - virtual ~LLDragHandle(); - - virtual void setValue(const LLSD& value); - - void setForeground(bool b) { mForeground = b; } - bool getForeground() const { return mForeground; } - void setMaxTitleWidth(S32 max_width) {mMaxTitleWidth = llmin(max_width, mMaxTitleWidth); } - S32 getMaxTitleWidth() const { return mMaxTitleWidth; } - void setButtonsRect(const LLRect& rect){ mButtonsRect = rect; } - LLRect getButtonsRect() { return mButtonsRect; } - void setTitleVisible(bool visible); - - virtual void setTitle( const std::string& title ) = 0; - virtual std::string getTitle() const = 0; - - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - -protected: - LLDragHandle(const Params&); - friend class LLUICtrlFactory; - -protected: - LLTextBox* mTitleBox; - -private: - LLRect mButtonsRect; - S32 mDragLastScreenX; - S32 mDragLastScreenY; - S32 mLastMouseScreenX; - S32 mLastMouseScreenY; - LLCoordGL mLastMouseDir; - LLUIColor mDragHighlightColor; - LLUIColor mDragShadowColor; - S32 mMaxTitleWidth; - bool mForeground; - - // Pixels near the edge to snap floaters. - static S32 sSnapMargin; -}; - - -// Use this one for traditional top-of-window draggers -class LLDragHandleTop -: public LLDragHandle -{ -protected: - LLDragHandleTop(const Params& p) : LLDragHandle(p) {} - friend class LLUICtrlFactory; -public: - virtual void setTitle( const std::string& title ); - virtual std::string getTitle() const; - virtual void draw(); - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - -private: - void reshapeTitleBox(); -}; - - -// Use this for left-side, vertical text draggers -class LLDragHandleLeft -: public LLDragHandle -{ -protected: - LLDragHandleLeft(const Params& p) : LLDragHandle(p) {} - friend class LLUICtrlFactory; -public: - virtual void setTitle( const std::string& title ); - virtual std::string getTitle() const; - virtual void draw(); - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - -}; - -const S32 DRAG_HANDLE_HEIGHT = 16; -const S32 DRAG_HANDLE_WIDTH = 16; - -#endif // LL_DRAGHANDLE_H +/** + * @file lldraghandle.h + * @brief LLDragHandle base 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$ + */ + +// A widget for dragging a view around the screen using the mouse. + +#ifndef LL_DRAGHANDLE_H +#define LL_DRAGHANDLE_H + +#include "llview.h" +#include "v4color.h" +#include "llrect.h" +#include "llcoord.h" + +class LLTextBox; + +class LLDragHandle : public LLView +{ +public: + struct Params + : public LLInitParam::Block + { + Optional label; + Optional drag_highlight_color; + Optional drag_shadow_color; + + Params() + : label("label"), + drag_highlight_color("drag_highlight_color", LLUIColorTable::instance().getColor("DefaultHighlightLight")), + drag_shadow_color("drag_shadow_color", LLUIColorTable::instance().getColor("DefaultShadowDark")) + { + changeDefault(mouse_opaque, true); + changeDefault(follows.flags, FOLLOWS_ALL); + } + }; + void initFromParams(const Params&); + + virtual ~LLDragHandle(); + + virtual void setValue(const LLSD& value); + + void setForeground(bool b) { mForeground = b; } + bool getForeground() const { return mForeground; } + void setMaxTitleWidth(S32 max_width) {mMaxTitleWidth = llmin(max_width, mMaxTitleWidth); } + S32 getMaxTitleWidth() const { return mMaxTitleWidth; } + void setButtonsRect(const LLRect& rect){ mButtonsRect = rect; } + LLRect getButtonsRect() { return mButtonsRect; } + void setTitleVisible(bool visible); + + virtual void setTitle( const std::string& title ) = 0; + virtual std::string getTitle() const = 0; + + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + +protected: + LLDragHandle(const Params&); + friend class LLUICtrlFactory; + +protected: + LLTextBox* mTitleBox; + +private: + LLRect mButtonsRect; + S32 mDragLastScreenX; + S32 mDragLastScreenY; + S32 mLastMouseScreenX; + S32 mLastMouseScreenY; + LLCoordGL mLastMouseDir; + LLUIColor mDragHighlightColor; + LLUIColor mDragShadowColor; + S32 mMaxTitleWidth; + bool mForeground; + + // Pixels near the edge to snap floaters. + static S32 sSnapMargin; +}; + + +// Use this one for traditional top-of-window draggers +class LLDragHandleTop +: public LLDragHandle +{ +protected: + LLDragHandleTop(const Params& p) : LLDragHandle(p) {} + friend class LLUICtrlFactory; +public: + virtual void setTitle( const std::string& title ); + virtual std::string getTitle() const; + virtual void draw(); + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + +private: + void reshapeTitleBox(); +}; + + +// Use this for left-side, vertical text draggers +class LLDragHandleLeft +: public LLDragHandle +{ +protected: + LLDragHandleLeft(const Params& p) : LLDragHandle(p) {} + friend class LLUICtrlFactory; +public: + virtual void setTitle( const std::string& title ); + virtual std::string getTitle() const; + virtual void draw(); + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + +}; + +const S32 DRAG_HANDLE_HEIGHT = 16; +const S32 DRAG_HANDLE_WIDTH = 16; + +#endif // LL_DRAGHANDLE_H diff --git a/indra/llui/lleditmenuhandler.h b/indra/llui/lleditmenuhandler.h index 3770502058..ff111c16a7 100644 --- a/indra/llui/lleditmenuhandler.h +++ b/indra/llui/lleditmenuhandler.h @@ -1,71 +1,71 @@ -/** -* @file lleditmenuhandler.h -* @authors Aaron Yonas, James Cook -* -* $LicenseInfo:firstyear=2006&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$ -*/ - -#ifndef LLEDITMENUHANDLER_H -#define LLEDITMENUHANDLER_H - -// Interface used by menu system for plug-in hotkey/menu handling -class LLEditMenuHandler -{ -public: - // this is needed even though this is just an interface class. - virtual ~LLEditMenuHandler(); - - virtual void undo() {}; - virtual bool canUndo() const { return false; } - - virtual void redo() {}; - virtual bool canRedo() const { return false; } - - virtual void cut() {}; - virtual bool canCut() const { return false; } - - virtual void copy() {}; - virtual bool canCopy() const { return false; } - - virtual void paste() {}; - virtual bool canPaste() const { return false; } - - // "delete" is a keyword - virtual void doDelete() {}; - virtual bool canDoDelete() const { return false; } - - virtual void selectAll() {}; - virtual bool canSelectAll() const { return false; } - - virtual void deselect() {}; - virtual bool canDeselect() const { return false; } - - // TODO: Instead of being a public data member, it would be better to hide it altogether - // and have a "set" method and then a bunch of static versions of the cut, copy, paste - // methods, etc that operate on the current global instance. That would drastically - // simplify the existing code that accesses this global variable by putting all the - // null checks in the one implementation of those static methods. -MG - static LLEditMenuHandler* gEditMenuHandler; -}; - - -#endif +/** +* @file lleditmenuhandler.h +* @authors Aaron Yonas, James Cook +* +* $LicenseInfo:firstyear=2006&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$ +*/ + +#ifndef LLEDITMENUHANDLER_H +#define LLEDITMENUHANDLER_H + +// Interface used by menu system for plug-in hotkey/menu handling +class LLEditMenuHandler +{ +public: + // this is needed even though this is just an interface class. + virtual ~LLEditMenuHandler(); + + virtual void undo() {}; + virtual bool canUndo() const { return false; } + + virtual void redo() {}; + virtual bool canRedo() const { return false; } + + virtual void cut() {}; + virtual bool canCut() const { return false; } + + virtual void copy() {}; + virtual bool canCopy() const { return false; } + + virtual void paste() {}; + virtual bool canPaste() const { return false; } + + // "delete" is a keyword + virtual void doDelete() {}; + virtual bool canDoDelete() const { return false; } + + virtual void selectAll() {}; + virtual bool canSelectAll() const { return false; } + + virtual void deselect() {}; + virtual bool canDeselect() const { return false; } + + // TODO: Instead of being a public data member, it would be better to hide it altogether + // and have a "set" method and then a bunch of static versions of the cut, copy, paste + // methods, etc that operate on the current global instance. That would drastically + // simplify the existing code that accesses this global variable by putting all the + // null checks in the one implementation of those static methods. -MG + static LLEditMenuHandler* gEditMenuHandler; +}; + + +#endif diff --git a/indra/llui/llfiltereditor.cpp b/indra/llui/llfiltereditor.cpp index 1311914131..f7b3a1e9a6 100644 --- a/indra/llui/llfiltereditor.cpp +++ b/indra/llui/llfiltereditor.cpp @@ -1,46 +1,46 @@ -/** - * @file llfiltereditor.cpp - * @brief LLFilterEditor implementation - * - * $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$ - */ - -// Text editor widget to let users enter a single line. - -#include "linden_common.h" - -#include "llfiltereditor.h" - -LLFilterEditor::LLFilterEditor(const LLFilterEditor::Params& p) -: LLSearchEditor(p) -{ - setCommitOnFocusLost(false); // we'll commit on every keystroke, don't re-commit when we take focus away (i.e. we go to interact with the actual results!) -} - - -void LLFilterEditor::handleKeystroke() -{ - this->LLSearchEditor::handleKeystroke(); - - // Commit on every keystroke. - onCommit(); -} +/** + * @file llfiltereditor.cpp + * @brief LLFilterEditor implementation + * + * $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$ + */ + +// Text editor widget to let users enter a single line. + +#include "linden_common.h" + +#include "llfiltereditor.h" + +LLFilterEditor::LLFilterEditor(const LLFilterEditor::Params& p) +: LLSearchEditor(p) +{ + setCommitOnFocusLost(false); // we'll commit on every keystroke, don't re-commit when we take focus away (i.e. we go to interact with the actual results!) +} + + +void LLFilterEditor::handleKeystroke() +{ + this->LLSearchEditor::handleKeystroke(); + + // Commit on every keystroke. + onCommit(); +} diff --git a/indra/llui/llflashtimer.cpp b/indra/llui/llflashtimer.cpp index 1506279232..c3db24c987 100644 --- a/indra/llui/llflashtimer.cpp +++ b/indra/llui/llflashtimer.cpp @@ -1,98 +1,98 @@ -/** - * @file llflashtimer.cpp - * @brief LLFlashTimer class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, 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 "llflashtimer.h" -#include "lleventtimer.h" -#include "llui.h" - -LLFlashTimer::LLFlashTimer(callback_t cb, S32 count, F32 period) -: LLEventTimer(period), - mCallback(cb), - mCurrentTickCount(0), - mIsFlashingInProgress(false), - mIsCurrentlyHighlighted(false), - mUnset(false) -{ - mEventTimer.stop(); - - // By default use settings from settings.xml to be able change them via Debug settings. See EXT-5973. - // Due to Timer is implemented as derived class from EventTimer it is impossible to change period - // in runtime. So, both settings are made as required restart. - mFlashCount = 2 * ((count > 0) ? count : LLUI::getInstance()->mSettingGroups["config"]->getS32("FlashCount")); - if (mPeriod <= 0) - { - mPeriod = LLUI::getInstance()->mSettingGroups["config"]->getF32("FlashPeriod"); - } -} - -void LLFlashTimer::unset() -{ - mUnset = true; - mCallback = NULL; -} - -bool LLFlashTimer::tick() -{ - mIsCurrentlyHighlighted = !mIsCurrentlyHighlighted; - - if (mCallback) - { - mCallback(mIsCurrentlyHighlighted); - } - - if (++mCurrentTickCount >= mFlashCount) - { - stopFlashing(); - } - - return mUnset; -} - -void LLFlashTimer::startFlashing() -{ - mIsFlashingInProgress = true; - mIsCurrentlyHighlighted = true; - mEventTimer.start(); -} - -void LLFlashTimer::stopFlashing() -{ - mEventTimer.stop(); - mIsFlashingInProgress = false; - mIsCurrentlyHighlighted = false; - mCurrentTickCount = 0; -} - -bool LLFlashTimer::isFlashingInProgress() -{ - return mIsFlashingInProgress; -} - -bool LLFlashTimer::isCurrentlyHighlighted() -{ - return mIsCurrentlyHighlighted; -} - - +/** + * @file llflashtimer.cpp + * @brief LLFlashTimer class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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 "llflashtimer.h" +#include "lleventtimer.h" +#include "llui.h" + +LLFlashTimer::LLFlashTimer(callback_t cb, S32 count, F32 period) +: LLEventTimer(period), + mCallback(cb), + mCurrentTickCount(0), + mIsFlashingInProgress(false), + mIsCurrentlyHighlighted(false), + mUnset(false) +{ + mEventTimer.stop(); + + // By default use settings from settings.xml to be able change them via Debug settings. See EXT-5973. + // Due to Timer is implemented as derived class from EventTimer it is impossible to change period + // in runtime. So, both settings are made as required restart. + mFlashCount = 2 * ((count > 0) ? count : LLUI::getInstance()->mSettingGroups["config"]->getS32("FlashCount")); + if (mPeriod <= 0) + { + mPeriod = LLUI::getInstance()->mSettingGroups["config"]->getF32("FlashPeriod"); + } +} + +void LLFlashTimer::unset() +{ + mUnset = true; + mCallback = NULL; +} + +bool LLFlashTimer::tick() +{ + mIsCurrentlyHighlighted = !mIsCurrentlyHighlighted; + + if (mCallback) + { + mCallback(mIsCurrentlyHighlighted); + } + + if (++mCurrentTickCount >= mFlashCount) + { + stopFlashing(); + } + + return mUnset; +} + +void LLFlashTimer::startFlashing() +{ + mIsFlashingInProgress = true; + mIsCurrentlyHighlighted = true; + mEventTimer.start(); +} + +void LLFlashTimer::stopFlashing() +{ + mEventTimer.stop(); + mIsFlashingInProgress = false; + mIsCurrentlyHighlighted = false; + mCurrentTickCount = 0; +} + +bool LLFlashTimer::isFlashingInProgress() +{ + return mIsFlashingInProgress; +} + +bool LLFlashTimer::isCurrentlyHighlighted() +{ + return mIsCurrentlyHighlighted; +} + + diff --git a/indra/llui/llflashtimer.h b/indra/llui/llflashtimer.h index 6da6f55deb..b55ce53fc0 100644 --- a/indra/llui/llflashtimer.h +++ b/indra/llui/llflashtimer.h @@ -1,74 +1,74 @@ -/** - * @file llflashtimer.h - * @brief LLFlashTimer class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, 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$ - */ - -#ifndef LL_FLASHTIMER_H -#define LL_FLASHTIMER_H - -#include "lleventtimer.h" -#include "boost/function.hpp" - -class LLFlashTimer : public LLEventTimer -{ -public: - - typedef boost::function callback_t; - - /** - * Constructor. - * - * @param count - how many times callback should be called (twice to not change original state) - * @param period - how frequently callback should be called - * @param cb - callback to be called each tick - */ - LLFlashTimer(callback_t cb = NULL, S32 count = 0, F32 period = 0.0); - ~LLFlashTimer() {}; - - /*virtual*/ bool tick(); - - void startFlashing(); - void stopFlashing(); - - bool isFlashingInProgress(); - bool isCurrentlyHighlighted(); - /* - * Use this instead of deleting this object. - * The next call to tick() will return true and that will destroy this object. - */ - void unset(); - -private: - callback_t mCallback; - /** - * How many times parent will blink. - */ - S32 mFlashCount; - S32 mCurrentTickCount; - bool mIsCurrentlyHighlighted; - bool mIsFlashingInProgress; - bool mUnset; -}; - -#endif /* LL_FLASHTIMER_H */ +/** + * @file llflashtimer.h + * @brief LLFlashTimer class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, 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$ + */ + +#ifndef LL_FLASHTIMER_H +#define LL_FLASHTIMER_H + +#include "lleventtimer.h" +#include "boost/function.hpp" + +class LLFlashTimer : public LLEventTimer +{ +public: + + typedef boost::function callback_t; + + /** + * Constructor. + * + * @param count - how many times callback should be called (twice to not change original state) + * @param period - how frequently callback should be called + * @param cb - callback to be called each tick + */ + LLFlashTimer(callback_t cb = NULL, S32 count = 0, F32 period = 0.0); + ~LLFlashTimer() {}; + + /*virtual*/ bool tick(); + + void startFlashing(); + void stopFlashing(); + + bool isFlashingInProgress(); + bool isCurrentlyHighlighted(); + /* + * Use this instead of deleting this object. + * The next call to tick() will return true and that will destroy this object. + */ + void unset(); + +private: + callback_t mCallback; + /** + * How many times parent will blink. + */ + S32 mFlashCount; + S32 mCurrentTickCount; + bool mIsCurrentlyHighlighted; + bool mIsFlashingInProgress; + bool mUnset; +}; + +#endif /* LL_FLASHTIMER_H */ diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index 67a0068b2f..b1a95715f1 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -1,1439 +1,1439 @@ -/** - * @file llflatlistview.cpp - * @brief LLFlatListView base class and extension to support messages for several cases of an empty list. - * - * $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 "llpanel.h" -#include "lltextbox.h" - -#include "llflatlistview.h" - -static const LLDefaultChildRegistry::Register flat_list_view("flat_list_view"); - -const LLSD SELECTED_EVENT = LLSD().with("selected", true); -const LLSD UNSELECTED_EVENT = LLSD().with("selected", false); - -//forward declaration -bool llsds_are_equal(const LLSD& llsd_1, const LLSD& llsd_2); - -LLFlatListView::Params::Params() -: item_pad("item_pad"), - allow_select("allow_select"), - multi_select("multi_select"), - keep_one_selected("keep_one_selected"), - keep_selection_visible_on_reshape("keep_selection_visible_on_reshape",false), - no_items_text("no_items_text") -{}; - -void LLFlatListView::reshape(S32 width, S32 height, bool called_from_parent /* = true */) -{ - S32 delta = height - getRect().getHeight(); - LLScrollContainer::reshape(width, height, called_from_parent); - setItemsNoScrollWidth(width); - rearrangeItems(); - - if(delta!= 0 && mKeepSelectionVisibleOnReshape) - { - ensureSelectedVisible(); - } -} - -const LLRect& LLFlatListView::getItemsRect() const -{ - return mItemsPanel->getRect(); -} - -bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null*/, EAddPosition pos /*= ADD_BOTTOM*/,bool rearrange /*= true*/) -{ - if (!item) return false; - if (value.isUndefined()) return false; - - //force uniqueness of items, easiest check but unreliable - if (item->getParent() == mItemsPanel) return false; - - item_pair_t* new_pair = new item_pair_t(item, value); - switch (pos) - { - case ADD_TOP: - mItemPairs.push_front(new_pair); - //in LLView::draw() children are iterated in backorder - mItemsPanel->addChildInBack(item); - break; - case ADD_BOTTOM: - mItemPairs.push_back(new_pair); - mItemsPanel->addChild(item); - break; - default: - break; - } - - //_4 is for MASK - item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); - item->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); - - // Children don't accept the focus - item->setTabStop(false); - - if (rearrange) - { - rearrangeItems(); - notifyParentItemsRectChanged(); - } - return true; -} - -bool LLFlatListView::addItemPairs(pairs_list_t panel_list, bool rearrange /*= true*/) -{ - if (!mItemComparator) - { - LL_WARNS_ONCE() << "No comparator specified for inserting FlatListView items." << LL_ENDL; - return false; - } - if (panel_list.size() == 0) - { - return false; - } - - // presort list so that it will be easier to sort elements into mItemPairs - panel_list.sort(ComparatorAdaptor(*mItemComparator)); - - pairs_const_iterator_t new_pair_it = panel_list.begin(); - item_pair_t* new_pair = *new_pair_it; - pairs_iterator_t pair_it = mItemPairs.begin(); - item_pair_t* item_pair = *pair_it; - - // sort panel_list into mItemPars - while (new_pair_it != panel_list.end() && pair_it != mItemPairs.end()) - { - if (!new_pair->first || new_pair->first->getParent() == mItemsPanel) - { - // iterator already used or we are reusing existing panel - new_pair_it++; - new_pair = *new_pair_it; - } - else if (mItemComparator->compare(new_pair->first, item_pair->first)) - { - LLPanel* panel = new_pair->first; - - mItemPairs.insert(pair_it, new_pair); - mItemsPanel->addChild(panel); - - //_4 is for MASK - panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); - panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); - // Children don't accept the focus - panel->setTabStop(false); - } - else - { - pair_it++; - item_pair = *pair_it; - } - } - - // Add what is left of panel_list into the end of mItemPairs. - for (; new_pair_it != panel_list.end(); ++new_pair_it) - { - item_pair_t* item_pair = *new_pair_it; - LLPanel *panel = item_pair->first; - if (panel && panel->getParent() != mItemsPanel) - { - mItemPairs.push_back(item_pair); - mItemsPanel->addChild(panel); - - //_4 is for MASK - panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, item_pair, _4)); - panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, item_pair, _4)); - // Children don't accept the focus - panel->setTabStop(false); - } - } - - if (rearrange) - { - rearrangeItems(); - notifyParentItemsRectChanged(); - } - return true; -} - - -bool LLFlatListView::insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, const LLSD& value /*= LLUUID::null*/) -{ - if (!after_item) return false; - if (!item_to_add) return false; - if (value.isUndefined()) return false; - - if (mItemPairs.empty()) return false; - - //force uniqueness of items, easiest check but unreliable - if (item_to_add->getParent() == mItemsPanel) return false; - - item_pair_t* after_pair = getItemPair(after_item); - if (!after_pair) return false; - - item_pair_t* new_pair = new item_pair_t(item_to_add, value); - if (after_pair == mItemPairs.back()) - { - mItemPairs.push_back(new_pair); - mItemsPanel->addChild(item_to_add); - } - else - { - pairs_iterator_t it = mItemPairs.begin(); - for (; it != mItemPairs.end(); ++it) - { - if (*it == after_pair) - { - // insert new elements before the element at position of passed iterator. - mItemPairs.insert(++it, new_pair); - mItemsPanel->addChild(item_to_add); - break; - } - } - } - - //_4 is for MASK - item_to_add->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); - item_to_add->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); - - rearrangeItems(); - notifyParentItemsRectChanged(); - return true; -} - - -bool LLFlatListView::removeItem(LLPanel* item, bool rearrange) -{ - if (!item) return false; - if (item->getParent() != mItemsPanel) return false; - - item_pair_t* item_pair = getItemPair(item); - if (!item_pair) return false; - - return removeItemPair(item_pair, rearrange); -} - -bool LLFlatListView::removeItemByValue(const LLSD& value, bool rearrange) -{ - if (value.isUndefined()) return false; - - item_pair_t* item_pair = getItemPair(value); - if (!item_pair) return false; - - return removeItemPair(item_pair, rearrange); -} - -bool LLFlatListView::removeItemByUUID(const LLUUID& uuid, bool rearrange) -{ - return removeItemByValue(LLSD(uuid), rearrange); -} - -LLPanel* LLFlatListView::getItemByValue(const LLSD& value) const -{ - if (value.isUndefined()) return NULL; - - item_pair_t* pair = getItemPair(value); - if (pair) return pair->first; - return NULL; -} - -bool LLFlatListView::selectItem(LLPanel* item, bool select /*= true*/) -{ - if (!item) return false; - if (item->getParent() != mItemsPanel) return false; - - item_pair_t* item_pair = getItemPair(item); - if (!item_pair) return false; - - return selectItemPair(item_pair, select); -} - -bool LLFlatListView::selectItemByValue(const LLSD& value, bool select /*= true*/) -{ - if (value.isUndefined()) return false; - - item_pair_t* item_pair = getItemPair(value); - if (!item_pair) return false; - - return selectItemPair(item_pair, select); -} - -bool LLFlatListView::selectItemByUUID(const LLUUID& uuid, bool select /* = true*/) -{ - return selectItemByValue(LLSD(uuid), select); -} - - -LLSD LLFlatListView::getSelectedValue() const -{ - if (mSelectedItemPairs.empty()) return LLSD(); - - item_pair_t* first_selected_pair = mSelectedItemPairs.front(); - return first_selected_pair->second; -} - -void LLFlatListView::getSelectedValues(std::vector& selected_values) const -{ - if (mSelectedItemPairs.empty()) return; - - for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) - { - selected_values.push_back((*it)->second); - } -} - -LLUUID LLFlatListView::getSelectedUUID() const -{ - const LLSD& value = getSelectedValue(); - if (value.isDefined() && value.isUUID()) - { - return value.asUUID(); - } - else - { - return LLUUID::null; - } -} - -void LLFlatListView::getSelectedUUIDs(uuid_vec_t& selected_uuids) const -{ - if (mSelectedItemPairs.empty()) return; - - for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) - { - selected_uuids.push_back((*it)->second.asUUID()); - } -} - -LLPanel* LLFlatListView::getSelectedItem() const -{ - if (mSelectedItemPairs.empty()) return NULL; - - return mSelectedItemPairs.front()->first; -} - -void LLFlatListView::getSelectedItems(std::vector& selected_items) const -{ - if (mSelectedItemPairs.empty()) return; - - for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) - { - selected_items.push_back((*it)->first); - } -} - -void LLFlatListView::resetSelection(bool no_commit_on_deselection /*= false*/) -{ - if (mSelectedItemPairs.empty()) return; - - for (pairs_iterator_t it= mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) - { - item_pair_t* pair_to_deselect = *it; - LLPanel* item = pair_to_deselect->first; - item->setValue(UNSELECTED_EVENT); - } - - mSelectedItemPairs.clear(); - - if (mCommitOnSelectionChange && !no_commit_on_deselection) - { - onCommit(); - } - - // Stretch selected item rect to ensure it won't be clipped - mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); -} - -void LLFlatListView::setNoItemsCommentText(const std::string& comment_text) -{ - mNoItemsCommentTextbox->setValue(comment_text); -} - -U32 LLFlatListView::size(const bool only_visible_items) const -{ - if (only_visible_items) - { - U32 size = 0; - for (pairs_const_iterator_t - iter = mItemPairs.begin(), - iter_end = mItemPairs.end(); - iter != iter_end; ++iter) - { - if ((*iter)->first->getVisible()) - ++size; - } - return size; - } - else - { - return mItemPairs.size(); - } -} - -void LLFlatListView::clear() -{ - // This will clear mSelectedItemPairs, calling all appropriate callbacks. - resetSelection(); - - // do not use LLView::deleteAllChildren to avoid removing nonvisible items. drag-n-drop for ex. - for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) - { - mItemsPanel->removeChild((*it)->first); - (*it)->first->die(); - delete *it; - } - mItemPairs.clear(); - - // also set items panel height to zero. Reshape it to allow reshaping of non-item children - LLRect rc = mItemsPanel->getRect(); - rc.mBottom = rc.mTop; - mItemsPanel->reshape(rc.getWidth(), rc.getHeight()); - mItemsPanel->setRect(rc); - - setNoItemsCommentVisible(true); - notifyParentItemsRectChanged(); -} - -void LLFlatListView::sort() -{ - if (!mItemComparator) - { - LL_WARNS() << "No comparator specified for sorting FlatListView items." << LL_ENDL; - return; - } - - mItemPairs.sort(ComparatorAdaptor(*mItemComparator)); - rearrangeItems(); -} - -bool LLFlatListView::updateValue(const LLSD& old_value, const LLSD& new_value) -{ - if (old_value.isUndefined() || new_value.isUndefined()) return false; - if (llsds_are_equal(old_value, new_value)) return false; - - item_pair_t* item_pair = getItemPair(old_value); - if (!item_pair) return false; - - item_pair->second = new_value; - return true; -} - -////////////////////////////////////////////////////////////////////////// -// PROTECTED STUFF -////////////////////////////////////////////////////////////////////////// - -LLFlatListView::LLFlatListView(const LLFlatListView::Params& p) -: LLScrollContainer(p) - , mItemComparator(NULL) - , mItemsPanel(NULL) - , mItemPad(p.item_pad) - , mAllowSelection(p.allow_select) - , mMultipleSelection(p.multi_select) - , mKeepOneItemSelected(p.keep_one_selected) - , mCommitOnSelectionChange(false) - , mPrevNotifyParentRect(LLRect()) - , mNoItemsCommentTextbox(NULL) - , mIsConsecutiveSelection(false) - , mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape) -{ - mBorderThickness = getBorderWidth(); - - LLRect scroll_rect = getRect(); - LLRect items_rect; - - setItemsNoScrollWidth(scroll_rect.getWidth()); - items_rect.setLeftTopAndSize(mBorderThickness, scroll_rect.getHeight() - mBorderThickness, mItemsNoScrollWidth, 0); - - LLPanel::Params pp; - pp.rect(items_rect); - mItemsPanel = LLUICtrlFactory::create (pp); - addChild(mItemsPanel); - - //we don't need to stretch in vertical direction on reshaping by a parent - //no bottom following! - mItemsPanel->setFollows(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_TOP); - - LLViewBorder::Params params; - params.name("scroll border"); - params.rect(getLastSelectedItemRect()); - params.visible(false); - params.bevel_style(LLViewBorder::BEVEL_IN); - mSelectedItemsBorder = LLUICtrlFactory::create (params); - mItemsPanel->addChild( mSelectedItemsBorder ); - - { - // create textbox for "No Items" comment text - LLTextBox::Params text_p = p.no_items_text; - if (!text_p.rect.isProvided()) - { - LLRect comment_rect = getRect(); - comment_rect.setOriginAndSize(0, 0, comment_rect.getWidth(), comment_rect.getHeight()); - comment_rect.stretch(-getBorderWidth()); - text_p.rect(comment_rect); - } - text_p.border_visible(false); - - if (!text_p.follows.isProvided()) - { - text_p.follows.flags(FOLLOWS_ALL); - } - mNoItemsCommentTextbox = LLUICtrlFactory::create(text_p, this); - } -}; - -LLFlatListView::~LLFlatListView() -{ - for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) - { - mItemsPanel->removeChild((*it)->first); - (*it)->first->die(); - delete *it; - } - mItemPairs.clear(); -} - -// virtual -void LLFlatListView::draw() -{ - // Highlight border if a child of this container has keyboard focus - if( mSelectedItemsBorder->getVisible() ) - { - mSelectedItemsBorder->setKeyboardFocusHighlight( hasFocus() ); - } - LLScrollContainer::draw(); -} - -// virtual -bool LLFlatListView::postBuild() -{ - setTabStop(true); - return LLScrollContainer::postBuild(); -} - -void LLFlatListView::rearrangeItems() -{ - static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); - - setNoItemsCommentVisible(0==size()); - - if (mItemPairs.empty()) return; - - //calculating required height - assuming items can be of different height - //list should accommodate all its items - S32 height = 0; - - S32 invisible_children_count = 0; - pairs_iterator_t it = mItemPairs.begin(); - for (; it != mItemPairs.end(); ++it) - { - LLPanel* item = (*it)->first; - - // skip invisible child - if (!item->getVisible()) - { - ++invisible_children_count; - continue; - } - - height += item->getRect().getHeight(); - } - - // add paddings between items, excluding invisible ones - height += mItemPad * (mItemPairs.size() - invisible_children_count - 1); - - LLRect rc = mItemsPanel->getRect(); - S32 width = mItemsNoScrollWidth; - - // update width to avoid horizontal scrollbar - if (height > getRect().getHeight() - 2 * mBorderThickness) - width -= scrollbar_size; - - //changes the bottom, end of the list goes down in the scroll container - rc.setLeftTopAndSize(rc.mLeft, rc.mTop, width, height); - mItemsPanel->setRect(rc); - - //reshaping items - S32 item_new_top = height; - pairs_iterator_t it2, first_it = mItemPairs.begin(); - for (it2 = first_it; it2 != mItemPairs.end(); ++it2) - { - LLPanel* item = (*it2)->first; - - // skip invisible child - if (!item->getVisible()) - continue; - - LLRect rc = item->getRect(); - rc.setLeftTopAndSize(rc.mLeft, item_new_top, width, rc.getHeight()); - item->reshape(rc.getWidth(), rc.getHeight()); - item->setRect(rc); - - // move top for next item in list - item_new_top -= (rc.getHeight() + mItemPad); - } - - // Stretch selected item rect to ensure it won't be clipped - mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); -} - -void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) -{ - if (!item_pair) return; - - if (!item_pair->first) - { - LL_WARNS() << "Attempt to selet an item pair containing null panel item" << LL_ENDL; - return; - } - - setFocus(true); - - bool select_item = !isSelected(item_pair); - - //*TODO find a better place for that enforcing stuff - if (mKeepOneItemSelected && numSelected() == 1 && !select_item) return; - - if ( (mask & MASK_SHIFT) && !(mask & MASK_CONTROL) - && mMultipleSelection && !mSelectedItemPairs.empty() ) - { - item_pair_t* last_selected_pair = mSelectedItemPairs.back(); - - // If item_pair is already selected - do nothing - if (last_selected_pair == item_pair) - return; - - bool grab_items = false; - bool reverse = false; - pairs_list_t pairs_to_select; - - // Pick out items from list between last selected and current clicked item_pair. - for (pairs_iterator_t - iter = mItemPairs.begin(), - iter_end = mItemPairs.end(); - iter != iter_end; ++iter) - { - item_pair_t* cur = *iter; - if (cur == last_selected_pair || cur == item_pair) - { - // We've got reverse selection if last grabed item isn't a new selection. - reverse = grab_items && (cur != item_pair); - grab_items = !grab_items; - // Skip last selected and current clicked item pairs. - continue; - } - if (!cur->first->getVisible()) - { - // Skip invisible item pairs. - continue; - } - if (grab_items) - { - pairs_to_select.push_back(cur); - } - } - - if (reverse) - { - pairs_to_select.reverse(); - } - - pairs_to_select.push_back(item_pair); - - for (pairs_iterator_t - iter = pairs_to_select.begin(), - iter_end = pairs_to_select.end(); - iter != iter_end; ++iter) - { - item_pair_t* pair_to_select = *iter; - if (isSelected(pair_to_select)) - { - // Item was already selected but there is a need to keep order from last selected pair to new selection. - // Do it here to prevent extra mCommitOnSelectionChange in selectItemPair(). - mSelectedItemPairs.remove(pair_to_select); - mSelectedItemPairs.push_back(pair_to_select); - } - else - { - selectItemPair(pair_to_select, true); - } - } - - if (!select_item) - { - // Update last selected item border. - mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); - } - return; - } - - //no need to do additional commit on selection reset - if (!(mask & MASK_CONTROL) || !mMultipleSelection) resetSelection(true); - - //only CTRL usage allows to deselect an item, usual clicking on an item cannot deselect it - if (mask & MASK_CONTROL) - selectItemPair(item_pair, select_item); - else - selectItemPair(item_pair, true); -} - -void LLFlatListView::onItemRightMouseClick(item_pair_t* item_pair, MASK mask) -{ - if (!item_pair) - return; - - // Forbid deselecting of items on right mouse button click if mMultipleSelection flag is set on, - // because some of derived classes may have context menu and selected items must be kept. - if ( !(mask & MASK_CONTROL) && mMultipleSelection && isSelected(item_pair) ) - return; - - // else got same behavior as at onItemMouseClick - onItemMouseClick(item_pair, mask); -} - -bool LLFlatListView::handleKeyHere(KEY key, MASK mask) -{ - bool reset_selection = (mask != MASK_SHIFT); - bool handled = false; - switch (key) - { - case KEY_RETURN: - { - if (mSelectedItemPairs.size() && mask == MASK_NONE) - { - mOnReturnSignal(this, getValue()); - handled = true; - } - break; - } - case KEY_UP: - { - if ( !selectNextItemPair(true, reset_selection) && reset_selection) - { - // If case we are in accordion tab notify parent to go to the previous accordion - if(notifyParent(LLSD().with("action","select_prev")) > 0 )//message was processed - resetSelection(); - } - break; - } - case KEY_DOWN: - { - if ( !selectNextItemPair(false, reset_selection) && reset_selection) - { - // If case we are in accordion tab notify parent to go to the next accordion - if( notifyParent(LLSD().with("action","select_next")) > 0 ) //message was processed - resetSelection(); - } - break; - } - case KEY_ESCAPE: - { - if (mask == MASK_NONE) - { - setFocus(false); // pass focus to the game area (EXT-8357) - } - break; - } - default: - break; - } - - if ( ( key == KEY_UP || key == KEY_DOWN ) && mSelectedItemPairs.size() ) - { - ensureSelectedVisible(); - /* - LLRect visible_rc = getVisibleContentRect(); - LLRect selected_rc = getLastSelectedItemRect(); - - if ( !visible_rc.contains (selected_rc) ) - { - // But scroll in Items panel coordinates - scrollToShowRect(selected_rc); - } - - // In case we are in accordion tab notify parent to show selected rectangle - LLRect screen_rc; - localRectToScreen(selected_rc, &screen_rc); - notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue()));*/ - - handled = true; - } - - return handled ? handled : LLScrollContainer::handleKeyHere(key, mask); -} - -LLFlatListView::item_pair_t* LLFlatListView::getItemPair(LLPanel* item) const -{ - llassert(item); - - for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it) - { - item_pair_t* item_pair = *it; - if (item_pair->first == item) return item_pair; - } - return NULL; -} - -//compares two LLSD's -bool llsds_are_equal(const LLSD& llsd_1, const LLSD& llsd_2) -{ - llassert(llsd_1.isDefined()); - llassert(llsd_2.isDefined()); - - if (llsd_1.type() != llsd_2.type()) return false; - - if (!llsd_1.isMap()) - { - if (llsd_1.isUUID()) return llsd_1.asUUID() == llsd_2.asUUID(); - - //assumptions that string representaion is enough for other types - return llsd_1.asString() == llsd_2.asString(); - } - - if (llsd_1.size() != llsd_2.size()) return false; - - LLSD::map_const_iterator llsd_1_it = llsd_1.beginMap(); - LLSD::map_const_iterator llsd_2_it = llsd_2.beginMap(); - for (S32 i = 0; i < llsd_1.size(); ++i) - { - if ((*llsd_1_it).first != (*llsd_2_it).first) return false; - if (!llsds_are_equal((*llsd_1_it).second, (*llsd_2_it).second)) return false; - ++llsd_1_it; - ++llsd_2_it; - } - return true; -} - -LLFlatListView::item_pair_t* LLFlatListView::getItemPair(const LLSD& value) const -{ - llassert(value.isDefined()); - - for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it) - { - item_pair_t* item_pair = *it; - if (llsds_are_equal(item_pair->second, value)) return item_pair; - } - return NULL; -} - -bool LLFlatListView::selectItemPair(item_pair_t* item_pair, bool select) -{ - llassert(item_pair); - - if (!mAllowSelection && select) return false; - - if (isSelected(item_pair) == select) return true; //already in specified selection state - if (select) - { - mSelectedItemPairs.push_back(item_pair); - } - else - { - mSelectedItemPairs.remove(item_pair); - } - - //a way of notifying panel of selection state changes - LLPanel* item = item_pair->first; - item->setValue(select ? SELECTED_EVENT : UNSELECTED_EVENT); - - if (mCommitOnSelectionChange) - { - onCommit(); - } - - // Stretch selected item rect to ensure it won't be clipped - mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); - // By default mark it as not consecutive selection - mIsConsecutiveSelection = false; - - return true; -} - -void LLFlatListView::scrollToShowFirstSelectedItem() -{ - if (!mSelectedItemPairs.size()) return; - - LLRect selected_rc = mSelectedItemPairs.front()->first->getRect(); - - if (selected_rc.isValid()) - { - scrollToShowRect(selected_rc); - } -} - -LLRect LLFlatListView::getLastSelectedItemRect() -{ - if (!mSelectedItemPairs.size()) - { - return LLRect::null; - } - - return mSelectedItemPairs.back()->first->getRect(); -} - -void LLFlatListView::selectFirstItem () -{ - // No items - no actions! - if (0 == size()) return; - - // Select first visible item - for (pairs_iterator_t - iter = mItemPairs.begin(), - iter_end = mItemPairs.end(); - iter != iter_end; ++iter) - { - // skip invisible items - if ( (*iter)->first->getVisible() ) - { - selectItemPair(*iter, true); - ensureSelectedVisible(); - break; - } - } -} - -void LLFlatListView::selectLastItem () -{ - // No items - no actions! - if (0 == size()) return; - - // Select last visible item - for (pairs_list_t::reverse_iterator - r_iter = mItemPairs.rbegin(), - r_iter_end = mItemPairs.rend(); - r_iter != r_iter_end; ++r_iter) - { - // skip invisible items - if ( (*r_iter)->first->getVisible() ) - { - selectItemPair(*r_iter, true); - ensureSelectedVisible(); - break; - } - } -} - -void LLFlatListView::ensureSelectedVisible() -{ - LLRect selected_rc = getLastSelectedItemRect(); - - if ( selected_rc.isValid() ) - { - scrollToShowRect(selected_rc); - } -} - - -// virtual -bool LLFlatListView::selectNextItemPair(bool is_up_direction, bool reset_selection) -{ - // No items - no actions! - if ( 0 == size() ) - return false; - - if (!mIsConsecutiveSelection) - { - // Leave only one item selected if list has not consecutive selection - if (mSelectedItemPairs.size() && !reset_selection) - { - item_pair_t* cur_sel_pair = mSelectedItemPairs.back(); - resetSelection(); - selectItemPair (cur_sel_pair, true); - } - } - - if ( mSelectedItemPairs.size() ) - { - item_pair_t* to_sel_pair = NULL; - item_pair_t* cur_sel_pair = NULL; - - // Take the last selected pair - cur_sel_pair = mSelectedItemPairs.back(); - // Bases on given direction choose next item to select - if ( is_up_direction ) - { - // Find current selected item position in mItemPairs list - pairs_list_t::reverse_iterator sel_it = std::find(mItemPairs.rbegin(), mItemPairs.rend(), cur_sel_pair); - - for (;++sel_it != mItemPairs.rend();) - { - // skip invisible items - if ( (*sel_it)->first->getVisible() ) - { - to_sel_pair = *sel_it; - break; - } - } - } - else - { - // Find current selected item position in mItemPairs list - pairs_list_t::iterator sel_it = std::find(mItemPairs.begin(), mItemPairs.end(), cur_sel_pair); - - for (;++sel_it != mItemPairs.end();) - { - // skip invisible items - if ( (*sel_it)->first->getVisible() ) - { - to_sel_pair = *sel_it; - break; - } - } - } - - if ( to_sel_pair ) - { - bool select = true; - if ( reset_selection ) - { - // Reset current selection if we were asked about it - resetSelection(); - } - else - { - // If item already selected and no reset request than we should deselect last selected item. - select = (mSelectedItemPairs.end() == std::find(mSelectedItemPairs.begin(), mSelectedItemPairs.end(), to_sel_pair)); - } - // Select/Deselect next item - selectItemPair(select ? to_sel_pair : cur_sel_pair, select); - // Mark it as consecutive selection - mIsConsecutiveSelection = true; - return true; - } - } - else - { - // If there weren't selected items then choose the first one bases on given direction - // Force selection to first item - if (is_up_direction) - selectLastItem(); - else - selectFirstItem(); - // Mark it as consecutive selection - mIsConsecutiveSelection = true; - return true; - } - - return false; -} - -bool LLFlatListView::canSelectAll() const -{ - return 0 != size() && mAllowSelection && mMultipleSelection; -} - -void LLFlatListView::selectAll() -{ - if (!mAllowSelection || !mMultipleSelection) - return; - - mSelectedItemPairs.clear(); - - for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it) - { - item_pair_t* item_pair = *it; - mSelectedItemPairs.push_back(item_pair); - //a way of notifying panel of selection state changes - LLPanel* item = item_pair->first; - item->setValue(SELECTED_EVENT); - } - - if (mCommitOnSelectionChange) - { - onCommit(); - } - - // Stretch selected item rect to ensure it won't be clipped - mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); -} - -bool LLFlatListView::isSelected(item_pair_t* item_pair) const -{ - llassert(item_pair); - - pairs_const_iterator_t it_end = mSelectedItemPairs.end(); - return std::find(mSelectedItemPairs.begin(), it_end, item_pair) != it_end; -} - -bool LLFlatListView::removeItemPair(item_pair_t* item_pair, bool rearrange) -{ - llassert(item_pair); - - bool deleted = false; - bool selection_changed = false; - for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) - { - item_pair_t* _item_pair = *it; - if (_item_pair == item_pair) - { - mItemPairs.erase(it); - deleted = true; - break; - } - } - - if (!deleted) return false; - - for (pairs_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) - { - item_pair_t* selected_item_pair = *it; - if (selected_item_pair == item_pair) - { - it = mSelectedItemPairs.erase(it); - selection_changed = true; - break; - } - } - - mItemsPanel->removeChild(item_pair->first); - item_pair->first->die(); - delete item_pair; - - if (rearrange) - { - rearrangeItems(); - notifyParentItemsRectChanged(); - } - - if (selection_changed && mCommitOnSelectionChange) - { - onCommit(); - } - - return true; -} - -void LLFlatListView::notifyParentItemsRectChanged() -{ - S32 comment_height = 0; - - // take into account comment text height if exists - if (mNoItemsCommentTextbox && mNoItemsCommentTextbox->getVisible()) - { - // top text padding inside the textbox is included into the height - comment_height = mNoItemsCommentTextbox->getTextPixelHeight(); - - // take into account a distance from parent's top border to textbox's top - comment_height += getRect().getHeight() - mNoItemsCommentTextbox->getRect().mTop; - } - - LLRect req_rect = getItemsRect(); - - // get maximum of items total height and comment text height - req_rect.setOriginAndSize(req_rect.mLeft, req_rect.mBottom, req_rect.getWidth(), llmax(req_rect.getHeight(), comment_height)); - - // take into account border size. - req_rect.stretch(getBorderWidth()); - - if (req_rect == mPrevNotifyParentRect) - return; - - mPrevNotifyParentRect = req_rect; - - LLSD params; - params["action"] = "size_changes"; - params["width"] = req_rect.getWidth(); - params["height"] = req_rect.getHeight(); - - if (getParent()) // dummy widgets don't have a parent - getParent()->notifyParent(params); -} - -void LLFlatListView::setNoItemsCommentVisible(bool visible) const -{ - if (mNoItemsCommentTextbox) - { - mSelectedItemsBorder->setVisible(!visible); - mNoItemsCommentTextbox->setVisible(visible); - } -} - -void LLFlatListView::getItems(std::vector& items) const -{ - if (mItemPairs.empty()) return; - - items.clear(); - for (pairs_const_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) - { - items.push_back((*it)->first); - } -} - -void LLFlatListView::getValues(std::vector& values) const -{ - if (mItemPairs.empty()) return; - - values.clear(); - for (pairs_const_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) - { - values.push_back((*it)->second); - } -} - -// virtual -void LLFlatListView::onFocusReceived() -{ - if (size()) - { - mSelectedItemsBorder->setVisible(true); - } - gEditMenuHandler = this; -} -// virtual -void LLFlatListView::onFocusLost() -{ - mSelectedItemsBorder->setVisible(false); - // Route menu back to the default - if (gEditMenuHandler == this) - { - gEditMenuHandler = NULL; - } -} - -//virtual -S32 LLFlatListView::notify(const LLSD& info) -{ - if (info.has("action")) - { - std::string str_action = info["action"]; - if (str_action == "select_first") - { - setFocus(true); - selectFirstItem(); - return 1; - } - else if (str_action == "select_last") - { - setFocus(true); - selectLastItem(); - return 1; - } - } - else if (info.has("rearrange")) - { - rearrangeItems(); - notifyParentItemsRectChanged(); - return 1; - } - - return 0; -} - -void LLFlatListView::detachItems(std::vector& detached_items) -{ - LLSD action; - action.with("detach", LLSD()); - // Clear detached_items list - detached_items.clear(); - // Go through items and detach valid items, remove them from items panel - // and add to detached_items. - pairs_iterator_t iter = mItemPairs.begin(), iter_end = mItemPairs.end(); - while (iter != iter_end) - { - LLPanel* pItem = (*iter)->first; - if (1 == pItem->notify(action)) - { - selectItemPair((*iter), false); - mItemsPanel->removeChild(pItem); - detached_items.push_back(pItem); - } - iter++; - } - if (!detached_items.empty()) - { - // Some items were detached, clean ourself from unusable memory - if (detached_items.size() == mItemPairs.size()) - { - // This way will be faster if all items were disconnected - pairs_iterator_t iter = mItemPairs.begin(), iter_end = mItemPairs.end(); - while (iter != iter_end) - { - (*iter)->first = NULL; - delete *iter; - iter++; - } - mItemPairs.clear(); - // Also set items panel height to zero. - // Reshape it to allow reshaping of non-item children. - LLRect rc = mItemsPanel->getRect(); - rc.mBottom = rc.mTop; - mItemsPanel->reshape(rc.getWidth(), rc.getHeight()); - mItemsPanel->setRect(rc); - setNoItemsCommentVisible(true); - } - else - { - std::vector::const_iterator - detached_iter = detached_items.begin(), - detached_iter_end = detached_items.end(); - while (detached_iter < detached_iter_end) - { - LLPanel* pDetachedItem = *detached_iter; - pairs_iterator_t iter = mItemPairs.begin(), iter_end = mItemPairs.end(); - while (iter != iter_end) - { - item_pair_t* item_pair = *iter; - if (item_pair->first == pDetachedItem) - { - mItemPairs.erase(iter); - item_pair->first = NULL; - delete item_pair; - break; - } - iter++; - } - detached_iter++; - } - rearrangeItems(); - } - notifyParentItemsRectChanged(); - } -} - - -/************************************************************************/ -/* LLFlatListViewEx implementation */ -/************************************************************************/ -LLFlatListViewEx::Params::Params() -: no_items_msg("no_items_msg") -, no_filtered_items_msg("no_filtered_items_msg") -{ -} - -LLFlatListViewEx::LLFlatListViewEx(const Params& p) -: LLFlatListView(p) -, mNoFilteredItemsMsg(p.no_filtered_items_msg) -, mNoItemsMsg(p.no_items_msg) -, mForceShowingUnmatchedItems(false) -, mHasMatchedItems(false) -{ -} - -void LLFlatListViewEx::updateNoItemsMessage(const std::string& filter_string) -{ - bool items_filtered = !filter_string.empty(); - if (items_filtered) - { - // items were filtered - LLStringUtil::format_map_t args; - args["[SEARCH_TERM]"] = LLURI::escape(filter_string); - std::string text = mNoFilteredItemsMsg; - LLStringUtil::format(text, args); - setNoItemsCommentText(text); - } - else - { - // list does not contain any items at all - setNoItemsCommentText(mNoItemsMsg); - } -} - -bool LLFlatListViewEx::getForceShowingUnmatchedItems() -{ - return mForceShowingUnmatchedItems; -} - -void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show) -{ - mForceShowingUnmatchedItems = show; -} - -void LLFlatListViewEx::setFilterSubString(const std::string& filter_str, bool notify_parent) -{ - if (0 != LLStringUtil::compareInsensitive(filter_str, mFilterSubString)) - { - mFilterSubString = filter_str; - updateNoItemsMessage(mFilterSubString); - filterItems(false, notify_parent); - } -} - -bool LLFlatListViewEx::updateItemVisibility(LLPanel* item, const LLSD &action) -{ - if (!item) - return false; - - bool visible = true; - - // 0 signifies that filter is matched, - // i.e. we don't hide items that don't support 'match_filter' action, separators etc. - if (0 == item->notify(action)) - { - mHasMatchedItems = true; - } - else - { - // TODO: implement (re)storing of current selection. - if (!mForceShowingUnmatchedItems) - { - selectItem(item, false); - visible = false; - } - } - - if (item->getVisible() != visible) - { - item->setVisible(visible); - return true; - } - - return false; -} - -void LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent) -{ - std::string cur_filter = mFilterSubString; - LLStringUtil::toUpper(cur_filter); - - LLSD action; - action.with("match_filter", cur_filter); - - mHasMatchedItems = false; - bool visibility_changed = false; - pairs_const_iterator_t iter = getItemPairs().begin(), iter_end = getItemPairs().end(); - while (iter != iter_end) - { - LLPanel* pItem = (*(iter++))->first; - visibility_changed |= updateItemVisibility(pItem, action); - } - - if (re_sort) - { - sort(); - } - - if (visibility_changed && notify_parent) - { - notifyParentItemsRectChanged(); - } -} - -bool LLFlatListViewEx::hasMatchedItems() -{ - return mHasMatchedItems; -} - -//EOF +/** + * @file llflatlistview.cpp + * @brief LLFlatListView base class and extension to support messages for several cases of an empty list. + * + * $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 "llpanel.h" +#include "lltextbox.h" + +#include "llflatlistview.h" + +static const LLDefaultChildRegistry::Register flat_list_view("flat_list_view"); + +const LLSD SELECTED_EVENT = LLSD().with("selected", true); +const LLSD UNSELECTED_EVENT = LLSD().with("selected", false); + +//forward declaration +bool llsds_are_equal(const LLSD& llsd_1, const LLSD& llsd_2); + +LLFlatListView::Params::Params() +: item_pad("item_pad"), + allow_select("allow_select"), + multi_select("multi_select"), + keep_one_selected("keep_one_selected"), + keep_selection_visible_on_reshape("keep_selection_visible_on_reshape",false), + no_items_text("no_items_text") +{}; + +void LLFlatListView::reshape(S32 width, S32 height, bool called_from_parent /* = true */) +{ + S32 delta = height - getRect().getHeight(); + LLScrollContainer::reshape(width, height, called_from_parent); + setItemsNoScrollWidth(width); + rearrangeItems(); + + if(delta!= 0 && mKeepSelectionVisibleOnReshape) + { + ensureSelectedVisible(); + } +} + +const LLRect& LLFlatListView::getItemsRect() const +{ + return mItemsPanel->getRect(); +} + +bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null*/, EAddPosition pos /*= ADD_BOTTOM*/,bool rearrange /*= true*/) +{ + if (!item) return false; + if (value.isUndefined()) return false; + + //force uniqueness of items, easiest check but unreliable + if (item->getParent() == mItemsPanel) return false; + + item_pair_t* new_pair = new item_pair_t(item, value); + switch (pos) + { + case ADD_TOP: + mItemPairs.push_front(new_pair); + //in LLView::draw() children are iterated in backorder + mItemsPanel->addChildInBack(item); + break; + case ADD_BOTTOM: + mItemPairs.push_back(new_pair); + mItemsPanel->addChild(item); + break; + default: + break; + } + + //_4 is for MASK + item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + item->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); + + // Children don't accept the focus + item->setTabStop(false); + + if (rearrange) + { + rearrangeItems(); + notifyParentItemsRectChanged(); + } + return true; +} + +bool LLFlatListView::addItemPairs(pairs_list_t panel_list, bool rearrange /*= true*/) +{ + if (!mItemComparator) + { + LL_WARNS_ONCE() << "No comparator specified for inserting FlatListView items." << LL_ENDL; + return false; + } + if (panel_list.size() == 0) + { + return false; + } + + // presort list so that it will be easier to sort elements into mItemPairs + panel_list.sort(ComparatorAdaptor(*mItemComparator)); + + pairs_const_iterator_t new_pair_it = panel_list.begin(); + item_pair_t* new_pair = *new_pair_it; + pairs_iterator_t pair_it = mItemPairs.begin(); + item_pair_t* item_pair = *pair_it; + + // sort panel_list into mItemPars + while (new_pair_it != panel_list.end() && pair_it != mItemPairs.end()) + { + if (!new_pair->first || new_pair->first->getParent() == mItemsPanel) + { + // iterator already used or we are reusing existing panel + new_pair_it++; + new_pair = *new_pair_it; + } + else if (mItemComparator->compare(new_pair->first, item_pair->first)) + { + LLPanel* panel = new_pair->first; + + mItemPairs.insert(pair_it, new_pair); + mItemsPanel->addChild(panel); + + //_4 is for MASK + panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); + // Children don't accept the focus + panel->setTabStop(false); + } + else + { + pair_it++; + item_pair = *pair_it; + } + } + + // Add what is left of panel_list into the end of mItemPairs. + for (; new_pair_it != panel_list.end(); ++new_pair_it) + { + item_pair_t* item_pair = *new_pair_it; + LLPanel *panel = item_pair->first; + if (panel && panel->getParent() != mItemsPanel) + { + mItemPairs.push_back(item_pair); + mItemsPanel->addChild(panel); + + //_4 is for MASK + panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, item_pair, _4)); + panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, item_pair, _4)); + // Children don't accept the focus + panel->setTabStop(false); + } + } + + if (rearrange) + { + rearrangeItems(); + notifyParentItemsRectChanged(); + } + return true; +} + + +bool LLFlatListView::insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, const LLSD& value /*= LLUUID::null*/) +{ + if (!after_item) return false; + if (!item_to_add) return false; + if (value.isUndefined()) return false; + + if (mItemPairs.empty()) return false; + + //force uniqueness of items, easiest check but unreliable + if (item_to_add->getParent() == mItemsPanel) return false; + + item_pair_t* after_pair = getItemPair(after_item); + if (!after_pair) return false; + + item_pair_t* new_pair = new item_pair_t(item_to_add, value); + if (after_pair == mItemPairs.back()) + { + mItemPairs.push_back(new_pair); + mItemsPanel->addChild(item_to_add); + } + else + { + pairs_iterator_t it = mItemPairs.begin(); + for (; it != mItemPairs.end(); ++it) + { + if (*it == after_pair) + { + // insert new elements before the element at position of passed iterator. + mItemPairs.insert(++it, new_pair); + mItemsPanel->addChild(item_to_add); + break; + } + } + } + + //_4 is for MASK + item_to_add->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + item_to_add->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4)); + + rearrangeItems(); + notifyParentItemsRectChanged(); + return true; +} + + +bool LLFlatListView::removeItem(LLPanel* item, bool rearrange) +{ + if (!item) return false; + if (item->getParent() != mItemsPanel) return false; + + item_pair_t* item_pair = getItemPair(item); + if (!item_pair) return false; + + return removeItemPair(item_pair, rearrange); +} + +bool LLFlatListView::removeItemByValue(const LLSD& value, bool rearrange) +{ + if (value.isUndefined()) return false; + + item_pair_t* item_pair = getItemPair(value); + if (!item_pair) return false; + + return removeItemPair(item_pair, rearrange); +} + +bool LLFlatListView::removeItemByUUID(const LLUUID& uuid, bool rearrange) +{ + return removeItemByValue(LLSD(uuid), rearrange); +} + +LLPanel* LLFlatListView::getItemByValue(const LLSD& value) const +{ + if (value.isUndefined()) return NULL; + + item_pair_t* pair = getItemPair(value); + if (pair) return pair->first; + return NULL; +} + +bool LLFlatListView::selectItem(LLPanel* item, bool select /*= true*/) +{ + if (!item) return false; + if (item->getParent() != mItemsPanel) return false; + + item_pair_t* item_pair = getItemPair(item); + if (!item_pair) return false; + + return selectItemPair(item_pair, select); +} + +bool LLFlatListView::selectItemByValue(const LLSD& value, bool select /*= true*/) +{ + if (value.isUndefined()) return false; + + item_pair_t* item_pair = getItemPair(value); + if (!item_pair) return false; + + return selectItemPair(item_pair, select); +} + +bool LLFlatListView::selectItemByUUID(const LLUUID& uuid, bool select /* = true*/) +{ + return selectItemByValue(LLSD(uuid), select); +} + + +LLSD LLFlatListView::getSelectedValue() const +{ + if (mSelectedItemPairs.empty()) return LLSD(); + + item_pair_t* first_selected_pair = mSelectedItemPairs.front(); + return first_selected_pair->second; +} + +void LLFlatListView::getSelectedValues(std::vector& selected_values) const +{ + if (mSelectedItemPairs.empty()) return; + + for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) + { + selected_values.push_back((*it)->second); + } +} + +LLUUID LLFlatListView::getSelectedUUID() const +{ + const LLSD& value = getSelectedValue(); + if (value.isDefined() && value.isUUID()) + { + return value.asUUID(); + } + else + { + return LLUUID::null; + } +} + +void LLFlatListView::getSelectedUUIDs(uuid_vec_t& selected_uuids) const +{ + if (mSelectedItemPairs.empty()) return; + + for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) + { + selected_uuids.push_back((*it)->second.asUUID()); + } +} + +LLPanel* LLFlatListView::getSelectedItem() const +{ + if (mSelectedItemPairs.empty()) return NULL; + + return mSelectedItemPairs.front()->first; +} + +void LLFlatListView::getSelectedItems(std::vector& selected_items) const +{ + if (mSelectedItemPairs.empty()) return; + + for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) + { + selected_items.push_back((*it)->first); + } +} + +void LLFlatListView::resetSelection(bool no_commit_on_deselection /*= false*/) +{ + if (mSelectedItemPairs.empty()) return; + + for (pairs_iterator_t it= mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) + { + item_pair_t* pair_to_deselect = *it; + LLPanel* item = pair_to_deselect->first; + item->setValue(UNSELECTED_EVENT); + } + + mSelectedItemPairs.clear(); + + if (mCommitOnSelectionChange && !no_commit_on_deselection) + { + onCommit(); + } + + // Stretch selected item rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); +} + +void LLFlatListView::setNoItemsCommentText(const std::string& comment_text) +{ + mNoItemsCommentTextbox->setValue(comment_text); +} + +U32 LLFlatListView::size(const bool only_visible_items) const +{ + if (only_visible_items) + { + U32 size = 0; + for (pairs_const_iterator_t + iter = mItemPairs.begin(), + iter_end = mItemPairs.end(); + iter != iter_end; ++iter) + { + if ((*iter)->first->getVisible()) + ++size; + } + return size; + } + else + { + return mItemPairs.size(); + } +} + +void LLFlatListView::clear() +{ + // This will clear mSelectedItemPairs, calling all appropriate callbacks. + resetSelection(); + + // do not use LLView::deleteAllChildren to avoid removing nonvisible items. drag-n-drop for ex. + for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + mItemsPanel->removeChild((*it)->first); + (*it)->first->die(); + delete *it; + } + mItemPairs.clear(); + + // also set items panel height to zero. Reshape it to allow reshaping of non-item children + LLRect rc = mItemsPanel->getRect(); + rc.mBottom = rc.mTop; + mItemsPanel->reshape(rc.getWidth(), rc.getHeight()); + mItemsPanel->setRect(rc); + + setNoItemsCommentVisible(true); + notifyParentItemsRectChanged(); +} + +void LLFlatListView::sort() +{ + if (!mItemComparator) + { + LL_WARNS() << "No comparator specified for sorting FlatListView items." << LL_ENDL; + return; + } + + mItemPairs.sort(ComparatorAdaptor(*mItemComparator)); + rearrangeItems(); +} + +bool LLFlatListView::updateValue(const LLSD& old_value, const LLSD& new_value) +{ + if (old_value.isUndefined() || new_value.isUndefined()) return false; + if (llsds_are_equal(old_value, new_value)) return false; + + item_pair_t* item_pair = getItemPair(old_value); + if (!item_pair) return false; + + item_pair->second = new_value; + return true; +} + +////////////////////////////////////////////////////////////////////////// +// PROTECTED STUFF +////////////////////////////////////////////////////////////////////////// + +LLFlatListView::LLFlatListView(const LLFlatListView::Params& p) +: LLScrollContainer(p) + , mItemComparator(NULL) + , mItemsPanel(NULL) + , mItemPad(p.item_pad) + , mAllowSelection(p.allow_select) + , mMultipleSelection(p.multi_select) + , mKeepOneItemSelected(p.keep_one_selected) + , mCommitOnSelectionChange(false) + , mPrevNotifyParentRect(LLRect()) + , mNoItemsCommentTextbox(NULL) + , mIsConsecutiveSelection(false) + , mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape) +{ + mBorderThickness = getBorderWidth(); + + LLRect scroll_rect = getRect(); + LLRect items_rect; + + setItemsNoScrollWidth(scroll_rect.getWidth()); + items_rect.setLeftTopAndSize(mBorderThickness, scroll_rect.getHeight() - mBorderThickness, mItemsNoScrollWidth, 0); + + LLPanel::Params pp; + pp.rect(items_rect); + mItemsPanel = LLUICtrlFactory::create (pp); + addChild(mItemsPanel); + + //we don't need to stretch in vertical direction on reshaping by a parent + //no bottom following! + mItemsPanel->setFollows(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_TOP); + + LLViewBorder::Params params; + params.name("scroll border"); + params.rect(getLastSelectedItemRect()); + params.visible(false); + params.bevel_style(LLViewBorder::BEVEL_IN); + mSelectedItemsBorder = LLUICtrlFactory::create (params); + mItemsPanel->addChild( mSelectedItemsBorder ); + + { + // create textbox for "No Items" comment text + LLTextBox::Params text_p = p.no_items_text; + if (!text_p.rect.isProvided()) + { + LLRect comment_rect = getRect(); + comment_rect.setOriginAndSize(0, 0, comment_rect.getWidth(), comment_rect.getHeight()); + comment_rect.stretch(-getBorderWidth()); + text_p.rect(comment_rect); + } + text_p.border_visible(false); + + if (!text_p.follows.isProvided()) + { + text_p.follows.flags(FOLLOWS_ALL); + } + mNoItemsCommentTextbox = LLUICtrlFactory::create(text_p, this); + } +}; + +LLFlatListView::~LLFlatListView() +{ + for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + mItemsPanel->removeChild((*it)->first); + (*it)->first->die(); + delete *it; + } + mItemPairs.clear(); +} + +// virtual +void LLFlatListView::draw() +{ + // Highlight border if a child of this container has keyboard focus + if( mSelectedItemsBorder->getVisible() ) + { + mSelectedItemsBorder->setKeyboardFocusHighlight( hasFocus() ); + } + LLScrollContainer::draw(); +} + +// virtual +bool LLFlatListView::postBuild() +{ + setTabStop(true); + return LLScrollContainer::postBuild(); +} + +void LLFlatListView::rearrangeItems() +{ + static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); + + setNoItemsCommentVisible(0==size()); + + if (mItemPairs.empty()) return; + + //calculating required height - assuming items can be of different height + //list should accommodate all its items + S32 height = 0; + + S32 invisible_children_count = 0; + pairs_iterator_t it = mItemPairs.begin(); + for (; it != mItemPairs.end(); ++it) + { + LLPanel* item = (*it)->first; + + // skip invisible child + if (!item->getVisible()) + { + ++invisible_children_count; + continue; + } + + height += item->getRect().getHeight(); + } + + // add paddings between items, excluding invisible ones + height += mItemPad * (mItemPairs.size() - invisible_children_count - 1); + + LLRect rc = mItemsPanel->getRect(); + S32 width = mItemsNoScrollWidth; + + // update width to avoid horizontal scrollbar + if (height > getRect().getHeight() - 2 * mBorderThickness) + width -= scrollbar_size; + + //changes the bottom, end of the list goes down in the scroll container + rc.setLeftTopAndSize(rc.mLeft, rc.mTop, width, height); + mItemsPanel->setRect(rc); + + //reshaping items + S32 item_new_top = height; + pairs_iterator_t it2, first_it = mItemPairs.begin(); + for (it2 = first_it; it2 != mItemPairs.end(); ++it2) + { + LLPanel* item = (*it2)->first; + + // skip invisible child + if (!item->getVisible()) + continue; + + LLRect rc = item->getRect(); + rc.setLeftTopAndSize(rc.mLeft, item_new_top, width, rc.getHeight()); + item->reshape(rc.getWidth(), rc.getHeight()); + item->setRect(rc); + + // move top for next item in list + item_new_top -= (rc.getHeight() + mItemPad); + } + + // Stretch selected item rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); +} + +void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) +{ + if (!item_pair) return; + + if (!item_pair->first) + { + LL_WARNS() << "Attempt to selet an item pair containing null panel item" << LL_ENDL; + return; + } + + setFocus(true); + + bool select_item = !isSelected(item_pair); + + //*TODO find a better place for that enforcing stuff + if (mKeepOneItemSelected && numSelected() == 1 && !select_item) return; + + if ( (mask & MASK_SHIFT) && !(mask & MASK_CONTROL) + && mMultipleSelection && !mSelectedItemPairs.empty() ) + { + item_pair_t* last_selected_pair = mSelectedItemPairs.back(); + + // If item_pair is already selected - do nothing + if (last_selected_pair == item_pair) + return; + + bool grab_items = false; + bool reverse = false; + pairs_list_t pairs_to_select; + + // Pick out items from list between last selected and current clicked item_pair. + for (pairs_iterator_t + iter = mItemPairs.begin(), + iter_end = mItemPairs.end(); + iter != iter_end; ++iter) + { + item_pair_t* cur = *iter; + if (cur == last_selected_pair || cur == item_pair) + { + // We've got reverse selection if last grabed item isn't a new selection. + reverse = grab_items && (cur != item_pair); + grab_items = !grab_items; + // Skip last selected and current clicked item pairs. + continue; + } + if (!cur->first->getVisible()) + { + // Skip invisible item pairs. + continue; + } + if (grab_items) + { + pairs_to_select.push_back(cur); + } + } + + if (reverse) + { + pairs_to_select.reverse(); + } + + pairs_to_select.push_back(item_pair); + + for (pairs_iterator_t + iter = pairs_to_select.begin(), + iter_end = pairs_to_select.end(); + iter != iter_end; ++iter) + { + item_pair_t* pair_to_select = *iter; + if (isSelected(pair_to_select)) + { + // Item was already selected but there is a need to keep order from last selected pair to new selection. + // Do it here to prevent extra mCommitOnSelectionChange in selectItemPair(). + mSelectedItemPairs.remove(pair_to_select); + mSelectedItemPairs.push_back(pair_to_select); + } + else + { + selectItemPair(pair_to_select, true); + } + } + + if (!select_item) + { + // Update last selected item border. + mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); + } + return; + } + + //no need to do additional commit on selection reset + if (!(mask & MASK_CONTROL) || !mMultipleSelection) resetSelection(true); + + //only CTRL usage allows to deselect an item, usual clicking on an item cannot deselect it + if (mask & MASK_CONTROL) + selectItemPair(item_pair, select_item); + else + selectItemPair(item_pair, true); +} + +void LLFlatListView::onItemRightMouseClick(item_pair_t* item_pair, MASK mask) +{ + if (!item_pair) + return; + + // Forbid deselecting of items on right mouse button click if mMultipleSelection flag is set on, + // because some of derived classes may have context menu and selected items must be kept. + if ( !(mask & MASK_CONTROL) && mMultipleSelection && isSelected(item_pair) ) + return; + + // else got same behavior as at onItemMouseClick + onItemMouseClick(item_pair, mask); +} + +bool LLFlatListView::handleKeyHere(KEY key, MASK mask) +{ + bool reset_selection = (mask != MASK_SHIFT); + bool handled = false; + switch (key) + { + case KEY_RETURN: + { + if (mSelectedItemPairs.size() && mask == MASK_NONE) + { + mOnReturnSignal(this, getValue()); + handled = true; + } + break; + } + case KEY_UP: + { + if ( !selectNextItemPair(true, reset_selection) && reset_selection) + { + // If case we are in accordion tab notify parent to go to the previous accordion + if(notifyParent(LLSD().with("action","select_prev")) > 0 )//message was processed + resetSelection(); + } + break; + } + case KEY_DOWN: + { + if ( !selectNextItemPair(false, reset_selection) && reset_selection) + { + // If case we are in accordion tab notify parent to go to the next accordion + if( notifyParent(LLSD().with("action","select_next")) > 0 ) //message was processed + resetSelection(); + } + break; + } + case KEY_ESCAPE: + { + if (mask == MASK_NONE) + { + setFocus(false); // pass focus to the game area (EXT-8357) + } + break; + } + default: + break; + } + + if ( ( key == KEY_UP || key == KEY_DOWN ) && mSelectedItemPairs.size() ) + { + ensureSelectedVisible(); + /* + LLRect visible_rc = getVisibleContentRect(); + LLRect selected_rc = getLastSelectedItemRect(); + + if ( !visible_rc.contains (selected_rc) ) + { + // But scroll in Items panel coordinates + scrollToShowRect(selected_rc); + } + + // In case we are in accordion tab notify parent to show selected rectangle + LLRect screen_rc; + localRectToScreen(selected_rc, &screen_rc); + notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue()));*/ + + handled = true; + } + + return handled ? handled : LLScrollContainer::handleKeyHere(key, mask); +} + +LLFlatListView::item_pair_t* LLFlatListView::getItemPair(LLPanel* item) const +{ + llassert(item); + + for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + item_pair_t* item_pair = *it; + if (item_pair->first == item) return item_pair; + } + return NULL; +} + +//compares two LLSD's +bool llsds_are_equal(const LLSD& llsd_1, const LLSD& llsd_2) +{ + llassert(llsd_1.isDefined()); + llassert(llsd_2.isDefined()); + + if (llsd_1.type() != llsd_2.type()) return false; + + if (!llsd_1.isMap()) + { + if (llsd_1.isUUID()) return llsd_1.asUUID() == llsd_2.asUUID(); + + //assumptions that string representaion is enough for other types + return llsd_1.asString() == llsd_2.asString(); + } + + if (llsd_1.size() != llsd_2.size()) return false; + + LLSD::map_const_iterator llsd_1_it = llsd_1.beginMap(); + LLSD::map_const_iterator llsd_2_it = llsd_2.beginMap(); + for (S32 i = 0; i < llsd_1.size(); ++i) + { + if ((*llsd_1_it).first != (*llsd_2_it).first) return false; + if (!llsds_are_equal((*llsd_1_it).second, (*llsd_2_it).second)) return false; + ++llsd_1_it; + ++llsd_2_it; + } + return true; +} + +LLFlatListView::item_pair_t* LLFlatListView::getItemPair(const LLSD& value) const +{ + llassert(value.isDefined()); + + for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + item_pair_t* item_pair = *it; + if (llsds_are_equal(item_pair->second, value)) return item_pair; + } + return NULL; +} + +bool LLFlatListView::selectItemPair(item_pair_t* item_pair, bool select) +{ + llassert(item_pair); + + if (!mAllowSelection && select) return false; + + if (isSelected(item_pair) == select) return true; //already in specified selection state + if (select) + { + mSelectedItemPairs.push_back(item_pair); + } + else + { + mSelectedItemPairs.remove(item_pair); + } + + //a way of notifying panel of selection state changes + LLPanel* item = item_pair->first; + item->setValue(select ? SELECTED_EVENT : UNSELECTED_EVENT); + + if (mCommitOnSelectionChange) + { + onCommit(); + } + + // Stretch selected item rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); + // By default mark it as not consecutive selection + mIsConsecutiveSelection = false; + + return true; +} + +void LLFlatListView::scrollToShowFirstSelectedItem() +{ + if (!mSelectedItemPairs.size()) return; + + LLRect selected_rc = mSelectedItemPairs.front()->first->getRect(); + + if (selected_rc.isValid()) + { + scrollToShowRect(selected_rc); + } +} + +LLRect LLFlatListView::getLastSelectedItemRect() +{ + if (!mSelectedItemPairs.size()) + { + return LLRect::null; + } + + return mSelectedItemPairs.back()->first->getRect(); +} + +void LLFlatListView::selectFirstItem () +{ + // No items - no actions! + if (0 == size()) return; + + // Select first visible item + for (pairs_iterator_t + iter = mItemPairs.begin(), + iter_end = mItemPairs.end(); + iter != iter_end; ++iter) + { + // skip invisible items + if ( (*iter)->first->getVisible() ) + { + selectItemPair(*iter, true); + ensureSelectedVisible(); + break; + } + } +} + +void LLFlatListView::selectLastItem () +{ + // No items - no actions! + if (0 == size()) return; + + // Select last visible item + for (pairs_list_t::reverse_iterator + r_iter = mItemPairs.rbegin(), + r_iter_end = mItemPairs.rend(); + r_iter != r_iter_end; ++r_iter) + { + // skip invisible items + if ( (*r_iter)->first->getVisible() ) + { + selectItemPair(*r_iter, true); + ensureSelectedVisible(); + break; + } + } +} + +void LLFlatListView::ensureSelectedVisible() +{ + LLRect selected_rc = getLastSelectedItemRect(); + + if ( selected_rc.isValid() ) + { + scrollToShowRect(selected_rc); + } +} + + +// virtual +bool LLFlatListView::selectNextItemPair(bool is_up_direction, bool reset_selection) +{ + // No items - no actions! + if ( 0 == size() ) + return false; + + if (!mIsConsecutiveSelection) + { + // Leave only one item selected if list has not consecutive selection + if (mSelectedItemPairs.size() && !reset_selection) + { + item_pair_t* cur_sel_pair = mSelectedItemPairs.back(); + resetSelection(); + selectItemPair (cur_sel_pair, true); + } + } + + if ( mSelectedItemPairs.size() ) + { + item_pair_t* to_sel_pair = NULL; + item_pair_t* cur_sel_pair = NULL; + + // Take the last selected pair + cur_sel_pair = mSelectedItemPairs.back(); + // Bases on given direction choose next item to select + if ( is_up_direction ) + { + // Find current selected item position in mItemPairs list + pairs_list_t::reverse_iterator sel_it = std::find(mItemPairs.rbegin(), mItemPairs.rend(), cur_sel_pair); + + for (;++sel_it != mItemPairs.rend();) + { + // skip invisible items + if ( (*sel_it)->first->getVisible() ) + { + to_sel_pair = *sel_it; + break; + } + } + } + else + { + // Find current selected item position in mItemPairs list + pairs_list_t::iterator sel_it = std::find(mItemPairs.begin(), mItemPairs.end(), cur_sel_pair); + + for (;++sel_it != mItemPairs.end();) + { + // skip invisible items + if ( (*sel_it)->first->getVisible() ) + { + to_sel_pair = *sel_it; + break; + } + } + } + + if ( to_sel_pair ) + { + bool select = true; + if ( reset_selection ) + { + // Reset current selection if we were asked about it + resetSelection(); + } + else + { + // If item already selected and no reset request than we should deselect last selected item. + select = (mSelectedItemPairs.end() == std::find(mSelectedItemPairs.begin(), mSelectedItemPairs.end(), to_sel_pair)); + } + // Select/Deselect next item + selectItemPair(select ? to_sel_pair : cur_sel_pair, select); + // Mark it as consecutive selection + mIsConsecutiveSelection = true; + return true; + } + } + else + { + // If there weren't selected items then choose the first one bases on given direction + // Force selection to first item + if (is_up_direction) + selectLastItem(); + else + selectFirstItem(); + // Mark it as consecutive selection + mIsConsecutiveSelection = true; + return true; + } + + return false; +} + +bool LLFlatListView::canSelectAll() const +{ + return 0 != size() && mAllowSelection && mMultipleSelection; +} + +void LLFlatListView::selectAll() +{ + if (!mAllowSelection || !mMultipleSelection) + return; + + mSelectedItemPairs.clear(); + + for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + item_pair_t* item_pair = *it; + mSelectedItemPairs.push_back(item_pair); + //a way of notifying panel of selection state changes + LLPanel* item = item_pair->first; + item->setValue(SELECTED_EVENT); + } + + if (mCommitOnSelectionChange) + { + onCommit(); + } + + // Stretch selected item rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); +} + +bool LLFlatListView::isSelected(item_pair_t* item_pair) const +{ + llassert(item_pair); + + pairs_const_iterator_t it_end = mSelectedItemPairs.end(); + return std::find(mSelectedItemPairs.begin(), it_end, item_pair) != it_end; +} + +bool LLFlatListView::removeItemPair(item_pair_t* item_pair, bool rearrange) +{ + llassert(item_pair); + + bool deleted = false; + bool selection_changed = false; + for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + item_pair_t* _item_pair = *it; + if (_item_pair == item_pair) + { + mItemPairs.erase(it); + deleted = true; + break; + } + } + + if (!deleted) return false; + + for (pairs_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) + { + item_pair_t* selected_item_pair = *it; + if (selected_item_pair == item_pair) + { + it = mSelectedItemPairs.erase(it); + selection_changed = true; + break; + } + } + + mItemsPanel->removeChild(item_pair->first); + item_pair->first->die(); + delete item_pair; + + if (rearrange) + { + rearrangeItems(); + notifyParentItemsRectChanged(); + } + + if (selection_changed && mCommitOnSelectionChange) + { + onCommit(); + } + + return true; +} + +void LLFlatListView::notifyParentItemsRectChanged() +{ + S32 comment_height = 0; + + // take into account comment text height if exists + if (mNoItemsCommentTextbox && mNoItemsCommentTextbox->getVisible()) + { + // top text padding inside the textbox is included into the height + comment_height = mNoItemsCommentTextbox->getTextPixelHeight(); + + // take into account a distance from parent's top border to textbox's top + comment_height += getRect().getHeight() - mNoItemsCommentTextbox->getRect().mTop; + } + + LLRect req_rect = getItemsRect(); + + // get maximum of items total height and comment text height + req_rect.setOriginAndSize(req_rect.mLeft, req_rect.mBottom, req_rect.getWidth(), llmax(req_rect.getHeight(), comment_height)); + + // take into account border size. + req_rect.stretch(getBorderWidth()); + + if (req_rect == mPrevNotifyParentRect) + return; + + mPrevNotifyParentRect = req_rect; + + LLSD params; + params["action"] = "size_changes"; + params["width"] = req_rect.getWidth(); + params["height"] = req_rect.getHeight(); + + if (getParent()) // dummy widgets don't have a parent + getParent()->notifyParent(params); +} + +void LLFlatListView::setNoItemsCommentVisible(bool visible) const +{ + if (mNoItemsCommentTextbox) + { + mSelectedItemsBorder->setVisible(!visible); + mNoItemsCommentTextbox->setVisible(visible); + } +} + +void LLFlatListView::getItems(std::vector& items) const +{ + if (mItemPairs.empty()) return; + + items.clear(); + for (pairs_const_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + items.push_back((*it)->first); + } +} + +void LLFlatListView::getValues(std::vector& values) const +{ + if (mItemPairs.empty()) return; + + values.clear(); + for (pairs_const_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + values.push_back((*it)->second); + } +} + +// virtual +void LLFlatListView::onFocusReceived() +{ + if (size()) + { + mSelectedItemsBorder->setVisible(true); + } + gEditMenuHandler = this; +} +// virtual +void LLFlatListView::onFocusLost() +{ + mSelectedItemsBorder->setVisible(false); + // Route menu back to the default + if (gEditMenuHandler == this) + { + gEditMenuHandler = NULL; + } +} + +//virtual +S32 LLFlatListView::notify(const LLSD& info) +{ + if (info.has("action")) + { + std::string str_action = info["action"]; + if (str_action == "select_first") + { + setFocus(true); + selectFirstItem(); + return 1; + } + else if (str_action == "select_last") + { + setFocus(true); + selectLastItem(); + return 1; + } + } + else if (info.has("rearrange")) + { + rearrangeItems(); + notifyParentItemsRectChanged(); + return 1; + } + + return 0; +} + +void LLFlatListView::detachItems(std::vector& detached_items) +{ + LLSD action; + action.with("detach", LLSD()); + // Clear detached_items list + detached_items.clear(); + // Go through items and detach valid items, remove them from items panel + // and add to detached_items. + pairs_iterator_t iter = mItemPairs.begin(), iter_end = mItemPairs.end(); + while (iter != iter_end) + { + LLPanel* pItem = (*iter)->first; + if (1 == pItem->notify(action)) + { + selectItemPair((*iter), false); + mItemsPanel->removeChild(pItem); + detached_items.push_back(pItem); + } + iter++; + } + if (!detached_items.empty()) + { + // Some items were detached, clean ourself from unusable memory + if (detached_items.size() == mItemPairs.size()) + { + // This way will be faster if all items were disconnected + pairs_iterator_t iter = mItemPairs.begin(), iter_end = mItemPairs.end(); + while (iter != iter_end) + { + (*iter)->first = NULL; + delete *iter; + iter++; + } + mItemPairs.clear(); + // Also set items panel height to zero. + // Reshape it to allow reshaping of non-item children. + LLRect rc = mItemsPanel->getRect(); + rc.mBottom = rc.mTop; + mItemsPanel->reshape(rc.getWidth(), rc.getHeight()); + mItemsPanel->setRect(rc); + setNoItemsCommentVisible(true); + } + else + { + std::vector::const_iterator + detached_iter = detached_items.begin(), + detached_iter_end = detached_items.end(); + while (detached_iter < detached_iter_end) + { + LLPanel* pDetachedItem = *detached_iter; + pairs_iterator_t iter = mItemPairs.begin(), iter_end = mItemPairs.end(); + while (iter != iter_end) + { + item_pair_t* item_pair = *iter; + if (item_pair->first == pDetachedItem) + { + mItemPairs.erase(iter); + item_pair->first = NULL; + delete item_pair; + break; + } + iter++; + } + detached_iter++; + } + rearrangeItems(); + } + notifyParentItemsRectChanged(); + } +} + + +/************************************************************************/ +/* LLFlatListViewEx implementation */ +/************************************************************************/ +LLFlatListViewEx::Params::Params() +: no_items_msg("no_items_msg") +, no_filtered_items_msg("no_filtered_items_msg") +{ +} + +LLFlatListViewEx::LLFlatListViewEx(const Params& p) +: LLFlatListView(p) +, mNoFilteredItemsMsg(p.no_filtered_items_msg) +, mNoItemsMsg(p.no_items_msg) +, mForceShowingUnmatchedItems(false) +, mHasMatchedItems(false) +{ +} + +void LLFlatListViewEx::updateNoItemsMessage(const std::string& filter_string) +{ + bool items_filtered = !filter_string.empty(); + if (items_filtered) + { + // items were filtered + LLStringUtil::format_map_t args; + args["[SEARCH_TERM]"] = LLURI::escape(filter_string); + std::string text = mNoFilteredItemsMsg; + LLStringUtil::format(text, args); + setNoItemsCommentText(text); + } + else + { + // list does not contain any items at all + setNoItemsCommentText(mNoItemsMsg); + } +} + +bool LLFlatListViewEx::getForceShowingUnmatchedItems() +{ + return mForceShowingUnmatchedItems; +} + +void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show) +{ + mForceShowingUnmatchedItems = show; +} + +void LLFlatListViewEx::setFilterSubString(const std::string& filter_str, bool notify_parent) +{ + if (0 != LLStringUtil::compareInsensitive(filter_str, mFilterSubString)) + { + mFilterSubString = filter_str; + updateNoItemsMessage(mFilterSubString); + filterItems(false, notify_parent); + } +} + +bool LLFlatListViewEx::updateItemVisibility(LLPanel* item, const LLSD &action) +{ + if (!item) + return false; + + bool visible = true; + + // 0 signifies that filter is matched, + // i.e. we don't hide items that don't support 'match_filter' action, separators etc. + if (0 == item->notify(action)) + { + mHasMatchedItems = true; + } + else + { + // TODO: implement (re)storing of current selection. + if (!mForceShowingUnmatchedItems) + { + selectItem(item, false); + visible = false; + } + } + + if (item->getVisible() != visible) + { + item->setVisible(visible); + return true; + } + + return false; +} + +void LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent) +{ + std::string cur_filter = mFilterSubString; + LLStringUtil::toUpper(cur_filter); + + LLSD action; + action.with("match_filter", cur_filter); + + mHasMatchedItems = false; + bool visibility_changed = false; + pairs_const_iterator_t iter = getItemPairs().begin(), iter_end = getItemPairs().end(); + while (iter != iter_end) + { + LLPanel* pItem = (*(iter++))->first; + visibility_changed |= updateItemVisibility(pItem, action); + } + + if (re_sort) + { + sort(); + } + + if (visibility_changed && notify_parent) + { + notifyParentItemsRectChanged(); + } +} + +bool LLFlatListViewEx::hasMatchedItems() +{ + return mHasMatchedItems; +} + +//EOF diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index c24fd34ae6..b64a9862a2 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -1,535 +1,535 @@ -/** - * @file llflatlistview.h - * @brief LLFlatListView base class and extension to support messages for several cases of an empty list. - * - * $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$ - */ - -#ifndef LL_LLFLATLISTVIEW_H -#define LL_LLFLATLISTVIEW_H - -#include "llpanel.h" -#include "llscrollcontainer.h" -#include "lltextbox.h" - - -/** - * LLFlatListView represents a flat list ui control that operates on items in a form of LLPanel's. - * LLSD can be associated with each added item, it can keep data from an item in digested form. - * Associated LLSD's can be of any type (singular, a map etc.). - * Items (LLPanel's subclasses) can be of different height. - * The list is LLPanel created in itself and grows in height while new items are added. - * - * The control can manage selection of its items when the flag "allow_select" is set. Also ability to select - * multiple items (by using CTRL) is enabled through setting the flag "multi_select" - if selection is not allowed that flag - * is ignored. The option "keep_one_selected" forces at least one item to be selected at any time (only for mouse events on items) - * since any item of the list was selected. - * - * Examples of using this control are presented in Picks panel (My Profile and Profile View), where this control is used to - * manage the list of pick items. - * - * ASSUMPTIONS AND STUFF - * - NULL pointers and undefined LLSD's are not accepted by any method of this class unless specified otherwise - * - Order of returned selected items are not guaranteed - * - The control assumes that all items being added are unique. - */ -class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler -{ - LOG_CLASS(LLFlatListView); -public: - - /** - * Abstract comparator for comparing flat list items in a form of LLPanel - */ - class ItemComparator - { - public: - ItemComparator() {}; - virtual ~ItemComparator() {}; - - /** Returns true if item1 < item2, false otherwise */ - virtual bool compare(const LLPanel* item1, const LLPanel* item2) const = 0; - }; - - /** - * Represents reverse comparator which acts as a decorator for a comparator that need to be reversed - */ - class ItemReverseComparator : public ItemComparator - { - public: - ItemReverseComparator(const ItemComparator& comparator) : mComparator(comparator) {}; - virtual ~ItemReverseComparator() {}; - - virtual bool compare(const LLPanel* item1, const LLPanel* item2) const - { - return mComparator.compare(item2, item1); - } - - private: - const ItemComparator& mComparator; - }; - - - struct Params : public LLInitParam::Block - { - /** turning on/off selection support */ - Optional allow_select; - - /** turning on/off multiple selection (works while clicking and holding CTRL)*/ - Optional multi_select; - - /** don't allow to deselect all selected items (for mouse events on items only) */ - Optional keep_one_selected; - - /** try to keep selection visible after reshape */ - Optional keep_selection_visible_on_reshape; - - /** padding between items */ - Optional item_pad; - - /** textbox with info message when list is empty*/ - Optional no_items_text; - - Params(); - }; - - // disable traversal when finding widget to hand focus off to - /*virtual*/ bool canFocusChildren() const { return false; } - - /** - * Connects callback to signal called when Return key is pressed. - */ - boost::signals2::connection setReturnCallback( const commit_signal_t::slot_type& cb ) { return mOnReturnSignal.connect(cb); } - - /** Overridden LLPanel's reshape, height is ignored, the list sets its height to accommodate all items */ - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - - /** Returns full rect of child panel */ - const LLRect& getItemsRect() const; - - LLRect getRequiredRect() { return getItemsRect(); } - - /** Returns distance between items */ - const S32 getItemsPad() { return mItemPad; } - - /** - * Adds and item and LLSD value associated with it to the list at specified position - * @return true if the item was added, false otherwise - */ - virtual bool addItem(LLPanel * item, const LLSD& value = LLUUID::null, EAddPosition pos = ADD_BOTTOM, bool rearrange = true); - - /** - * Insert item_to_add along with associated value to the list right after the after_item. - * @return true if the item was successfully added, false otherwise - */ - virtual bool insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, const LLSD& value = LLUUID::null); - - /** - * Remove specified item - * @return true if the item was removed, false otherwise - */ - virtual bool removeItem(LLPanel* item, bool rearrange = true); - - /** - * Remove an item specified by value - * @return true if the item was removed, false otherwise - */ - virtual bool removeItemByValue(const LLSD& value, bool rearrange = true); - - /** - * Remove an item specified by uuid - * @return true if the item was removed, false otherwise - */ - virtual bool removeItemByUUID(const LLUUID& uuid, bool rearrange = true); - - /** - * Get an item by value - * @return the item as LLPanel if associated with value, NULL otherwise - */ - virtual LLPanel* getItemByValue(const LLSD& value) const; - - template - T* getTypedItemByValue(const LLSD& value) const - { - return dynamic_cast(getItemByValue(value)); - } - - /** - * Select or deselect specified item based on select - * @return true if succeed, false otherwise - */ - virtual bool selectItem(LLPanel* item, bool select = true); - - /** - * Select or deselect an item by associated value based on select - * @return true if succeed, false otherwise - */ - virtual bool selectItemByValue(const LLSD& value, bool select = true); - - /** - * Select or deselect an item by associated uuid based on select - * @return true if succeed, false otherwise - */ - virtual bool selectItemByUUID(const LLUUID& uuid, bool select = true); - - /** - * Get all panels stored in the list. - */ - virtual void getItems(std::vector& items) const; - - /** - * Get all items values. - */ - virtual void getValues(std::vector& values) const; - - /** - * Get LLSD associated with the first selected item - */ - virtual LLSD getSelectedValue() const; - - /** - * Get LLSD's associated with selected items. - * @param selected_values std::vector being populated with LLSD associated with selected items - */ - virtual void getSelectedValues(std::vector& selected_values) const; - - - /** - * Get LLUUID associated with selected item - * @return LLUUID if such was associated with selected item - */ - virtual LLUUID getSelectedUUID() const; - - /** - * Get LLUUIDs associated with selected items - * @param selected_uuids An std::vector being populated with LLUUIDs associated with selected items - */ - virtual void getSelectedUUIDs(uuid_vec_t& selected_uuids) const; - - /** Get the top selected item */ - virtual LLPanel* getSelectedItem() const; - - /** - * Get selected items - * @param selected_items An std::vector being populated with pointers to selected items - */ - virtual void getSelectedItems(std::vector& selected_items) const; - - - /** - * Resets selection of items. - * - * It calls onCommit callback if setCommitOnSelectionChange(bool b) was called with "true" - * argument for current Flat List. - * @param no_commit_on_deselection - if true onCommit callback will not be called - */ - virtual void resetSelection(bool no_commit_on_deselection = false); - - /** - * Sets comment text which will be shown in the list is it is empty. - * - * Textbox to hold passed text is created while this method is called at the first time. - * - * @param comment_text - string to be shown as a comment. - */ - void setNoItemsCommentText( const std::string& comment_text); - - /** Turn on/off multiple selection support */ - void setAllowMultipleSelection(bool allow) { mMultipleSelection = allow; } - - /** Turn on/off selection support */ - void setAllowSelection(bool can_select) { mAllowSelection = can_select; } - - /** Sets flag whether onCommit should be fired if selection was changed */ - // FIXME: this should really be a separate signal, since "Commit" implies explicit user action, and selection changes can happen more indirectly. - void setCommitOnSelectionChange(bool b) { mCommitOnSelectionChange = b; } - - /** Get number of selected items in the list */ - U32 numSelected() const {return mSelectedItemPairs.size(); } - - /** Get number of (visible) items in the list */ - U32 size(const bool only_visible_items = true) const; - - /** Removes all items from the list */ - virtual void clear(); - - /** - * Removes all items that can be detached from the list but doesn't destroy - * them, caller responsible to manage items after they are detached. - * Detachable item should accept "detach" action via notify() method, - * where it disconnect all callbacks, does other valuable routines and - * return 1. - */ - void detachItems(std::vector& detached_items); - - /** - * Set comparator to use for future sorts. - * - * This class does NOT manage lifetime of the comparator - * but assumes that the comparator is always alive. - */ - void setComparator(const ItemComparator* comp) { mItemComparator = comp; } - void sort(); - - bool updateValue(const LLSD& old_value, const LLSD& new_value); - - void scrollToShowFirstSelectedItem(); - - void selectFirstItem (); - void selectLastItem (); - - virtual S32 notify(const LLSD& info) ; - - virtual ~LLFlatListView(); - -protected: - - /** Pairs LLpanel representing a single item LLPanel and LLSD associated with it */ - typedef std::pair item_pair_t; - - typedef std::list pairs_list_t; - typedef pairs_list_t::iterator pairs_iterator_t; - typedef pairs_list_t::const_iterator pairs_const_iterator_t; - - /** An adapter for a ItemComparator */ - struct ComparatorAdaptor - { - ComparatorAdaptor(const ItemComparator& comparator) : mComparator(comparator) {}; - - bool operator()(const item_pair_t* item_pair1, const item_pair_t* item_pair2) - { - return mComparator.compare(item_pair1->first, item_pair2->first); - } - - const ItemComparator& mComparator; - }; - - - friend class LLUICtrlFactory; - LLFlatListView(const LLFlatListView::Params& p); - - /** Manage selection on mouse events */ - void onItemMouseClick(item_pair_t* item_pair, MASK mask); - - void onItemRightMouseClick(item_pair_t* item_pair, MASK mask); - - /** - * Updates position of items. - * It does not take into account invisible items. - */ - virtual void rearrangeItems(); - - virtual item_pair_t* getItemPair(LLPanel* item) const; - - virtual item_pair_t* getItemPair(const LLSD& value) const; - - virtual bool selectItemPair(item_pair_t* item_pair, bool select); - - virtual bool selectNextItemPair(bool is_up_direction, bool reset_selection); - - virtual bool canSelectAll() const; - virtual void selectAll(); - - virtual bool isSelected(item_pair_t* item_pair) const; - - virtual bool removeItemPair(item_pair_t* item_pair, bool rearrange); - - bool addItemPairs(pairs_list_t panel_list, bool rearrange = true); - - /** - * Notify parent about changed size of internal controls with "size_changes" action - * - * Size includes Items Rect width and either Items Rect height or comment text height. - * Comment text height is included if comment text is set and visible. - * List border size is also included into notified size. - */ - void notifyParentItemsRectChanged(); - - virtual bool handleKeyHere(KEY key, MASK mask); - - virtual bool postBuild(); - - virtual void onFocusReceived(); - - virtual void onFocusLost(); - - virtual void draw(); - - LLRect getLastSelectedItemRect(); - - void ensureSelectedVisible(); - - const pairs_list_t& getItemPairs() { return mItemPairs; } - -private: - - void setItemsNoScrollWidth(S32 new_width) {mItemsNoScrollWidth = new_width - 2 * mBorderThickness;} - - void setNoItemsCommentVisible(bool visible) const; - -protected: - - /** Comparator to use when sorting the list. */ - const ItemComparator* mItemComparator; - - -private: - - LLPanel* mItemsPanel; - - S32 mItemsNoScrollWidth; - - S32 mBorderThickness; - - /** Items padding */ - S32 mItemPad; - - /** Selection support flag */ - bool mAllowSelection; - - /** Multiselection support flag, ignored if selection is not supported */ - bool mMultipleSelection; - - /** - * Flag specified whether onCommit be called if selection is changed in the list. - * - * Can be ignored in the resetSelection() method. - * @see resetSelection() - */ - bool mCommitOnSelectionChange; - - bool mKeepOneItemSelected; - - bool mIsConsecutiveSelection; - - bool mKeepSelectionVisibleOnReshape; - - /** All pairs of the list */ - pairs_list_t mItemPairs; - - /** Selected pairs for faster access */ - pairs_list_t mSelectedItemPairs; - - /** - * Rectangle contained previous size of items parent notified last time. - * Is used to reduce amount of parentNotify() calls if size was not changed. - */ - LLRect mPrevNotifyParentRect; - - LLTextBox* mNoItemsCommentTextbox; - - LLViewBorder* mSelectedItemsBorder; - - commit_signal_t mOnReturnSignal; -}; - -/** - * Extends LLFlatListView functionality to show different messages when there are no items in the - * list depend on whether they are filtered or not. - * - * Class provides one message per case of empty list. - * It also provides protected updateNoItemsMessage() method to be called each time when derived list - * is changed to update base mNoItemsCommentTextbox value. - * - * It is implemented to avoid duplication of this functionality in concrete implementations of the - * lists. It is intended to be used as a base class for lists which should support two different - * messages for empty state. Can be improved to support more than two messages via state-to-message map. - */ -class LLFlatListViewEx : public LLFlatListView -{ -public: - LOG_CLASS(LLFlatListViewEx); - - struct Params : public LLInitParam::Block - { - /** - * Contains a message for empty list when it does not contain any items at all. - */ - Optional no_items_msg; - - /** - * Contains a message for empty list when its items are removed by filtering. - */ - Optional no_filtered_items_msg; - Params(); - }; - - // *WORKAROUND: two methods to overload appropriate Params due to localization issue: - // no_items_msg & no_filtered_items_msg attributes are not defined as translatable in VLT. See EXT-5931 - void setNoItemsMsg(const std::string& msg) { mNoItemsMsg = msg; } - void setNoFilteredItemsMsg(const std::string& msg) { mNoFilteredItemsMsg = msg; } - - bool getForceShowingUnmatchedItems(); - - void setForceShowingUnmatchedItems(bool show); - - /** - * Sets up new filter string and filters the list. - */ - void setFilterSubString(const std::string& filter_str, bool notify_parent); - std::string getFilterSubString() { return mFilterSubString; } - - /** - * Filters the list, rearranges and notifies parent about shape changes. - * Derived classes may want to overload rearrangeItems() to exclude repeated separators after filtration. - */ - void filterItems(bool re_sort, bool notify_parent); - - /** - * Returns true if last call of filterItems() found at least one matching item - */ - bool hasMatchedItems(); - -protected: - LLFlatListViewEx(const Params& p); - - /** - * Applies a message for empty list depend on passed argument. - * - * @param filter_string - if is not empty, message for filtered items will be set, otherwise for - * completely empty list. Value of filter string will be passed as search_term in SLURL. - */ - void updateNoItemsMessage(const std::string& filter_string); - - /** - * Applies visibility acording to action and LLFlatListView settings. - * - * @param item - item we are changing - * @param item - action - parameters to determin visibility from - */ - bool updateItemVisibility(LLPanel* item, const LLSD &action); - -private: - std::string mNoFilteredItemsMsg; - std::string mNoItemsMsg; - std::string mFilterSubString; - /** - * Show list items that don't match current filter - */ - bool mForceShowingUnmatchedItems; - /** - * True if last call of filterItems() found at least one matching item - */ - bool mHasMatchedItems; -}; - -#endif +/** + * @file llflatlistview.h + * @brief LLFlatListView base class and extension to support messages for several cases of an empty list. + * + * $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$ + */ + +#ifndef LL_LLFLATLISTVIEW_H +#define LL_LLFLATLISTVIEW_H + +#include "llpanel.h" +#include "llscrollcontainer.h" +#include "lltextbox.h" + + +/** + * LLFlatListView represents a flat list ui control that operates on items in a form of LLPanel's. + * LLSD can be associated with each added item, it can keep data from an item in digested form. + * Associated LLSD's can be of any type (singular, a map etc.). + * Items (LLPanel's subclasses) can be of different height. + * The list is LLPanel created in itself and grows in height while new items are added. + * + * The control can manage selection of its items when the flag "allow_select" is set. Also ability to select + * multiple items (by using CTRL) is enabled through setting the flag "multi_select" - if selection is not allowed that flag + * is ignored. The option "keep_one_selected" forces at least one item to be selected at any time (only for mouse events on items) + * since any item of the list was selected. + * + * Examples of using this control are presented in Picks panel (My Profile and Profile View), where this control is used to + * manage the list of pick items. + * + * ASSUMPTIONS AND STUFF + * - NULL pointers and undefined LLSD's are not accepted by any method of this class unless specified otherwise + * - Order of returned selected items are not guaranteed + * - The control assumes that all items being added are unique. + */ +class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler +{ + LOG_CLASS(LLFlatListView); +public: + + /** + * Abstract comparator for comparing flat list items in a form of LLPanel + */ + class ItemComparator + { + public: + ItemComparator() {}; + virtual ~ItemComparator() {}; + + /** Returns true if item1 < item2, false otherwise */ + virtual bool compare(const LLPanel* item1, const LLPanel* item2) const = 0; + }; + + /** + * Represents reverse comparator which acts as a decorator for a comparator that need to be reversed + */ + class ItemReverseComparator : public ItemComparator + { + public: + ItemReverseComparator(const ItemComparator& comparator) : mComparator(comparator) {}; + virtual ~ItemReverseComparator() {}; + + virtual bool compare(const LLPanel* item1, const LLPanel* item2) const + { + return mComparator.compare(item2, item1); + } + + private: + const ItemComparator& mComparator; + }; + + + struct Params : public LLInitParam::Block + { + /** turning on/off selection support */ + Optional allow_select; + + /** turning on/off multiple selection (works while clicking and holding CTRL)*/ + Optional multi_select; + + /** don't allow to deselect all selected items (for mouse events on items only) */ + Optional keep_one_selected; + + /** try to keep selection visible after reshape */ + Optional keep_selection_visible_on_reshape; + + /** padding between items */ + Optional item_pad; + + /** textbox with info message when list is empty*/ + Optional no_items_text; + + Params(); + }; + + // disable traversal when finding widget to hand focus off to + /*virtual*/ bool canFocusChildren() const { return false; } + + /** + * Connects callback to signal called when Return key is pressed. + */ + boost::signals2::connection setReturnCallback( const commit_signal_t::slot_type& cb ) { return mOnReturnSignal.connect(cb); } + + /** Overridden LLPanel's reshape, height is ignored, the list sets its height to accommodate all items */ + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + + /** Returns full rect of child panel */ + const LLRect& getItemsRect() const; + + LLRect getRequiredRect() { return getItemsRect(); } + + /** Returns distance between items */ + const S32 getItemsPad() { return mItemPad; } + + /** + * Adds and item and LLSD value associated with it to the list at specified position + * @return true if the item was added, false otherwise + */ + virtual bool addItem(LLPanel * item, const LLSD& value = LLUUID::null, EAddPosition pos = ADD_BOTTOM, bool rearrange = true); + + /** + * Insert item_to_add along with associated value to the list right after the after_item. + * @return true if the item was successfully added, false otherwise + */ + virtual bool insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, const LLSD& value = LLUUID::null); + + /** + * Remove specified item + * @return true if the item was removed, false otherwise + */ + virtual bool removeItem(LLPanel* item, bool rearrange = true); + + /** + * Remove an item specified by value + * @return true if the item was removed, false otherwise + */ + virtual bool removeItemByValue(const LLSD& value, bool rearrange = true); + + /** + * Remove an item specified by uuid + * @return true if the item was removed, false otherwise + */ + virtual bool removeItemByUUID(const LLUUID& uuid, bool rearrange = true); + + /** + * Get an item by value + * @return the item as LLPanel if associated with value, NULL otherwise + */ + virtual LLPanel* getItemByValue(const LLSD& value) const; + + template + T* getTypedItemByValue(const LLSD& value) const + { + return dynamic_cast(getItemByValue(value)); + } + + /** + * Select or deselect specified item based on select + * @return true if succeed, false otherwise + */ + virtual bool selectItem(LLPanel* item, bool select = true); + + /** + * Select or deselect an item by associated value based on select + * @return true if succeed, false otherwise + */ + virtual bool selectItemByValue(const LLSD& value, bool select = true); + + /** + * Select or deselect an item by associated uuid based on select + * @return true if succeed, false otherwise + */ + virtual bool selectItemByUUID(const LLUUID& uuid, bool select = true); + + /** + * Get all panels stored in the list. + */ + virtual void getItems(std::vector& items) const; + + /** + * Get all items values. + */ + virtual void getValues(std::vector& values) const; + + /** + * Get LLSD associated with the first selected item + */ + virtual LLSD getSelectedValue() const; + + /** + * Get LLSD's associated with selected items. + * @param selected_values std::vector being populated with LLSD associated with selected items + */ + virtual void getSelectedValues(std::vector& selected_values) const; + + + /** + * Get LLUUID associated with selected item + * @return LLUUID if such was associated with selected item + */ + virtual LLUUID getSelectedUUID() const; + + /** + * Get LLUUIDs associated with selected items + * @param selected_uuids An std::vector being populated with LLUUIDs associated with selected items + */ + virtual void getSelectedUUIDs(uuid_vec_t& selected_uuids) const; + + /** Get the top selected item */ + virtual LLPanel* getSelectedItem() const; + + /** + * Get selected items + * @param selected_items An std::vector being populated with pointers to selected items + */ + virtual void getSelectedItems(std::vector& selected_items) const; + + + /** + * Resets selection of items. + * + * It calls onCommit callback if setCommitOnSelectionChange(bool b) was called with "true" + * argument for current Flat List. + * @param no_commit_on_deselection - if true onCommit callback will not be called + */ + virtual void resetSelection(bool no_commit_on_deselection = false); + + /** + * Sets comment text which will be shown in the list is it is empty. + * + * Textbox to hold passed text is created while this method is called at the first time. + * + * @param comment_text - string to be shown as a comment. + */ + void setNoItemsCommentText( const std::string& comment_text); + + /** Turn on/off multiple selection support */ + void setAllowMultipleSelection(bool allow) { mMultipleSelection = allow; } + + /** Turn on/off selection support */ + void setAllowSelection(bool can_select) { mAllowSelection = can_select; } + + /** Sets flag whether onCommit should be fired if selection was changed */ + // FIXME: this should really be a separate signal, since "Commit" implies explicit user action, and selection changes can happen more indirectly. + void setCommitOnSelectionChange(bool b) { mCommitOnSelectionChange = b; } + + /** Get number of selected items in the list */ + U32 numSelected() const {return mSelectedItemPairs.size(); } + + /** Get number of (visible) items in the list */ + U32 size(const bool only_visible_items = true) const; + + /** Removes all items from the list */ + virtual void clear(); + + /** + * Removes all items that can be detached from the list but doesn't destroy + * them, caller responsible to manage items after they are detached. + * Detachable item should accept "detach" action via notify() method, + * where it disconnect all callbacks, does other valuable routines and + * return 1. + */ + void detachItems(std::vector& detached_items); + + /** + * Set comparator to use for future sorts. + * + * This class does NOT manage lifetime of the comparator + * but assumes that the comparator is always alive. + */ + void setComparator(const ItemComparator* comp) { mItemComparator = comp; } + void sort(); + + bool updateValue(const LLSD& old_value, const LLSD& new_value); + + void scrollToShowFirstSelectedItem(); + + void selectFirstItem (); + void selectLastItem (); + + virtual S32 notify(const LLSD& info) ; + + virtual ~LLFlatListView(); + +protected: + + /** Pairs LLpanel representing a single item LLPanel and LLSD associated with it */ + typedef std::pair item_pair_t; + + typedef std::list pairs_list_t; + typedef pairs_list_t::iterator pairs_iterator_t; + typedef pairs_list_t::const_iterator pairs_const_iterator_t; + + /** An adapter for a ItemComparator */ + struct ComparatorAdaptor + { + ComparatorAdaptor(const ItemComparator& comparator) : mComparator(comparator) {}; + + bool operator()(const item_pair_t* item_pair1, const item_pair_t* item_pair2) + { + return mComparator.compare(item_pair1->first, item_pair2->first); + } + + const ItemComparator& mComparator; + }; + + + friend class LLUICtrlFactory; + LLFlatListView(const LLFlatListView::Params& p); + + /** Manage selection on mouse events */ + void onItemMouseClick(item_pair_t* item_pair, MASK mask); + + void onItemRightMouseClick(item_pair_t* item_pair, MASK mask); + + /** + * Updates position of items. + * It does not take into account invisible items. + */ + virtual void rearrangeItems(); + + virtual item_pair_t* getItemPair(LLPanel* item) const; + + virtual item_pair_t* getItemPair(const LLSD& value) const; + + virtual bool selectItemPair(item_pair_t* item_pair, bool select); + + virtual bool selectNextItemPair(bool is_up_direction, bool reset_selection); + + virtual bool canSelectAll() const; + virtual void selectAll(); + + virtual bool isSelected(item_pair_t* item_pair) const; + + virtual bool removeItemPair(item_pair_t* item_pair, bool rearrange); + + bool addItemPairs(pairs_list_t panel_list, bool rearrange = true); + + /** + * Notify parent about changed size of internal controls with "size_changes" action + * + * Size includes Items Rect width and either Items Rect height or comment text height. + * Comment text height is included if comment text is set and visible. + * List border size is also included into notified size. + */ + void notifyParentItemsRectChanged(); + + virtual bool handleKeyHere(KEY key, MASK mask); + + virtual bool postBuild(); + + virtual void onFocusReceived(); + + virtual void onFocusLost(); + + virtual void draw(); + + LLRect getLastSelectedItemRect(); + + void ensureSelectedVisible(); + + const pairs_list_t& getItemPairs() { return mItemPairs; } + +private: + + void setItemsNoScrollWidth(S32 new_width) {mItemsNoScrollWidth = new_width - 2 * mBorderThickness;} + + void setNoItemsCommentVisible(bool visible) const; + +protected: + + /** Comparator to use when sorting the list. */ + const ItemComparator* mItemComparator; + + +private: + + LLPanel* mItemsPanel; + + S32 mItemsNoScrollWidth; + + S32 mBorderThickness; + + /** Items padding */ + S32 mItemPad; + + /** Selection support flag */ + bool mAllowSelection; + + /** Multiselection support flag, ignored if selection is not supported */ + bool mMultipleSelection; + + /** + * Flag specified whether onCommit be called if selection is changed in the list. + * + * Can be ignored in the resetSelection() method. + * @see resetSelection() + */ + bool mCommitOnSelectionChange; + + bool mKeepOneItemSelected; + + bool mIsConsecutiveSelection; + + bool mKeepSelectionVisibleOnReshape; + + /** All pairs of the list */ + pairs_list_t mItemPairs; + + /** Selected pairs for faster access */ + pairs_list_t mSelectedItemPairs; + + /** + * Rectangle contained previous size of items parent notified last time. + * Is used to reduce amount of parentNotify() calls if size was not changed. + */ + LLRect mPrevNotifyParentRect; + + LLTextBox* mNoItemsCommentTextbox; + + LLViewBorder* mSelectedItemsBorder; + + commit_signal_t mOnReturnSignal; +}; + +/** + * Extends LLFlatListView functionality to show different messages when there are no items in the + * list depend on whether they are filtered or not. + * + * Class provides one message per case of empty list. + * It also provides protected updateNoItemsMessage() method to be called each time when derived list + * is changed to update base mNoItemsCommentTextbox value. + * + * It is implemented to avoid duplication of this functionality in concrete implementations of the + * lists. It is intended to be used as a base class for lists which should support two different + * messages for empty state. Can be improved to support more than two messages via state-to-message map. + */ +class LLFlatListViewEx : public LLFlatListView +{ +public: + LOG_CLASS(LLFlatListViewEx); + + struct Params : public LLInitParam::Block + { + /** + * Contains a message for empty list when it does not contain any items at all. + */ + Optional no_items_msg; + + /** + * Contains a message for empty list when its items are removed by filtering. + */ + Optional no_filtered_items_msg; + Params(); + }; + + // *WORKAROUND: two methods to overload appropriate Params due to localization issue: + // no_items_msg & no_filtered_items_msg attributes are not defined as translatable in VLT. See EXT-5931 + void setNoItemsMsg(const std::string& msg) { mNoItemsMsg = msg; } + void setNoFilteredItemsMsg(const std::string& msg) { mNoFilteredItemsMsg = msg; } + + bool getForceShowingUnmatchedItems(); + + void setForceShowingUnmatchedItems(bool show); + + /** + * Sets up new filter string and filters the list. + */ + void setFilterSubString(const std::string& filter_str, bool notify_parent); + std::string getFilterSubString() { return mFilterSubString; } + + /** + * Filters the list, rearranges and notifies parent about shape changes. + * Derived classes may want to overload rearrangeItems() to exclude repeated separators after filtration. + */ + void filterItems(bool re_sort, bool notify_parent); + + /** + * Returns true if last call of filterItems() found at least one matching item + */ + bool hasMatchedItems(); + +protected: + LLFlatListViewEx(const Params& p); + + /** + * Applies a message for empty list depend on passed argument. + * + * @param filter_string - if is not empty, message for filtered items will be set, otherwise for + * completely empty list. Value of filter string will be passed as search_term in SLURL. + */ + void updateNoItemsMessage(const std::string& filter_string); + + /** + * Applies visibility acording to action and LLFlatListView settings. + * + * @param item - item we are changing + * @param item - action - parameters to determin visibility from + */ + bool updateItemVisibility(LLPanel* item, const LLSD &action); + +private: + std::string mNoFilteredItemsMsg; + std::string mNoItemsMsg; + std::string mFilterSubString; + /** + * Show list items that don't match current filter + */ + bool mForceShowingUnmatchedItems; + /** + * True if last call of filterItems() found at least one matching item + */ + bool mHasMatchedItems; +}; + +#endif diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index aa8f2ad04b..e6ecf3c283 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -1,3735 +1,3735 @@ -/** - * @file llfloater.cpp - * @brief LLFloater base class - * - * $LicenseInfo:firstyear=2002&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$ - */ - -// Floating "windows" within the GL display, like the inventory floater, -// mini-map floater, etc. - -#include "linden_common.h" -#include "llviewereventrecorder.h" -#include "llfloater.h" - -#include "llfocusmgr.h" - -#include "lluictrlfactory.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcriticaldamp.h" // LLSmoothInterpolation -#include "lldir.h" -#include "lldraghandle.h" -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "llresizebar.h" -#include "llresizehandle.h" -#include "llkeyboard.h" -#include "llmenugl.h" // MENU_BAR_HEIGHT -#include "llmodaldialog.h" -#include "lltextbox.h" -#include "llresmgr.h" -#include "llui.h" -#include "llwindow.h" -#include "llstl.h" -#include "llcontrol.h" -#include "lltabcontainer.h" -#include "v2math.h" -#include "lltrans.h" -#include "llhelp.h" -#include "llmultifloater.h" -#include "llsdutil.h" -#include "lluiusage.h" - - -// use this to control "jumping" behavior when Ctrl-Tabbing -const S32 TABBED_FLOATER_OFFSET = 0; - -const F32 LLFloater::CONTEXT_CONE_IN_ALPHA = 0.0f; -const F32 LLFloater::CONTEXT_CONE_OUT_ALPHA = 1.f; -const F32 LLFloater::CONTEXT_CONE_FADE_TIME = 0.08f; - -namespace LLInitParam -{ - void TypeValues::declareValues() - { - declare("relative", LLFloaterEnums::POSITIONING_RELATIVE); - declare("cascading", LLFloaterEnums::POSITIONING_CASCADING); - declare("centered", LLFloaterEnums::POSITIONING_CENTERED); - declare("specified", LLFloaterEnums::POSITIONING_SPECIFIED); - } -} - -std::string LLFloater::sButtonNames[BUTTON_COUNT] = -{ - "llfloater_close_btn", //BUTTON_CLOSE - "llfloater_restore_btn", //BUTTON_RESTORE - "llfloater_minimize_btn", //BUTTON_MINIMIZE - "llfloater_tear_off_btn", //BUTTON_TEAR_OFF - "llfloater_dock_btn", //BUTTON_DOCK - "llfloater_help_btn" //BUTTON_HELP -}; - -std::string LLFloater::sButtonToolTips[BUTTON_COUNT]; - -std::string LLFloater::sButtonToolTipsIndex[BUTTON_COUNT]= -{ -#ifdef LL_DARWIN - "BUTTON_CLOSE_DARWIN", //"Close (Cmd-W)", //BUTTON_CLOSE -#else - "BUTTON_CLOSE_WIN", //"Close (Ctrl-W)", //BUTTON_CLOSE -#endif - "BUTTON_RESTORE", //"Restore", //BUTTON_RESTORE - "BUTTON_MINIMIZE", //"Minimize", //BUTTON_MINIMIZE - "BUTTON_TEAR_OFF", //"Tear Off", //BUTTON_TEAR_OFF - "BUTTON_DOCK", - "BUTTON_HELP" -}; - -LLFloater::click_callback LLFloater::sButtonCallbacks[BUTTON_COUNT] = -{ - LLFloater::onClickClose, //BUTTON_CLOSE - LLFloater::onClickMinimize, //BUTTON_RESTORE - LLFloater::onClickMinimize, //BUTTON_MINIMIZE - LLFloater::onClickTearOff, //BUTTON_TEAR_OFF - LLFloater::onClickDock, //BUTTON_DOCK - LLFloater::onClickHelp //BUTTON_HELP -}; - -LLMultiFloater* LLFloater::sHostp = NULL; -bool LLFloater::sQuitting = false; // Flag to prevent storing visibility controls while quitting - -LLFloaterView* gFloaterView = NULL; - -/*==========================================================================*| -// DEV-38598: The fundamental problem with this operation is that it can only -// support a subset of LLSD values. While it's plausible to compare two arrays -// lexicographically, what strict ordering can you impose on maps? -// (LLFloaterTOS's current key is an LLSD map.) - -// Of course something like this is necessary if you want to build a std::set -// or std::map with LLSD keys. Fortunately we're getting by with other -// container types for now. - -//static -bool LLFloater::KeyCompare::compare(const LLSD& a, const LLSD& b) -{ - if (a.type() != b.type()) - { - //LL_ERRS() << "Mismatched LLSD types: (" << a << ") mismatches (" << b << ")" << LL_ENDL; - return false; - } - else if (a.isUndefined()) - return false; - else if (a.isInteger()) - return a.asInteger() < b.asInteger(); - else if (a.isReal()) - return a.asReal() < b.asReal(); - else if (a.isString()) - return a.asString() < b.asString(); - else if (a.isUUID()) - return a.asUUID() < b.asUUID(); - else if (a.isDate()) - return a.asDate() < b.asDate(); - else if (a.isURI()) - return a.asString() < b.asString(); // compare URIs as strings - else if (a.isBoolean()) - return a.asBoolean() < b.asBoolean(); - else - return false; // no valid operation for Binary -} -|*==========================================================================*/ - -bool LLFloater::KeyCompare::equate(const LLSD& a, const LLSD& b) -{ - return llsd_equals(a, b); -} - -//************************************ - -LLFloater::Params::Params() -: title("title"), - short_title("short_title"), - single_instance("single_instance", false), - reuse_instance("reuse_instance", false), - can_resize("can_resize", false), - can_minimize("can_minimize", true), - can_close("can_close", true), - can_drag_on_left("can_drag_on_left", false), - can_tear_off("can_tear_off", true), - save_dock_state("save_dock_state", false), - save_rect("save_rect", false), - save_visibility("save_visibility", false), - can_dock("can_dock", false), - show_title("show_title", true), - auto_close("auto_close", false), - positioning("positioning", LLFloaterEnums::POSITIONING_RELATIVE), - header_height("header_height", 0), - legacy_header_height("legacy_header_height", 0), - close_image("close_image"), - restore_image("restore_image"), - minimize_image("minimize_image"), - tear_off_image("tear_off_image"), - dock_image("dock_image"), - help_image("help_image"), - close_pressed_image("close_pressed_image"), - restore_pressed_image("restore_pressed_image"), - minimize_pressed_image("minimize_pressed_image"), - tear_off_pressed_image("tear_off_pressed_image"), - dock_pressed_image("dock_pressed_image"), - help_pressed_image("help_pressed_image"), - open_callback("open_callback"), - close_callback("close_callback"), - follows("follows"), - rel_x("rel_x", 0), - rel_y("rel_y", 0) -{ - changeDefault(visible, false); -} - - -//static -const LLFloater::Params& LLFloater::getDefaultParams() -{ - return LLUICtrlFactory::getDefaultParams(); -} - -//static -void LLFloater::initClass() -{ - // translate tooltips for floater buttons - for (S32 i = 0; i < BUTTON_COUNT; i++) - { - sButtonToolTips[i] = LLTrans::getString( sButtonToolTipsIndex[i] ); - } - - LLControlVariable* ctrl = LLUI::getInstance()->mSettingGroups["config"]->getControl("ActiveFloaterTransparency").get(); - if (ctrl) - { - ctrl->getSignal()->connect(boost::bind(&LLFloater::updateActiveFloaterTransparency)); - updateActiveFloaterTransparency(); - } - - ctrl = LLUI::getInstance()->mSettingGroups["config"]->getControl("InactiveFloaterTransparency").get(); - if (ctrl) - { - ctrl->getSignal()->connect(boost::bind(&LLFloater::updateInactiveFloaterTransparency)); - updateInactiveFloaterTransparency(); - } - -} - -// defaults for floater param block pulled from widgets/floater.xml -static LLWidgetNameRegistry::StaticRegistrar sRegisterFloaterParams(&typeid(LLFloater::Params), "floater"); - -LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) -: LLPanel(), // intentionally do not pass params here, see initFromParams - mDragHandle(NULL), - mTitle(p.title), - mShortTitle(p.short_title), - mSingleInstance(p.single_instance), - mReuseInstance(p.reuse_instance.isProvided() ? p.reuse_instance : p.single_instance), // reuse single-instance floaters by default - mKey(key), - mCanTearOff(p.can_tear_off), - mCanMinimize(p.can_minimize), - mCanClose(p.can_close), - mDragOnLeft(p.can_drag_on_left), - mResizable(p.can_resize), - mAutoClose(p.auto_close), - mPositioning(p.positioning), - mMinWidth(p.min_width), - mMinHeight(p.min_height), - mHeaderHeight(p.header_height), - mLegacyHeaderHeight(p.legacy_header_height), - mDefaultRectForGroup(true), - mMinimized(false), - mForeground(false), - mFirstLook(true), - mButtonScale(1.0f), - mAutoFocus(true), // automatically take focus when opened - mCanDock(false), - mDocked(false), - mTornOff(false), - mHasBeenDraggedWhileMinimized(false), - mPreviousMinimizedBottom(0), - mPreviousMinimizedLeft(0), - mDefaultRelativeX(p.rel_x), - mDefaultRelativeY(p.rel_y), - mMinimizeSignal(NULL) -// mNotificationContext(NULL) -{ - mPosition.setFloater(*this); -// mNotificationContext = new LLFloaterNotificationContext(getHandle()); - - // Clicks stop here. - setMouseOpaque(true); - - // Floaters always draw their background, unlike every other panel. - setBackgroundVisible(true); - - // Floaters start not minimized. When minimized, they save their - // prior rectangle to be used on restore. - mExpandedRect.set(0,0,0,0); - - memset(mButtonsEnabled, 0, BUTTON_COUNT * sizeof(bool)); - memset(mButtons, 0, BUTTON_COUNT * sizeof(LLButton*)); - - addDragHandle(); - addResizeCtrls(); - - initFromParams(p); - - initFloater(p); -} - -// Note: Floaters constructed from XML call init() twice! -void LLFloater::initFloater(const Params& p) -{ - // Close button. - if (mCanClose) - { - mButtonsEnabled[BUTTON_CLOSE] = true; - } - - // Help button: '?' - //SL-14050 Disable all Help question marks - mButtonsEnabled[BUTTON_HELP] = false; - - // Minimize button only for top draggers - if ( !mDragOnLeft && mCanMinimize ) - { - mButtonsEnabled[BUTTON_MINIMIZE] = true; - } - - if(mCanDock) - { - mButtonsEnabled[BUTTON_DOCK] = true; - } - - buildButtons(p); - - // Floaters are created in the invisible state - setVisible(false); - - if (!getParent()) - { - gFloaterView->addChild(this); - } -} - -void LLFloater::addDragHandle() -{ - if (!mDragHandle) - { - if (mDragOnLeft) - { - LLDragHandleLeft::Params p; - p.name("drag"); - p.follows.flags(FOLLOWS_ALL); - p.label(mTitle); - mDragHandle = LLUICtrlFactory::create(p); - } - else // drag on top - { - LLDragHandleTop::Params p; - p.name("Drag Handle"); - p.follows.flags(FOLLOWS_ALL); - p.label(mTitle); - mDragHandle = LLUICtrlFactory::create(p); - } - addChild(mDragHandle); - } - layoutDragHandle(); - applyTitle(); -} - -void LLFloater::layoutDragHandle() -{ - static LLUICachedControl floater_close_box_size ("UIFloaterCloseBoxSize", 0); - S32 close_box_size = mCanClose ? floater_close_box_size : 0; - - LLRect rect; - if (mDragOnLeft) - { - rect.setLeftTopAndSize(0, 0, DRAG_HANDLE_WIDTH, getRect().getHeight() - LLPANEL_BORDER_WIDTH - close_box_size); - } - else // drag on top - { - rect = getLocalRect(); - } - mDragHandle->setShape(rect); - updateTitleButtons(); -} - -// static -void LLFloater::updateActiveFloaterTransparency() -{ - static LLCachedControl active_transparency(*LLUI::getInstance()->mSettingGroups["config"], "ActiveFloaterTransparency", 1.f); - sActiveControlTransparency = active_transparency; -} - -// static -void LLFloater::updateInactiveFloaterTransparency() -{ - static LLCachedControl inactive_transparency(*LLUI::getInstance()->mSettingGroups["config"], "InactiveFloaterTransparency", 0.95f); - sInactiveControlTransparency = inactive_transparency; -} - -void LLFloater::addResizeCtrls() -{ - // Resize bars (sides) - LLResizeBar::Params p; - p.name("resizebar_left"); - p.resizing_view(this); - p.min_size(mMinWidth); - p.side(LLResizeBar::LEFT); - mResizeBar[LLResizeBar::LEFT] = LLUICtrlFactory::create(p); - addChild( mResizeBar[LLResizeBar::LEFT] ); - - p.name("resizebar_top"); - p.min_size(mMinHeight); - p.side(LLResizeBar::TOP); - - mResizeBar[LLResizeBar::TOP] = LLUICtrlFactory::create(p); - addChild( mResizeBar[LLResizeBar::TOP] ); - - p.name("resizebar_right"); - p.min_size(mMinWidth); - p.side(LLResizeBar::RIGHT); - mResizeBar[LLResizeBar::RIGHT] = LLUICtrlFactory::create(p); - addChild( mResizeBar[LLResizeBar::RIGHT] ); - - p.name("resizebar_bottom"); - p.min_size(mMinHeight); - p.side(LLResizeBar::BOTTOM); - mResizeBar[LLResizeBar::BOTTOM] = LLUICtrlFactory::create(p); - addChild( mResizeBar[LLResizeBar::BOTTOM] ); - - // Resize handles (corners) - LLResizeHandle::Params handle_p; - // handles must not be mouse-opaque, otherwise they block hover events - // to other buttons like the close box. JC - handle_p.mouse_opaque(false); - handle_p.min_width(mMinWidth); - handle_p.min_height(mMinHeight); - handle_p.corner(LLResizeHandle::RIGHT_BOTTOM); - mResizeHandle[0] = LLUICtrlFactory::create(handle_p); - addChild(mResizeHandle[0]); - - handle_p.corner(LLResizeHandle::RIGHT_TOP); - mResizeHandle[1] = LLUICtrlFactory::create(handle_p); - addChild(mResizeHandle[1]); - - handle_p.corner(LLResizeHandle::LEFT_BOTTOM); - mResizeHandle[2] = LLUICtrlFactory::create(handle_p); - addChild(mResizeHandle[2]); - - handle_p.corner(LLResizeHandle::LEFT_TOP); - mResizeHandle[3] = LLUICtrlFactory::create(handle_p); - addChild(mResizeHandle[3]); - - layoutResizeCtrls(); -} - -void LLFloater::layoutResizeCtrls() -{ - LLRect rect; - - // Resize bars (sides) - const S32 RESIZE_BAR_THICKNESS = 3; - rect = LLRect( 0, getRect().getHeight(), RESIZE_BAR_THICKNESS, 0); - mResizeBar[LLResizeBar::LEFT]->setRect(rect); - - rect = LLRect( 0, getRect().getHeight(), getRect().getWidth(), getRect().getHeight() - RESIZE_BAR_THICKNESS); - mResizeBar[LLResizeBar::TOP]->setRect(rect); - - rect = LLRect(getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0); - mResizeBar[LLResizeBar::RIGHT]->setRect(rect); - - rect = LLRect(0, RESIZE_BAR_THICKNESS, getRect().getWidth(), 0); - mResizeBar[LLResizeBar::BOTTOM]->setRect(rect); - - // Resize handles (corners) - rect = LLRect( getRect().getWidth() - RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_HEIGHT, getRect().getWidth(), 0); - mResizeHandle[0]->setRect(rect); - - rect = LLRect( getRect().getWidth() - RESIZE_HANDLE_WIDTH, getRect().getHeight(), getRect().getWidth(), getRect().getHeight() - RESIZE_HANDLE_HEIGHT); - mResizeHandle[1]->setRect(rect); - - rect = LLRect( 0, RESIZE_HANDLE_HEIGHT, RESIZE_HANDLE_WIDTH, 0 ); - mResizeHandle[2]->setRect(rect); - - rect = LLRect( 0, getRect().getHeight(), RESIZE_HANDLE_WIDTH, getRect().getHeight() - RESIZE_HANDLE_HEIGHT ); - mResizeHandle[3]->setRect(rect); -} - -void LLFloater::enableResizeCtrls(bool enable, bool width, bool height) -{ - mResizeBar[LLResizeBar::LEFT]->setVisible(enable && width); - mResizeBar[LLResizeBar::LEFT]->setEnabled(enable && width); - - mResizeBar[LLResizeBar::TOP]->setVisible(enable && height); - mResizeBar[LLResizeBar::TOP]->setEnabled(enable && height); - - mResizeBar[LLResizeBar::RIGHT]->setVisible(enable && width); - mResizeBar[LLResizeBar::RIGHT]->setEnabled(enable && width); - - mResizeBar[LLResizeBar::BOTTOM]->setVisible(enable && height); - mResizeBar[LLResizeBar::BOTTOM]->setEnabled(enable && height); - - for (S32 i = 0; i < 4; ++i) - { - mResizeHandle[i]->setVisible(enable && width && height); - mResizeHandle[i]->setEnabled(enable && width && height); - } -} - -void LLFloater::destroy() -{ - // LLFloaterReg should be synchronized with "dead" floater to avoid returning dead instance before - // it was deleted via LLMortician::updateClass(). See EXT-8458. - LLFloaterReg::removeInstance(mInstanceName, mKey); - die(); -} - -// virtual -LLFloater::~LLFloater() -{ - if (!isDead()) - { - // If it's dead, instance is supposed to be already removed, and - // in case of single instance we can remove new one by accident - LLFloaterReg::removeInstance(mInstanceName, mKey); - } - - if( gFocusMgr.childHasKeyboardFocus(this)) - { - // Just in case we might still have focus here, release it. - releaseFocus(); - } - - // This is important so that floaters with persistent rects (i.e., those - // created with rect control rather than an LLRect) are restored in their - // correct, non-minimized positions. - setMinimized( false ); - - delete mDragHandle; - for (S32 i = 0; i < 4; i++) - { - delete mResizeBar[i]; - delete mResizeHandle[i]; - } - - setVisible(false); // We're not visible if we're destroyed - storeVisibilityControl(); - storeDockStateControl(); - delete mMinimizeSignal; -} - -void LLFloater::storeRectControl() -{ - if (!mRectControl.empty()) - { - getControlGroup()->setRect( mRectControl, getRect() ); - } - if (!mPosXControl.empty() && mPositioning == LLFloaterEnums::POSITIONING_RELATIVE) - { - getControlGroup()->setF32( mPosXControl, mPosition.mX ); - } - if (!mPosYControl.empty() && mPositioning == LLFloaterEnums::POSITIONING_RELATIVE) - { - getControlGroup()->setF32( mPosYControl, mPosition.mY ); - } -} - -void LLFloater::storeVisibilityControl() -{ - if( !sQuitting && mVisibilityControl.size() > 1 ) - { - getControlGroup()->setBOOL( mVisibilityControl, getVisible() ); - } -} - -void LLFloater::storeDockStateControl() -{ - if( !sQuitting && mDocStateControl.size() > 1 ) - { - getControlGroup()->setBOOL( mDocStateControl, isDocked() ); - } -} - -// static -std::string LLFloater::getControlName(const std::string& name, const LLSD& key) -{ - std::string ctrl_name = name; - - // Add the key to the control name if appropriate. - if (key.isString() && !key.asString().empty()) - { - ctrl_name += "_" + key.asString(); - } - - return ctrl_name; -} - -// static -LLControlGroup* LLFloater::getControlGroup() -{ - // Floater size, position, visibility, etc are saved in per-account settings. - return LLUI::getInstance()->mSettingGroups["account"]; -} - -void LLFloater::setVisible( bool visible ) -{ - LLPanel::setVisible(visible); // calls onVisibilityChange() - if( visible && mFirstLook ) - { - mFirstLook = false; - } - - if( !visible ) - { - LLUI::getInstance()->removePopup(this); - - if( gFocusMgr.childHasMouseCapture( this ) ) - { - gFocusMgr.setMouseCapture(NULL); - } - } - - for(handle_set_iter_t dependent_it = mDependents.begin(); - dependent_it != mDependents.end(); ) - { - LLFloater* floaterp = dependent_it->get(); - - if (floaterp) - { - floaterp->setVisible(visible); - } - ++dependent_it; - } - - storeVisibilityControl(); -} - - -void LLFloater::setIsSingleInstance(bool is_single_instance) -{ - mSingleInstance = is_single_instance; - if (!mIsReuseInitialized) - { - mReuseInstance = is_single_instance; // reuse single-instance floaters by default - } -} - - -// virtual -void LLFloater::onVisibilityChange ( bool new_visibility ) -{ - if (new_visibility) - { - if (getHost()) - getHost()->setFloaterFlashing(this, false); - } - LLPanel::onVisibilityChange ( new_visibility ); -} - -void LLFloater::openFloater(const LLSD& key) -{ - LL_INFOS() << "Opening floater " << getName() << " full path: " << getPathname() << LL_ENDL; - - LLViewerEventRecorder::instance().logVisibilityChange( getPathname(), getName(), true,"floater"); // Last param is event subtype or empty string - - mKey = key; // in case we need to open ourselves again - - if (getSoundFlags() != SILENT - // don't play open sound for hosted (tabbed) windows - && !getHost() - && !getFloaterHost() - && (!getVisible() || isMinimized())) - { - make_ui_sound("UISndWindowOpen"); - } - - //RN: for now, we don't allow rehosting from one multifloater to another - // just need to fix the bugs - if (getFloaterHost() != NULL && getHost() == NULL) - { - // needs a host - // only select tabs if window they are hosted in is visible - getFloaterHost()->addFloater(this, getFloaterHost()->getVisible()); - } - - if (getHost() != NULL) - { - getHost()->setMinimized(false); - getHost()->setVisibleAndFrontmost(mAutoFocus && !getIsChrome()); - getHost()->showFloater(this); - } - else - { - LLFloater* floater_to_stack = LLFloaterReg::getLastFloaterInGroup(mInstanceName); - if (!floater_to_stack) - { - floater_to_stack = LLFloaterReg::getLastFloaterCascading(); - } - applyControlsAndPosition(floater_to_stack); - setMinimized(false); - setVisibleAndFrontmost(mAutoFocus && !getIsChrome()); - } - - mOpenSignal(this, key); - onOpen(key); - - dirtyRect(); -} - -void LLFloater::closeFloater(bool app_quitting) -{ - LL_INFOS() << "Closing floater " << getName() << LL_ENDL; - LLViewerEventRecorder::instance().logVisibilityChange( getPathname(), getName(), false,"floater"); // Last param is event subtype or empty string - if (app_quitting) - { - LLFloater::sQuitting = true; - } - - // Always unminimize before trying to close. - // Most of the time the user will never see this state. - setMinimized(false); - - if (canClose()) - { - if (getHost()) - { - ((LLMultiFloater*)getHost())->removeFloater(this); - gFloaterView->addChild(this); - } - - if (getSoundFlags() != SILENT - && getVisible() - && !getHost() - && !app_quitting) - { - make_ui_sound("UISndWindowClose"); - } - - gFocusMgr.clearLastFocusForGroup(this); - - if (hasFocus()) - { - // Do this early, so UI controls will commit before the - // window is taken down. - releaseFocus(); - - // give focus to dependee floater if it exists, and we had focus first - if (isDependent()) - { - LLFloater* dependee = mDependeeHandle.get(); - if (dependee && !dependee->isDead()) - { - dependee->setFocus(true); - } - } - } - - - //If floater is a dependent, remove it from parent (dependee) - LLFloater* dependee = mDependeeHandle.get(); - if (dependee) - { - dependee->removeDependentFloater(this); - } - - // now close dependent floater - while(mDependents.size() > 0) - { - handle_set_iter_t dependent_it = mDependents.begin(); - LLFloater* floaterp = dependent_it->get(); - // normally removeDependentFloater will do this, but in - // case floaterp is somehow invalid or orphaned, erase now - mDependents.erase(dependent_it); - if (floaterp) - { - floaterp->mDependeeHandle = LLHandle(); - floaterp->closeFloater(app_quitting); - } - } - - cleanupHandles(); - - dirtyRect(); - - // Close callbacks - onClose(app_quitting); - mCloseSignal(this, LLSD(app_quitting)); - - // Hide or Destroy - if (mSingleInstance) - { - // Hide the instance - if (getHost()) - { - getHost()->setVisible(false); - } - else - { - setVisible(false); - if (!mReuseInstance) - { - destroy(); - } - } - } - else - { - setVisible(false); // hide before destroying (so onVisibilityChange() gets called) - if (!mReuseInstance) - { - destroy(); - } - } - } -} - -/*virtual*/ -void LLFloater::closeHostedFloater() -{ - // When toggling *visibility*, close the host instead of the floater when hosted - if (getHost()) - { - getHost()->closeFloater(); - } - else - { - closeFloater(); - } -} - -/*virtual*/ -void LLFloater::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLPanel::reshape(width, height, called_from_parent); -} - -// virtual -void LLFloater::translate(S32 x, S32 y) -{ - LLView::translate(x, y); - - if (!mTranslateWithDependents || mDependents.empty()) - return; - - for (const LLHandle& handle : mDependents) - { - LLFloater* floater = handle.get(); - if (floater && floater->getSnapTarget() == getHandle()) - { - floater->LLView::translate(x, y); - } - } -} - -void LLFloater::releaseFocus() -{ - LLUI::getInstance()->removePopup(this); - - setFocus(false); - - if( gFocusMgr.childHasMouseCapture( this ) ) - { - gFocusMgr.setMouseCapture(NULL); - } -} - - -void LLFloater::setResizeLimits( S32 min_width, S32 min_height ) -{ - mMinWidth = min_width; - mMinHeight = min_height; - - for( S32 i = 0; i < 4; i++ ) - { - if( mResizeBar[i] ) - { - if (i == LLResizeBar::LEFT || i == LLResizeBar::RIGHT) - { - mResizeBar[i]->setResizeLimits( min_width, S32_MAX ); - } - else - { - mResizeBar[i]->setResizeLimits( min_height, S32_MAX ); - } - } - if( mResizeHandle[i] ) - { - mResizeHandle[i]->setResizeLimits( min_width, min_height ); - } - } -} - - -void LLFloater::center() -{ - if(getHost()) - { - // hosted floaters can't move - return; - } - centerWithin(gFloaterView->getRect()); -} - -LLMultiFloater* LLFloater::getHost() -{ - return (LLMultiFloater*)mHostHandle.get(); -} - -void LLFloater::applyControlsAndPosition(LLFloater* other) -{ - if (!applyDockState()) - { - if (!applyRectControl()) - { - applyPositioning(other, true); - } - } -} - -bool LLFloater::applyRectControl() -{ - bool saved_rect = false; - - LLRect screen_rect = calcScreenRect(); - mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); - - LLFloater* last_in_group = LLFloaterReg::getLastFloaterInGroup(mInstanceName); - if (last_in_group && last_in_group != this) - { - // other floaters in our group, position ourselves relative to them and don't save the rect - if (mDefaultRectForGroup) - { - mRectControl.clear(); - } - mPositioning = LLFloaterEnums::POSITIONING_CASCADE_GROUP; - } - else - { - bool rect_specified = false; - if (!mRectControl.empty()) - { - // If we have a saved rect, use it - const LLRect& rect = getControlGroup()->getRect(mRectControl); - if (rect.notEmpty()) saved_rect = true; - if (saved_rect) - { - setOrigin(rect.mLeft, rect.mBottom); - - if (mResizable) - { - reshape(llmax(mMinWidth, rect.getWidth()), llmax(mMinHeight, rect.getHeight())); - } - mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; - LLRect screen_rect = calcScreenRect(); - mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); - rect_specified = true; - } - } - - LLControlVariablePtr x_control = getControlGroup()->getControl(mPosXControl); - LLControlVariablePtr y_control = getControlGroup()->getControl(mPosYControl); - if (x_control.notNull() - && y_control.notNull() - && !x_control->isDefault() - && !y_control->isDefault()) - { - mPosition.mX = x_control->getValue().asReal(); - mPosition.mY = y_control->getValue().asReal(); - mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; - applyRelativePosition(); - - saved_rect = true; - } - else if ((mDefaultRelativeX != 0) && (mDefaultRelativeY != 0)) - { - mPosition.mX = mDefaultRelativeX; - mPosition.mY = mDefaultRelativeY; - mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; - applyRelativePosition(); - - saved_rect = true; - } - - // remember updated position - if (rect_specified) - { - storeRectControl(); - } - } - - if (saved_rect) - { - // propagate any derived positioning data back to settings file - storeRectControl(); - } - - - return saved_rect; -} - -bool LLFloater::applyDockState() -{ - bool docked = false; - - if (mDocStateControl.size() > 1) - { - docked = getControlGroup()->getBOOL(mDocStateControl); - setDocked(docked); - } - - return docked; -} - -void LLFloater::applyPositioning(LLFloater* other, bool on_open) -{ - // Otherwise position according to the positioning code - switch (mPositioning) - { - case LLFloaterEnums::POSITIONING_CENTERED: - center(); - break; - - case LLFloaterEnums::POSITIONING_SPECIFIED: - break; - - case LLFloaterEnums::POSITIONING_CASCADING: - if (!on_open) - { - applyRelativePosition(); - } - // fall through - case LLFloaterEnums::POSITIONING_CASCADE_GROUP: - if (on_open) - { - if (other != NULL && other != this) - { - stackWith(*other); - } - else - { - static const U32 CASCADING_FLOATER_HOFFSET = 0; - static const U32 CASCADING_FLOATER_VOFFSET = 0; - - const LLRect& snap_rect = gFloaterView->getSnapRect(); - - const S32 horizontal_offset = CASCADING_FLOATER_HOFFSET; - const S32 vertical_offset = snap_rect.getHeight() - CASCADING_FLOATER_VOFFSET; - - S32 rect_height = getRect().getHeight(); - setOrigin(horizontal_offset, vertical_offset - rect_height); - - translate(snap_rect.mLeft, snap_rect.mBottom); - } - setFollows(FOLLOWS_TOP | FOLLOWS_LEFT); - } - break; - - case LLFloaterEnums::POSITIONING_RELATIVE: - { - applyRelativePosition(); - - break; - } - default: - // Do nothing - break; - } -} - -void LLFloater::applyTitle() -{ - if (!mDragHandle) - { - return; - } - - if (isMinimized() && !mShortTitle.empty()) - { - mDragHandle->setTitle( mShortTitle ); - } - else - { - mDragHandle->setTitle ( mTitle ); - } - - if (getHost()) - { - getHost()->updateFloaterTitle(this); - } -} - -std::string LLFloater::getCurrentTitle() const -{ - return mDragHandle ? mDragHandle->getTitle() : LLStringUtil::null; -} - -void LLFloater::setTitle( const std::string& title ) -{ - mTitle = title; - applyTitle(); -} - -std::string LLFloater::getTitle() const -{ - if (mTitle.empty()) - { - return mDragHandle ? mDragHandle->getTitle() : LLStringUtil::null; - } - else - { - return mTitle; - } -} - -void LLFloater::setShortTitle( const std::string& short_title ) -{ - mShortTitle = short_title; - applyTitle(); -} - -std::string LLFloater::getShortTitle() const -{ - if (mShortTitle.empty()) - { - return mDragHandle ? mDragHandle->getTitle() : LLStringUtil::null; - } - else - { - return mShortTitle; - } -} - -bool LLFloater::canSnapTo(const LLView* other_view) -{ - if (NULL == other_view) - { - LL_WARNS() << "other_view is NULL" << LL_ENDL; - return false; - } - - if (other_view != getParent()) - { - const LLFloater* other_floaterp = dynamic_cast(other_view); - if (other_floaterp - && other_floaterp->getSnapTarget() == getHandle() - && mDependents.find(other_floaterp->getHandle()) != mDependents.end()) - { - // this is a dependent that is already snapped to us, so don't snap back to it - return false; - } - } - - return LLPanel::canSnapTo(other_view); -} - -void LLFloater::setSnappedTo(const LLView* snap_view) -{ - if (!snap_view || snap_view == getParent()) - { - clearSnapTarget(); - } - else - { - //RN: assume it's a floater as it must be a sibling to our parent floater - const LLFloater* floaterp = dynamic_cast(snap_view); - if (floaterp) - { - setSnapTarget(floaterp->getHandle()); - } - } -} - -void LLFloater::handleReshape(const LLRect& new_rect, bool by_user) -{ - const LLRect old_rect = getRect(); - LLView::handleReshape(new_rect, by_user); - - if (by_user && !getHost()) - { - LLFloaterView * floaterVp = dynamic_cast(getParent()); - if (floaterVp) - { - floaterVp->adjustToFitScreen(this, !isMinimized()); - } - } - - // if not minimized, adjust all snapped dependents to new shape - if (!isMinimized()) - { - if (by_user) - { - if (isDocked()) - { - setDocked( false, false); - } - mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; - LLRect screen_rect = calcScreenRect(); - mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); - } - storeRectControl(); - - // gather all snapped dependents - for(handle_set_iter_t dependent_it = mDependents.begin(); - dependent_it != mDependents.end(); ++dependent_it) - { - LLFloater* floaterp = dependent_it->get(); - // is a dependent snapped to us? - if (floaterp && floaterp->getSnapTarget() == getHandle()) - { - S32 delta_x = 0; - S32 delta_y = 0; - // check to see if it snapped to right or top, and move if dependee floater is resizing - LLRect dependent_rect = floaterp->getRect(); - if (dependent_rect.mLeft - getRect().mLeft >= old_rect.getWidth() || // dependent on my right? - dependent_rect.mRight == getRect().mLeft + old_rect.getWidth()) // dependent aligned with my right - { - // was snapped directly onto right side or aligned with it - delta_x += new_rect.getWidth() - old_rect.getWidth(); - } - if (dependent_rect.mBottom - getRect().mBottom >= old_rect.getHeight() || - dependent_rect.mTop == getRect().mBottom + old_rect.getHeight()) - { - // was snapped directly onto top side or aligned with it - delta_y += new_rect.getHeight() - old_rect.getHeight(); - } - - // take translation of dependee floater into account as well - delta_x += new_rect.mLeft - old_rect.mLeft; - delta_y += new_rect.mBottom - old_rect.mBottom; - - dependent_rect.translate(delta_x, delta_y); - floaterp->setShape(dependent_rect, by_user); - } - } - } - else - { - // If minimized, and origin has changed, set - // mHasBeenDraggedWhileMinimized to true - if ((new_rect.mLeft != old_rect.mLeft) || - (new_rect.mBottom != old_rect.mBottom)) - { - mHasBeenDraggedWhileMinimized = true; - } - } -} - -void LLFloater::setMinimized(bool minimize) -{ - const LLFloater::Params& default_params = LLFloater::getDefaultParams(); - S32 floater_header_size = default_params.header_height; - static LLUICachedControl minimized_width ("UIMinimizedWidth", 0); - - if (minimize == mMinimized) return; - - if (mMinimizeSignal) - { - (*mMinimizeSignal)(this, LLSD(minimize)); - } - - if (minimize) - { - // minimized flag should be turned on before release focus - mMinimized = true; - mExpandedRect = getRect(); - - // If the floater has been dragged while minimized in the - // past, then locate it at its previous minimized location. - // Otherwise, ask the view for a minimize position. - if (mHasBeenDraggedWhileMinimized) - { - setOrigin(mPreviousMinimizedLeft, mPreviousMinimizedBottom); - } - else - { - S32 left, bottom; - gFloaterView->getMinimizePosition(&left, &bottom); - setOrigin( left, bottom ); - } - - if (mButtonsEnabled[BUTTON_MINIMIZE]) - { - mButtonsEnabled[BUTTON_MINIMIZE] = false; - mButtonsEnabled[BUTTON_RESTORE] = true; - } - - setBorderVisible(true); - - for(handle_set_iter_t dependent_it = mDependents.begin(); - dependent_it != mDependents.end(); - ++dependent_it) - { - LLFloater* floaterp = dependent_it->get(); - if (floaterp) - { - if (floaterp->isMinimizeable()) - { - floaterp->setMinimized(true); - } - else if (!floaterp->isMinimized()) - { - floaterp->setVisible(false); - } - } - } - - // Lose keyboard focus when minimized - releaseFocus(); - - for (S32 i = 0; i < 4; i++) - { - if (mResizeBar[i] != NULL) - { - mResizeBar[i]->setEnabled(false); - } - if (mResizeHandle[i] != NULL) - { - mResizeHandle[i]->setEnabled(false); - } - } - - // Reshape *after* setting mMinimized - reshape( minimized_width, floater_header_size, true); - } - else - { - // If this window has been dragged while minimized (at any time), - // remember its position for the next time it's minimized. - if (mHasBeenDraggedWhileMinimized) - { - const LLRect& currentRect = getRect(); - mPreviousMinimizedLeft = currentRect.mLeft; - mPreviousMinimizedBottom = currentRect.mBottom; - } - - setOrigin( mExpandedRect.mLeft, mExpandedRect.mBottom ); - if (mButtonsEnabled[BUTTON_RESTORE]) - { - mButtonsEnabled[BUTTON_MINIMIZE] = true; - mButtonsEnabled[BUTTON_RESTORE] = false; - } - - // show dependent floater - for(handle_set_iter_t dependent_it = mDependents.begin(); - dependent_it != mDependents.end(); - ++dependent_it) - { - LLFloater* floaterp = dependent_it->get(); - if (floaterp) - { - floaterp->setMinimized(false); - floaterp->setVisible(true); - } - } - - for (S32 i = 0; i < 4; i++) - { - if (mResizeBar[i] != NULL) - { - mResizeBar[i]->setEnabled(isResizable()); - } - if (mResizeHandle[i] != NULL) - { - mResizeHandle[i]->setEnabled(isResizable()); - } - } - - mMinimized = false; - setFrontmost(); - // Reshape *after* setting mMinimized - reshape( mExpandedRect.getWidth(), mExpandedRect.getHeight(), true ); - } - - make_ui_sound("UISndWindowClose"); - updateTitleButtons(); - applyTitle (); -} - -void LLFloater::setFocus( bool b ) -{ - if (b && getIsChrome()) - { - return; - } - LLView* last_focus = gFocusMgr.getLastFocusForGroup(this); - // a descendent already has focus - bool child_had_focus = hasFocus(); - - // give focus to first valid descendent - LLPanel::setFocus(b); - - if (b) - { - // only push focused floaters to front of stack if not in midst of ctrl-tab cycle - LLFloaterView * parent = dynamic_cast(getParent()); - if (!getHost() && parent && !parent->getCycleMode()) - { - if (!isFrontmost()) - { - setFrontmost(); - } - } - - // when getting focus, delegate to last descendent which had focus - if (last_focus && !child_had_focus && - last_focus->isInEnabledChain() && - last_focus->isInVisibleChain()) - { - // *FIX: should handle case where focus doesn't stick - last_focus->setFocus(true); - } - } - updateTransparency(b ? TT_ACTIVE : TT_INACTIVE); -} - -// virtual -void LLFloater::setRect(const LLRect &rect) -{ - LLPanel::setRect(rect); - layoutDragHandle(); - layoutResizeCtrls(); -} - -// virtual -void LLFloater::setIsChrome(bool is_chrome) -{ - // chrome floaters don't take focus at all - if (is_chrome) - { - // remove focus if we're changing to chrome - setFocus(false); - // can't Ctrl-Tab to "chrome" floaters - setFocusRoot(false); - mButtons[BUTTON_CLOSE]->setToolTip(LLStringExplicit(getButtonTooltip(Params(), BUTTON_CLOSE, is_chrome))); - } - - LLPanel::setIsChrome(is_chrome); -} - -// Change the draw style to account for the foreground state. -void LLFloater::setForeground(bool front) -{ - if (front != mForeground) - { - mForeground = front; - if (mDragHandle) - mDragHandle->setForeground( front ); - - if (!front) - { - releaseFocus(); - } - - setBackgroundOpaque( front ); - } -} - -void LLFloater::cleanupHandles() -{ - // remove handles to non-existent dependents - for(handle_set_iter_t dependent_it = mDependents.begin(); - dependent_it != mDependents.end(); ) - { - LLFloater* floaterp = dependent_it->get(); - if (!floaterp) - { - dependent_it = mDependents.erase(dependent_it); - } - else - { - ++dependent_it; - } - } -} - -void LLFloater::setHost(LLMultiFloater* host) -{ - if (mHostHandle.isDead() && host) - { - // make buttons smaller for hosted windows to differentiate from parent - mButtonScale = 0.9f; - - // add tear off button - if (mCanTearOff) - { - mButtonsEnabled[BUTTON_TEAR_OFF] = true; - } - } - else if (!mHostHandle.isDead() && !host) - { - mButtonScale = 1.f; - //mButtonsEnabled[BUTTON_TEAR_OFF] = false; - } - if (host) - { - mHostHandle = host->getHandle(); - mLastHostHandle = host->getHandle(); - } - else - { - mHostHandle.markDead(); - } - - updateTitleButtons(); -} - -void LLFloater::moveResizeHandlesToFront() -{ - for( S32 i = 0; i < 4; i++ ) - { - if( mResizeBar[i] ) - { - sendChildToFront(mResizeBar[i]); - } - } - - for( S32 i = 0; i < 4; i++ ) - { - if( mResizeHandle[i] ) - { - sendChildToFront(mResizeHandle[i]); - } - } -} - -/*virtual*/ -bool LLFloater::isFrontmost() -{ - LLFloaterView* floater_view = getParentByType(); - return getVisible() - && (floater_view - && floater_view->getFrontmost() == this); -} - -void LLFloater::addDependentFloater(LLFloater* floaterp, bool reposition, bool resize) -{ - mDependents.insert(floaterp->getHandle()); - floaterp->mDependeeHandle = getHandle(); - - if (reposition) - { - LLRect rect = gFloaterView->findNeighboringPosition(this, floaterp); - if (resize) - { - const LLRect& base = getRect(); - if (rect.mTop == base.mTop) - rect.mBottom = base.mBottom; - else if (rect.mLeft == base.mLeft) - rect.mRight = base.mRight; - floaterp->reshape(rect.getWidth(), rect.getHeight(), false); - } - floaterp->setRect(rect); - floaterp->setSnapTarget(getHandle()); - } - gFloaterView->adjustToFitScreen(floaterp, false, true); - if (floaterp->isFrontmost()) - { - // make sure to bring self and sibling floaters to front - gFloaterView->bringToFront(floaterp, floaterp->getAutoFocus() && !getIsChrome()); - } -} - -void LLFloater::addDependentFloater(LLHandle dependent, bool reposition, bool resize) -{ - LLFloater* dependent_floaterp = dependent.get(); - if(dependent_floaterp) - { - addDependentFloater(dependent_floaterp, reposition, resize); - } -} - -void LLFloater::removeDependentFloater(LLFloater* floaterp) -{ - mDependents.erase(floaterp->getHandle()); - floaterp->mDependeeHandle = LLHandle(); -} - -void LLFloater::fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& constraint, S32 min_overlap_pixels) -{ - LLRect total_rect = getRect(); - - for (const LLHandle& handle : mDependents) - { - LLFloater* floater = handle.get(); - if (floater && floater->getSnapTarget() == getHandle()) - { - total_rect.unionWith(floater->getRect()); - } - } - - S32 delta_left = left.notEmpty() ? left.mRight - total_rect.mRight : 0; - S32 delta_bottom = bottom.notEmpty() ? bottom.mTop - total_rect.mTop : 0; - S32 delta_right = right.notEmpty() ? right.mLeft - total_rect.mLeft : 0; - - // move floater with dependings fully onscreen - mTranslateWithDependents = true; - if (translateRectIntoRect(total_rect, constraint, min_overlap_pixels)) - { - clearSnapTarget(); - } - else if (delta_left > 0 && total_rect.mTop < left.mTop && total_rect.mBottom > left.mBottom) - { - translate(delta_left, 0); - } - else if (delta_bottom > 0 && total_rect.mLeft > bottom.mLeft && total_rect.mRight < bottom.mRight) - { - translate(0, delta_bottom); - } - else if (delta_right < 0 && total_rect.mTop < right.mTop && total_rect.mBottom > right.mBottom) - { - translate(delta_right, 0); - } - mTranslateWithDependents = false; -} - -bool LLFloater::offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index) -{ - if( mButtonsEnabled[index] ) - { - LLButton* my_butt = mButtons[index]; - S32 local_x = x - my_butt->getRect().mLeft; - S32 local_y = y - my_butt->getRect().mBottom; - - if ( - my_butt->pointInView(local_x, local_y) && - my_butt->handleMouseDown(local_x, local_y, mask)) - { - // the button handled it - return true; - } - } - return false; -} - -bool LLFloater::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - LLPanel::handleScrollWheel(x,y,clicks); - return true;//always -} - -// virtual -bool LLFloater::handleMouseUp(S32 x, S32 y, MASK mask) -{ - LL_DEBUGS() << "LLFloater::handleMouseUp calling LLPanel (really LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << LL_ENDL; - bool handled = LLPanel::handleMouseUp(x,y,mask); // Not implemented in LLPanel so this actually calls LLView - if (handled) { - LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); - } - return handled; -} - -// virtual -bool LLFloater::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if( mMinimized ) - { - // Offer the click to titlebar buttons. - // Note: this block and the offerClickToButton helper method can be removed - // because the parent container will handle it for us but we'll keep it here - // for safety until after reworking the panel code to manage hidden children. - if(offerClickToButton(x, y, mask, BUTTON_CLOSE)) return true; - if(offerClickToButton(x, y, mask, BUTTON_RESTORE)) return true; - if(offerClickToButton(x, y, mask, BUTTON_TEAR_OFF)) return true; - if(offerClickToButton(x, y, mask, BUTTON_DOCK)) return true; - - setFrontmost(true, false); - // Otherwise pass to drag handle for movement - return mDragHandle->handleMouseDown(x, y, mask); - } - else - { - bringToFront( x, y ); - bool handled = LLPanel::handleMouseDown( x, y, mask ); - if (handled) { - LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); - } - return handled; - } -} - -// virtual -bool LLFloater::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool was_minimized = mMinimized; - bringToFront( x, y ); - return was_minimized || LLPanel::handleRightMouseDown( x, y, mask ); -} - -bool LLFloater::handleMiddleMouseDown(S32 x, S32 y, MASK mask) -{ - bringToFront( x, y ); - return LLPanel::handleMiddleMouseDown( x, y, mask ); -} - - -// virtual -bool LLFloater::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - bool was_minimized = mMinimized; - setMinimized(false); - return was_minimized || LLPanel::handleDoubleClick(x, y, mask); -} - -// virtual -void LLFloater::bringToFront( S32 x, S32 y ) -{ - if (getVisible() && pointInView(x, y)) - { - LLMultiFloater* hostp = getHost(); - if (hostp) - { - hostp->showFloater(this); - } - else - { - LLFloaterView* parent = dynamic_cast( getParent() ); - if (parent) - { - parent->bringToFront(this, !getIsChrome()); - } - } - } -} - -// virtual -void LLFloater::goneFromFront() -{ - if (mAutoClose) - { - closeFloater(); - } -} - -// virtual -void LLFloater::setVisibleAndFrontmost(bool take_focus,const LLSD& key) -{ - LLUIUsage::instance().logFloater(getInstanceName()); - LLMultiFloater* hostp = getHost(); - if (hostp) - { - hostp->setVisible(true); - hostp->setFrontmost(take_focus); - } - else - { - setVisible(true); - setFrontmost(take_focus); - } -} - -void LLFloater::setFrontmost(bool take_focus, bool restore) -{ - LLMultiFloater* hostp = getHost(); - if (hostp) - { - // this will bring the host floater to the front and select - // the appropriate panel - hostp->showFloater(this); - } - else - { - // there are more than one floater view - // so we need to query our parent directly - LLFloaterView * parent = dynamic_cast( getParent() ); - if (parent) - { - parent->bringToFront(this, take_focus, restore); - } - - // Make sure to set the appropriate transparency type (STORM-732). - updateTransparency(hasFocus() || getIsChrome() ? TT_ACTIVE : TT_INACTIVE); - } -} - -void LLFloater::setCanDock(bool b) -{ - if(b != mCanDock) - { - mCanDock = b; - if(mCanDock) - { - mButtonsEnabled[BUTTON_DOCK] = !mDocked; - } - else - { - mButtonsEnabled[BUTTON_DOCK] = false; - } - } - updateTitleButtons(); -} - -void LLFloater::setDocked(bool docked, bool pop_on_undock) -{ - if(docked != mDocked && mCanDock) - { - mDocked = docked; - mButtonsEnabled[BUTTON_DOCK] = !mDocked; - - if (mDocked) - { - setMinimized(false); - mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; - } - - updateTitleButtons(); - - storeDockStateControl(); - } - -} - -// static -void LLFloater::onClickMinimize(LLFloater* self) -{ - if (!self) - return; - self->setMinimized( !self->isMinimized() ); -} - -void LLFloater::onClickTearOff(LLFloater* self) -{ - if (!self) - return; - S32 floater_header_size = self->mHeaderHeight; - LLMultiFloater* host_floater = self->getHost(); - if (host_floater) //Tear off - { - LLRect new_rect; - host_floater->removeFloater(self); - // reparent to floater view - gFloaterView->addChild(self); - - self->openFloater(self->getKey()); - if (self->mSaveRect && !self->mRectControl.empty()) - { - self->applyRectControl(); - } - else - { // only force position for floaters that don't have that data saved - new_rect.setLeftTopAndSize(host_floater->getRect().mLeft + 5, host_floater->getRect().mTop - floater_header_size - 5, self->getRect().getWidth(), self->getRect().getHeight()); - self->setRect(new_rect); - } - gFloaterView->adjustToFitScreen(self, false); - // give focus to new window to keep continuity for the user - self->setFocus(true); - self->setTornOff(true); - } - else //Attach to parent. - { - LLMultiFloater* new_host = (LLMultiFloater*)self->mLastHostHandle.get(); - if (new_host) - { - if (self->mSaveRect) - { - LLRect screen_rect = self->calcScreenRect(); - self->mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); - self->storeRectControl(); - } - self->setMinimized(false); // to reenable minimize button if it was minimized - new_host->showFloater(self); - // make sure host is visible - new_host->openFloater(new_host->getKey()); - } - self->setTornOff(false); - } - self->updateTitleButtons(); - self->setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); -} - -// static -void LLFloater::onClickDock(LLFloater* self) -{ - if(self && self->mCanDock) - { - self->setDocked(!self->mDocked, true); - } -} - -// static -void LLFloater::onClickHelp( LLFloater* self ) -{ - if (self && LLUI::getInstance()->mHelpImpl) - { - // find the current help context for this floater - std::string help_topic; - if (self->findHelpTopic(help_topic)) - { - LLUI::getInstance()->mHelpImpl->showTopic(help_topic); - } - } -} - -void LLFloater::initRectControl() -{ - // save_rect and save_visibility only apply to registered floaters - if (mSaveRect) - { - std::string ctrl_name = getControlName(mInstanceName, mKey); - mRectControl = LLFloaterReg::declareRectControl(ctrl_name); - mPosXControl = LLFloaterReg::declarePosXControl(ctrl_name); - mPosYControl = LLFloaterReg::declarePosYControl(ctrl_name); - } -} - -// static -void LLFloater::closeFrontmostFloater() -{ - LLFloater* floater_to_close = gFloaterView->getFrontmostClosableFloater(); - if(floater_to_close) - { - floater_to_close->closeFloater(); - } - - // if nothing took focus after closing focused floater - // give it to next floater (to allow closing multiple windows via keyboard in rapid succession) - if (gFocusMgr.getKeyboardFocus() == NULL) - { - // HACK: use gFloaterView directly in case we are using Ctrl-W to close snapshot window - // which sits in gSnapshotFloaterView, and needs to pass focus on to normal floater view - gFloaterView->focusFrontFloater(); - } -} - - -// static -void LLFloater::onClickClose( LLFloater* self ) -{ - if (!self) - return; - self->onClickCloseBtn(); -} - -void LLFloater::onClickCloseBtn(bool app_quitting) -{ - closeFloater(false); -} - - -// virtual -void LLFloater::draw() -{ - const F32 alpha = getCurrentTransparency(); - - // draw background - if( isBackgroundVisible() ) - { - drawShadow(this); - - S32 left = LLPANEL_BORDER_WIDTH; - S32 top = getRect().getHeight() - LLPANEL_BORDER_WIDTH; - S32 right = getRect().getWidth() - LLPANEL_BORDER_WIDTH; - S32 bottom = LLPANEL_BORDER_WIDTH; - - LLUIImage* image = NULL; - LLColor4 color; - LLColor4 overlay_color; - if (isBackgroundOpaque()) - { - // NOTE: image may not be set - image = getBackgroundImage(); - color = getBackgroundColor(); - overlay_color = getBackgroundImageOverlay(); - } - else - { - image = getTransparentImage(); - color = getTransparentColor(); - overlay_color = getTransparentImageOverlay(); - } - - if (image) - { - // We're using images for this floater's backgrounds - image->draw(getLocalRect(), overlay_color % alpha); - } - else - { - // We're not using images, use old-school flat colors - gl_rect_2d( left, top, right, bottom, color % alpha ); - - // draw highlight on title bar to indicate focus. RDW - if(hasFocus() - && !getIsChrome() - && !getCurrentTitle().empty()) - { - static LLUIColor titlebar_focus_color = LLUIColorTable::instance().getColor("TitleBarFocusColor"); - - const LLFontGL* font = LLFontGL::getFontSansSerif(); - LLRect r = getRect(); - gl_rect_2d_offset_local(0, r.getHeight(), r.getWidth(), r.getHeight() - font->getLineHeight() - 1, - titlebar_focus_color % alpha, 0, true); - } - } - } - - LLPanel::updateDefaultBtn(); - - if( getDefaultButton() ) - { - if (hasFocus() && getDefaultButton()->getEnabled()) - { - LLFocusableElement* focus_ctrl = gFocusMgr.getKeyboardFocus(); - // is this button a direct descendent and not a nested widget (e.g. checkbox)? - bool focus_is_child_button = dynamic_cast(focus_ctrl) != NULL && dynamic_cast(focus_ctrl)->getParent() == this; - // only enable default button when current focus is not a button - getDefaultButton()->setBorderEnabled(!focus_is_child_button); - } - else - { - getDefaultButton()->setBorderEnabled(false); - } - } - if (isMinimized()) - { - for (S32 i = 0; i < BUTTON_COUNT; i++) - { - drawChild(mButtons[i]); - } - drawChild(mDragHandle, 0, 0, true); - } - else - { - // don't call LLPanel::draw() since we've implemented custom background rendering - LLView::draw(); - } - - // update tearoff button for torn off floaters - // when last host goes away - if (mCanTearOff && !getHost()) - { - LLFloater* old_host = mLastHostHandle.get(); - if (!old_host) - { - setCanTearOff(false); - } - } -} - -void LLFloater::drawShadow(LLPanel* panel) -{ - S32 left = LLPANEL_BORDER_WIDTH; - S32 top = panel->getRect().getHeight() - LLPANEL_BORDER_WIDTH; - S32 right = panel->getRect().getWidth() - LLPANEL_BORDER_WIDTH; - S32 bottom = LLPANEL_BORDER_WIDTH; - - static LLUIColor shadow_color_cached = LLUIColorTable::instance().getColor("ColorDropShadow"); - LLColor4 shadow_color = shadow_color_cached; - F32 shadow_offset = (F32)DROP_SHADOW_FLOATER; - - if (!panel->isBackgroundOpaque()) - { - shadow_offset *= 0.2f; - shadow_color.mV[VALPHA] *= 0.5f; - } - gl_drop_shadow(left, top, right, bottom, - shadow_color % getCurrentTransparency(), - ll_round(shadow_offset)); -} - -void LLFloater::updateTransparency(LLView* view, ETypeTransparency transparency_type) -{ - if (!view) return; - child_list_t children = *view->getChildList(); - child_list_t::iterator it = children.begin(); - - LLUICtrl* ctrl = dynamic_cast(view); - if (ctrl) - { - ctrl->setTransparencyType(transparency_type); - } - - for(; it != children.end(); ++it) - { - updateTransparency(*it, transparency_type); - } -} - -void LLFloater::updateTransparency(ETypeTransparency transparency_type) -{ - updateTransparency(this, transparency_type); -} - -void LLFloater::setCanMinimize(bool can_minimize) -{ - // if removing minimize/restore button programmatically, - // go ahead and unminimize floater - mCanMinimize = can_minimize; - if (!can_minimize) - { - setMinimized(false); - } - - mButtonsEnabled[BUTTON_MINIMIZE] = can_minimize && !isMinimized(); - mButtonsEnabled[BUTTON_RESTORE] = can_minimize && isMinimized(); - - updateTitleButtons(); -} - -void LLFloater::setCanClose(bool can_close) -{ - mCanClose = can_close; - mButtonsEnabled[BUTTON_CLOSE] = can_close; - - updateTitleButtons(); -} - -void LLFloater::setCanTearOff(bool can_tear_off) -{ - mCanTearOff = can_tear_off; - mButtonsEnabled[BUTTON_TEAR_OFF] = mCanTearOff && !mHostHandle.isDead(); - - updateTitleButtons(); -} - - -void LLFloater::setCanResize(bool can_resize) -{ - mResizable = can_resize; - enableResizeCtrls(can_resize); -} - -void LLFloater::setCanDrag(bool can_drag) -{ - // if we delete drag handle, we no longer have access to the floater's title - // so just enable/disable it - if (!can_drag && mDragHandle->getEnabled()) - { - mDragHandle->setEnabled(false); - } - else if (can_drag && !mDragHandle->getEnabled()) - { - mDragHandle->setEnabled(true); - } -} - -bool LLFloater::getCanDrag() -{ - return mDragHandle->getEnabled(); -} - - -void LLFloater::updateTitleButtons() -{ - static LLUICachedControl floater_close_box_size ("UIFloaterCloseBoxSize", 0); - static LLUICachedControl close_box_from_top ("UICloseBoxFromTop", 0); - LLRect buttons_rect; - S32 button_count = 0; - for (S32 i = 0; i < BUTTON_COUNT; i++) - { - if (!mButtons[i]) - { - continue; - } - - bool enabled = mButtonsEnabled[i]; - if (i == BUTTON_HELP) - { - // don't show the help button if the floater is minimized - // or if it is a docked tear-off floater - if (isMinimized() || (mButtonsEnabled[BUTTON_TEAR_OFF] && ! mTornOff)) - { - enabled = false; - } - } - if (i == BUTTON_CLOSE && mButtonScale != 1.f) - { - //*HACK: always render close button for hosted floaters so - //that users don't accidentally hit the button when - //closing multiple windows in the chatterbox - enabled = true; - } - - mButtons[i]->setEnabled(enabled); - - if (enabled) - { - button_count++; - - LLRect btn_rect; - if (mDragOnLeft) - { - btn_rect.setLeftTopAndSize( - LLPANEL_BORDER_WIDTH, - getRect().getHeight() - close_box_from_top - (floater_close_box_size + 1) * button_count, - ll_round((F32)floater_close_box_size * mButtonScale), - ll_round((F32)floater_close_box_size * mButtonScale)); - } - else - { - btn_rect.setLeftTopAndSize( - getRect().getWidth() - LLPANEL_BORDER_WIDTH - (floater_close_box_size + 1) * button_count, - getRect().getHeight() - close_box_from_top, - ll_round((F32)floater_close_box_size * mButtonScale), - ll_round((F32)floater_close_box_size * mButtonScale)); - } - - // first time here, init 'buttons_rect' - if(1 == button_count) - { - buttons_rect = btn_rect; - } - else - { - // if mDragOnLeft=true then buttons are on top-left side vertically aligned - // title is not displayed in this case, calculating 'buttons_rect' for future use - mDragOnLeft ? buttons_rect.mBottom -= btn_rect.mBottom : - buttons_rect.mLeft = btn_rect.mLeft; - } - mButtons[i]->setRect(btn_rect); - mButtons[i]->setVisible(true); - // the restore button should have a tab stop so that it takes action when you Ctrl-Tab to a minimized floater - mButtons[i]->setTabStop(i == BUTTON_RESTORE); - } - else - { - mButtons[i]->setVisible(false); - } - } - if (mDragHandle) - { - localRectToOtherView(buttons_rect, &buttons_rect, mDragHandle); - mDragHandle->setButtonsRect(buttons_rect); - } -} - -void LLFloater::drawConeToOwner(F32 &context_cone_opacity, - F32 max_cone_opacity, - LLView *owner_view, - F32 fade_time, - F32 contex_cone_in_alpha, - F32 contex_cone_out_alpha) -{ - if (owner_view - && owner_view->isInVisibleChain() - && hasFocus() - && context_cone_opacity > 0.001f - && gFocusMgr.childHasKeyboardFocus(this)) - { - // draw cone of context pointing back to owner (e.x. texture swatch) - LLRect owner_rect; - owner_view->localRectToOtherView(owner_view->getLocalRect(), &owner_rect, this); - LLRect local_rect = getLocalRect(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLEnable(GL_CULL_FACE); - gGL.begin(LLRender::QUADS); - { - gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); - gGL.vertex2i(owner_rect.mLeft, owner_rect.mTop); - gGL.vertex2i(owner_rect.mRight, owner_rect.mTop); - gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); - gGL.vertex2i(local_rect.mRight, local_rect.mTop); - gGL.vertex2i(local_rect.mLeft, local_rect.mTop); - - gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); - gGL.vertex2i(local_rect.mLeft, local_rect.mTop); - gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); - gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); - gGL.vertex2i(owner_rect.mLeft, owner_rect.mBottom); - gGL.vertex2i(owner_rect.mLeft, owner_rect.mTop); - - gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); - gGL.vertex2i(local_rect.mRight, local_rect.mBottom); - gGL.vertex2i(local_rect.mRight, local_rect.mTop); - gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); - gGL.vertex2i(owner_rect.mRight, owner_rect.mTop); - gGL.vertex2i(owner_rect.mRight, owner_rect.mBottom); - - - gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); - gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); - gGL.vertex2i(local_rect.mRight, local_rect.mBottom); - gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); - gGL.vertex2i(owner_rect.mRight, owner_rect.mBottom); - gGL.vertex2i(owner_rect.mLeft, owner_rect.mBottom); - } - gGL.end(); - } - - if (gFocusMgr.childHasMouseCapture(getDragHandle())) - { - context_cone_opacity = lerp(context_cone_opacity, max_cone_opacity, LLSmoothInterpolation::getInterpolant(fade_time)); - } - else - { - context_cone_opacity = lerp(context_cone_opacity, 0.f, LLSmoothInterpolation::getInterpolant(fade_time)); - } -} - -void LLFloater::buildButtons(const Params& floater_params) -{ - static LLUICachedControl floater_close_box_size ("UIFloaterCloseBoxSize", 0); - static LLUICachedControl close_box_from_top ("UICloseBoxFromTop", 0); - for (S32 i = 0; i < BUTTON_COUNT; i++) - { - if (mButtons[i]) - { - removeChild(mButtons[i]); - delete mButtons[i]; - mButtons[i] = NULL; - } - - LLRect btn_rect; - if (mDragOnLeft) - { - btn_rect.setLeftTopAndSize( - LLPANEL_BORDER_WIDTH, - getRect().getHeight() - close_box_from_top - (floater_close_box_size + 1) * (i + 1), - ll_round(floater_close_box_size * mButtonScale), - ll_round(floater_close_box_size * mButtonScale)); - } - else - { - btn_rect.setLeftTopAndSize( - getRect().getWidth() - LLPANEL_BORDER_WIDTH - (floater_close_box_size + 1) * (i + 1), - getRect().getHeight() - close_box_from_top, - ll_round(floater_close_box_size * mButtonScale), - ll_round(floater_close_box_size * mButtonScale)); - } - - LLButton::Params p; - p.name(sButtonNames[i]); - p.rect(btn_rect); - p.image_unselected = getButtonImage(floater_params, (EFloaterButton)i); - // Selected, no matter if hovered or not, is "pressed" - LLUIImage* pressed_image = getButtonPressedImage(floater_params, (EFloaterButton)i); - p.image_selected = pressed_image; - p.image_hover_selected = pressed_image; - // Use a glow effect when the user hovers over the button - // These icons are really small, need glow amount increased - p.hover_glow_amount( 0.33f ); - p.click_callback.function(boost::bind(sButtonCallbacks[i], this)); - p.tab_stop(false); - p.follows.flags(FOLLOWS_TOP|FOLLOWS_RIGHT); - p.tool_tip = getButtonTooltip(floater_params, (EFloaterButton)i, getIsChrome()); - p.scale_image(true); - p.chrome(true); - - LLButton* buttonp = LLUICtrlFactory::create(p); - addChild(buttonp); - mButtons[i] = buttonp; - } - - updateTitleButtons(); -} - -// static -LLUIImage* LLFloater::getButtonImage(const Params& p, EFloaterButton e) -{ - switch(e) - { - default: - case BUTTON_CLOSE: - return p.close_image; - case BUTTON_RESTORE: - return p.restore_image; - case BUTTON_MINIMIZE: - return p.minimize_image; - case BUTTON_TEAR_OFF: - return p.tear_off_image; - case BUTTON_DOCK: - return p.dock_image; - case BUTTON_HELP: - return p.help_image; - } -} - -// static -LLUIImage* LLFloater::getButtonPressedImage(const Params& p, EFloaterButton e) -{ - switch(e) - { - default: - case BUTTON_CLOSE: - return p.close_pressed_image; - case BUTTON_RESTORE: - return p.restore_pressed_image; - case BUTTON_MINIMIZE: - return p.minimize_pressed_image; - case BUTTON_TEAR_OFF: - return p.tear_off_pressed_image; - case BUTTON_DOCK: - return p.dock_pressed_image; - case BUTTON_HELP: - return p.help_pressed_image; - } -} - -// static -std::string LLFloater::getButtonTooltip(const Params& p, EFloaterButton e, bool is_chrome) -{ - // EXT-4081 (Lag Meter: Ctrl+W does not close floater) - // If floater is chrome set 'Close' text for close button's tooltip - if(is_chrome && BUTTON_CLOSE == e) - { - static std::string close_tooltip_chrome = LLTrans::getString("BUTTON_CLOSE_CHROME"); - return close_tooltip_chrome; - } - // TODO: per-floater localizable tooltips set in XML - return sButtonToolTips[e]; -} - -///////////////////////////////////////////////////// -// LLFloaterView - -static LLDefaultChildRegistry::Register r("floater_view"); - -LLFloaterView::LLFloaterView (const Params& p) -: LLUICtrl (p), - mFocusCycleMode(false), - mMinimizePositionVOffset(0), - mSnapOffsetBottom(0), - mSnapOffsetRight(0) -{ - mSnapView = getHandle(); -} - -// By default, adjust vertical. -void LLFloaterView::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLView::reshape(width, height, called_from_parent); - - mLastSnapRect = getSnapRect(); - - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - LLFloater* floaterp = dynamic_cast(viewp); - if (floaterp->isDependent()) - { - // dependents are moved with their "dependee" - continue; - } - - if (!floaterp->isMinimized() && floaterp->getCanDrag()) - { - LLRect old_rect = floaterp->getRect(); - floaterp->applyPositioning(NULL, false); - LLRect new_rect = floaterp->getRect(); - - //LLRect r = floaterp->getRect(); - - //// Compute absolute distance from each edge of screen - //S32 left_offset = llabs(r.mLeft - 0); - //S32 right_offset = llabs(old_right - r.mRight); - - //S32 top_offset = llabs(old_top - r.mTop); - //S32 bottom_offset = llabs(r.mBottom - 0); - - S32 translate_x = new_rect.mLeft - old_rect.mLeft; - S32 translate_y = new_rect.mBottom - old_rect.mBottom; - - //if (left_offset > right_offset) - //{ - // translate_x = new_right - old_right; - //} - - //if (top_offset < bottom_offset) - //{ - // translate_y = new_top - old_top; - //} - - // don't reposition immovable floaters - //if (floaterp->getCanDrag()) - //{ - // floaterp->translate(translate_x, translate_y); - //} - for (LLHandle dependent_floater : floaterp->mDependents) - { - if (dependent_floater.get()) - { - dependent_floater.get()->translate(translate_x, translate_y); - } - } - } - } -} - - -void LLFloaterView::restoreAll() -{ - // make sure all subwindows aren't minimized - child_list_t child_list = *(getChildList()); - for (LLView* child : child_list) - { - LLFloater* floaterp = dynamic_cast(child); - if (floaterp) - { - floaterp->setMinimized(false); - } - } - - // *FIX: make sure dependents are restored - - // children then deleted by default view constructor -} - - -LLRect LLFloaterView::findNeighboringPosition( LLFloater* reference_floater, LLFloater* neighbor ) -{ - LLRect base_rect = reference_floater->getRect(); - LLRect::tCoordType width = neighbor->getRect().getWidth(); - LLRect::tCoordType height = neighbor->getRect().getHeight(); - LLRect new_rect = neighbor->getRect(); - - LLRect expanded_base_rect = base_rect; - expanded_base_rect.stretch(10); - for(LLFloater::handle_set_iter_t dependent_it = reference_floater->mDependents.begin(); - dependent_it != reference_floater->mDependents.end(); ++dependent_it) - { - LLFloater* sibling = dependent_it->get(); - // check for dependents within 10 pixels of base floater - if (sibling && - sibling != neighbor && - sibling->getVisible() && - expanded_base_rect.overlaps(sibling->getRect())) - { - base_rect.unionWith(sibling->getRect()); - } - } - - LLRect::tCoordType left_margin = llmax(0, base_rect.mLeft); - LLRect::tCoordType right_margin = llmax(0, getRect().getWidth() - base_rect.mRight); - LLRect::tCoordType top_margin = llmax(0, getRect().getHeight() - base_rect.mTop); - LLRect::tCoordType bottom_margin = llmax(0, base_rect.mBottom); - - // find position for floater in following order - // right->left->bottom->top - for (S32 i = 0; i < 5; i++) - { - if (right_margin > width) - { - new_rect.translate(base_rect.mRight - neighbor->getRect().mLeft, base_rect.mTop - neighbor->getRect().mTop); - return new_rect; - } - else if (left_margin > width) - { - new_rect.translate(base_rect.mLeft - neighbor->getRect().mRight, base_rect.mTop - neighbor->getRect().mTop); - return new_rect; - } - else if (bottom_margin > height) - { - new_rect.translate(base_rect.mLeft - neighbor->getRect().mLeft, base_rect.mBottom - neighbor->getRect().mTop); - return new_rect; - } - else if (top_margin > height) - { - new_rect.translate(base_rect.mLeft - neighbor->getRect().mLeft, base_rect.mTop - neighbor->getRect().mBottom); - return new_rect; - } - - // keep growing margins to find "best" fit - left_margin += 20; - right_margin += 20; - top_margin += 20; - bottom_margin += 20; - } - - // didn't find anything, return initial rect - return new_rect; -} - - -void LLFloaterView::bringToFront(LLFloater* child, bool give_focus, bool restore) -{ - if (!child) - return; - - LLFloater* front_child = mFrontChildHandle.get(); - if (front_child == child) - { - if (give_focus && child->canFocusStealFrontmost() && !gFocusMgr.childHasKeyboardFocus(child)) - { - child->setFocus(true); - } - return; - } - - if (front_child && front_child->getVisible()) - { - front_child->goneFromFront(); - } - - mFrontChildHandle = child->getHandle(); - - // *TODO: make this respect floater's mAutoFocus value, instead of - // using parameter - if (child->getHost()) - { - // this floater is hosted elsewhere and hence not one of our children, abort - return; - } - std::vector floaters_to_move; - // Look at all floaters...tab - for (child_list_const_iter_t child_it = beginChild(); child_it != endChild(); ++child_it) - { - LLFloater* floater = dynamic_cast(*child_it); - - // ...but if I'm a dependent floater... - if (floater && child->isDependent()) - { - // ...look for floaters that have me as a dependent... - LLFloater::handle_set_iter_t found_dependent = floater->mDependents.find(child->getHandle()); - - if (found_dependent != floater->mDependents.end()) - { - // ...and make sure all children of that floater (including me) are brought to front... - for (LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); - dependent_it != floater->mDependents.end(); ++dependent_it) - { - LLFloater* sibling = dependent_it->get(); - if (sibling) - { - floaters_to_move.push_back(sibling); - } - } - //...before bringing my parent to the front... - floaters_to_move.push_back(floater); - } - } - } - - std::vector::iterator floater_it; - for(floater_it = floaters_to_move.begin(); floater_it != floaters_to_move.end(); ++floater_it) - { - LLFloater* floaterp = *floater_it; - sendChildToFront(floaterp); - - // always unminimize dependee, but allow dependents to stay minimized - if (!floaterp->isDependent()) - { - floaterp->setMinimized(false); - } - } - floaters_to_move.clear(); - - // ...then bringing my own dependents to the front... - for (LLFloater::handle_set_iter_t dependent_it = child->mDependents.begin(); - dependent_it != child->mDependents.end(); ++dependent_it) - { - LLFloater* dependent = dependent_it->get(); - if (dependent) - { - sendChildToFront(dependent); - } - } - - // ...and finally bringing myself to front - // (do this last, so that I'm left in front at end of this call) - if (*beginChild() != child) - { - sendChildToFront(child); - } - - if(restore) - { - child->setMinimized(false); - } - - if (give_focus && !gFocusMgr.childHasKeyboardFocus(child)) - { - child->setFocus(true); - // floater did not take focus, so relinquish focus to world - if (!child->hasFocus()) - { - gFocusMgr.setKeyboardFocus(NULL); - } - } -} - -void LLFloaterView::highlightFocusedFloater() -{ - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLFloater *floater = (LLFloater *)(*child_it); - - // skip dependent floaters, as we'll handle them in a batch along with their dependee(?) - if (floater->isDependent()) - { - continue; - } - - bool floater_or_dependent_has_focus = gFocusMgr.childHasKeyboardFocus(floater); - for(LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); - dependent_it != floater->mDependents.end(); - ++dependent_it) - { - LLFloater* dependent_floaterp = dependent_it->get(); - if (dependent_floaterp && gFocusMgr.childHasKeyboardFocus(dependent_floaterp)) - { - floater_or_dependent_has_focus = true; - } - } - - // now set this floater and all its dependents - floater->setForeground(floater_or_dependent_has_focus); - - for(LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); - dependent_it != floater->mDependents.end(); ) - { - LLFloater* dependent_floaterp = dependent_it->get(); - if (dependent_floaterp) - { - dependent_floaterp->setForeground(floater_or_dependent_has_focus); - } - ++dependent_it; - } - - floater->cleanupHandles(); - } -} - -LLFloater* LLFloaterView::getFrontmostClosableFloater() -{ - child_list_const_iter_t child_it; - LLFloater* frontmost_floater = NULL; - - for ( child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - frontmost_floater = (LLFloater *)(*child_it); - - if (frontmost_floater->isInVisibleChain() && frontmost_floater->isCloseable()) - { - return frontmost_floater; - } - } - - return NULL; -} - -void LLFloaterView::unhighlightFocusedFloater() -{ - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLFloater *floater = (LLFloater *)(*child_it); - - floater->setForeground(false); - } -} - -void LLFloaterView::focusFrontFloater() -{ - LLFloater* floaterp = getFrontmost(); - if (floaterp) - { - floaterp->setFocus(true); - } -} - -void LLFloaterView::getMinimizePosition(S32 *left, S32 *bottom) -{ - const LLFloater::Params& default_params = LLFloater::getDefaultParams(); - S32 floater_header_size = default_params.header_height; - static LLUICachedControl minimized_width ("UIMinimizedWidth", 0); - LLRect snap_rect_local = getLocalSnapRect(); - snap_rect_local.mTop += mMinimizePositionVOffset; - for(S32 col = snap_rect_local.mLeft; - col < snap_rect_local.getWidth() - minimized_width; - col += minimized_width) - { - for(S32 row = snap_rect_local.mTop - floater_header_size; - row > floater_header_size; - row -= floater_header_size ) //loop rows - { - - bool foundGap = true; - for(child_list_const_iter_t child_it = getChildList()->begin(); - child_it != getChildList()->end(); - ++child_it) //loop floaters - { - // Examine minimized children. - LLFloater* floater = dynamic_cast(*child_it); - if(floater->isMinimized()) - { - LLRect r = floater->getRect(); - if((r.mBottom < (row + floater_header_size)) - && (r.mBottom > (row - floater_header_size)) - && (r.mLeft < (col + minimized_width)) - && (r.mLeft > (col - minimized_width))) - { - // needs the check for off grid. can't drag, - // but window resize makes them off - foundGap = false; - break; - } - } - } //done floaters - if(foundGap) - { - *left = col; - *bottom = row; - return; //done - } - } //done this col - } - - // crude - stack'em all at 0,0 when screen is full of minimized - // floaters. - *left = snap_rect_local.mLeft; - *bottom = snap_rect_local.mBottom; -} - - -void LLFloaterView::destroyAllChildren() -{ - LLView::deleteAllChildren(); -} - -void LLFloaterView::closeAllChildren(bool app_quitting) -{ - // iterate over a copy of the list, because closing windows will destroy - // some windows on the list. - child_list_t child_list = *(getChildList()); - - for (child_list_const_iter_t it = child_list.begin(); it != child_list.end(); ++it) - { - LLView* viewp = *it; - child_list_const_iter_t exists = std::find(getChildList()->begin(), getChildList()->end(), viewp); - if (exists == getChildList()->end()) - { - // this floater has already been removed - continue; - } - - LLFloater* floaterp = dynamic_cast(viewp); - - // Attempt to close floater. This will cause the "do you want to save" - // dialogs to appear. - // Skip invisible floaters if we're not quitting (STORM-192). - if (floaterp->canClose() && !floaterp->isDead() && - (app_quitting || floaterp->getVisible())) - { - floaterp->closeFloater(app_quitting); - } - } -} - -void LLFloaterView::hiddenFloaterClosed(LLFloater* floater) -{ - for (hidden_floaters_t::iterator it = mHiddenFloaters.begin(), end_it = mHiddenFloaters.end(); - it != end_it; - ++it) - { - if (it->first.get() == floater) - { - it->second.disconnect(); - mHiddenFloaters.erase(it); - break; - } - } -} - -void LLFloaterView::hideAllFloaters() -{ - child_list_t child_list = *(getChildList()); - - for (child_list_iter_t it = child_list.begin(); it != child_list.end(); ++it) - { - LLFloater* floaterp = dynamic_cast(*it); - if (floaterp && floaterp->getVisible()) - { - floaterp->setVisible(false); - boost::signals2::connection connection = floaterp->mCloseSignal.connect(boost::bind(&LLFloaterView::hiddenFloaterClosed, this, floaterp)); - mHiddenFloaters.push_back(std::make_pair(floaterp->getHandle(), connection)); - } - } -} - -void LLFloaterView::showHiddenFloaters() -{ - for (hidden_floaters_t::iterator it = mHiddenFloaters.begin(), end_it = mHiddenFloaters.end(); - it != end_it; - ++it) - { - LLFloater* floaterp = it->first.get(); - if (floaterp) - { - floaterp->setVisible(true); - } - it->second.disconnect(); - } - mHiddenFloaters.clear(); -} - -bool LLFloaterView::allChildrenClosed() -{ - // see if there are any visible floaters (some floaters "close" - // by setting themselves invisible) - for (child_list_const_iter_t it = getChildList()->begin(); it != getChildList()->end(); ++it) - { - LLFloater* floaterp = dynamic_cast(*it); - - if (floaterp->getVisible() && !floaterp->isDead() && floaterp->isCloseable()) - { - return false; - } - } - return true; -} - -void LLFloaterView::shiftFloaters(S32 x_offset, S32 y_offset) -{ - for (child_list_const_iter_t it = getChildList()->begin(); it != getChildList()->end(); ++it) - { - LLFloater* floaterp = dynamic_cast(*it); - - if (floaterp && floaterp->isMinimized()) - { - floaterp->translate(x_offset, y_offset); - } - } -} - -void LLFloaterView::refresh() -{ - LLRect snap_rect = getSnapRect(); - if (snap_rect != mLastSnapRect) - { - reshape(getRect().getWidth(), getRect().getHeight(), true); - } - - // Constrain children to be entirely on the screen - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLFloater* floaterp = dynamic_cast(*child_it); - if (floaterp && floaterp->getVisible() ) - { - // minimized floaters are kept fully onscreen - adjustToFitScreen(floaterp, !floaterp->isMinimized()); - } - } -} - -void LLFloaterView::adjustToFitScreen(LLFloater* floater, bool allow_partial_outside, bool snap_in_toolbars/* = false*/) -{ - if (floater->getParent() != this) - { - // floater is hosted elsewhere, so ignore - return; - } - - if (floater->getDependee() && - floater->getDependee() == floater->getSnapTarget().get()) - { - // floater depends on other and snaps to it, so ignore - return; - } - - LLRect::tCoordType screen_width = getSnapRect().getWidth(); - LLRect::tCoordType screen_height = getSnapRect().getHeight(); - - // only automatically resize non-minimized, resizable floaters - if( floater->isResizable() && !floater->isMinimized() ) - { - LLRect view_rect = floater->getRect(); - S32 old_width = view_rect.getWidth(); - S32 old_height = view_rect.getHeight(); - S32 min_width; - S32 min_height; - floater->getResizeLimits( &min_width, &min_height ); - - // Make sure floater isn't already smaller than its min height/width? - S32 new_width = llmax( min_width, old_width ); - S32 new_height = llmax( min_height, old_height); - - if((new_width > screen_width) || (new_height > screen_height)) - { - // We have to make this window able to fit on screen - new_width = llmin(new_width, screen_width); - new_height = llmin(new_height, screen_height); - - // ...while respecting minimum width/height - new_width = llmax(new_width, min_width); - new_height = llmax(new_height, min_height); - - LLRect new_rect; - new_rect.setLeftTopAndSize(view_rect.mLeft,view_rect.mTop,new_width, new_height); - - floater->setShape(new_rect); - - if (floater->followsRight()) - { - floater->translate(old_width - new_width, 0); - } - - if (floater->followsTop()) - { - floater->translate(0, old_height - new_height); - } - } - } - - const LLRect& constraint = snap_in_toolbars ? getSnapRect() : gFloaterView->getRect(); - S32 min_overlap_pixels = allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX; - - floater->fitWithDependentsOnScreen(mToolbarLeftRect, mToolbarBottomRect, mToolbarRightRect, constraint, min_overlap_pixels); -} - -void LLFloaterView::draw() -{ - refresh(); - - // hide focused floater if in cycle mode, so that it can be drawn on top - LLFloater* focused_floater = getFocusedFloater(); - - if (mFocusCycleMode && focused_floater) - { - child_list_const_iter_t child_it = getChildList()->begin(); - for (;child_it != getChildList()->end(); ++child_it) - { - if ((*child_it) != focused_floater) - { - drawChild(*child_it); - } - } - - drawChild(focused_floater, -TABBED_FLOATER_OFFSET, TABBED_FLOATER_OFFSET); - } - else - { - LLView::draw(); - } -} - -LLRect LLFloaterView::getSnapRect() const -{ - LLRect snap_rect = getLocalRect(); - - LLView* snap_view = mSnapView.get(); - if (snap_view) - { - snap_view->localRectToOtherView(snap_view->getLocalRect(), &snap_rect, this); - } - - return snap_rect; -} - -LLFloater *LLFloaterView::getFocusedFloater() const -{ - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - if ((*child_it)->isCtrl()) - { - LLFloater* ctrlp = dynamic_cast(*child_it); - if ( ctrlp && ctrlp->hasFocus() ) - { - return ctrlp; - } - } - } - return NULL; -} - -LLFloater *LLFloaterView::getFrontmost() const -{ - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - if ( viewp->getVisible() && !viewp->isDead()) - { - return (LLFloater *)viewp; - } - } - return NULL; -} - -LLFloater *LLFloaterView::getBackmost() const -{ - LLFloater* back_most = NULL; - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - if ( viewp->getVisible() ) - { - back_most = (LLFloater *)viewp; - } - } - return back_most; -} - -void LLFloaterView::syncFloaterTabOrder() -{ - LLFloater* front_child = mFrontChildHandle.get(); - if (front_child && front_child->getIsChrome()) - return; - - // look for a visible modal dialog, starting from first - LLModalDialog* modal_dialog = NULL; - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLModalDialog* dialog = dynamic_cast(*child_it); - if (dialog && dialog->isModal() && dialog->getVisible()) - { - modal_dialog = dialog; - break; - } - } - - if (modal_dialog) - { - // If we have a visible modal dialog, make sure that it has focus - LLUI::getInstance()->addPopup(modal_dialog); - - if( !gFocusMgr.childHasKeyboardFocus( modal_dialog ) ) - { - modal_dialog->setFocus(true); - } - - if( !gFocusMgr.childHasMouseCapture( modal_dialog ) ) - { - gFocusMgr.setMouseCapture( modal_dialog ); - } - } - else - { - // otherwise, make sure the focused floater is in the front of the child list - for ( child_list_const_reverse_iter_t child_it = getChildList()->rbegin(); child_it != getChildList()->rend(); ++child_it) - { - LLFloater* floaterp = dynamic_cast(*child_it); - if (gFocusMgr.childHasKeyboardFocus(floaterp)) - { - LLFloater* front_child = mFrontChildHandle.get(); - if (front_child != floaterp) - { - // Grab a list of the top floaters that want to stay on top of the focused floater - std::list listTop; - if (front_child && !front_child->canFocusStealFrontmost()) - { - for (LLView* childp : *getChildList()) - { - LLFloater* child_floaterp = static_cast(childp); - if (child_floaterp->canFocusStealFrontmost()) - break; - listTop.push_back(child_floaterp); - } - } - - bringToFront(floaterp, false); - - // Restore top floaters - if (!listTop.empty()) - { - for (LLView* childp : listTop) - { - sendChildToFront(childp); - } - mFrontChildHandle = listTop.back()->getHandle(); - } - } - - break; - } - } - } -} - -LLFloater* LLFloaterView::getParentFloater(LLView* viewp) const -{ - LLView* parentp = viewp->getParent(); - - while(parentp && parentp != this) - { - viewp = parentp; - parentp = parentp->getParent(); - } - - if (parentp == this) - { - return dynamic_cast(viewp); - } - - return NULL; -} - -S32 LLFloaterView::getZOrder(LLFloater* child) -{ - S32 rv = 0; - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - if(viewp == child) - { - break; - } - ++rv; - } - return rv; -} - -void LLFloaterView::pushVisibleAll(bool visible, const skip_list_t& skip_list) -{ - for (child_list_const_iter_t child_iter = getChildList()->begin(); - child_iter != getChildList()->end(); ++child_iter) - { - LLView *view = *child_iter; - if (skip_list.find(view) == skip_list.end()) - { - view->pushVisible(visible); - } - } - - LLFloaterReg::blockShowFloaters(true); -} - -void LLFloaterView::popVisibleAll(const skip_list_t& skip_list) -{ - // make a copy of the list since some floaters change their - // order in the childList when changing visibility. - child_list_t child_list_copy = *getChildList(); - - for (child_list_const_iter_t child_iter = child_list_copy.begin(); - child_iter != child_list_copy.end(); ++child_iter) - { - LLView *view = *child_iter; - if (skip_list.find(view) == skip_list.end()) - { - view->popVisible(); - } - } - - LLFloaterReg::blockShowFloaters(false); -} - -void LLFloaterView::setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LLRect& toolbar_rect) -{ - switch (tb) - { - case LLToolBarEnums::TOOLBAR_LEFT: - mToolbarLeftRect = toolbar_rect; - break; - case LLToolBarEnums::TOOLBAR_BOTTOM: - mToolbarBottomRect = toolbar_rect; - break; - case LLToolBarEnums::TOOLBAR_RIGHT: - mToolbarRightRect = toolbar_rect; - break; - default: - LL_WARNS() << "setToolbarRect() passed odd toolbar number " << (S32) tb << LL_ENDL; - break; - } -} - -void LLFloater::setInstanceName(const std::string& name) -{ - if (name != mInstanceName) - { - llassert_always(mInstanceName.empty()); - mInstanceName = name; - if (!mInstanceName.empty()) - { - std::string ctrl_name = getControlName(mInstanceName, mKey); - initRectControl(); - if (!mVisibilityControl.empty()) - { - mVisibilityControl = LLFloaterReg::declareVisibilityControl(ctrl_name); - } - if(!mDocStateControl.empty()) - { - mDocStateControl = LLFloaterReg::declareDockStateControl(ctrl_name); - } - } -} -} - -void LLFloater::setKey(const LLSD& newkey) -{ - // Note: We don't have to do anything special with registration when we change keys - mKey = newkey; -} - -//static -void LLFloater::setupParamsForExport(Params& p, LLView* parent) -{ - // Do rectangle munging to topleft layout first - LLPanel::setupParamsForExport(p, parent); - - // Copy the rectangle out to apply layout constraints - LLRect rect = p.rect; - - // Null out other settings - p.rect.left.setProvided(false); - p.rect.top.setProvided(false); - p.rect.right.setProvided(false); - p.rect.bottom.setProvided(false); - - // Explicitly set width/height - p.rect.width.set( rect.getWidth(), true ); - p.rect.height.set( rect.getHeight(), true ); - - // If you can't resize this floater, don't export min_height - // and min_width - bool can_resize = p.can_resize; - if (!can_resize) - { - p.min_height.setProvided(false); - p.min_width.setProvided(false); - } -} - -void LLFloater::initFromParams(const LLFloater::Params& p) -{ - // *NOTE: We have too many classes derived from LLFloater to retrofit them - // all to pass in params via constructors. So we use this method. - - // control_name, tab_stop, focus_lost_callback, initial_value, rect, enabled, visible - LLPanel::initFromParams(p); - - // override any follows flags - if (mPositioning != LLFloaterEnums::POSITIONING_SPECIFIED) - { - setFollows(FOLLOWS_NONE); - } - - mTitle = p.title; - mShortTitle = p.short_title; - applyTitle(); - - setCanTearOff(p.can_tear_off); - setCanMinimize(p.can_minimize); - setCanClose(p.can_close); - setCanDock(p.can_dock); - setCanResize(p.can_resize); - setResizeLimits(p.min_width, p.min_height); - - mDragOnLeft = p.can_drag_on_left; - mHeaderHeight = p.header_height; - mLegacyHeaderHeight = p.legacy_header_height; - mSingleInstance = p.single_instance; - mReuseInstance = p.reuse_instance.isProvided() ? p.reuse_instance : p.single_instance; - - mDefaultRelativeX = p.rel_x; - mDefaultRelativeY = p.rel_y; - - mPositioning = p.positioning; - mAutoClose = p.auto_close; - - mSaveRect = p.save_rect; - if (p.save_visibility) - { - mVisibilityControl = "t"; // flag to build mVisibilityControl name once mInstanceName is set - } - if(p.save_dock_state) - { - mDocStateControl = "t"; // flag to build mDocStateControl name once mInstanceName is set - } - - // open callback - if (p.open_callback.isProvided()) - { - setOpenCallback(initCommitCallback(p.open_callback)); - } - // close callback - if (p.close_callback.isProvided()) - { - setCloseCallback(initCommitCallback(p.close_callback)); - } - - if (mDragHandle) - { - mDragHandle->setTitleVisible(p.show_title); - } -} - -boost::signals2::connection LLFloater::setMinimizeCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mMinimizeSignal) mMinimizeSignal = new commit_signal_t(); - return mMinimizeSignal->connect(cb); -} - -boost::signals2::connection LLFloater::setOpenCallback( const commit_signal_t::slot_type& cb ) -{ - return mOpenSignal.connect(cb); -} - -boost::signals2::connection LLFloater::setCloseCallback( const commit_signal_t::slot_type& cb ) -{ - return mCloseSignal.connect(cb); -} - -bool LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, const std::string& filename, LLXMLNodePtr output_node) -{ - LL_PROFILE_ZONE_SCOPED; - Params default_params(LLUICtrlFactory::getDefaultParams()); - Params params(default_params); - - LLXUIParser parser; - parser.readXUI(node, params, filename); // *TODO: Error checking - - std::string xml_filename = params.filename; - - if (!xml_filename.empty()) - { - LLXMLNodePtr referenced_xml; - - if (output_node) - { - //if we are exporting, we want to export the current xml - //not the referenced xml - Params output_params; - parser.readXUI(node, output_params, LLUICtrlFactory::getInstance()->getCurFileName()); - setupParamsForExport(output_params, parent); - output_node->setName(node->getName()->mString); - parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); - return true; - } - - LLUICtrlFactory::instance().pushFileName(xml_filename); - - if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml)) - { - LL_WARNS() << "Couldn't parse panel from: " << xml_filename << LL_ENDL; - - return false; - } - - Params referenced_params; - parser.readXUI(referenced_xml, referenced_params, LLUICtrlFactory::getInstance()->getCurFileName()); - params.fillFrom(referenced_params); - - // add children using dimensions from referenced xml for consistent layout - setShape(params.rect); - LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance()); - - LLUICtrlFactory::instance().popFileName(); - } - - - if (output_node) - { - Params output_params(params); - setupParamsForExport(output_params, parent); - output_node->setName(node->getName()->mString); - parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); - } - - // Default floater position to top-left corner of screen - // However, some legacy floaters have explicit top or bottom - // coordinates set, so respect their wishes. - if (!params.rect.top.isProvided() && !params.rect.bottom.isProvided()) - { - params.rect.top.set(0); - } - if (!params.rect.left.isProvided() && !params.rect.right.isProvided()) - { - params.rect.left.set(0); - } - params.from_xui = true; - applyXUILayout(params, parent, parent == gFloaterView ? gFloaterView->getSnapRect() : parent->getLocalRect()); - initFromParams(params); - - initFloater(params); - - LLMultiFloater* last_host = LLFloater::getFloaterHost(); - if (node->hasName("multi_floater")) - { - LLFloater::setFloaterHost((LLMultiFloater*) this); - } - - LLUICtrlFactory::createChildren(this, node, child_registry_t::instance(), output_node); - - if (node->hasName("multi_floater")) - { - LLFloater::setFloaterHost(last_host); - } - - // HACK: When we changed the header height to 25 pixels in Viewer 2, rather - // than re-layout all the floaters we use this value in pixels to make the - // whole floater bigger and change the top-left coordinate for widgets. - // The goal is to eventually set mLegacyHeaderHeight to zero, which would - // make the top-left corner for widget layout the same as the top-left - // corner of the window's content area. James - S32 header_stretch = (mHeaderHeight - mLegacyHeaderHeight); - if (header_stretch > 0) - { - // Stretch the floater vertically, don't move widgets - LLRect rect = getRect(); - rect.mTop += header_stretch; - - // This will also update drag handle, title bar, close box, etc. - setRect(rect); - } - - bool result; - result = postBuild(); - - if (!result) - { - LL_ERRS() << "Failed to construct floater " << getName() << LL_ENDL; - } - - applyRectControl(); // If we have a saved rect control, apply it - gFloaterView->adjustToFitScreen(this, false); // Floaters loaded from XML should all fit on screen - - moveResizeHandlesToFront(); - - applyDockState(); - - return true; // *TODO: Error checking -} - -bool LLFloater::isShown() const -{ - return ! isMinimized() && isInVisibleChain(); -} - -bool LLFloater::isDetachedAndNotMinimized() -{ - return !getHost() && !isMinimized(); -} - -/* static */ -bool LLFloater::isShown(const LLFloater* floater) -{ - return floater && floater->isShown(); -} - -/* static */ -bool LLFloater::isMinimized(const LLFloater* floater) -{ - return floater && floater->isMinimized(); -} - -/* static */ -bool LLFloater::isVisible(const LLFloater* floater) -{ - return floater && floater->getVisible(); -} - -bool LLFloater::buildFromFile(const std::string& filename) -{ - LL_PROFILE_ZONE_SCOPED; - LLXMLNodePtr root; - - if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) - { - LL_WARNS() << "Couldn't find (or parse) floater from: " << filename << LL_ENDL; - return false; - } - - // root must be called floater - if( !(root->hasName("floater") || root->hasName("multi_floater")) ) - { - LL_WARNS() << "Root node should be named floater in: " << filename << LL_ENDL; - return false; - } - - bool res = true; - - LL_DEBUGS() << "Building floater " << filename << LL_ENDL; - LLUICtrlFactory::instance().pushFileName(filename); - { - if (!getFactoryMap().empty()) - { - LLPanel::sFactoryStack.push_front(&getFactoryMap()); - } - - // for local registry callbacks; define in constructor, referenced in XUI or postBuild - getCommitCallbackRegistrar().pushScope(); - getEnableCallbackRegistrar().pushScope(); - - res = initFloaterXML(root, getParent(), filename, NULL); - - setXMLFilename(filename); - - getCommitCallbackRegistrar().popScope(); - getEnableCallbackRegistrar().popScope(); - - if (!getFactoryMap().empty()) - { - LLPanel::sFactoryStack.pop_front(); - } - } - LLUICtrlFactory::instance().popFileName(); - - return res; -} - -void LLFloater::stackWith(LLFloater& other) -{ - static LLUICachedControl floater_offset ("UIFloaterOffset", 16); - - LLRect next_rect; - if (other.getHost()) - { - next_rect = other.getHost()->getRect(); - } - else - { - next_rect = other.getRect(); - } - next_rect.translate(floater_offset, -floater_offset); - - const LLRect& rect = getControlGroup()->getRect(mRectControl); - if (rect.notEmpty() && !mDefaultRectForGroup && mResizable) - { - next_rect.setLeftTopAndSize(next_rect.mLeft, next_rect.mTop, llmax(mMinWidth, rect.getWidth()), llmax(mMinHeight, rect.getHeight())); - } - else - { - next_rect.setLeftTopAndSize(next_rect.mLeft, next_rect.mTop, getRect().getWidth(), getRect().getHeight()); - } - setShape(next_rect); - - if (!other.getHost()) - { - other.mPositioning = LLFloaterEnums::POSITIONING_CASCADE_GROUP; - other.setFollows(FOLLOWS_LEFT | FOLLOWS_TOP); - } -} - -void LLFloater::applyRelativePosition() -{ - LLRect snap_rect = gFloaterView->getSnapRect(); - LLRect floater_view_screen_rect = gFloaterView->calcScreenRect(); - snap_rect.translate(floater_view_screen_rect.mLeft, floater_view_screen_rect.mBottom); - LLRect floater_screen_rect = calcScreenRect(); - - LLCoordGL new_center = mPosition.convert(); - LLCoordGL cur_center(floater_screen_rect.getCenterX(), floater_screen_rect.getCenterY()); - translate(new_center.mX - cur_center.mX, new_center.mY - cur_center.mY); -} - - -LLCoordFloater::LLCoordFloater(F32 x, F32 y, LLFloater& floater) -: coord_t((S32)x, (S32)y) -{ - mFloater = floater.getHandle(); -} - - -LLCoordFloater::LLCoordFloater(const LLCoordCommon& other, LLFloater& floater) -{ - mFloater = floater.getHandle(); - convertFromCommon(other); -} - -LLCoordFloater& LLCoordFloater::operator=(const LLCoordFloater& other) -{ - mFloater = other.mFloater; - coord_t::operator =(other); - return *this; -} - -void LLCoordFloater::setFloater(LLFloater& floater) -{ - mFloater = floater.getHandle(); -} - -bool LLCoordFloater::operator==(const LLCoordFloater& other) const -{ - return mX == other.mX && mY == other.mY && mFloater == other.mFloater; -} - -LLCoordCommon LL_COORD_FLOATER::convertToCommon() const -{ - const LLCoordFloater& self = static_cast(LLCoordFloater::getTypedCoords(*this)); - - LLRect snap_rect = gFloaterView->getSnapRect(); - LLRect floater_view_screen_rect = gFloaterView->calcScreenRect(); - snap_rect.translate(floater_view_screen_rect.mLeft, floater_view_screen_rect.mBottom); - - LLFloater* floaterp = mFloater.get(); - S32 floater_width = floaterp ? floaterp->getRect().getWidth() : 0; - S32 floater_height = floaterp ? floaterp->getRect().getHeight() : 0; - LLCoordCommon out; - if (self.mX < -0.5f) - { - out.mX = ll_round(rescale(self.mX, -1.f, -0.5f, snap_rect.mLeft - (floater_width - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mLeft)); - } - else if (self.mX > 0.5f) - { - out.mX = ll_round(rescale(self.mX, 0.5f, 1.f, snap_rect.mRight - floater_width, snap_rect.mRight - FLOATER_MIN_VISIBLE_PIXELS)); - } - else - { - out.mX = ll_round(rescale(self.mX, -0.5f, 0.5f, snap_rect.mLeft, snap_rect.mRight - floater_width)); - } - - if (self.mY < -0.5f) - { - out.mY = ll_round(rescale(self.mY, -1.f, -0.5f, snap_rect.mBottom - (floater_height - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mBottom)); - } - else if (self.mY > 0.5f) - { - out.mY = ll_round(rescale(self.mY, 0.5f, 1.f, snap_rect.mTop - floater_height, snap_rect.mTop - FLOATER_MIN_VISIBLE_PIXELS)); - } - else - { - out.mY = ll_round(rescale(self.mY, -0.5f, 0.5f, snap_rect.mBottom, snap_rect.mTop - floater_height)); - } - - // return center point instead of lower left - out.mX += floater_width / 2; - out.mY += floater_height / 2; - - return out; -} - -void LL_COORD_FLOATER::convertFromCommon(const LLCoordCommon& from) -{ - LLCoordFloater& self = static_cast(LLCoordFloater::getTypedCoords(*this)); - LLRect snap_rect = gFloaterView->getSnapRect(); - LLRect floater_view_screen_rect = gFloaterView->calcScreenRect(); - snap_rect.translate(floater_view_screen_rect.mLeft, floater_view_screen_rect.mBottom); - - - LLFloater* floaterp = mFloater.get(); - S32 floater_width = floaterp ? floaterp->getRect().getWidth() : 0; - S32 floater_height = floaterp ? floaterp->getRect().getHeight() : 0; - - S32 from_x = from.mX - floater_width / 2; - S32 from_y = from.mY - floater_height / 2; - - if (from_x < snap_rect.mLeft) - { - self.mX = rescale(from_x, snap_rect.mLeft - (floater_width - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mLeft, -1.f, -0.5f); - } - else if (from_x + floater_width > snap_rect.mRight) - { - self.mX = rescale(from_x, snap_rect.mRight - floater_width, snap_rect.mRight - FLOATER_MIN_VISIBLE_PIXELS, 0.5f, 1.f); - } - else - { - self.mX = rescale(from_x, snap_rect.mLeft, snap_rect.mRight - floater_width, -0.5f, 0.5f); - } - - if (from_y < snap_rect.mBottom) - { - self.mY = rescale(from_y, snap_rect.mBottom - (floater_height - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mBottom, -1.f, -0.5f); - } - else if (from_y + floater_height > snap_rect.mTop) - { - self.mY = rescale(from_y, snap_rect.mTop - floater_height, snap_rect.mTop - FLOATER_MIN_VISIBLE_PIXELS, 0.5f, 1.f); - } - else - { - self.mY = rescale(from_y, snap_rect.mBottom, snap_rect.mTop - floater_height, -0.5f, 0.5f); - } -} +/** + * @file llfloater.cpp + * @brief LLFloater base class + * + * $LicenseInfo:firstyear=2002&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$ + */ + +// Floating "windows" within the GL display, like the inventory floater, +// mini-map floater, etc. + +#include "linden_common.h" +#include "llviewereventrecorder.h" +#include "llfloater.h" + +#include "llfocusmgr.h" + +#include "lluictrlfactory.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcriticaldamp.h" // LLSmoothInterpolation +#include "lldir.h" +#include "lldraghandle.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "llresizebar.h" +#include "llresizehandle.h" +#include "llkeyboard.h" +#include "llmenugl.h" // MENU_BAR_HEIGHT +#include "llmodaldialog.h" +#include "lltextbox.h" +#include "llresmgr.h" +#include "llui.h" +#include "llwindow.h" +#include "llstl.h" +#include "llcontrol.h" +#include "lltabcontainer.h" +#include "v2math.h" +#include "lltrans.h" +#include "llhelp.h" +#include "llmultifloater.h" +#include "llsdutil.h" +#include "lluiusage.h" + + +// use this to control "jumping" behavior when Ctrl-Tabbing +const S32 TABBED_FLOATER_OFFSET = 0; + +const F32 LLFloater::CONTEXT_CONE_IN_ALPHA = 0.0f; +const F32 LLFloater::CONTEXT_CONE_OUT_ALPHA = 1.f; +const F32 LLFloater::CONTEXT_CONE_FADE_TIME = 0.08f; + +namespace LLInitParam +{ + void TypeValues::declareValues() + { + declare("relative", LLFloaterEnums::POSITIONING_RELATIVE); + declare("cascading", LLFloaterEnums::POSITIONING_CASCADING); + declare("centered", LLFloaterEnums::POSITIONING_CENTERED); + declare("specified", LLFloaterEnums::POSITIONING_SPECIFIED); + } +} + +std::string LLFloater::sButtonNames[BUTTON_COUNT] = +{ + "llfloater_close_btn", //BUTTON_CLOSE + "llfloater_restore_btn", //BUTTON_RESTORE + "llfloater_minimize_btn", //BUTTON_MINIMIZE + "llfloater_tear_off_btn", //BUTTON_TEAR_OFF + "llfloater_dock_btn", //BUTTON_DOCK + "llfloater_help_btn" //BUTTON_HELP +}; + +std::string LLFloater::sButtonToolTips[BUTTON_COUNT]; + +std::string LLFloater::sButtonToolTipsIndex[BUTTON_COUNT]= +{ +#ifdef LL_DARWIN + "BUTTON_CLOSE_DARWIN", //"Close (Cmd-W)", //BUTTON_CLOSE +#else + "BUTTON_CLOSE_WIN", //"Close (Ctrl-W)", //BUTTON_CLOSE +#endif + "BUTTON_RESTORE", //"Restore", //BUTTON_RESTORE + "BUTTON_MINIMIZE", //"Minimize", //BUTTON_MINIMIZE + "BUTTON_TEAR_OFF", //"Tear Off", //BUTTON_TEAR_OFF + "BUTTON_DOCK", + "BUTTON_HELP" +}; + +LLFloater::click_callback LLFloater::sButtonCallbacks[BUTTON_COUNT] = +{ + LLFloater::onClickClose, //BUTTON_CLOSE + LLFloater::onClickMinimize, //BUTTON_RESTORE + LLFloater::onClickMinimize, //BUTTON_MINIMIZE + LLFloater::onClickTearOff, //BUTTON_TEAR_OFF + LLFloater::onClickDock, //BUTTON_DOCK + LLFloater::onClickHelp //BUTTON_HELP +}; + +LLMultiFloater* LLFloater::sHostp = NULL; +bool LLFloater::sQuitting = false; // Flag to prevent storing visibility controls while quitting + +LLFloaterView* gFloaterView = NULL; + +/*==========================================================================*| +// DEV-38598: The fundamental problem with this operation is that it can only +// support a subset of LLSD values. While it's plausible to compare two arrays +// lexicographically, what strict ordering can you impose on maps? +// (LLFloaterTOS's current key is an LLSD map.) + +// Of course something like this is necessary if you want to build a std::set +// or std::map with LLSD keys. Fortunately we're getting by with other +// container types for now. + +//static +bool LLFloater::KeyCompare::compare(const LLSD& a, const LLSD& b) +{ + if (a.type() != b.type()) + { + //LL_ERRS() << "Mismatched LLSD types: (" << a << ") mismatches (" << b << ")" << LL_ENDL; + return false; + } + else if (a.isUndefined()) + return false; + else if (a.isInteger()) + return a.asInteger() < b.asInteger(); + else if (a.isReal()) + return a.asReal() < b.asReal(); + else if (a.isString()) + return a.asString() < b.asString(); + else if (a.isUUID()) + return a.asUUID() < b.asUUID(); + else if (a.isDate()) + return a.asDate() < b.asDate(); + else if (a.isURI()) + return a.asString() < b.asString(); // compare URIs as strings + else if (a.isBoolean()) + return a.asBoolean() < b.asBoolean(); + else + return false; // no valid operation for Binary +} +|*==========================================================================*/ + +bool LLFloater::KeyCompare::equate(const LLSD& a, const LLSD& b) +{ + return llsd_equals(a, b); +} + +//************************************ + +LLFloater::Params::Params() +: title("title"), + short_title("short_title"), + single_instance("single_instance", false), + reuse_instance("reuse_instance", false), + can_resize("can_resize", false), + can_minimize("can_minimize", true), + can_close("can_close", true), + can_drag_on_left("can_drag_on_left", false), + can_tear_off("can_tear_off", true), + save_dock_state("save_dock_state", false), + save_rect("save_rect", false), + save_visibility("save_visibility", false), + can_dock("can_dock", false), + show_title("show_title", true), + auto_close("auto_close", false), + positioning("positioning", LLFloaterEnums::POSITIONING_RELATIVE), + header_height("header_height", 0), + legacy_header_height("legacy_header_height", 0), + close_image("close_image"), + restore_image("restore_image"), + minimize_image("minimize_image"), + tear_off_image("tear_off_image"), + dock_image("dock_image"), + help_image("help_image"), + close_pressed_image("close_pressed_image"), + restore_pressed_image("restore_pressed_image"), + minimize_pressed_image("minimize_pressed_image"), + tear_off_pressed_image("tear_off_pressed_image"), + dock_pressed_image("dock_pressed_image"), + help_pressed_image("help_pressed_image"), + open_callback("open_callback"), + close_callback("close_callback"), + follows("follows"), + rel_x("rel_x", 0), + rel_y("rel_y", 0) +{ + changeDefault(visible, false); +} + + +//static +const LLFloater::Params& LLFloater::getDefaultParams() +{ + return LLUICtrlFactory::getDefaultParams(); +} + +//static +void LLFloater::initClass() +{ + // translate tooltips for floater buttons + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + sButtonToolTips[i] = LLTrans::getString( sButtonToolTipsIndex[i] ); + } + + LLControlVariable* ctrl = LLUI::getInstance()->mSettingGroups["config"]->getControl("ActiveFloaterTransparency").get(); + if (ctrl) + { + ctrl->getSignal()->connect(boost::bind(&LLFloater::updateActiveFloaterTransparency)); + updateActiveFloaterTransparency(); + } + + ctrl = LLUI::getInstance()->mSettingGroups["config"]->getControl("InactiveFloaterTransparency").get(); + if (ctrl) + { + ctrl->getSignal()->connect(boost::bind(&LLFloater::updateInactiveFloaterTransparency)); + updateInactiveFloaterTransparency(); + } + +} + +// defaults for floater param block pulled from widgets/floater.xml +static LLWidgetNameRegistry::StaticRegistrar sRegisterFloaterParams(&typeid(LLFloater::Params), "floater"); + +LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) +: LLPanel(), // intentionally do not pass params here, see initFromParams + mDragHandle(NULL), + mTitle(p.title), + mShortTitle(p.short_title), + mSingleInstance(p.single_instance), + mReuseInstance(p.reuse_instance.isProvided() ? p.reuse_instance : p.single_instance), // reuse single-instance floaters by default + mKey(key), + mCanTearOff(p.can_tear_off), + mCanMinimize(p.can_minimize), + mCanClose(p.can_close), + mDragOnLeft(p.can_drag_on_left), + mResizable(p.can_resize), + mAutoClose(p.auto_close), + mPositioning(p.positioning), + mMinWidth(p.min_width), + mMinHeight(p.min_height), + mHeaderHeight(p.header_height), + mLegacyHeaderHeight(p.legacy_header_height), + mDefaultRectForGroup(true), + mMinimized(false), + mForeground(false), + mFirstLook(true), + mButtonScale(1.0f), + mAutoFocus(true), // automatically take focus when opened + mCanDock(false), + mDocked(false), + mTornOff(false), + mHasBeenDraggedWhileMinimized(false), + mPreviousMinimizedBottom(0), + mPreviousMinimizedLeft(0), + mDefaultRelativeX(p.rel_x), + mDefaultRelativeY(p.rel_y), + mMinimizeSignal(NULL) +// mNotificationContext(NULL) +{ + mPosition.setFloater(*this); +// mNotificationContext = new LLFloaterNotificationContext(getHandle()); + + // Clicks stop here. + setMouseOpaque(true); + + // Floaters always draw their background, unlike every other panel. + setBackgroundVisible(true); + + // Floaters start not minimized. When minimized, they save their + // prior rectangle to be used on restore. + mExpandedRect.set(0,0,0,0); + + memset(mButtonsEnabled, 0, BUTTON_COUNT * sizeof(bool)); + memset(mButtons, 0, BUTTON_COUNT * sizeof(LLButton*)); + + addDragHandle(); + addResizeCtrls(); + + initFromParams(p); + + initFloater(p); +} + +// Note: Floaters constructed from XML call init() twice! +void LLFloater::initFloater(const Params& p) +{ + // Close button. + if (mCanClose) + { + mButtonsEnabled[BUTTON_CLOSE] = true; + } + + // Help button: '?' + //SL-14050 Disable all Help question marks + mButtonsEnabled[BUTTON_HELP] = false; + + // Minimize button only for top draggers + if ( !mDragOnLeft && mCanMinimize ) + { + mButtonsEnabled[BUTTON_MINIMIZE] = true; + } + + if(mCanDock) + { + mButtonsEnabled[BUTTON_DOCK] = true; + } + + buildButtons(p); + + // Floaters are created in the invisible state + setVisible(false); + + if (!getParent()) + { + gFloaterView->addChild(this); + } +} + +void LLFloater::addDragHandle() +{ + if (!mDragHandle) + { + if (mDragOnLeft) + { + LLDragHandleLeft::Params p; + p.name("drag"); + p.follows.flags(FOLLOWS_ALL); + p.label(mTitle); + mDragHandle = LLUICtrlFactory::create(p); + } + else // drag on top + { + LLDragHandleTop::Params p; + p.name("Drag Handle"); + p.follows.flags(FOLLOWS_ALL); + p.label(mTitle); + mDragHandle = LLUICtrlFactory::create(p); + } + addChild(mDragHandle); + } + layoutDragHandle(); + applyTitle(); +} + +void LLFloater::layoutDragHandle() +{ + static LLUICachedControl floater_close_box_size ("UIFloaterCloseBoxSize", 0); + S32 close_box_size = mCanClose ? floater_close_box_size : 0; + + LLRect rect; + if (mDragOnLeft) + { + rect.setLeftTopAndSize(0, 0, DRAG_HANDLE_WIDTH, getRect().getHeight() - LLPANEL_BORDER_WIDTH - close_box_size); + } + else // drag on top + { + rect = getLocalRect(); + } + mDragHandle->setShape(rect); + updateTitleButtons(); +} + +// static +void LLFloater::updateActiveFloaterTransparency() +{ + static LLCachedControl active_transparency(*LLUI::getInstance()->mSettingGroups["config"], "ActiveFloaterTransparency", 1.f); + sActiveControlTransparency = active_transparency; +} + +// static +void LLFloater::updateInactiveFloaterTransparency() +{ + static LLCachedControl inactive_transparency(*LLUI::getInstance()->mSettingGroups["config"], "InactiveFloaterTransparency", 0.95f); + sInactiveControlTransparency = inactive_transparency; +} + +void LLFloater::addResizeCtrls() +{ + // Resize bars (sides) + LLResizeBar::Params p; + p.name("resizebar_left"); + p.resizing_view(this); + p.min_size(mMinWidth); + p.side(LLResizeBar::LEFT); + mResizeBar[LLResizeBar::LEFT] = LLUICtrlFactory::create(p); + addChild( mResizeBar[LLResizeBar::LEFT] ); + + p.name("resizebar_top"); + p.min_size(mMinHeight); + p.side(LLResizeBar::TOP); + + mResizeBar[LLResizeBar::TOP] = LLUICtrlFactory::create(p); + addChild( mResizeBar[LLResizeBar::TOP] ); + + p.name("resizebar_right"); + p.min_size(mMinWidth); + p.side(LLResizeBar::RIGHT); + mResizeBar[LLResizeBar::RIGHT] = LLUICtrlFactory::create(p); + addChild( mResizeBar[LLResizeBar::RIGHT] ); + + p.name("resizebar_bottom"); + p.min_size(mMinHeight); + p.side(LLResizeBar::BOTTOM); + mResizeBar[LLResizeBar::BOTTOM] = LLUICtrlFactory::create(p); + addChild( mResizeBar[LLResizeBar::BOTTOM] ); + + // Resize handles (corners) + LLResizeHandle::Params handle_p; + // handles must not be mouse-opaque, otherwise they block hover events + // to other buttons like the close box. JC + handle_p.mouse_opaque(false); + handle_p.min_width(mMinWidth); + handle_p.min_height(mMinHeight); + handle_p.corner(LLResizeHandle::RIGHT_BOTTOM); + mResizeHandle[0] = LLUICtrlFactory::create(handle_p); + addChild(mResizeHandle[0]); + + handle_p.corner(LLResizeHandle::RIGHT_TOP); + mResizeHandle[1] = LLUICtrlFactory::create(handle_p); + addChild(mResizeHandle[1]); + + handle_p.corner(LLResizeHandle::LEFT_BOTTOM); + mResizeHandle[2] = LLUICtrlFactory::create(handle_p); + addChild(mResizeHandle[2]); + + handle_p.corner(LLResizeHandle::LEFT_TOP); + mResizeHandle[3] = LLUICtrlFactory::create(handle_p); + addChild(mResizeHandle[3]); + + layoutResizeCtrls(); +} + +void LLFloater::layoutResizeCtrls() +{ + LLRect rect; + + // Resize bars (sides) + const S32 RESIZE_BAR_THICKNESS = 3; + rect = LLRect( 0, getRect().getHeight(), RESIZE_BAR_THICKNESS, 0); + mResizeBar[LLResizeBar::LEFT]->setRect(rect); + + rect = LLRect( 0, getRect().getHeight(), getRect().getWidth(), getRect().getHeight() - RESIZE_BAR_THICKNESS); + mResizeBar[LLResizeBar::TOP]->setRect(rect); + + rect = LLRect(getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0); + mResizeBar[LLResizeBar::RIGHT]->setRect(rect); + + rect = LLRect(0, RESIZE_BAR_THICKNESS, getRect().getWidth(), 0); + mResizeBar[LLResizeBar::BOTTOM]->setRect(rect); + + // Resize handles (corners) + rect = LLRect( getRect().getWidth() - RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_HEIGHT, getRect().getWidth(), 0); + mResizeHandle[0]->setRect(rect); + + rect = LLRect( getRect().getWidth() - RESIZE_HANDLE_WIDTH, getRect().getHeight(), getRect().getWidth(), getRect().getHeight() - RESIZE_HANDLE_HEIGHT); + mResizeHandle[1]->setRect(rect); + + rect = LLRect( 0, RESIZE_HANDLE_HEIGHT, RESIZE_HANDLE_WIDTH, 0 ); + mResizeHandle[2]->setRect(rect); + + rect = LLRect( 0, getRect().getHeight(), RESIZE_HANDLE_WIDTH, getRect().getHeight() - RESIZE_HANDLE_HEIGHT ); + mResizeHandle[3]->setRect(rect); +} + +void LLFloater::enableResizeCtrls(bool enable, bool width, bool height) +{ + mResizeBar[LLResizeBar::LEFT]->setVisible(enable && width); + mResizeBar[LLResizeBar::LEFT]->setEnabled(enable && width); + + mResizeBar[LLResizeBar::TOP]->setVisible(enable && height); + mResizeBar[LLResizeBar::TOP]->setEnabled(enable && height); + + mResizeBar[LLResizeBar::RIGHT]->setVisible(enable && width); + mResizeBar[LLResizeBar::RIGHT]->setEnabled(enable && width); + + mResizeBar[LLResizeBar::BOTTOM]->setVisible(enable && height); + mResizeBar[LLResizeBar::BOTTOM]->setEnabled(enable && height); + + for (S32 i = 0; i < 4; ++i) + { + mResizeHandle[i]->setVisible(enable && width && height); + mResizeHandle[i]->setEnabled(enable && width && height); + } +} + +void LLFloater::destroy() +{ + // LLFloaterReg should be synchronized with "dead" floater to avoid returning dead instance before + // it was deleted via LLMortician::updateClass(). See EXT-8458. + LLFloaterReg::removeInstance(mInstanceName, mKey); + die(); +} + +// virtual +LLFloater::~LLFloater() +{ + if (!isDead()) + { + // If it's dead, instance is supposed to be already removed, and + // in case of single instance we can remove new one by accident + LLFloaterReg::removeInstance(mInstanceName, mKey); + } + + if( gFocusMgr.childHasKeyboardFocus(this)) + { + // Just in case we might still have focus here, release it. + releaseFocus(); + } + + // This is important so that floaters with persistent rects (i.e., those + // created with rect control rather than an LLRect) are restored in their + // correct, non-minimized positions. + setMinimized( false ); + + delete mDragHandle; + for (S32 i = 0; i < 4; i++) + { + delete mResizeBar[i]; + delete mResizeHandle[i]; + } + + setVisible(false); // We're not visible if we're destroyed + storeVisibilityControl(); + storeDockStateControl(); + delete mMinimizeSignal; +} + +void LLFloater::storeRectControl() +{ + if (!mRectControl.empty()) + { + getControlGroup()->setRect( mRectControl, getRect() ); + } + if (!mPosXControl.empty() && mPositioning == LLFloaterEnums::POSITIONING_RELATIVE) + { + getControlGroup()->setF32( mPosXControl, mPosition.mX ); + } + if (!mPosYControl.empty() && mPositioning == LLFloaterEnums::POSITIONING_RELATIVE) + { + getControlGroup()->setF32( mPosYControl, mPosition.mY ); + } +} + +void LLFloater::storeVisibilityControl() +{ + if( !sQuitting && mVisibilityControl.size() > 1 ) + { + getControlGroup()->setBOOL( mVisibilityControl, getVisible() ); + } +} + +void LLFloater::storeDockStateControl() +{ + if( !sQuitting && mDocStateControl.size() > 1 ) + { + getControlGroup()->setBOOL( mDocStateControl, isDocked() ); + } +} + +// static +std::string LLFloater::getControlName(const std::string& name, const LLSD& key) +{ + std::string ctrl_name = name; + + // Add the key to the control name if appropriate. + if (key.isString() && !key.asString().empty()) + { + ctrl_name += "_" + key.asString(); + } + + return ctrl_name; +} + +// static +LLControlGroup* LLFloater::getControlGroup() +{ + // Floater size, position, visibility, etc are saved in per-account settings. + return LLUI::getInstance()->mSettingGroups["account"]; +} + +void LLFloater::setVisible( bool visible ) +{ + LLPanel::setVisible(visible); // calls onVisibilityChange() + if( visible && mFirstLook ) + { + mFirstLook = false; + } + + if( !visible ) + { + LLUI::getInstance()->removePopup(this); + + if( gFocusMgr.childHasMouseCapture( this ) ) + { + gFocusMgr.setMouseCapture(NULL); + } + } + + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + LLFloater* floaterp = dependent_it->get(); + + if (floaterp) + { + floaterp->setVisible(visible); + } + ++dependent_it; + } + + storeVisibilityControl(); +} + + +void LLFloater::setIsSingleInstance(bool is_single_instance) +{ + mSingleInstance = is_single_instance; + if (!mIsReuseInitialized) + { + mReuseInstance = is_single_instance; // reuse single-instance floaters by default + } +} + + +// virtual +void LLFloater::onVisibilityChange ( bool new_visibility ) +{ + if (new_visibility) + { + if (getHost()) + getHost()->setFloaterFlashing(this, false); + } + LLPanel::onVisibilityChange ( new_visibility ); +} + +void LLFloater::openFloater(const LLSD& key) +{ + LL_INFOS() << "Opening floater " << getName() << " full path: " << getPathname() << LL_ENDL; + + LLViewerEventRecorder::instance().logVisibilityChange( getPathname(), getName(), true,"floater"); // Last param is event subtype or empty string + + mKey = key; // in case we need to open ourselves again + + if (getSoundFlags() != SILENT + // don't play open sound for hosted (tabbed) windows + && !getHost() + && !getFloaterHost() + && (!getVisible() || isMinimized())) + { + make_ui_sound("UISndWindowOpen"); + } + + //RN: for now, we don't allow rehosting from one multifloater to another + // just need to fix the bugs + if (getFloaterHost() != NULL && getHost() == NULL) + { + // needs a host + // only select tabs if window they are hosted in is visible + getFloaterHost()->addFloater(this, getFloaterHost()->getVisible()); + } + + if (getHost() != NULL) + { + getHost()->setMinimized(false); + getHost()->setVisibleAndFrontmost(mAutoFocus && !getIsChrome()); + getHost()->showFloater(this); + } + else + { + LLFloater* floater_to_stack = LLFloaterReg::getLastFloaterInGroup(mInstanceName); + if (!floater_to_stack) + { + floater_to_stack = LLFloaterReg::getLastFloaterCascading(); + } + applyControlsAndPosition(floater_to_stack); + setMinimized(false); + setVisibleAndFrontmost(mAutoFocus && !getIsChrome()); + } + + mOpenSignal(this, key); + onOpen(key); + + dirtyRect(); +} + +void LLFloater::closeFloater(bool app_quitting) +{ + LL_INFOS() << "Closing floater " << getName() << LL_ENDL; + LLViewerEventRecorder::instance().logVisibilityChange( getPathname(), getName(), false,"floater"); // Last param is event subtype or empty string + if (app_quitting) + { + LLFloater::sQuitting = true; + } + + // Always unminimize before trying to close. + // Most of the time the user will never see this state. + setMinimized(false); + + if (canClose()) + { + if (getHost()) + { + ((LLMultiFloater*)getHost())->removeFloater(this); + gFloaterView->addChild(this); + } + + if (getSoundFlags() != SILENT + && getVisible() + && !getHost() + && !app_quitting) + { + make_ui_sound("UISndWindowClose"); + } + + gFocusMgr.clearLastFocusForGroup(this); + + if (hasFocus()) + { + // Do this early, so UI controls will commit before the + // window is taken down. + releaseFocus(); + + // give focus to dependee floater if it exists, and we had focus first + if (isDependent()) + { + LLFloater* dependee = mDependeeHandle.get(); + if (dependee && !dependee->isDead()) + { + dependee->setFocus(true); + } + } + } + + + //If floater is a dependent, remove it from parent (dependee) + LLFloater* dependee = mDependeeHandle.get(); + if (dependee) + { + dependee->removeDependentFloater(this); + } + + // now close dependent floater + while(mDependents.size() > 0) + { + handle_set_iter_t dependent_it = mDependents.begin(); + LLFloater* floaterp = dependent_it->get(); + // normally removeDependentFloater will do this, but in + // case floaterp is somehow invalid or orphaned, erase now + mDependents.erase(dependent_it); + if (floaterp) + { + floaterp->mDependeeHandle = LLHandle(); + floaterp->closeFloater(app_quitting); + } + } + + cleanupHandles(); + + dirtyRect(); + + // Close callbacks + onClose(app_quitting); + mCloseSignal(this, LLSD(app_quitting)); + + // Hide or Destroy + if (mSingleInstance) + { + // Hide the instance + if (getHost()) + { + getHost()->setVisible(false); + } + else + { + setVisible(false); + if (!mReuseInstance) + { + destroy(); + } + } + } + else + { + setVisible(false); // hide before destroying (so onVisibilityChange() gets called) + if (!mReuseInstance) + { + destroy(); + } + } + } +} + +/*virtual*/ +void LLFloater::closeHostedFloater() +{ + // When toggling *visibility*, close the host instead of the floater when hosted + if (getHost()) + { + getHost()->closeFloater(); + } + else + { + closeFloater(); + } +} + +/*virtual*/ +void LLFloater::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLPanel::reshape(width, height, called_from_parent); +} + +// virtual +void LLFloater::translate(S32 x, S32 y) +{ + LLView::translate(x, y); + + if (!mTranslateWithDependents || mDependents.empty()) + return; + + for (const LLHandle& handle : mDependents) + { + LLFloater* floater = handle.get(); + if (floater && floater->getSnapTarget() == getHandle()) + { + floater->LLView::translate(x, y); + } + } +} + +void LLFloater::releaseFocus() +{ + LLUI::getInstance()->removePopup(this); + + setFocus(false); + + if( gFocusMgr.childHasMouseCapture( this ) ) + { + gFocusMgr.setMouseCapture(NULL); + } +} + + +void LLFloater::setResizeLimits( S32 min_width, S32 min_height ) +{ + mMinWidth = min_width; + mMinHeight = min_height; + + for( S32 i = 0; i < 4; i++ ) + { + if( mResizeBar[i] ) + { + if (i == LLResizeBar::LEFT || i == LLResizeBar::RIGHT) + { + mResizeBar[i]->setResizeLimits( min_width, S32_MAX ); + } + else + { + mResizeBar[i]->setResizeLimits( min_height, S32_MAX ); + } + } + if( mResizeHandle[i] ) + { + mResizeHandle[i]->setResizeLimits( min_width, min_height ); + } + } +} + + +void LLFloater::center() +{ + if(getHost()) + { + // hosted floaters can't move + return; + } + centerWithin(gFloaterView->getRect()); +} + +LLMultiFloater* LLFloater::getHost() +{ + return (LLMultiFloater*)mHostHandle.get(); +} + +void LLFloater::applyControlsAndPosition(LLFloater* other) +{ + if (!applyDockState()) + { + if (!applyRectControl()) + { + applyPositioning(other, true); + } + } +} + +bool LLFloater::applyRectControl() +{ + bool saved_rect = false; + + LLRect screen_rect = calcScreenRect(); + mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); + + LLFloater* last_in_group = LLFloaterReg::getLastFloaterInGroup(mInstanceName); + if (last_in_group && last_in_group != this) + { + // other floaters in our group, position ourselves relative to them and don't save the rect + if (mDefaultRectForGroup) + { + mRectControl.clear(); + } + mPositioning = LLFloaterEnums::POSITIONING_CASCADE_GROUP; + } + else + { + bool rect_specified = false; + if (!mRectControl.empty()) + { + // If we have a saved rect, use it + const LLRect& rect = getControlGroup()->getRect(mRectControl); + if (rect.notEmpty()) saved_rect = true; + if (saved_rect) + { + setOrigin(rect.mLeft, rect.mBottom); + + if (mResizable) + { + reshape(llmax(mMinWidth, rect.getWidth()), llmax(mMinHeight, rect.getHeight())); + } + mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; + LLRect screen_rect = calcScreenRect(); + mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); + rect_specified = true; + } + } + + LLControlVariablePtr x_control = getControlGroup()->getControl(mPosXControl); + LLControlVariablePtr y_control = getControlGroup()->getControl(mPosYControl); + if (x_control.notNull() + && y_control.notNull() + && !x_control->isDefault() + && !y_control->isDefault()) + { + mPosition.mX = x_control->getValue().asReal(); + mPosition.mY = y_control->getValue().asReal(); + mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; + applyRelativePosition(); + + saved_rect = true; + } + else if ((mDefaultRelativeX != 0) && (mDefaultRelativeY != 0)) + { + mPosition.mX = mDefaultRelativeX; + mPosition.mY = mDefaultRelativeY; + mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; + applyRelativePosition(); + + saved_rect = true; + } + + // remember updated position + if (rect_specified) + { + storeRectControl(); + } + } + + if (saved_rect) + { + // propagate any derived positioning data back to settings file + storeRectControl(); + } + + + return saved_rect; +} + +bool LLFloater::applyDockState() +{ + bool docked = false; + + if (mDocStateControl.size() > 1) + { + docked = getControlGroup()->getBOOL(mDocStateControl); + setDocked(docked); + } + + return docked; +} + +void LLFloater::applyPositioning(LLFloater* other, bool on_open) +{ + // Otherwise position according to the positioning code + switch (mPositioning) + { + case LLFloaterEnums::POSITIONING_CENTERED: + center(); + break; + + case LLFloaterEnums::POSITIONING_SPECIFIED: + break; + + case LLFloaterEnums::POSITIONING_CASCADING: + if (!on_open) + { + applyRelativePosition(); + } + // fall through + case LLFloaterEnums::POSITIONING_CASCADE_GROUP: + if (on_open) + { + if (other != NULL && other != this) + { + stackWith(*other); + } + else + { + static const U32 CASCADING_FLOATER_HOFFSET = 0; + static const U32 CASCADING_FLOATER_VOFFSET = 0; + + const LLRect& snap_rect = gFloaterView->getSnapRect(); + + const S32 horizontal_offset = CASCADING_FLOATER_HOFFSET; + const S32 vertical_offset = snap_rect.getHeight() - CASCADING_FLOATER_VOFFSET; + + S32 rect_height = getRect().getHeight(); + setOrigin(horizontal_offset, vertical_offset - rect_height); + + translate(snap_rect.mLeft, snap_rect.mBottom); + } + setFollows(FOLLOWS_TOP | FOLLOWS_LEFT); + } + break; + + case LLFloaterEnums::POSITIONING_RELATIVE: + { + applyRelativePosition(); + + break; + } + default: + // Do nothing + break; + } +} + +void LLFloater::applyTitle() +{ + if (!mDragHandle) + { + return; + } + + if (isMinimized() && !mShortTitle.empty()) + { + mDragHandle->setTitle( mShortTitle ); + } + else + { + mDragHandle->setTitle ( mTitle ); + } + + if (getHost()) + { + getHost()->updateFloaterTitle(this); + } +} + +std::string LLFloater::getCurrentTitle() const +{ + return mDragHandle ? mDragHandle->getTitle() : LLStringUtil::null; +} + +void LLFloater::setTitle( const std::string& title ) +{ + mTitle = title; + applyTitle(); +} + +std::string LLFloater::getTitle() const +{ + if (mTitle.empty()) + { + return mDragHandle ? mDragHandle->getTitle() : LLStringUtil::null; + } + else + { + return mTitle; + } +} + +void LLFloater::setShortTitle( const std::string& short_title ) +{ + mShortTitle = short_title; + applyTitle(); +} + +std::string LLFloater::getShortTitle() const +{ + if (mShortTitle.empty()) + { + return mDragHandle ? mDragHandle->getTitle() : LLStringUtil::null; + } + else + { + return mShortTitle; + } +} + +bool LLFloater::canSnapTo(const LLView* other_view) +{ + if (NULL == other_view) + { + LL_WARNS() << "other_view is NULL" << LL_ENDL; + return false; + } + + if (other_view != getParent()) + { + const LLFloater* other_floaterp = dynamic_cast(other_view); + if (other_floaterp + && other_floaterp->getSnapTarget() == getHandle() + && mDependents.find(other_floaterp->getHandle()) != mDependents.end()) + { + // this is a dependent that is already snapped to us, so don't snap back to it + return false; + } + } + + return LLPanel::canSnapTo(other_view); +} + +void LLFloater::setSnappedTo(const LLView* snap_view) +{ + if (!snap_view || snap_view == getParent()) + { + clearSnapTarget(); + } + else + { + //RN: assume it's a floater as it must be a sibling to our parent floater + const LLFloater* floaterp = dynamic_cast(snap_view); + if (floaterp) + { + setSnapTarget(floaterp->getHandle()); + } + } +} + +void LLFloater::handleReshape(const LLRect& new_rect, bool by_user) +{ + const LLRect old_rect = getRect(); + LLView::handleReshape(new_rect, by_user); + + if (by_user && !getHost()) + { + LLFloaterView * floaterVp = dynamic_cast(getParent()); + if (floaterVp) + { + floaterVp->adjustToFitScreen(this, !isMinimized()); + } + } + + // if not minimized, adjust all snapped dependents to new shape + if (!isMinimized()) + { + if (by_user) + { + if (isDocked()) + { + setDocked( false, false); + } + mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; + LLRect screen_rect = calcScreenRect(); + mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); + } + storeRectControl(); + + // gather all snapped dependents + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ++dependent_it) + { + LLFloater* floaterp = dependent_it->get(); + // is a dependent snapped to us? + if (floaterp && floaterp->getSnapTarget() == getHandle()) + { + S32 delta_x = 0; + S32 delta_y = 0; + // check to see if it snapped to right or top, and move if dependee floater is resizing + LLRect dependent_rect = floaterp->getRect(); + if (dependent_rect.mLeft - getRect().mLeft >= old_rect.getWidth() || // dependent on my right? + dependent_rect.mRight == getRect().mLeft + old_rect.getWidth()) // dependent aligned with my right + { + // was snapped directly onto right side or aligned with it + delta_x += new_rect.getWidth() - old_rect.getWidth(); + } + if (dependent_rect.mBottom - getRect().mBottom >= old_rect.getHeight() || + dependent_rect.mTop == getRect().mBottom + old_rect.getHeight()) + { + // was snapped directly onto top side or aligned with it + delta_y += new_rect.getHeight() - old_rect.getHeight(); + } + + // take translation of dependee floater into account as well + delta_x += new_rect.mLeft - old_rect.mLeft; + delta_y += new_rect.mBottom - old_rect.mBottom; + + dependent_rect.translate(delta_x, delta_y); + floaterp->setShape(dependent_rect, by_user); + } + } + } + else + { + // If minimized, and origin has changed, set + // mHasBeenDraggedWhileMinimized to true + if ((new_rect.mLeft != old_rect.mLeft) || + (new_rect.mBottom != old_rect.mBottom)) + { + mHasBeenDraggedWhileMinimized = true; + } + } +} + +void LLFloater::setMinimized(bool minimize) +{ + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + static LLUICachedControl minimized_width ("UIMinimizedWidth", 0); + + if (minimize == mMinimized) return; + + if (mMinimizeSignal) + { + (*mMinimizeSignal)(this, LLSD(minimize)); + } + + if (minimize) + { + // minimized flag should be turned on before release focus + mMinimized = true; + mExpandedRect = getRect(); + + // If the floater has been dragged while minimized in the + // past, then locate it at its previous minimized location. + // Otherwise, ask the view for a minimize position. + if (mHasBeenDraggedWhileMinimized) + { + setOrigin(mPreviousMinimizedLeft, mPreviousMinimizedBottom); + } + else + { + S32 left, bottom; + gFloaterView->getMinimizePosition(&left, &bottom); + setOrigin( left, bottom ); + } + + if (mButtonsEnabled[BUTTON_MINIMIZE]) + { + mButtonsEnabled[BUTTON_MINIMIZE] = false; + mButtonsEnabled[BUTTON_RESTORE] = true; + } + + setBorderVisible(true); + + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); + ++dependent_it) + { + LLFloater* floaterp = dependent_it->get(); + if (floaterp) + { + if (floaterp->isMinimizeable()) + { + floaterp->setMinimized(true); + } + else if (!floaterp->isMinimized()) + { + floaterp->setVisible(false); + } + } + } + + // Lose keyboard focus when minimized + releaseFocus(); + + for (S32 i = 0; i < 4; i++) + { + if (mResizeBar[i] != NULL) + { + mResizeBar[i]->setEnabled(false); + } + if (mResizeHandle[i] != NULL) + { + mResizeHandle[i]->setEnabled(false); + } + } + + // Reshape *after* setting mMinimized + reshape( minimized_width, floater_header_size, true); + } + else + { + // If this window has been dragged while minimized (at any time), + // remember its position for the next time it's minimized. + if (mHasBeenDraggedWhileMinimized) + { + const LLRect& currentRect = getRect(); + mPreviousMinimizedLeft = currentRect.mLeft; + mPreviousMinimizedBottom = currentRect.mBottom; + } + + setOrigin( mExpandedRect.mLeft, mExpandedRect.mBottom ); + if (mButtonsEnabled[BUTTON_RESTORE]) + { + mButtonsEnabled[BUTTON_MINIMIZE] = true; + mButtonsEnabled[BUTTON_RESTORE] = false; + } + + // show dependent floater + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); + ++dependent_it) + { + LLFloater* floaterp = dependent_it->get(); + if (floaterp) + { + floaterp->setMinimized(false); + floaterp->setVisible(true); + } + } + + for (S32 i = 0; i < 4; i++) + { + if (mResizeBar[i] != NULL) + { + mResizeBar[i]->setEnabled(isResizable()); + } + if (mResizeHandle[i] != NULL) + { + mResizeHandle[i]->setEnabled(isResizable()); + } + } + + mMinimized = false; + setFrontmost(); + // Reshape *after* setting mMinimized + reshape( mExpandedRect.getWidth(), mExpandedRect.getHeight(), true ); + } + + make_ui_sound("UISndWindowClose"); + updateTitleButtons(); + applyTitle (); +} + +void LLFloater::setFocus( bool b ) +{ + if (b && getIsChrome()) + { + return; + } + LLView* last_focus = gFocusMgr.getLastFocusForGroup(this); + // a descendent already has focus + bool child_had_focus = hasFocus(); + + // give focus to first valid descendent + LLPanel::setFocus(b); + + if (b) + { + // only push focused floaters to front of stack if not in midst of ctrl-tab cycle + LLFloaterView * parent = dynamic_cast(getParent()); + if (!getHost() && parent && !parent->getCycleMode()) + { + if (!isFrontmost()) + { + setFrontmost(); + } + } + + // when getting focus, delegate to last descendent which had focus + if (last_focus && !child_had_focus && + last_focus->isInEnabledChain() && + last_focus->isInVisibleChain()) + { + // *FIX: should handle case where focus doesn't stick + last_focus->setFocus(true); + } + } + updateTransparency(b ? TT_ACTIVE : TT_INACTIVE); +} + +// virtual +void LLFloater::setRect(const LLRect &rect) +{ + LLPanel::setRect(rect); + layoutDragHandle(); + layoutResizeCtrls(); +} + +// virtual +void LLFloater::setIsChrome(bool is_chrome) +{ + // chrome floaters don't take focus at all + if (is_chrome) + { + // remove focus if we're changing to chrome + setFocus(false); + // can't Ctrl-Tab to "chrome" floaters + setFocusRoot(false); + mButtons[BUTTON_CLOSE]->setToolTip(LLStringExplicit(getButtonTooltip(Params(), BUTTON_CLOSE, is_chrome))); + } + + LLPanel::setIsChrome(is_chrome); +} + +// Change the draw style to account for the foreground state. +void LLFloater::setForeground(bool front) +{ + if (front != mForeground) + { + mForeground = front; + if (mDragHandle) + mDragHandle->setForeground( front ); + + if (!front) + { + releaseFocus(); + } + + setBackgroundOpaque( front ); + } +} + +void LLFloater::cleanupHandles() +{ + // remove handles to non-existent dependents + for(handle_set_iter_t dependent_it = mDependents.begin(); + dependent_it != mDependents.end(); ) + { + LLFloater* floaterp = dependent_it->get(); + if (!floaterp) + { + dependent_it = mDependents.erase(dependent_it); + } + else + { + ++dependent_it; + } + } +} + +void LLFloater::setHost(LLMultiFloater* host) +{ + if (mHostHandle.isDead() && host) + { + // make buttons smaller for hosted windows to differentiate from parent + mButtonScale = 0.9f; + + // add tear off button + if (mCanTearOff) + { + mButtonsEnabled[BUTTON_TEAR_OFF] = true; + } + } + else if (!mHostHandle.isDead() && !host) + { + mButtonScale = 1.f; + //mButtonsEnabled[BUTTON_TEAR_OFF] = false; + } + if (host) + { + mHostHandle = host->getHandle(); + mLastHostHandle = host->getHandle(); + } + else + { + mHostHandle.markDead(); + } + + updateTitleButtons(); +} + +void LLFloater::moveResizeHandlesToFront() +{ + for( S32 i = 0; i < 4; i++ ) + { + if( mResizeBar[i] ) + { + sendChildToFront(mResizeBar[i]); + } + } + + for( S32 i = 0; i < 4; i++ ) + { + if( mResizeHandle[i] ) + { + sendChildToFront(mResizeHandle[i]); + } + } +} + +/*virtual*/ +bool LLFloater::isFrontmost() +{ + LLFloaterView* floater_view = getParentByType(); + return getVisible() + && (floater_view + && floater_view->getFrontmost() == this); +} + +void LLFloater::addDependentFloater(LLFloater* floaterp, bool reposition, bool resize) +{ + mDependents.insert(floaterp->getHandle()); + floaterp->mDependeeHandle = getHandle(); + + if (reposition) + { + LLRect rect = gFloaterView->findNeighboringPosition(this, floaterp); + if (resize) + { + const LLRect& base = getRect(); + if (rect.mTop == base.mTop) + rect.mBottom = base.mBottom; + else if (rect.mLeft == base.mLeft) + rect.mRight = base.mRight; + floaterp->reshape(rect.getWidth(), rect.getHeight(), false); + } + floaterp->setRect(rect); + floaterp->setSnapTarget(getHandle()); + } + gFloaterView->adjustToFitScreen(floaterp, false, true); + if (floaterp->isFrontmost()) + { + // make sure to bring self and sibling floaters to front + gFloaterView->bringToFront(floaterp, floaterp->getAutoFocus() && !getIsChrome()); + } +} + +void LLFloater::addDependentFloater(LLHandle dependent, bool reposition, bool resize) +{ + LLFloater* dependent_floaterp = dependent.get(); + if(dependent_floaterp) + { + addDependentFloater(dependent_floaterp, reposition, resize); + } +} + +void LLFloater::removeDependentFloater(LLFloater* floaterp) +{ + mDependents.erase(floaterp->getHandle()); + floaterp->mDependeeHandle = LLHandle(); +} + +void LLFloater::fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& constraint, S32 min_overlap_pixels) +{ + LLRect total_rect = getRect(); + + for (const LLHandle& handle : mDependents) + { + LLFloater* floater = handle.get(); + if (floater && floater->getSnapTarget() == getHandle()) + { + total_rect.unionWith(floater->getRect()); + } + } + + S32 delta_left = left.notEmpty() ? left.mRight - total_rect.mRight : 0; + S32 delta_bottom = bottom.notEmpty() ? bottom.mTop - total_rect.mTop : 0; + S32 delta_right = right.notEmpty() ? right.mLeft - total_rect.mLeft : 0; + + // move floater with dependings fully onscreen + mTranslateWithDependents = true; + if (translateRectIntoRect(total_rect, constraint, min_overlap_pixels)) + { + clearSnapTarget(); + } + else if (delta_left > 0 && total_rect.mTop < left.mTop && total_rect.mBottom > left.mBottom) + { + translate(delta_left, 0); + } + else if (delta_bottom > 0 && total_rect.mLeft > bottom.mLeft && total_rect.mRight < bottom.mRight) + { + translate(0, delta_bottom); + } + else if (delta_right < 0 && total_rect.mTop < right.mTop && total_rect.mBottom > right.mBottom) + { + translate(delta_right, 0); + } + mTranslateWithDependents = false; +} + +bool LLFloater::offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index) +{ + if( mButtonsEnabled[index] ) + { + LLButton* my_butt = mButtons[index]; + S32 local_x = x - my_butt->getRect().mLeft; + S32 local_y = y - my_butt->getRect().mBottom; + + if ( + my_butt->pointInView(local_x, local_y) && + my_butt->handleMouseDown(local_x, local_y, mask)) + { + // the button handled it + return true; + } + } + return false; +} + +bool LLFloater::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + LLPanel::handleScrollWheel(x,y,clicks); + return true;//always +} + +// virtual +bool LLFloater::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LL_DEBUGS() << "LLFloater::handleMouseUp calling LLPanel (really LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << LL_ENDL; + bool handled = LLPanel::handleMouseUp(x,y,mask); // Not implemented in LLPanel so this actually calls LLView + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + } + return handled; +} + +// virtual +bool LLFloater::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if( mMinimized ) + { + // Offer the click to titlebar buttons. + // Note: this block and the offerClickToButton helper method can be removed + // because the parent container will handle it for us but we'll keep it here + // for safety until after reworking the panel code to manage hidden children. + if(offerClickToButton(x, y, mask, BUTTON_CLOSE)) return true; + if(offerClickToButton(x, y, mask, BUTTON_RESTORE)) return true; + if(offerClickToButton(x, y, mask, BUTTON_TEAR_OFF)) return true; + if(offerClickToButton(x, y, mask, BUTTON_DOCK)) return true; + + setFrontmost(true, false); + // Otherwise pass to drag handle for movement + return mDragHandle->handleMouseDown(x, y, mask); + } + else + { + bringToFront( x, y ); + bool handled = LLPanel::handleMouseDown( x, y, mask ); + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + } + return handled; + } +} + +// virtual +bool LLFloater::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool was_minimized = mMinimized; + bringToFront( x, y ); + return was_minimized || LLPanel::handleRightMouseDown( x, y, mask ); +} + +bool LLFloater::handleMiddleMouseDown(S32 x, S32 y, MASK mask) +{ + bringToFront( x, y ); + return LLPanel::handleMiddleMouseDown( x, y, mask ); +} + + +// virtual +bool LLFloater::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + bool was_minimized = mMinimized; + setMinimized(false); + return was_minimized || LLPanel::handleDoubleClick(x, y, mask); +} + +// virtual +void LLFloater::bringToFront( S32 x, S32 y ) +{ + if (getVisible() && pointInView(x, y)) + { + LLMultiFloater* hostp = getHost(); + if (hostp) + { + hostp->showFloater(this); + } + else + { + LLFloaterView* parent = dynamic_cast( getParent() ); + if (parent) + { + parent->bringToFront(this, !getIsChrome()); + } + } + } +} + +// virtual +void LLFloater::goneFromFront() +{ + if (mAutoClose) + { + closeFloater(); + } +} + +// virtual +void LLFloater::setVisibleAndFrontmost(bool take_focus,const LLSD& key) +{ + LLUIUsage::instance().logFloater(getInstanceName()); + LLMultiFloater* hostp = getHost(); + if (hostp) + { + hostp->setVisible(true); + hostp->setFrontmost(take_focus); + } + else + { + setVisible(true); + setFrontmost(take_focus); + } +} + +void LLFloater::setFrontmost(bool take_focus, bool restore) +{ + LLMultiFloater* hostp = getHost(); + if (hostp) + { + // this will bring the host floater to the front and select + // the appropriate panel + hostp->showFloater(this); + } + else + { + // there are more than one floater view + // so we need to query our parent directly + LLFloaterView * parent = dynamic_cast( getParent() ); + if (parent) + { + parent->bringToFront(this, take_focus, restore); + } + + // Make sure to set the appropriate transparency type (STORM-732). + updateTransparency(hasFocus() || getIsChrome() ? TT_ACTIVE : TT_INACTIVE); + } +} + +void LLFloater::setCanDock(bool b) +{ + if(b != mCanDock) + { + mCanDock = b; + if(mCanDock) + { + mButtonsEnabled[BUTTON_DOCK] = !mDocked; + } + else + { + mButtonsEnabled[BUTTON_DOCK] = false; + } + } + updateTitleButtons(); +} + +void LLFloater::setDocked(bool docked, bool pop_on_undock) +{ + if(docked != mDocked && mCanDock) + { + mDocked = docked; + mButtonsEnabled[BUTTON_DOCK] = !mDocked; + + if (mDocked) + { + setMinimized(false); + mPositioning = LLFloaterEnums::POSITIONING_RELATIVE; + } + + updateTitleButtons(); + + storeDockStateControl(); + } + +} + +// static +void LLFloater::onClickMinimize(LLFloater* self) +{ + if (!self) + return; + self->setMinimized( !self->isMinimized() ); +} + +void LLFloater::onClickTearOff(LLFloater* self) +{ + if (!self) + return; + S32 floater_header_size = self->mHeaderHeight; + LLMultiFloater* host_floater = self->getHost(); + if (host_floater) //Tear off + { + LLRect new_rect; + host_floater->removeFloater(self); + // reparent to floater view + gFloaterView->addChild(self); + + self->openFloater(self->getKey()); + if (self->mSaveRect && !self->mRectControl.empty()) + { + self->applyRectControl(); + } + else + { // only force position for floaters that don't have that data saved + new_rect.setLeftTopAndSize(host_floater->getRect().mLeft + 5, host_floater->getRect().mTop - floater_header_size - 5, self->getRect().getWidth(), self->getRect().getHeight()); + self->setRect(new_rect); + } + gFloaterView->adjustToFitScreen(self, false); + // give focus to new window to keep continuity for the user + self->setFocus(true); + self->setTornOff(true); + } + else //Attach to parent. + { + LLMultiFloater* new_host = (LLMultiFloater*)self->mLastHostHandle.get(); + if (new_host) + { + if (self->mSaveRect) + { + LLRect screen_rect = self->calcScreenRect(); + self->mPosition = LLCoordGL(screen_rect.getCenterX(), screen_rect.getCenterY()).convert(); + self->storeRectControl(); + } + self->setMinimized(false); // to reenable minimize button if it was minimized + new_host->showFloater(self); + // make sure host is visible + new_host->openFloater(new_host->getKey()); + } + self->setTornOff(false); + } + self->updateTitleButtons(); + self->setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); +} + +// static +void LLFloater::onClickDock(LLFloater* self) +{ + if(self && self->mCanDock) + { + self->setDocked(!self->mDocked, true); + } +} + +// static +void LLFloater::onClickHelp( LLFloater* self ) +{ + if (self && LLUI::getInstance()->mHelpImpl) + { + // find the current help context for this floater + std::string help_topic; + if (self->findHelpTopic(help_topic)) + { + LLUI::getInstance()->mHelpImpl->showTopic(help_topic); + } + } +} + +void LLFloater::initRectControl() +{ + // save_rect and save_visibility only apply to registered floaters + if (mSaveRect) + { + std::string ctrl_name = getControlName(mInstanceName, mKey); + mRectControl = LLFloaterReg::declareRectControl(ctrl_name); + mPosXControl = LLFloaterReg::declarePosXControl(ctrl_name); + mPosYControl = LLFloaterReg::declarePosYControl(ctrl_name); + } +} + +// static +void LLFloater::closeFrontmostFloater() +{ + LLFloater* floater_to_close = gFloaterView->getFrontmostClosableFloater(); + if(floater_to_close) + { + floater_to_close->closeFloater(); + } + + // if nothing took focus after closing focused floater + // give it to next floater (to allow closing multiple windows via keyboard in rapid succession) + if (gFocusMgr.getKeyboardFocus() == NULL) + { + // HACK: use gFloaterView directly in case we are using Ctrl-W to close snapshot window + // which sits in gSnapshotFloaterView, and needs to pass focus on to normal floater view + gFloaterView->focusFrontFloater(); + } +} + + +// static +void LLFloater::onClickClose( LLFloater* self ) +{ + if (!self) + return; + self->onClickCloseBtn(); +} + +void LLFloater::onClickCloseBtn(bool app_quitting) +{ + closeFloater(false); +} + + +// virtual +void LLFloater::draw() +{ + const F32 alpha = getCurrentTransparency(); + + // draw background + if( isBackgroundVisible() ) + { + drawShadow(this); + + S32 left = LLPANEL_BORDER_WIDTH; + S32 top = getRect().getHeight() - LLPANEL_BORDER_WIDTH; + S32 right = getRect().getWidth() - LLPANEL_BORDER_WIDTH; + S32 bottom = LLPANEL_BORDER_WIDTH; + + LLUIImage* image = NULL; + LLColor4 color; + LLColor4 overlay_color; + if (isBackgroundOpaque()) + { + // NOTE: image may not be set + image = getBackgroundImage(); + color = getBackgroundColor(); + overlay_color = getBackgroundImageOverlay(); + } + else + { + image = getTransparentImage(); + color = getTransparentColor(); + overlay_color = getTransparentImageOverlay(); + } + + if (image) + { + // We're using images for this floater's backgrounds + image->draw(getLocalRect(), overlay_color % alpha); + } + else + { + // We're not using images, use old-school flat colors + gl_rect_2d( left, top, right, bottom, color % alpha ); + + // draw highlight on title bar to indicate focus. RDW + if(hasFocus() + && !getIsChrome() + && !getCurrentTitle().empty()) + { + static LLUIColor titlebar_focus_color = LLUIColorTable::instance().getColor("TitleBarFocusColor"); + + const LLFontGL* font = LLFontGL::getFontSansSerif(); + LLRect r = getRect(); + gl_rect_2d_offset_local(0, r.getHeight(), r.getWidth(), r.getHeight() - font->getLineHeight() - 1, + titlebar_focus_color % alpha, 0, true); + } + } + } + + LLPanel::updateDefaultBtn(); + + if( getDefaultButton() ) + { + if (hasFocus() && getDefaultButton()->getEnabled()) + { + LLFocusableElement* focus_ctrl = gFocusMgr.getKeyboardFocus(); + // is this button a direct descendent and not a nested widget (e.g. checkbox)? + bool focus_is_child_button = dynamic_cast(focus_ctrl) != NULL && dynamic_cast(focus_ctrl)->getParent() == this; + // only enable default button when current focus is not a button + getDefaultButton()->setBorderEnabled(!focus_is_child_button); + } + else + { + getDefaultButton()->setBorderEnabled(false); + } + } + if (isMinimized()) + { + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + drawChild(mButtons[i]); + } + drawChild(mDragHandle, 0, 0, true); + } + else + { + // don't call LLPanel::draw() since we've implemented custom background rendering + LLView::draw(); + } + + // update tearoff button for torn off floaters + // when last host goes away + if (mCanTearOff && !getHost()) + { + LLFloater* old_host = mLastHostHandle.get(); + if (!old_host) + { + setCanTearOff(false); + } + } +} + +void LLFloater::drawShadow(LLPanel* panel) +{ + S32 left = LLPANEL_BORDER_WIDTH; + S32 top = panel->getRect().getHeight() - LLPANEL_BORDER_WIDTH; + S32 right = panel->getRect().getWidth() - LLPANEL_BORDER_WIDTH; + S32 bottom = LLPANEL_BORDER_WIDTH; + + static LLUIColor shadow_color_cached = LLUIColorTable::instance().getColor("ColorDropShadow"); + LLColor4 shadow_color = shadow_color_cached; + F32 shadow_offset = (F32)DROP_SHADOW_FLOATER; + + if (!panel->isBackgroundOpaque()) + { + shadow_offset *= 0.2f; + shadow_color.mV[VALPHA] *= 0.5f; + } + gl_drop_shadow(left, top, right, bottom, + shadow_color % getCurrentTransparency(), + ll_round(shadow_offset)); +} + +void LLFloater::updateTransparency(LLView* view, ETypeTransparency transparency_type) +{ + if (!view) return; + child_list_t children = *view->getChildList(); + child_list_t::iterator it = children.begin(); + + LLUICtrl* ctrl = dynamic_cast(view); + if (ctrl) + { + ctrl->setTransparencyType(transparency_type); + } + + for(; it != children.end(); ++it) + { + updateTransparency(*it, transparency_type); + } +} + +void LLFloater::updateTransparency(ETypeTransparency transparency_type) +{ + updateTransparency(this, transparency_type); +} + +void LLFloater::setCanMinimize(bool can_minimize) +{ + // if removing minimize/restore button programmatically, + // go ahead and unminimize floater + mCanMinimize = can_minimize; + if (!can_minimize) + { + setMinimized(false); + } + + mButtonsEnabled[BUTTON_MINIMIZE] = can_minimize && !isMinimized(); + mButtonsEnabled[BUTTON_RESTORE] = can_minimize && isMinimized(); + + updateTitleButtons(); +} + +void LLFloater::setCanClose(bool can_close) +{ + mCanClose = can_close; + mButtonsEnabled[BUTTON_CLOSE] = can_close; + + updateTitleButtons(); +} + +void LLFloater::setCanTearOff(bool can_tear_off) +{ + mCanTearOff = can_tear_off; + mButtonsEnabled[BUTTON_TEAR_OFF] = mCanTearOff && !mHostHandle.isDead(); + + updateTitleButtons(); +} + + +void LLFloater::setCanResize(bool can_resize) +{ + mResizable = can_resize; + enableResizeCtrls(can_resize); +} + +void LLFloater::setCanDrag(bool can_drag) +{ + // if we delete drag handle, we no longer have access to the floater's title + // so just enable/disable it + if (!can_drag && mDragHandle->getEnabled()) + { + mDragHandle->setEnabled(false); + } + else if (can_drag && !mDragHandle->getEnabled()) + { + mDragHandle->setEnabled(true); + } +} + +bool LLFloater::getCanDrag() +{ + return mDragHandle->getEnabled(); +} + + +void LLFloater::updateTitleButtons() +{ + static LLUICachedControl floater_close_box_size ("UIFloaterCloseBoxSize", 0); + static LLUICachedControl close_box_from_top ("UICloseBoxFromTop", 0); + LLRect buttons_rect; + S32 button_count = 0; + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + if (!mButtons[i]) + { + continue; + } + + bool enabled = mButtonsEnabled[i]; + if (i == BUTTON_HELP) + { + // don't show the help button if the floater is minimized + // or if it is a docked tear-off floater + if (isMinimized() || (mButtonsEnabled[BUTTON_TEAR_OFF] && ! mTornOff)) + { + enabled = false; + } + } + if (i == BUTTON_CLOSE && mButtonScale != 1.f) + { + //*HACK: always render close button for hosted floaters so + //that users don't accidentally hit the button when + //closing multiple windows in the chatterbox + enabled = true; + } + + mButtons[i]->setEnabled(enabled); + + if (enabled) + { + button_count++; + + LLRect btn_rect; + if (mDragOnLeft) + { + btn_rect.setLeftTopAndSize( + LLPANEL_BORDER_WIDTH, + getRect().getHeight() - close_box_from_top - (floater_close_box_size + 1) * button_count, + ll_round((F32)floater_close_box_size * mButtonScale), + ll_round((F32)floater_close_box_size * mButtonScale)); + } + else + { + btn_rect.setLeftTopAndSize( + getRect().getWidth() - LLPANEL_BORDER_WIDTH - (floater_close_box_size + 1) * button_count, + getRect().getHeight() - close_box_from_top, + ll_round((F32)floater_close_box_size * mButtonScale), + ll_round((F32)floater_close_box_size * mButtonScale)); + } + + // first time here, init 'buttons_rect' + if(1 == button_count) + { + buttons_rect = btn_rect; + } + else + { + // if mDragOnLeft=true then buttons are on top-left side vertically aligned + // title is not displayed in this case, calculating 'buttons_rect' for future use + mDragOnLeft ? buttons_rect.mBottom -= btn_rect.mBottom : + buttons_rect.mLeft = btn_rect.mLeft; + } + mButtons[i]->setRect(btn_rect); + mButtons[i]->setVisible(true); + // the restore button should have a tab stop so that it takes action when you Ctrl-Tab to a minimized floater + mButtons[i]->setTabStop(i == BUTTON_RESTORE); + } + else + { + mButtons[i]->setVisible(false); + } + } + if (mDragHandle) + { + localRectToOtherView(buttons_rect, &buttons_rect, mDragHandle); + mDragHandle->setButtonsRect(buttons_rect); + } +} + +void LLFloater::drawConeToOwner(F32 &context_cone_opacity, + F32 max_cone_opacity, + LLView *owner_view, + F32 fade_time, + F32 contex_cone_in_alpha, + F32 contex_cone_out_alpha) +{ + if (owner_view + && owner_view->isInVisibleChain() + && hasFocus() + && context_cone_opacity > 0.001f + && gFocusMgr.childHasKeyboardFocus(this)) + { + // draw cone of context pointing back to owner (e.x. texture swatch) + LLRect owner_rect; + owner_view->localRectToOtherView(owner_view->getLocalRect(), &owner_rect, this); + LLRect local_rect = getLocalRect(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLEnable(GL_CULL_FACE); + gGL.begin(LLRender::QUADS); + { + gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); + gGL.vertex2i(owner_rect.mLeft, owner_rect.mTop); + gGL.vertex2i(owner_rect.mRight, owner_rect.mTop); + gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); + gGL.vertex2i(local_rect.mRight, local_rect.mTop); + gGL.vertex2i(local_rect.mLeft, local_rect.mTop); + + gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); + gGL.vertex2i(local_rect.mLeft, local_rect.mTop); + gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); + gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); + gGL.vertex2i(owner_rect.mLeft, owner_rect.mBottom); + gGL.vertex2i(owner_rect.mLeft, owner_rect.mTop); + + gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); + gGL.vertex2i(local_rect.mRight, local_rect.mBottom); + gGL.vertex2i(local_rect.mRight, local_rect.mTop); + gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); + gGL.vertex2i(owner_rect.mRight, owner_rect.mTop); + gGL.vertex2i(owner_rect.mRight, owner_rect.mBottom); + + + gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); + gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); + gGL.vertex2i(local_rect.mRight, local_rect.mBottom); + gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); + gGL.vertex2i(owner_rect.mRight, owner_rect.mBottom); + gGL.vertex2i(owner_rect.mLeft, owner_rect.mBottom); + } + gGL.end(); + } + + if (gFocusMgr.childHasMouseCapture(getDragHandle())) + { + context_cone_opacity = lerp(context_cone_opacity, max_cone_opacity, LLSmoothInterpolation::getInterpolant(fade_time)); + } + else + { + context_cone_opacity = lerp(context_cone_opacity, 0.f, LLSmoothInterpolation::getInterpolant(fade_time)); + } +} + +void LLFloater::buildButtons(const Params& floater_params) +{ + static LLUICachedControl floater_close_box_size ("UIFloaterCloseBoxSize", 0); + static LLUICachedControl close_box_from_top ("UICloseBoxFromTop", 0); + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + if (mButtons[i]) + { + removeChild(mButtons[i]); + delete mButtons[i]; + mButtons[i] = NULL; + } + + LLRect btn_rect; + if (mDragOnLeft) + { + btn_rect.setLeftTopAndSize( + LLPANEL_BORDER_WIDTH, + getRect().getHeight() - close_box_from_top - (floater_close_box_size + 1) * (i + 1), + ll_round(floater_close_box_size * mButtonScale), + ll_round(floater_close_box_size * mButtonScale)); + } + else + { + btn_rect.setLeftTopAndSize( + getRect().getWidth() - LLPANEL_BORDER_WIDTH - (floater_close_box_size + 1) * (i + 1), + getRect().getHeight() - close_box_from_top, + ll_round(floater_close_box_size * mButtonScale), + ll_round(floater_close_box_size * mButtonScale)); + } + + LLButton::Params p; + p.name(sButtonNames[i]); + p.rect(btn_rect); + p.image_unselected = getButtonImage(floater_params, (EFloaterButton)i); + // Selected, no matter if hovered or not, is "pressed" + LLUIImage* pressed_image = getButtonPressedImage(floater_params, (EFloaterButton)i); + p.image_selected = pressed_image; + p.image_hover_selected = pressed_image; + // Use a glow effect when the user hovers over the button + // These icons are really small, need glow amount increased + p.hover_glow_amount( 0.33f ); + p.click_callback.function(boost::bind(sButtonCallbacks[i], this)); + p.tab_stop(false); + p.follows.flags(FOLLOWS_TOP|FOLLOWS_RIGHT); + p.tool_tip = getButtonTooltip(floater_params, (EFloaterButton)i, getIsChrome()); + p.scale_image(true); + p.chrome(true); + + LLButton* buttonp = LLUICtrlFactory::create(p); + addChild(buttonp); + mButtons[i] = buttonp; + } + + updateTitleButtons(); +} + +// static +LLUIImage* LLFloater::getButtonImage(const Params& p, EFloaterButton e) +{ + switch(e) + { + default: + case BUTTON_CLOSE: + return p.close_image; + case BUTTON_RESTORE: + return p.restore_image; + case BUTTON_MINIMIZE: + return p.minimize_image; + case BUTTON_TEAR_OFF: + return p.tear_off_image; + case BUTTON_DOCK: + return p.dock_image; + case BUTTON_HELP: + return p.help_image; + } +} + +// static +LLUIImage* LLFloater::getButtonPressedImage(const Params& p, EFloaterButton e) +{ + switch(e) + { + default: + case BUTTON_CLOSE: + return p.close_pressed_image; + case BUTTON_RESTORE: + return p.restore_pressed_image; + case BUTTON_MINIMIZE: + return p.minimize_pressed_image; + case BUTTON_TEAR_OFF: + return p.tear_off_pressed_image; + case BUTTON_DOCK: + return p.dock_pressed_image; + case BUTTON_HELP: + return p.help_pressed_image; + } +} + +// static +std::string LLFloater::getButtonTooltip(const Params& p, EFloaterButton e, bool is_chrome) +{ + // EXT-4081 (Lag Meter: Ctrl+W does not close floater) + // If floater is chrome set 'Close' text for close button's tooltip + if(is_chrome && BUTTON_CLOSE == e) + { + static std::string close_tooltip_chrome = LLTrans::getString("BUTTON_CLOSE_CHROME"); + return close_tooltip_chrome; + } + // TODO: per-floater localizable tooltips set in XML + return sButtonToolTips[e]; +} + +///////////////////////////////////////////////////// +// LLFloaterView + +static LLDefaultChildRegistry::Register r("floater_view"); + +LLFloaterView::LLFloaterView (const Params& p) +: LLUICtrl (p), + mFocusCycleMode(false), + mMinimizePositionVOffset(0), + mSnapOffsetBottom(0), + mSnapOffsetRight(0) +{ + mSnapView = getHandle(); +} + +// By default, adjust vertical. +void LLFloaterView::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLView::reshape(width, height, called_from_parent); + + mLastSnapRect = getSnapRect(); + + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + LLFloater* floaterp = dynamic_cast(viewp); + if (floaterp->isDependent()) + { + // dependents are moved with their "dependee" + continue; + } + + if (!floaterp->isMinimized() && floaterp->getCanDrag()) + { + LLRect old_rect = floaterp->getRect(); + floaterp->applyPositioning(NULL, false); + LLRect new_rect = floaterp->getRect(); + + //LLRect r = floaterp->getRect(); + + //// Compute absolute distance from each edge of screen + //S32 left_offset = llabs(r.mLeft - 0); + //S32 right_offset = llabs(old_right - r.mRight); + + //S32 top_offset = llabs(old_top - r.mTop); + //S32 bottom_offset = llabs(r.mBottom - 0); + + S32 translate_x = new_rect.mLeft - old_rect.mLeft; + S32 translate_y = new_rect.mBottom - old_rect.mBottom; + + //if (left_offset > right_offset) + //{ + // translate_x = new_right - old_right; + //} + + //if (top_offset < bottom_offset) + //{ + // translate_y = new_top - old_top; + //} + + // don't reposition immovable floaters + //if (floaterp->getCanDrag()) + //{ + // floaterp->translate(translate_x, translate_y); + //} + for (LLHandle dependent_floater : floaterp->mDependents) + { + if (dependent_floater.get()) + { + dependent_floater.get()->translate(translate_x, translate_y); + } + } + } + } +} + + +void LLFloaterView::restoreAll() +{ + // make sure all subwindows aren't minimized + child_list_t child_list = *(getChildList()); + for (LLView* child : child_list) + { + LLFloater* floaterp = dynamic_cast(child); + if (floaterp) + { + floaterp->setMinimized(false); + } + } + + // *FIX: make sure dependents are restored + + // children then deleted by default view constructor +} + + +LLRect LLFloaterView::findNeighboringPosition( LLFloater* reference_floater, LLFloater* neighbor ) +{ + LLRect base_rect = reference_floater->getRect(); + LLRect::tCoordType width = neighbor->getRect().getWidth(); + LLRect::tCoordType height = neighbor->getRect().getHeight(); + LLRect new_rect = neighbor->getRect(); + + LLRect expanded_base_rect = base_rect; + expanded_base_rect.stretch(10); + for(LLFloater::handle_set_iter_t dependent_it = reference_floater->mDependents.begin(); + dependent_it != reference_floater->mDependents.end(); ++dependent_it) + { + LLFloater* sibling = dependent_it->get(); + // check for dependents within 10 pixels of base floater + if (sibling && + sibling != neighbor && + sibling->getVisible() && + expanded_base_rect.overlaps(sibling->getRect())) + { + base_rect.unionWith(sibling->getRect()); + } + } + + LLRect::tCoordType left_margin = llmax(0, base_rect.mLeft); + LLRect::tCoordType right_margin = llmax(0, getRect().getWidth() - base_rect.mRight); + LLRect::tCoordType top_margin = llmax(0, getRect().getHeight() - base_rect.mTop); + LLRect::tCoordType bottom_margin = llmax(0, base_rect.mBottom); + + // find position for floater in following order + // right->left->bottom->top + for (S32 i = 0; i < 5; i++) + { + if (right_margin > width) + { + new_rect.translate(base_rect.mRight - neighbor->getRect().mLeft, base_rect.mTop - neighbor->getRect().mTop); + return new_rect; + } + else if (left_margin > width) + { + new_rect.translate(base_rect.mLeft - neighbor->getRect().mRight, base_rect.mTop - neighbor->getRect().mTop); + return new_rect; + } + else if (bottom_margin > height) + { + new_rect.translate(base_rect.mLeft - neighbor->getRect().mLeft, base_rect.mBottom - neighbor->getRect().mTop); + return new_rect; + } + else if (top_margin > height) + { + new_rect.translate(base_rect.mLeft - neighbor->getRect().mLeft, base_rect.mTop - neighbor->getRect().mBottom); + return new_rect; + } + + // keep growing margins to find "best" fit + left_margin += 20; + right_margin += 20; + top_margin += 20; + bottom_margin += 20; + } + + // didn't find anything, return initial rect + return new_rect; +} + + +void LLFloaterView::bringToFront(LLFloater* child, bool give_focus, bool restore) +{ + if (!child) + return; + + LLFloater* front_child = mFrontChildHandle.get(); + if (front_child == child) + { + if (give_focus && child->canFocusStealFrontmost() && !gFocusMgr.childHasKeyboardFocus(child)) + { + child->setFocus(true); + } + return; + } + + if (front_child && front_child->getVisible()) + { + front_child->goneFromFront(); + } + + mFrontChildHandle = child->getHandle(); + + // *TODO: make this respect floater's mAutoFocus value, instead of + // using parameter + if (child->getHost()) + { + // this floater is hosted elsewhere and hence not one of our children, abort + return; + } + std::vector floaters_to_move; + // Look at all floaters...tab + for (child_list_const_iter_t child_it = beginChild(); child_it != endChild(); ++child_it) + { + LLFloater* floater = dynamic_cast(*child_it); + + // ...but if I'm a dependent floater... + if (floater && child->isDependent()) + { + // ...look for floaters that have me as a dependent... + LLFloater::handle_set_iter_t found_dependent = floater->mDependents.find(child->getHandle()); + + if (found_dependent != floater->mDependents.end()) + { + // ...and make sure all children of that floater (including me) are brought to front... + for (LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); + dependent_it != floater->mDependents.end(); ++dependent_it) + { + LLFloater* sibling = dependent_it->get(); + if (sibling) + { + floaters_to_move.push_back(sibling); + } + } + //...before bringing my parent to the front... + floaters_to_move.push_back(floater); + } + } + } + + std::vector::iterator floater_it; + for(floater_it = floaters_to_move.begin(); floater_it != floaters_to_move.end(); ++floater_it) + { + LLFloater* floaterp = *floater_it; + sendChildToFront(floaterp); + + // always unminimize dependee, but allow dependents to stay minimized + if (!floaterp->isDependent()) + { + floaterp->setMinimized(false); + } + } + floaters_to_move.clear(); + + // ...then bringing my own dependents to the front... + for (LLFloater::handle_set_iter_t dependent_it = child->mDependents.begin(); + dependent_it != child->mDependents.end(); ++dependent_it) + { + LLFloater* dependent = dependent_it->get(); + if (dependent) + { + sendChildToFront(dependent); + } + } + + // ...and finally bringing myself to front + // (do this last, so that I'm left in front at end of this call) + if (*beginChild() != child) + { + sendChildToFront(child); + } + + if(restore) + { + child->setMinimized(false); + } + + if (give_focus && !gFocusMgr.childHasKeyboardFocus(child)) + { + child->setFocus(true); + // floater did not take focus, so relinquish focus to world + if (!child->hasFocus()) + { + gFocusMgr.setKeyboardFocus(NULL); + } + } +} + +void LLFloaterView::highlightFocusedFloater() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater *floater = (LLFloater *)(*child_it); + + // skip dependent floaters, as we'll handle them in a batch along with their dependee(?) + if (floater->isDependent()) + { + continue; + } + + bool floater_or_dependent_has_focus = gFocusMgr.childHasKeyboardFocus(floater); + for(LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); + dependent_it != floater->mDependents.end(); + ++dependent_it) + { + LLFloater* dependent_floaterp = dependent_it->get(); + if (dependent_floaterp && gFocusMgr.childHasKeyboardFocus(dependent_floaterp)) + { + floater_or_dependent_has_focus = true; + } + } + + // now set this floater and all its dependents + floater->setForeground(floater_or_dependent_has_focus); + + for(LLFloater::handle_set_iter_t dependent_it = floater->mDependents.begin(); + dependent_it != floater->mDependents.end(); ) + { + LLFloater* dependent_floaterp = dependent_it->get(); + if (dependent_floaterp) + { + dependent_floaterp->setForeground(floater_or_dependent_has_focus); + } + ++dependent_it; + } + + floater->cleanupHandles(); + } +} + +LLFloater* LLFloaterView::getFrontmostClosableFloater() +{ + child_list_const_iter_t child_it; + LLFloater* frontmost_floater = NULL; + + for ( child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + frontmost_floater = (LLFloater *)(*child_it); + + if (frontmost_floater->isInVisibleChain() && frontmost_floater->isCloseable()) + { + return frontmost_floater; + } + } + + return NULL; +} + +void LLFloaterView::unhighlightFocusedFloater() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater *floater = (LLFloater *)(*child_it); + + floater->setForeground(false); + } +} + +void LLFloaterView::focusFrontFloater() +{ + LLFloater* floaterp = getFrontmost(); + if (floaterp) + { + floaterp->setFocus(true); + } +} + +void LLFloaterView::getMinimizePosition(S32 *left, S32 *bottom) +{ + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + static LLUICachedControl minimized_width ("UIMinimizedWidth", 0); + LLRect snap_rect_local = getLocalSnapRect(); + snap_rect_local.mTop += mMinimizePositionVOffset; + for(S32 col = snap_rect_local.mLeft; + col < snap_rect_local.getWidth() - minimized_width; + col += minimized_width) + { + for(S32 row = snap_rect_local.mTop - floater_header_size; + row > floater_header_size; + row -= floater_header_size ) //loop rows + { + + bool foundGap = true; + for(child_list_const_iter_t child_it = getChildList()->begin(); + child_it != getChildList()->end(); + ++child_it) //loop floaters + { + // Examine minimized children. + LLFloater* floater = dynamic_cast(*child_it); + if(floater->isMinimized()) + { + LLRect r = floater->getRect(); + if((r.mBottom < (row + floater_header_size)) + && (r.mBottom > (row - floater_header_size)) + && (r.mLeft < (col + minimized_width)) + && (r.mLeft > (col - minimized_width))) + { + // needs the check for off grid. can't drag, + // but window resize makes them off + foundGap = false; + break; + } + } + } //done floaters + if(foundGap) + { + *left = col; + *bottom = row; + return; //done + } + } //done this col + } + + // crude - stack'em all at 0,0 when screen is full of minimized + // floaters. + *left = snap_rect_local.mLeft; + *bottom = snap_rect_local.mBottom; +} + + +void LLFloaterView::destroyAllChildren() +{ + LLView::deleteAllChildren(); +} + +void LLFloaterView::closeAllChildren(bool app_quitting) +{ + // iterate over a copy of the list, because closing windows will destroy + // some windows on the list. + child_list_t child_list = *(getChildList()); + + for (child_list_const_iter_t it = child_list.begin(); it != child_list.end(); ++it) + { + LLView* viewp = *it; + child_list_const_iter_t exists = std::find(getChildList()->begin(), getChildList()->end(), viewp); + if (exists == getChildList()->end()) + { + // this floater has already been removed + continue; + } + + LLFloater* floaterp = dynamic_cast(viewp); + + // Attempt to close floater. This will cause the "do you want to save" + // dialogs to appear. + // Skip invisible floaters if we're not quitting (STORM-192). + if (floaterp->canClose() && !floaterp->isDead() && + (app_quitting || floaterp->getVisible())) + { + floaterp->closeFloater(app_quitting); + } + } +} + +void LLFloaterView::hiddenFloaterClosed(LLFloater* floater) +{ + for (hidden_floaters_t::iterator it = mHiddenFloaters.begin(), end_it = mHiddenFloaters.end(); + it != end_it; + ++it) + { + if (it->first.get() == floater) + { + it->second.disconnect(); + mHiddenFloaters.erase(it); + break; + } + } +} + +void LLFloaterView::hideAllFloaters() +{ + child_list_t child_list = *(getChildList()); + + for (child_list_iter_t it = child_list.begin(); it != child_list.end(); ++it) + { + LLFloater* floaterp = dynamic_cast(*it); + if (floaterp && floaterp->getVisible()) + { + floaterp->setVisible(false); + boost::signals2::connection connection = floaterp->mCloseSignal.connect(boost::bind(&LLFloaterView::hiddenFloaterClosed, this, floaterp)); + mHiddenFloaters.push_back(std::make_pair(floaterp->getHandle(), connection)); + } + } +} + +void LLFloaterView::showHiddenFloaters() +{ + for (hidden_floaters_t::iterator it = mHiddenFloaters.begin(), end_it = mHiddenFloaters.end(); + it != end_it; + ++it) + { + LLFloater* floaterp = it->first.get(); + if (floaterp) + { + floaterp->setVisible(true); + } + it->second.disconnect(); + } + mHiddenFloaters.clear(); +} + +bool LLFloaterView::allChildrenClosed() +{ + // see if there are any visible floaters (some floaters "close" + // by setting themselves invisible) + for (child_list_const_iter_t it = getChildList()->begin(); it != getChildList()->end(); ++it) + { + LLFloater* floaterp = dynamic_cast(*it); + + if (floaterp->getVisible() && !floaterp->isDead() && floaterp->isCloseable()) + { + return false; + } + } + return true; +} + +void LLFloaterView::shiftFloaters(S32 x_offset, S32 y_offset) +{ + for (child_list_const_iter_t it = getChildList()->begin(); it != getChildList()->end(); ++it) + { + LLFloater* floaterp = dynamic_cast(*it); + + if (floaterp && floaterp->isMinimized()) + { + floaterp->translate(x_offset, y_offset); + } + } +} + +void LLFloaterView::refresh() +{ + LLRect snap_rect = getSnapRect(); + if (snap_rect != mLastSnapRect) + { + reshape(getRect().getWidth(), getRect().getHeight(), true); + } + + // Constrain children to be entirely on the screen + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLFloater* floaterp = dynamic_cast(*child_it); + if (floaterp && floaterp->getVisible() ) + { + // minimized floaters are kept fully onscreen + adjustToFitScreen(floaterp, !floaterp->isMinimized()); + } + } +} + +void LLFloaterView::adjustToFitScreen(LLFloater* floater, bool allow_partial_outside, bool snap_in_toolbars/* = false*/) +{ + if (floater->getParent() != this) + { + // floater is hosted elsewhere, so ignore + return; + } + + if (floater->getDependee() && + floater->getDependee() == floater->getSnapTarget().get()) + { + // floater depends on other and snaps to it, so ignore + return; + } + + LLRect::tCoordType screen_width = getSnapRect().getWidth(); + LLRect::tCoordType screen_height = getSnapRect().getHeight(); + + // only automatically resize non-minimized, resizable floaters + if( floater->isResizable() && !floater->isMinimized() ) + { + LLRect view_rect = floater->getRect(); + S32 old_width = view_rect.getWidth(); + S32 old_height = view_rect.getHeight(); + S32 min_width; + S32 min_height; + floater->getResizeLimits( &min_width, &min_height ); + + // Make sure floater isn't already smaller than its min height/width? + S32 new_width = llmax( min_width, old_width ); + S32 new_height = llmax( min_height, old_height); + + if((new_width > screen_width) || (new_height > screen_height)) + { + // We have to make this window able to fit on screen + new_width = llmin(new_width, screen_width); + new_height = llmin(new_height, screen_height); + + // ...while respecting minimum width/height + new_width = llmax(new_width, min_width); + new_height = llmax(new_height, min_height); + + LLRect new_rect; + new_rect.setLeftTopAndSize(view_rect.mLeft,view_rect.mTop,new_width, new_height); + + floater->setShape(new_rect); + + if (floater->followsRight()) + { + floater->translate(old_width - new_width, 0); + } + + if (floater->followsTop()) + { + floater->translate(0, old_height - new_height); + } + } + } + + const LLRect& constraint = snap_in_toolbars ? getSnapRect() : gFloaterView->getRect(); + S32 min_overlap_pixels = allow_partial_outside ? FLOATER_MIN_VISIBLE_PIXELS : S32_MAX; + + floater->fitWithDependentsOnScreen(mToolbarLeftRect, mToolbarBottomRect, mToolbarRightRect, constraint, min_overlap_pixels); +} + +void LLFloaterView::draw() +{ + refresh(); + + // hide focused floater if in cycle mode, so that it can be drawn on top + LLFloater* focused_floater = getFocusedFloater(); + + if (mFocusCycleMode && focused_floater) + { + child_list_const_iter_t child_it = getChildList()->begin(); + for (;child_it != getChildList()->end(); ++child_it) + { + if ((*child_it) != focused_floater) + { + drawChild(*child_it); + } + } + + drawChild(focused_floater, -TABBED_FLOATER_OFFSET, TABBED_FLOATER_OFFSET); + } + else + { + LLView::draw(); + } +} + +LLRect LLFloaterView::getSnapRect() const +{ + LLRect snap_rect = getLocalRect(); + + LLView* snap_view = mSnapView.get(); + if (snap_view) + { + snap_view->localRectToOtherView(snap_view->getLocalRect(), &snap_rect, this); + } + + return snap_rect; +} + +LLFloater *LLFloaterView::getFocusedFloater() const +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + if ((*child_it)->isCtrl()) + { + LLFloater* ctrlp = dynamic_cast(*child_it); + if ( ctrlp && ctrlp->hasFocus() ) + { + return ctrlp; + } + } + } + return NULL; +} + +LLFloater *LLFloaterView::getFrontmost() const +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if ( viewp->getVisible() && !viewp->isDead()) + { + return (LLFloater *)viewp; + } + } + return NULL; +} + +LLFloater *LLFloaterView::getBackmost() const +{ + LLFloater* back_most = NULL; + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if ( viewp->getVisible() ) + { + back_most = (LLFloater *)viewp; + } + } + return back_most; +} + +void LLFloaterView::syncFloaterTabOrder() +{ + LLFloater* front_child = mFrontChildHandle.get(); + if (front_child && front_child->getIsChrome()) + return; + + // look for a visible modal dialog, starting from first + LLModalDialog* modal_dialog = NULL; + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLModalDialog* dialog = dynamic_cast(*child_it); + if (dialog && dialog->isModal() && dialog->getVisible()) + { + modal_dialog = dialog; + break; + } + } + + if (modal_dialog) + { + // If we have a visible modal dialog, make sure that it has focus + LLUI::getInstance()->addPopup(modal_dialog); + + if( !gFocusMgr.childHasKeyboardFocus( modal_dialog ) ) + { + modal_dialog->setFocus(true); + } + + if( !gFocusMgr.childHasMouseCapture( modal_dialog ) ) + { + gFocusMgr.setMouseCapture( modal_dialog ); + } + } + else + { + // otherwise, make sure the focused floater is in the front of the child list + for ( child_list_const_reverse_iter_t child_it = getChildList()->rbegin(); child_it != getChildList()->rend(); ++child_it) + { + LLFloater* floaterp = dynamic_cast(*child_it); + if (gFocusMgr.childHasKeyboardFocus(floaterp)) + { + LLFloater* front_child = mFrontChildHandle.get(); + if (front_child != floaterp) + { + // Grab a list of the top floaters that want to stay on top of the focused floater + std::list listTop; + if (front_child && !front_child->canFocusStealFrontmost()) + { + for (LLView* childp : *getChildList()) + { + LLFloater* child_floaterp = static_cast(childp); + if (child_floaterp->canFocusStealFrontmost()) + break; + listTop.push_back(child_floaterp); + } + } + + bringToFront(floaterp, false); + + // Restore top floaters + if (!listTop.empty()) + { + for (LLView* childp : listTop) + { + sendChildToFront(childp); + } + mFrontChildHandle = listTop.back()->getHandle(); + } + } + + break; + } + } + } +} + +LLFloater* LLFloaterView::getParentFloater(LLView* viewp) const +{ + LLView* parentp = viewp->getParent(); + + while(parentp && parentp != this) + { + viewp = parentp; + parentp = parentp->getParent(); + } + + if (parentp == this) + { + return dynamic_cast(viewp); + } + + return NULL; +} + +S32 LLFloaterView::getZOrder(LLFloater* child) +{ + S32 rv = 0; + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if(viewp == child) + { + break; + } + ++rv; + } + return rv; +} + +void LLFloaterView::pushVisibleAll(bool visible, const skip_list_t& skip_list) +{ + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *view = *child_iter; + if (skip_list.find(view) == skip_list.end()) + { + view->pushVisible(visible); + } + } + + LLFloaterReg::blockShowFloaters(true); +} + +void LLFloaterView::popVisibleAll(const skip_list_t& skip_list) +{ + // make a copy of the list since some floaters change their + // order in the childList when changing visibility. + child_list_t child_list_copy = *getChildList(); + + for (child_list_const_iter_t child_iter = child_list_copy.begin(); + child_iter != child_list_copy.end(); ++child_iter) + { + LLView *view = *child_iter; + if (skip_list.find(view) == skip_list.end()) + { + view->popVisible(); + } + } + + LLFloaterReg::blockShowFloaters(false); +} + +void LLFloaterView::setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LLRect& toolbar_rect) +{ + switch (tb) + { + case LLToolBarEnums::TOOLBAR_LEFT: + mToolbarLeftRect = toolbar_rect; + break; + case LLToolBarEnums::TOOLBAR_BOTTOM: + mToolbarBottomRect = toolbar_rect; + break; + case LLToolBarEnums::TOOLBAR_RIGHT: + mToolbarRightRect = toolbar_rect; + break; + default: + LL_WARNS() << "setToolbarRect() passed odd toolbar number " << (S32) tb << LL_ENDL; + break; + } +} + +void LLFloater::setInstanceName(const std::string& name) +{ + if (name != mInstanceName) + { + llassert_always(mInstanceName.empty()); + mInstanceName = name; + if (!mInstanceName.empty()) + { + std::string ctrl_name = getControlName(mInstanceName, mKey); + initRectControl(); + if (!mVisibilityControl.empty()) + { + mVisibilityControl = LLFloaterReg::declareVisibilityControl(ctrl_name); + } + if(!mDocStateControl.empty()) + { + mDocStateControl = LLFloaterReg::declareDockStateControl(ctrl_name); + } + } +} +} + +void LLFloater::setKey(const LLSD& newkey) +{ + // Note: We don't have to do anything special with registration when we change keys + mKey = newkey; +} + +//static +void LLFloater::setupParamsForExport(Params& p, LLView* parent) +{ + // Do rectangle munging to topleft layout first + LLPanel::setupParamsForExport(p, parent); + + // Copy the rectangle out to apply layout constraints + LLRect rect = p.rect; + + // Null out other settings + p.rect.left.setProvided(false); + p.rect.top.setProvided(false); + p.rect.right.setProvided(false); + p.rect.bottom.setProvided(false); + + // Explicitly set width/height + p.rect.width.set( rect.getWidth(), true ); + p.rect.height.set( rect.getHeight(), true ); + + // If you can't resize this floater, don't export min_height + // and min_width + bool can_resize = p.can_resize; + if (!can_resize) + { + p.min_height.setProvided(false); + p.min_width.setProvided(false); + } +} + +void LLFloater::initFromParams(const LLFloater::Params& p) +{ + // *NOTE: We have too many classes derived from LLFloater to retrofit them + // all to pass in params via constructors. So we use this method. + + // control_name, tab_stop, focus_lost_callback, initial_value, rect, enabled, visible + LLPanel::initFromParams(p); + + // override any follows flags + if (mPositioning != LLFloaterEnums::POSITIONING_SPECIFIED) + { + setFollows(FOLLOWS_NONE); + } + + mTitle = p.title; + mShortTitle = p.short_title; + applyTitle(); + + setCanTearOff(p.can_tear_off); + setCanMinimize(p.can_minimize); + setCanClose(p.can_close); + setCanDock(p.can_dock); + setCanResize(p.can_resize); + setResizeLimits(p.min_width, p.min_height); + + mDragOnLeft = p.can_drag_on_left; + mHeaderHeight = p.header_height; + mLegacyHeaderHeight = p.legacy_header_height; + mSingleInstance = p.single_instance; + mReuseInstance = p.reuse_instance.isProvided() ? p.reuse_instance : p.single_instance; + + mDefaultRelativeX = p.rel_x; + mDefaultRelativeY = p.rel_y; + + mPositioning = p.positioning; + mAutoClose = p.auto_close; + + mSaveRect = p.save_rect; + if (p.save_visibility) + { + mVisibilityControl = "t"; // flag to build mVisibilityControl name once mInstanceName is set + } + if(p.save_dock_state) + { + mDocStateControl = "t"; // flag to build mDocStateControl name once mInstanceName is set + } + + // open callback + if (p.open_callback.isProvided()) + { + setOpenCallback(initCommitCallback(p.open_callback)); + } + // close callback + if (p.close_callback.isProvided()) + { + setCloseCallback(initCommitCallback(p.close_callback)); + } + + if (mDragHandle) + { + mDragHandle->setTitleVisible(p.show_title); + } +} + +boost::signals2::connection LLFloater::setMinimizeCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMinimizeSignal) mMinimizeSignal = new commit_signal_t(); + return mMinimizeSignal->connect(cb); +} + +boost::signals2::connection LLFloater::setOpenCallback( const commit_signal_t::slot_type& cb ) +{ + return mOpenSignal.connect(cb); +} + +boost::signals2::connection LLFloater::setCloseCallback( const commit_signal_t::slot_type& cb ) +{ + return mCloseSignal.connect(cb); +} + +bool LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, const std::string& filename, LLXMLNodePtr output_node) +{ + LL_PROFILE_ZONE_SCOPED; + Params default_params(LLUICtrlFactory::getDefaultParams()); + Params params(default_params); + + LLXUIParser parser; + parser.readXUI(node, params, filename); // *TODO: Error checking + + std::string xml_filename = params.filename; + + if (!xml_filename.empty()) + { + LLXMLNodePtr referenced_xml; + + if (output_node) + { + //if we are exporting, we want to export the current xml + //not the referenced xml + Params output_params; + parser.readXUI(node, output_params, LLUICtrlFactory::getInstance()->getCurFileName()); + setupParamsForExport(output_params, parent); + output_node->setName(node->getName()->mString); + parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); + return true; + } + + LLUICtrlFactory::instance().pushFileName(xml_filename); + + if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml)) + { + LL_WARNS() << "Couldn't parse panel from: " << xml_filename << LL_ENDL; + + return false; + } + + Params referenced_params; + parser.readXUI(referenced_xml, referenced_params, LLUICtrlFactory::getInstance()->getCurFileName()); + params.fillFrom(referenced_params); + + // add children using dimensions from referenced xml for consistent layout + setShape(params.rect); + LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance()); + + LLUICtrlFactory::instance().popFileName(); + } + + + if (output_node) + { + Params output_params(params); + setupParamsForExport(output_params, parent); + output_node->setName(node->getName()->mString); + parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); + } + + // Default floater position to top-left corner of screen + // However, some legacy floaters have explicit top or bottom + // coordinates set, so respect their wishes. + if (!params.rect.top.isProvided() && !params.rect.bottom.isProvided()) + { + params.rect.top.set(0); + } + if (!params.rect.left.isProvided() && !params.rect.right.isProvided()) + { + params.rect.left.set(0); + } + params.from_xui = true; + applyXUILayout(params, parent, parent == gFloaterView ? gFloaterView->getSnapRect() : parent->getLocalRect()); + initFromParams(params); + + initFloater(params); + + LLMultiFloater* last_host = LLFloater::getFloaterHost(); + if (node->hasName("multi_floater")) + { + LLFloater::setFloaterHost((LLMultiFloater*) this); + } + + LLUICtrlFactory::createChildren(this, node, child_registry_t::instance(), output_node); + + if (node->hasName("multi_floater")) + { + LLFloater::setFloaterHost(last_host); + } + + // HACK: When we changed the header height to 25 pixels in Viewer 2, rather + // than re-layout all the floaters we use this value in pixels to make the + // whole floater bigger and change the top-left coordinate for widgets. + // The goal is to eventually set mLegacyHeaderHeight to zero, which would + // make the top-left corner for widget layout the same as the top-left + // corner of the window's content area. James + S32 header_stretch = (mHeaderHeight - mLegacyHeaderHeight); + if (header_stretch > 0) + { + // Stretch the floater vertically, don't move widgets + LLRect rect = getRect(); + rect.mTop += header_stretch; + + // This will also update drag handle, title bar, close box, etc. + setRect(rect); + } + + bool result; + result = postBuild(); + + if (!result) + { + LL_ERRS() << "Failed to construct floater " << getName() << LL_ENDL; + } + + applyRectControl(); // If we have a saved rect control, apply it + gFloaterView->adjustToFitScreen(this, false); // Floaters loaded from XML should all fit on screen + + moveResizeHandlesToFront(); + + applyDockState(); + + return true; // *TODO: Error checking +} + +bool LLFloater::isShown() const +{ + return ! isMinimized() && isInVisibleChain(); +} + +bool LLFloater::isDetachedAndNotMinimized() +{ + return !getHost() && !isMinimized(); +} + +/* static */ +bool LLFloater::isShown(const LLFloater* floater) +{ + return floater && floater->isShown(); +} + +/* static */ +bool LLFloater::isMinimized(const LLFloater* floater) +{ + return floater && floater->isMinimized(); +} + +/* static */ +bool LLFloater::isVisible(const LLFloater* floater) +{ + return floater && floater->getVisible(); +} + +bool LLFloater::buildFromFile(const std::string& filename) +{ + LL_PROFILE_ZONE_SCOPED; + LLXMLNodePtr root; + + if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + LL_WARNS() << "Couldn't find (or parse) floater from: " << filename << LL_ENDL; + return false; + } + + // root must be called floater + if( !(root->hasName("floater") || root->hasName("multi_floater")) ) + { + LL_WARNS() << "Root node should be named floater in: " << filename << LL_ENDL; + return false; + } + + bool res = true; + + LL_DEBUGS() << "Building floater " << filename << LL_ENDL; + LLUICtrlFactory::instance().pushFileName(filename); + { + if (!getFactoryMap().empty()) + { + LLPanel::sFactoryStack.push_front(&getFactoryMap()); + } + + // for local registry callbacks; define in constructor, referenced in XUI or postBuild + getCommitCallbackRegistrar().pushScope(); + getEnableCallbackRegistrar().pushScope(); + + res = initFloaterXML(root, getParent(), filename, NULL); + + setXMLFilename(filename); + + getCommitCallbackRegistrar().popScope(); + getEnableCallbackRegistrar().popScope(); + + if (!getFactoryMap().empty()) + { + LLPanel::sFactoryStack.pop_front(); + } + } + LLUICtrlFactory::instance().popFileName(); + + return res; +} + +void LLFloater::stackWith(LLFloater& other) +{ + static LLUICachedControl floater_offset ("UIFloaterOffset", 16); + + LLRect next_rect; + if (other.getHost()) + { + next_rect = other.getHost()->getRect(); + } + else + { + next_rect = other.getRect(); + } + next_rect.translate(floater_offset, -floater_offset); + + const LLRect& rect = getControlGroup()->getRect(mRectControl); + if (rect.notEmpty() && !mDefaultRectForGroup && mResizable) + { + next_rect.setLeftTopAndSize(next_rect.mLeft, next_rect.mTop, llmax(mMinWidth, rect.getWidth()), llmax(mMinHeight, rect.getHeight())); + } + else + { + next_rect.setLeftTopAndSize(next_rect.mLeft, next_rect.mTop, getRect().getWidth(), getRect().getHeight()); + } + setShape(next_rect); + + if (!other.getHost()) + { + other.mPositioning = LLFloaterEnums::POSITIONING_CASCADE_GROUP; + other.setFollows(FOLLOWS_LEFT | FOLLOWS_TOP); + } +} + +void LLFloater::applyRelativePosition() +{ + LLRect snap_rect = gFloaterView->getSnapRect(); + LLRect floater_view_screen_rect = gFloaterView->calcScreenRect(); + snap_rect.translate(floater_view_screen_rect.mLeft, floater_view_screen_rect.mBottom); + LLRect floater_screen_rect = calcScreenRect(); + + LLCoordGL new_center = mPosition.convert(); + LLCoordGL cur_center(floater_screen_rect.getCenterX(), floater_screen_rect.getCenterY()); + translate(new_center.mX - cur_center.mX, new_center.mY - cur_center.mY); +} + + +LLCoordFloater::LLCoordFloater(F32 x, F32 y, LLFloater& floater) +: coord_t((S32)x, (S32)y) +{ + mFloater = floater.getHandle(); +} + + +LLCoordFloater::LLCoordFloater(const LLCoordCommon& other, LLFloater& floater) +{ + mFloater = floater.getHandle(); + convertFromCommon(other); +} + +LLCoordFloater& LLCoordFloater::operator=(const LLCoordFloater& other) +{ + mFloater = other.mFloater; + coord_t::operator =(other); + return *this; +} + +void LLCoordFloater::setFloater(LLFloater& floater) +{ + mFloater = floater.getHandle(); +} + +bool LLCoordFloater::operator==(const LLCoordFloater& other) const +{ + return mX == other.mX && mY == other.mY && mFloater == other.mFloater; +} + +LLCoordCommon LL_COORD_FLOATER::convertToCommon() const +{ + const LLCoordFloater& self = static_cast(LLCoordFloater::getTypedCoords(*this)); + + LLRect snap_rect = gFloaterView->getSnapRect(); + LLRect floater_view_screen_rect = gFloaterView->calcScreenRect(); + snap_rect.translate(floater_view_screen_rect.mLeft, floater_view_screen_rect.mBottom); + + LLFloater* floaterp = mFloater.get(); + S32 floater_width = floaterp ? floaterp->getRect().getWidth() : 0; + S32 floater_height = floaterp ? floaterp->getRect().getHeight() : 0; + LLCoordCommon out; + if (self.mX < -0.5f) + { + out.mX = ll_round(rescale(self.mX, -1.f, -0.5f, snap_rect.mLeft - (floater_width - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mLeft)); + } + else if (self.mX > 0.5f) + { + out.mX = ll_round(rescale(self.mX, 0.5f, 1.f, snap_rect.mRight - floater_width, snap_rect.mRight - FLOATER_MIN_VISIBLE_PIXELS)); + } + else + { + out.mX = ll_round(rescale(self.mX, -0.5f, 0.5f, snap_rect.mLeft, snap_rect.mRight - floater_width)); + } + + if (self.mY < -0.5f) + { + out.mY = ll_round(rescale(self.mY, -1.f, -0.5f, snap_rect.mBottom - (floater_height - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mBottom)); + } + else if (self.mY > 0.5f) + { + out.mY = ll_round(rescale(self.mY, 0.5f, 1.f, snap_rect.mTop - floater_height, snap_rect.mTop - FLOATER_MIN_VISIBLE_PIXELS)); + } + else + { + out.mY = ll_round(rescale(self.mY, -0.5f, 0.5f, snap_rect.mBottom, snap_rect.mTop - floater_height)); + } + + // return center point instead of lower left + out.mX += floater_width / 2; + out.mY += floater_height / 2; + + return out; +} + +void LL_COORD_FLOATER::convertFromCommon(const LLCoordCommon& from) +{ + LLCoordFloater& self = static_cast(LLCoordFloater::getTypedCoords(*this)); + LLRect snap_rect = gFloaterView->getSnapRect(); + LLRect floater_view_screen_rect = gFloaterView->calcScreenRect(); + snap_rect.translate(floater_view_screen_rect.mLeft, floater_view_screen_rect.mBottom); + + + LLFloater* floaterp = mFloater.get(); + S32 floater_width = floaterp ? floaterp->getRect().getWidth() : 0; + S32 floater_height = floaterp ? floaterp->getRect().getHeight() : 0; + + S32 from_x = from.mX - floater_width / 2; + S32 from_y = from.mY - floater_height / 2; + + if (from_x < snap_rect.mLeft) + { + self.mX = rescale(from_x, snap_rect.mLeft - (floater_width - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mLeft, -1.f, -0.5f); + } + else if (from_x + floater_width > snap_rect.mRight) + { + self.mX = rescale(from_x, snap_rect.mRight - floater_width, snap_rect.mRight - FLOATER_MIN_VISIBLE_PIXELS, 0.5f, 1.f); + } + else + { + self.mX = rescale(from_x, snap_rect.mLeft, snap_rect.mRight - floater_width, -0.5f, 0.5f); + } + + if (from_y < snap_rect.mBottom) + { + self.mY = rescale(from_y, snap_rect.mBottom - (floater_height - FLOATER_MIN_VISIBLE_PIXELS), snap_rect.mBottom, -1.f, -0.5f); + } + else if (from_y + floater_height > snap_rect.mTop) + { + self.mY = rescale(from_y, snap_rect.mTop - floater_height, snap_rect.mTop - FLOATER_MIN_VISIBLE_PIXELS, 0.5f, 1.f); + } + else + { + self.mY = rescale(from_y, snap_rect.mBottom, snap_rect.mTop - floater_height, -0.5f, 0.5f); + } +} diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index ed3aed4db6..3d75c42f60 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -1,641 +1,641 @@ -/** - * @file llfloater.h - * @brief LLFloater base class - * - * $LicenseInfo:firstyear=2002&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$ - */ - -// Floating "windows" within the GL display, like the inventory floater, -// mini-map floater, etc. - - -#ifndef LL_FLOATER_H -#define LL_FLOATER_H - -#include "llpanel.h" -#include "lltoolbar.h" -#include "lluuid.h" -//#include "llnotificationsutil.h" -#include -#include - -class LLDragHandle; -class LLResizeHandle; -class LLResizeBar; -class LLButton; -class LLMultiFloater; -class LLFloater; - - -const bool RESIZE_YES = true; -const bool RESIZE_NO = false; - -const bool DRAG_ON_TOP = false; -const bool DRAG_ON_LEFT = true; - -const bool MINIMIZE_YES = true; -const bool MINIMIZE_NO = false; - -const bool CLOSE_YES = true; -const bool CLOSE_NO = false; - -const bool ADJUST_VERTICAL_YES = true; -const bool ADJUST_VERTICAL_NO = false; - -const F32 CONTEXT_CONE_IN_ALPHA = 0.f; -const F32 CONTEXT_CONE_OUT_ALPHA = 1.f; -const F32 CONTEXT_CONE_FADE_TIME = .08f; - -namespace LLFloaterEnums -{ - enum EOpenPositioning - { - POSITIONING_RELATIVE, - POSITIONING_CASCADING, - POSITIONING_CASCADE_GROUP, - POSITIONING_CENTERED, - POSITIONING_SPECIFIED, - POSITIONING_COUNT - }; -} - -namespace LLInitParam -{ - template<> - struct TypeValues : public TypeValuesHelper - { - static void declareValues(); - }; -} - -struct LL_COORD_FLOATER -{ - typedef F32 value_t; - - LLCoordCommon convertToCommon() const; - void convertFromCommon(const LLCoordCommon& from); -protected: - LLHandle mFloater; -}; - -struct LLCoordFloater : LLCoord -{ - typedef LLCoord coord_t; - - LLCoordFloater() {} - LLCoordFloater(F32 x, F32 y, LLFloater& floater); - LLCoordFloater(const LLCoordCommon& other, LLFloater& floater); - - LLCoordFloater& operator=(const LLCoordCommon& other) - { - convertFromCommon(other); - return *this; - } - - LLCoordFloater& operator=(const LLCoordFloater& other); - - bool operator==(const LLCoordFloater& other) const; - bool operator!=(const LLCoordFloater& other) const { return !(*this == other); } - - void setFloater(LLFloater& floater); -}; - -class LLFloater : public LLPanel, public LLInstanceTracker -{ - friend class LLFloaterView; - friend class LLFloaterReg; - friend class LLMultiFloater; - -public: - - struct KeyCompare - { -// static bool compare(const LLSD& a, const LLSD& b); - static bool equate(const LLSD& a, const LLSD& b); -/*==========================================================================*| - bool operator()(const LLSD& a, const LLSD& b) const - { - return compare(a, b); - } -|*==========================================================================*/ - }; - - enum EFloaterButton - { - BUTTON_CLOSE = 0, - BUTTON_RESTORE, - BUTTON_MINIMIZE, - BUTTON_TEAR_OFF, - BUTTON_DOCK, - BUTTON_HELP, - BUTTON_COUNT - }; - - struct Params - : public LLInitParam::Block - { - Optional title, - short_title; - - Optional single_instance, - reuse_instance, - can_resize, - can_minimize, - can_close, - can_drag_on_left, - can_tear_off, - save_rect, - save_visibility, - save_dock_state, - can_dock, - show_title, - auto_close; - - Optional positioning; - - Optional header_height, - legacy_header_height; // HACK see initFromXML() - - Optional rel_x, - rel_y; - - // Images for top-right controls - Optional close_image, - restore_image, - minimize_image, - tear_off_image, - dock_image, - help_image; - Optional close_pressed_image, - restore_pressed_image, - minimize_pressed_image, - tear_off_pressed_image, - dock_pressed_image, - help_pressed_image; - - Optional open_callback, - close_callback; - - Ignored follows; - - Params(); - }; - - // use this to avoid creating your own default LLFloater::Param instance - static const Params& getDefaultParams(); - - // Load translations for tooltips for standard buttons - static void initClass(); - - LLFloater(const LLSD& key, const Params& params = getDefaultParams()); - - virtual ~LLFloater(); - - // Don't export top/left for rect, only height/width - static void setupParamsForExport(Params& p, LLView* parent); - bool buildFromFile(const std::string &filename); - - boost::signals2::connection setMinimizeCallback( const commit_signal_t::slot_type& cb ); - boost::signals2::connection setOpenCallback( const commit_signal_t::slot_type& cb ); - boost::signals2::connection setCloseCallback( const commit_signal_t::slot_type& cb ); - - void initFromParams(const LLFloater::Params& p); - bool initFloaterXML(LLXMLNodePtr node, LLView *parent, const std::string& filename, LLXMLNodePtr output_node = NULL); - - /*virtual*/ void handleReshape(const LLRect& new_rect, bool by_user = false); - /*virtual*/ bool canSnapTo(const LLView* other_view); - /*virtual*/ void setSnappedTo(const LLView* snap_view); - /*virtual*/ void setFocus( bool b ); - /*virtual*/ void setIsChrome(bool is_chrome); - /*virtual*/ void setRect(const LLRect &rect); - void setIsSingleInstance(bool is_single_instance); - bool getIsSingleInstance() { return mSingleInstance; } - - void initFloater(const Params& p); - - void openFloater(const LLSD& key = LLSD()); - - // If allowed, close the floater cleanly, releasing focus. - virtual void closeFloater(bool app_quitting = false); - - // Close the floater or its host. Use when hidding or toggling a floater instance. - virtual void closeHostedFloater(); - - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - /*virtual*/ void translate(S32 x, S32 y); - - // Release keyboard and mouse focus - void releaseFocus(); - - // moves to center of gFloaterView - void center(); - - LLMultiFloater* getHost(); - bool isDetachedAndNotMinimized(); - - void applyTitle(); - std::string getCurrentTitle() const; - void setTitle( const std::string& title); - std::string getTitle() const; - void setShortTitle( const std::string& short_title ); - std::string getShortTitle() const; - virtual void setMinimized(bool b); - void moveResizeHandlesToFront(); - void addDependentFloater(LLFloater* dependent, bool reposition = true, bool resize = false); - void addDependentFloater(LLHandle dependent_handle, bool reposition = true, bool resize = false); - LLFloater* getDependee() { return (LLFloater*)mDependeeHandle.get(); } - void removeDependentFloater(LLFloater* dependent); - void fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& constraint, S32 min_overlap_pixels); - bool isMinimized() const { return mMinimized; } - /// isShown() differs from getVisible() in that isShown() also considers - /// isMinimized(). isShown() is true only if visible and not minimized. - bool isShown() const; - /// The static isShown() can accept a NULL pointer (which of course - /// returns false). When non-NULL, it calls the non-static isShown(). - static bool isShown(const LLFloater* floater); - static bool isVisible(const LLFloater* floater); - static bool isMinimized(const LLFloater* floater); - bool isFirstLook() { return mFirstLook; } // EXT-2653: This function is necessary to prevent overlapping for secondary showed toasts - virtual bool isFrontmost(); - bool isDependent() { return !mDependeeHandle.isDead(); } - void setCanMinimize(bool can_minimize); - void setCanClose(bool can_close); - void setCanTearOff(bool can_tear_off); - virtual void setCanResize(bool can_resize); - void setCanDrag(bool can_drag); - bool getCanDrag(); - void setHost(LLMultiFloater* host); - bool isResizable() const { return mResizable; } - void setResizeLimits( S32 min_width, S32 min_height ); - void getResizeLimits( S32* min_width, S32* min_height ) { *min_width = mMinWidth; *min_height = mMinHeight; } - - static std::string getControlName(const std::string& name, const LLSD& key); - static LLControlGroup* getControlGroup(); - - bool isMinimizeable() const{ return mCanMinimize; } - bool isCloseable() const{ return mCanClose; } - bool isDragOnLeft() const{ return mDragOnLeft; } - S32 getMinWidth() const{ return mMinWidth; } - S32 getMinHeight() const{ return mMinHeight; } - S32 getHeaderHeight() const { return mHeaderHeight; } - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); - virtual bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); - - virtual bool handleScrollWheel(S32 x, S32 y, S32 mask); - - virtual void draw(); - virtual void drawShadow(LLPanel* panel); - - virtual void onOpen(const LLSD& key) {} - virtual void onClose(bool app_quitting) {} - - // This cannot be "const" until all derived floater canClose() - // methods are const as well. JC - virtual bool canClose() { return true; } - - /*virtual*/ void setVisible(bool visible); // do not override - /*virtual*/ void onVisibilityChange ( bool new_visibility ); // do not override - - bool canFocusStealFrontmost() const { return mFocusStealsFrontmost; } - void setFocusStealsFrontmost(bool wants_frontmost) { mFocusStealsFrontmost = wants_frontmost; } - - void setFrontmost(bool take_focus = true, bool restore = true); - virtual void setVisibleAndFrontmost(bool take_focus = true, const LLSD& key = LLSD()); - - // Defaults to false. - virtual bool canSaveAs() const { return false; } - - virtual void saveAs() {} - - void setSnapTarget(LLHandle handle) { mSnappedTo = handle; } - void clearSnapTarget() { mSnappedTo.markDead(); } - LLHandle getSnapTarget() const { return mSnappedTo; } - - LLHandle getHandle() const { return getDerivedHandle(); } - const LLSD& getKey() { return mKey; } - virtual bool matchesKey(const LLSD& key) { return mSingleInstance || KeyCompare::equate(key, mKey); } - - const std::string& getInstanceName() { return mInstanceName; } - - bool isDockable() const { return mCanDock; } - void setCanDock(bool b); - - bool isDocked() const { return mDocked; } - virtual void setDocked(bool docked, bool pop_on_undock = true); - - virtual void setTornOff(bool torn_off) { mTornOff = torn_off; } - bool isTornOff() {return mTornOff;} - void setOpenPositioning(LLFloaterEnums::EOpenPositioning pos) {mPositioning = pos;} - - - // Close the floater returned by getFrontmostClosableFloater() and - // handle refocusing. - static void closeFrontmostFloater(); - - static bool isQuitRequested() { return sQuitting; } - -// LLNotification::Params contextualNotification(const std::string& name) -// { -// return LLNotification::Params(name).context(mNotificationContext); -// } - - static void onClickClose(LLFloater* floater); - static void onClickMinimize(LLFloater* floater); - static void onClickTearOff(LLFloater* floater); - static void onClickDock(LLFloater* floater); - static void onClickHelp(LLFloater* floater); - - static void setFloaterHost(LLMultiFloater* hostp) {sHostp = hostp; } - static LLMultiFloater* getFloaterHost() {return sHostp; } - - void updateTransparency(ETypeTransparency transparency_type); - - void enableResizeCtrls(bool enable, bool width = true, bool height = true); - - bool isPositioning(LLFloaterEnums::EOpenPositioning p) const { return (p == mPositioning); } -protected: - void applyControlsAndPosition(LLFloater* other); - - void stackWith(LLFloater& other); - - virtual void initRectControl(); - virtual bool applyRectControl(); - bool applyDockState(); - void applyPositioning(LLFloater* other, bool on_open); - void applyRelativePosition(); - - void storeRectControl(); - void storeVisibilityControl(); - void storeDockStateControl(); - - void setKey(const LLSD& key); - void setInstanceName(const std::string& name); - - virtual void bringToFront(S32 x, S32 y); - virtual void goneFromFront(); - - void setExpandedRect(const LLRect& rect) { mExpandedRect = rect; } // size when not minimized - const LLRect& getExpandedRect() const { return mExpandedRect; } - - void setAutoFocus(bool focus) { mAutoFocus = focus; } // whether to automatically take focus when opened - bool getAutoFocus() const { return mAutoFocus; } - LLDragHandle* getDragHandle() const { return mDragHandle; } - - void destroy(); // Don't call this directly. You probably want to call closeFloater() - - virtual void onClickCloseBtn(bool app_quitting = false); - - virtual void updateTitleButtons(); - - // Draws a cone from this floater to parent floater or view (owner) - // Modifies context_cone_opacity (interpolates according to fade time and returns new value) - void drawConeToOwner(F32 &context_cone_opacity, - F32 max_cone_opacity, - LLView *owner_view, - F32 context_fade_time = CONTEXT_CONE_FADE_TIME, - F32 contex_cone_in_alpha = CONTEXT_CONE_IN_ALPHA, - F32 contex_cone_out_alpha = CONTEXT_CONE_OUT_ALPHA); - -private: - void setForeground(bool b); // called only by floaterview - void cleanupHandles(); // remove handles to dead floaters - void createMinimizeButton(); - void buildButtons(const Params& p); - - // Images and tooltips are named in the XML, but we want to look them - // up by index. - static LLUIImage* getButtonImage(const Params& p, EFloaterButton e); - static LLUIImage* getButtonPressedImage(const Params& p, EFloaterButton e); - - /** - * @params is_chrome - if floater is Chrome it means that floater will never get focus. - * Therefore it can't be closed with 'Ctrl+W'. So the tooltip text of close button( X ) - * should be 'Close' not 'Close(Ctrl+W)' as for usual floaters. - */ - static std::string getButtonTooltip(const Params& p, EFloaterButton e, bool is_chrome); - - bool offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index); - void addResizeCtrls(); - void layoutResizeCtrls(); - void addDragHandle(); - void layoutDragHandle(); // repair layout - - static void updateActiveFloaterTransparency(); - static void updateInactiveFloaterTransparency(); - void updateTransparency(LLView* view, ETypeTransparency transparency_type); - -public: - static const F32 CONTEXT_CONE_IN_ALPHA; - static const F32 CONTEXT_CONE_OUT_ALPHA; - static const F32 CONTEXT_CONE_FADE_TIME; - - // Called when floater is opened, passes mKey - // Public so external views or floaters can watch for this floater opening - commit_signal_t mOpenSignal; - - // Called when floater is closed, passes app_qitting as LLSD() - // Public so external views or floaters can watch for this floater closing - commit_signal_t mCloseSignal; - - commit_signal_t* mMinimizeSignal; - -protected: - bool mSaveRect; - bool mDefaultRectForGroup; - std::string mRectControl; - std::string mPosXControl; - std::string mPosYControl; - std::string mVisibilityControl; - std::string mDocStateControl; - LLSD mKey; // Key used for retrieving instances; set (for now) by LLFLoaterReg - - LLDragHandle* mDragHandle; - LLResizeBar* mResizeBar[4]; - LLResizeHandle* mResizeHandle[4]; - - LLButton* mButtons[BUTTON_COUNT]; -private: - LLRect mExpandedRect; - - LLUIString mTitle; - LLUIString mShortTitle; - - bool mSingleInstance; // true if there is only ever one instance of the floater - bool mReuseInstance; // true if we want to hide the floater when we close it instead of destroying it - bool mIsReuseInitialized; // true if mReuseInstance already set from parameters - std::string mInstanceName; // Store the instance name so we can remove ourselves from the list - - bool mCanTearOff; - bool mCanMinimize; - bool mCanClose; - bool mFocusStealsFrontmost = true; // false if we don't want the currently focused floater to cover this floater without user interaction - bool mDragOnLeft; - bool mResizable; - bool mAutoClose; - - LLFloaterEnums::EOpenPositioning mPositioning; - LLCoordFloater mPosition; - - S32 mMinWidth; - S32 mMinHeight; - S32 mHeaderHeight; // height in pixels of header for title, drag bar - S32 mLegacyHeaderHeight;// HACK see initFloaterXML() - - bool mMinimized; - bool mForeground; - LLHandle mDependeeHandle; - - - bool mFirstLook; // true if the _next_ time this floater is visible will be the first time in the session that it is visible. - - typedef std::set > handle_set_t; - typedef std::set >::iterator handle_set_iter_t; - handle_set_t mDependents; - bool mTranslateWithDependents { false }; - - bool mButtonsEnabled[BUTTON_COUNT]; - F32 mButtonScale; - bool mAutoFocus; - LLHandle mSnappedTo; - - LLHandle mHostHandle; - LLHandle mLastHostHandle; - - bool mCanDock; - bool mDocked; - bool mTornOff; - - static LLMultiFloater* sHostp; - static bool sQuitting; - static std::string sButtonNames[BUTTON_COUNT]; - static std::string sButtonToolTips[BUTTON_COUNT]; - static std::string sButtonToolTipsIndex[BUTTON_COUNT]; - - typedef void(*click_callback)(LLFloater*); - static click_callback sButtonCallbacks[BUTTON_COUNT]; - - bool mHasBeenDraggedWhileMinimized; - S32 mPreviousMinimizedBottom; - S32 mPreviousMinimizedLeft; - - F32 mDefaultRelativeX; - F32 mDefaultRelativeY; -}; - - -///////////////////////////////////////////////////////////// -// LLFloaterView -// Parent of all floating panels - -const S32 FLOATER_MIN_VISIBLE_PIXELS = 16; - -class LLFloaterView : public LLUICtrl -{ -public: - struct Params : public LLInitParam::Block{}; - -protected: - LLFloaterView (const Params& p); - friend class LLUICtrlFactory; - -public: - - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - /*virtual*/ void draw(); - /*virtual*/ LLRect getSnapRect() const; - /*virtual*/ void refresh(); - - LLRect findNeighboringPosition( LLFloater* reference_floater, LLFloater* neighbor ); - - // Given a child of gFloaterView, make sure this view can fit entirely onscreen. - void adjustToFitScreen(LLFloater* floater, bool allow_partial_outside, bool snap_in_toolbars = false); - - void setMinimizePositionVerticalOffset(S32 offset) { mMinimizePositionVOffset = offset; } - void getMinimizePosition( S32 *left, S32 *bottom); - void restoreAll(); // un-minimize all floaters - typedef std::set skip_list_t; - void pushVisibleAll(bool visible, const skip_list_t& skip_list = skip_list_t()); - void popVisibleAll(const skip_list_t& skip_list = skip_list_t()); - - void setCycleMode(bool mode) { mFocusCycleMode = mode; } - bool getCycleMode() const { return mFocusCycleMode; } - void bringToFront( LLFloater* child, bool give_focus = true, bool restore = true ); - void highlightFocusedFloater(); - void unhighlightFocusedFloater(); - void focusFrontFloater(); - void destroyAllChildren(); - // attempt to close all floaters - void closeAllChildren(bool app_quitting); - bool allChildrenClosed(); - void shiftFloaters(S32 x_offset, S32 y_offset); - - void hideAllFloaters(); - void showHiddenFloaters(); - - - LLFloater* getFrontmost() const; - LLFloater* getBackmost() const; - LLFloater* getParentFloater(LLView* viewp) const; - LLFloater* getFocusedFloater() const; - void syncFloaterTabOrder(); - - // Returns z order of child provided. 0 is closest, larger numbers - // are deeper in the screen. If there is no such child, the return - // value is not defined. - S32 getZOrder(LLFloater* child); - - void setFloaterSnapView(LLHandle snap_view) {mSnapView = snap_view; } - LLFloater* getFrontmostClosableFloater(); - - void setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LLRect& toolbar_rect); - -private: - void hiddenFloaterClosed(LLFloater* floater); - - LLRect mLastSnapRect; - LLRect mToolbarLeftRect; - LLRect mToolbarBottomRect; - LLRect mToolbarRightRect; - LLHandle mSnapView; - bool mFocusCycleMode; - S32 mSnapOffsetBottom; - S32 mSnapOffsetRight; - S32 mMinimizePositionVOffset; - typedef std::vector, boost::signals2::connection> > hidden_floaters_t; - hidden_floaters_t mHiddenFloaters; - LLHandle mFrontChildHandle; -}; - -// -// Globals -// - -extern LLFloaterView* gFloaterView; - -#endif // LL_FLOATER_H - - - +/** + * @file llfloater.h + * @brief LLFloater base class + * + * $LicenseInfo:firstyear=2002&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$ + */ + +// Floating "windows" within the GL display, like the inventory floater, +// mini-map floater, etc. + + +#ifndef LL_FLOATER_H +#define LL_FLOATER_H + +#include "llpanel.h" +#include "lltoolbar.h" +#include "lluuid.h" +//#include "llnotificationsutil.h" +#include +#include + +class LLDragHandle; +class LLResizeHandle; +class LLResizeBar; +class LLButton; +class LLMultiFloater; +class LLFloater; + + +const bool RESIZE_YES = true; +const bool RESIZE_NO = false; + +const bool DRAG_ON_TOP = false; +const bool DRAG_ON_LEFT = true; + +const bool MINIMIZE_YES = true; +const bool MINIMIZE_NO = false; + +const bool CLOSE_YES = true; +const bool CLOSE_NO = false; + +const bool ADJUST_VERTICAL_YES = true; +const bool ADJUST_VERTICAL_NO = false; + +const F32 CONTEXT_CONE_IN_ALPHA = 0.f; +const F32 CONTEXT_CONE_OUT_ALPHA = 1.f; +const F32 CONTEXT_CONE_FADE_TIME = .08f; + +namespace LLFloaterEnums +{ + enum EOpenPositioning + { + POSITIONING_RELATIVE, + POSITIONING_CASCADING, + POSITIONING_CASCADE_GROUP, + POSITIONING_CENTERED, + POSITIONING_SPECIFIED, + POSITIONING_COUNT + }; +} + +namespace LLInitParam +{ + template<> + struct TypeValues : public TypeValuesHelper + { + static void declareValues(); + }; +} + +struct LL_COORD_FLOATER +{ + typedef F32 value_t; + + LLCoordCommon convertToCommon() const; + void convertFromCommon(const LLCoordCommon& from); +protected: + LLHandle mFloater; +}; + +struct LLCoordFloater : LLCoord +{ + typedef LLCoord coord_t; + + LLCoordFloater() {} + LLCoordFloater(F32 x, F32 y, LLFloater& floater); + LLCoordFloater(const LLCoordCommon& other, LLFloater& floater); + + LLCoordFloater& operator=(const LLCoordCommon& other) + { + convertFromCommon(other); + return *this; + } + + LLCoordFloater& operator=(const LLCoordFloater& other); + + bool operator==(const LLCoordFloater& other) const; + bool operator!=(const LLCoordFloater& other) const { return !(*this == other); } + + void setFloater(LLFloater& floater); +}; + +class LLFloater : public LLPanel, public LLInstanceTracker +{ + friend class LLFloaterView; + friend class LLFloaterReg; + friend class LLMultiFloater; + +public: + + struct KeyCompare + { +// static bool compare(const LLSD& a, const LLSD& b); + static bool equate(const LLSD& a, const LLSD& b); +/*==========================================================================*| + bool operator()(const LLSD& a, const LLSD& b) const + { + return compare(a, b); + } +|*==========================================================================*/ + }; + + enum EFloaterButton + { + BUTTON_CLOSE = 0, + BUTTON_RESTORE, + BUTTON_MINIMIZE, + BUTTON_TEAR_OFF, + BUTTON_DOCK, + BUTTON_HELP, + BUTTON_COUNT + }; + + struct Params + : public LLInitParam::Block + { + Optional title, + short_title; + + Optional single_instance, + reuse_instance, + can_resize, + can_minimize, + can_close, + can_drag_on_left, + can_tear_off, + save_rect, + save_visibility, + save_dock_state, + can_dock, + show_title, + auto_close; + + Optional positioning; + + Optional header_height, + legacy_header_height; // HACK see initFromXML() + + Optional rel_x, + rel_y; + + // Images for top-right controls + Optional close_image, + restore_image, + minimize_image, + tear_off_image, + dock_image, + help_image; + Optional close_pressed_image, + restore_pressed_image, + minimize_pressed_image, + tear_off_pressed_image, + dock_pressed_image, + help_pressed_image; + + Optional open_callback, + close_callback; + + Ignored follows; + + Params(); + }; + + // use this to avoid creating your own default LLFloater::Param instance + static const Params& getDefaultParams(); + + // Load translations for tooltips for standard buttons + static void initClass(); + + LLFloater(const LLSD& key, const Params& params = getDefaultParams()); + + virtual ~LLFloater(); + + // Don't export top/left for rect, only height/width + static void setupParamsForExport(Params& p, LLView* parent); + bool buildFromFile(const std::string &filename); + + boost::signals2::connection setMinimizeCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setOpenCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setCloseCallback( const commit_signal_t::slot_type& cb ); + + void initFromParams(const LLFloater::Params& p); + bool initFloaterXML(LLXMLNodePtr node, LLView *parent, const std::string& filename, LLXMLNodePtr output_node = NULL); + + /*virtual*/ void handleReshape(const LLRect& new_rect, bool by_user = false); + /*virtual*/ bool canSnapTo(const LLView* other_view); + /*virtual*/ void setSnappedTo(const LLView* snap_view); + /*virtual*/ void setFocus( bool b ); + /*virtual*/ void setIsChrome(bool is_chrome); + /*virtual*/ void setRect(const LLRect &rect); + void setIsSingleInstance(bool is_single_instance); + bool getIsSingleInstance() { return mSingleInstance; } + + void initFloater(const Params& p); + + void openFloater(const LLSD& key = LLSD()); + + // If allowed, close the floater cleanly, releasing focus. + virtual void closeFloater(bool app_quitting = false); + + // Close the floater or its host. Use when hidding or toggling a floater instance. + virtual void closeHostedFloater(); + + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + /*virtual*/ void translate(S32 x, S32 y); + + // Release keyboard and mouse focus + void releaseFocus(); + + // moves to center of gFloaterView + void center(); + + LLMultiFloater* getHost(); + bool isDetachedAndNotMinimized(); + + void applyTitle(); + std::string getCurrentTitle() const; + void setTitle( const std::string& title); + std::string getTitle() const; + void setShortTitle( const std::string& short_title ); + std::string getShortTitle() const; + virtual void setMinimized(bool b); + void moveResizeHandlesToFront(); + void addDependentFloater(LLFloater* dependent, bool reposition = true, bool resize = false); + void addDependentFloater(LLHandle dependent_handle, bool reposition = true, bool resize = false); + LLFloater* getDependee() { return (LLFloater*)mDependeeHandle.get(); } + void removeDependentFloater(LLFloater* dependent); + void fitWithDependentsOnScreen(const LLRect& left, const LLRect& bottom, const LLRect& right, const LLRect& constraint, S32 min_overlap_pixels); + bool isMinimized() const { return mMinimized; } + /// isShown() differs from getVisible() in that isShown() also considers + /// isMinimized(). isShown() is true only if visible and not minimized. + bool isShown() const; + /// The static isShown() can accept a NULL pointer (which of course + /// returns false). When non-NULL, it calls the non-static isShown(). + static bool isShown(const LLFloater* floater); + static bool isVisible(const LLFloater* floater); + static bool isMinimized(const LLFloater* floater); + bool isFirstLook() { return mFirstLook; } // EXT-2653: This function is necessary to prevent overlapping for secondary showed toasts + virtual bool isFrontmost(); + bool isDependent() { return !mDependeeHandle.isDead(); } + void setCanMinimize(bool can_minimize); + void setCanClose(bool can_close); + void setCanTearOff(bool can_tear_off); + virtual void setCanResize(bool can_resize); + void setCanDrag(bool can_drag); + bool getCanDrag(); + void setHost(LLMultiFloater* host); + bool isResizable() const { return mResizable; } + void setResizeLimits( S32 min_width, S32 min_height ); + void getResizeLimits( S32* min_width, S32* min_height ) { *min_width = mMinWidth; *min_height = mMinHeight; } + + static std::string getControlName(const std::string& name, const LLSD& key); + static LLControlGroup* getControlGroup(); + + bool isMinimizeable() const{ return mCanMinimize; } + bool isCloseable() const{ return mCanClose; } + bool isDragOnLeft() const{ return mDragOnLeft; } + S32 getMinWidth() const{ return mMinWidth; } + S32 getMinHeight() const{ return mMinHeight; } + S32 getHeaderHeight() const { return mHeaderHeight; } + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); + virtual bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); + + virtual bool handleScrollWheel(S32 x, S32 y, S32 mask); + + virtual void draw(); + virtual void drawShadow(LLPanel* panel); + + virtual void onOpen(const LLSD& key) {} + virtual void onClose(bool app_quitting) {} + + // This cannot be "const" until all derived floater canClose() + // methods are const as well. JC + virtual bool canClose() { return true; } + + /*virtual*/ void setVisible(bool visible); // do not override + /*virtual*/ void onVisibilityChange ( bool new_visibility ); // do not override + + bool canFocusStealFrontmost() const { return mFocusStealsFrontmost; } + void setFocusStealsFrontmost(bool wants_frontmost) { mFocusStealsFrontmost = wants_frontmost; } + + void setFrontmost(bool take_focus = true, bool restore = true); + virtual void setVisibleAndFrontmost(bool take_focus = true, const LLSD& key = LLSD()); + + // Defaults to false. + virtual bool canSaveAs() const { return false; } + + virtual void saveAs() {} + + void setSnapTarget(LLHandle handle) { mSnappedTo = handle; } + void clearSnapTarget() { mSnappedTo.markDead(); } + LLHandle getSnapTarget() const { return mSnappedTo; } + + LLHandle getHandle() const { return getDerivedHandle(); } + const LLSD& getKey() { return mKey; } + virtual bool matchesKey(const LLSD& key) { return mSingleInstance || KeyCompare::equate(key, mKey); } + + const std::string& getInstanceName() { return mInstanceName; } + + bool isDockable() const { return mCanDock; } + void setCanDock(bool b); + + bool isDocked() const { return mDocked; } + virtual void setDocked(bool docked, bool pop_on_undock = true); + + virtual void setTornOff(bool torn_off) { mTornOff = torn_off; } + bool isTornOff() {return mTornOff;} + void setOpenPositioning(LLFloaterEnums::EOpenPositioning pos) {mPositioning = pos;} + + + // Close the floater returned by getFrontmostClosableFloater() and + // handle refocusing. + static void closeFrontmostFloater(); + + static bool isQuitRequested() { return sQuitting; } + +// LLNotification::Params contextualNotification(const std::string& name) +// { +// return LLNotification::Params(name).context(mNotificationContext); +// } + + static void onClickClose(LLFloater* floater); + static void onClickMinimize(LLFloater* floater); + static void onClickTearOff(LLFloater* floater); + static void onClickDock(LLFloater* floater); + static void onClickHelp(LLFloater* floater); + + static void setFloaterHost(LLMultiFloater* hostp) {sHostp = hostp; } + static LLMultiFloater* getFloaterHost() {return sHostp; } + + void updateTransparency(ETypeTransparency transparency_type); + + void enableResizeCtrls(bool enable, bool width = true, bool height = true); + + bool isPositioning(LLFloaterEnums::EOpenPositioning p) const { return (p == mPositioning); } +protected: + void applyControlsAndPosition(LLFloater* other); + + void stackWith(LLFloater& other); + + virtual void initRectControl(); + virtual bool applyRectControl(); + bool applyDockState(); + void applyPositioning(LLFloater* other, bool on_open); + void applyRelativePosition(); + + void storeRectControl(); + void storeVisibilityControl(); + void storeDockStateControl(); + + void setKey(const LLSD& key); + void setInstanceName(const std::string& name); + + virtual void bringToFront(S32 x, S32 y); + virtual void goneFromFront(); + + void setExpandedRect(const LLRect& rect) { mExpandedRect = rect; } // size when not minimized + const LLRect& getExpandedRect() const { return mExpandedRect; } + + void setAutoFocus(bool focus) { mAutoFocus = focus; } // whether to automatically take focus when opened + bool getAutoFocus() const { return mAutoFocus; } + LLDragHandle* getDragHandle() const { return mDragHandle; } + + void destroy(); // Don't call this directly. You probably want to call closeFloater() + + virtual void onClickCloseBtn(bool app_quitting = false); + + virtual void updateTitleButtons(); + + // Draws a cone from this floater to parent floater or view (owner) + // Modifies context_cone_opacity (interpolates according to fade time and returns new value) + void drawConeToOwner(F32 &context_cone_opacity, + F32 max_cone_opacity, + LLView *owner_view, + F32 context_fade_time = CONTEXT_CONE_FADE_TIME, + F32 contex_cone_in_alpha = CONTEXT_CONE_IN_ALPHA, + F32 contex_cone_out_alpha = CONTEXT_CONE_OUT_ALPHA); + +private: + void setForeground(bool b); // called only by floaterview + void cleanupHandles(); // remove handles to dead floaters + void createMinimizeButton(); + void buildButtons(const Params& p); + + // Images and tooltips are named in the XML, but we want to look them + // up by index. + static LLUIImage* getButtonImage(const Params& p, EFloaterButton e); + static LLUIImage* getButtonPressedImage(const Params& p, EFloaterButton e); + + /** + * @params is_chrome - if floater is Chrome it means that floater will never get focus. + * Therefore it can't be closed with 'Ctrl+W'. So the tooltip text of close button( X ) + * should be 'Close' not 'Close(Ctrl+W)' as for usual floaters. + */ + static std::string getButtonTooltip(const Params& p, EFloaterButton e, bool is_chrome); + + bool offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index); + void addResizeCtrls(); + void layoutResizeCtrls(); + void addDragHandle(); + void layoutDragHandle(); // repair layout + + static void updateActiveFloaterTransparency(); + static void updateInactiveFloaterTransparency(); + void updateTransparency(LLView* view, ETypeTransparency transparency_type); + +public: + static const F32 CONTEXT_CONE_IN_ALPHA; + static const F32 CONTEXT_CONE_OUT_ALPHA; + static const F32 CONTEXT_CONE_FADE_TIME; + + // Called when floater is opened, passes mKey + // Public so external views or floaters can watch for this floater opening + commit_signal_t mOpenSignal; + + // Called when floater is closed, passes app_qitting as LLSD() + // Public so external views or floaters can watch for this floater closing + commit_signal_t mCloseSignal; + + commit_signal_t* mMinimizeSignal; + +protected: + bool mSaveRect; + bool mDefaultRectForGroup; + std::string mRectControl; + std::string mPosXControl; + std::string mPosYControl; + std::string mVisibilityControl; + std::string mDocStateControl; + LLSD mKey; // Key used for retrieving instances; set (for now) by LLFLoaterReg + + LLDragHandle* mDragHandle; + LLResizeBar* mResizeBar[4]; + LLResizeHandle* mResizeHandle[4]; + + LLButton* mButtons[BUTTON_COUNT]; +private: + LLRect mExpandedRect; + + LLUIString mTitle; + LLUIString mShortTitle; + + bool mSingleInstance; // true if there is only ever one instance of the floater + bool mReuseInstance; // true if we want to hide the floater when we close it instead of destroying it + bool mIsReuseInitialized; // true if mReuseInstance already set from parameters + std::string mInstanceName; // Store the instance name so we can remove ourselves from the list + + bool mCanTearOff; + bool mCanMinimize; + bool mCanClose; + bool mFocusStealsFrontmost = true; // false if we don't want the currently focused floater to cover this floater without user interaction + bool mDragOnLeft; + bool mResizable; + bool mAutoClose; + + LLFloaterEnums::EOpenPositioning mPositioning; + LLCoordFloater mPosition; + + S32 mMinWidth; + S32 mMinHeight; + S32 mHeaderHeight; // height in pixels of header for title, drag bar + S32 mLegacyHeaderHeight;// HACK see initFloaterXML() + + bool mMinimized; + bool mForeground; + LLHandle mDependeeHandle; + + + bool mFirstLook; // true if the _next_ time this floater is visible will be the first time in the session that it is visible. + + typedef std::set > handle_set_t; + typedef std::set >::iterator handle_set_iter_t; + handle_set_t mDependents; + bool mTranslateWithDependents { false }; + + bool mButtonsEnabled[BUTTON_COUNT]; + F32 mButtonScale; + bool mAutoFocus; + LLHandle mSnappedTo; + + LLHandle mHostHandle; + LLHandle mLastHostHandle; + + bool mCanDock; + bool mDocked; + bool mTornOff; + + static LLMultiFloater* sHostp; + static bool sQuitting; + static std::string sButtonNames[BUTTON_COUNT]; + static std::string sButtonToolTips[BUTTON_COUNT]; + static std::string sButtonToolTipsIndex[BUTTON_COUNT]; + + typedef void(*click_callback)(LLFloater*); + static click_callback sButtonCallbacks[BUTTON_COUNT]; + + bool mHasBeenDraggedWhileMinimized; + S32 mPreviousMinimizedBottom; + S32 mPreviousMinimizedLeft; + + F32 mDefaultRelativeX; + F32 mDefaultRelativeY; +}; + + +///////////////////////////////////////////////////////////// +// LLFloaterView +// Parent of all floating panels + +const S32 FLOATER_MIN_VISIBLE_PIXELS = 16; + +class LLFloaterView : public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block{}; + +protected: + LLFloaterView (const Params& p); + friend class LLUICtrlFactory; + +public: + + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + /*virtual*/ void draw(); + /*virtual*/ LLRect getSnapRect() const; + /*virtual*/ void refresh(); + + LLRect findNeighboringPosition( LLFloater* reference_floater, LLFloater* neighbor ); + + // Given a child of gFloaterView, make sure this view can fit entirely onscreen. + void adjustToFitScreen(LLFloater* floater, bool allow_partial_outside, bool snap_in_toolbars = false); + + void setMinimizePositionVerticalOffset(S32 offset) { mMinimizePositionVOffset = offset; } + void getMinimizePosition( S32 *left, S32 *bottom); + void restoreAll(); // un-minimize all floaters + typedef std::set skip_list_t; + void pushVisibleAll(bool visible, const skip_list_t& skip_list = skip_list_t()); + void popVisibleAll(const skip_list_t& skip_list = skip_list_t()); + + void setCycleMode(bool mode) { mFocusCycleMode = mode; } + bool getCycleMode() const { return mFocusCycleMode; } + void bringToFront( LLFloater* child, bool give_focus = true, bool restore = true ); + void highlightFocusedFloater(); + void unhighlightFocusedFloater(); + void focusFrontFloater(); + void destroyAllChildren(); + // attempt to close all floaters + void closeAllChildren(bool app_quitting); + bool allChildrenClosed(); + void shiftFloaters(S32 x_offset, S32 y_offset); + + void hideAllFloaters(); + void showHiddenFloaters(); + + + LLFloater* getFrontmost() const; + LLFloater* getBackmost() const; + LLFloater* getParentFloater(LLView* viewp) const; + LLFloater* getFocusedFloater() const; + void syncFloaterTabOrder(); + + // Returns z order of child provided. 0 is closest, larger numbers + // are deeper in the screen. If there is no such child, the return + // value is not defined. + S32 getZOrder(LLFloater* child); + + void setFloaterSnapView(LLHandle snap_view) {mSnapView = snap_view; } + LLFloater* getFrontmostClosableFloater(); + + void setToolbarRect(LLToolBarEnums::EToolBarLocation tb, const LLRect& toolbar_rect); + +private: + void hiddenFloaterClosed(LLFloater* floater); + + LLRect mLastSnapRect; + LLRect mToolbarLeftRect; + LLRect mToolbarBottomRect; + LLRect mToolbarRightRect; + LLHandle mSnapView; + bool mFocusCycleMode; + S32 mSnapOffsetBottom; + S32 mSnapOffsetRight; + S32 mMinimizePositionVOffset; + typedef std::vector, boost::signals2::connection> > hidden_floaters_t; + hidden_floaters_t mHiddenFloaters; + LLHandle mFrontChildHandle; +}; + +// +// Globals +// + +extern LLFloaterView* gFloaterView; + +#endif // LL_FLOATER_H + + + diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp index fad1155d90..fd5a370bc3 100644 --- a/indra/llui/llfloaterreg.cpp +++ b/indra/llui/llfloaterreg.cpp @@ -1,609 +1,609 @@ -/** - * @file llfloaterreg.cpp - * @brief LLFloaterReg Floater Registration Class - * - * $LicenseInfo:firstyear=2002&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 "llfloaterreg.h" - -//#include "llagent.h" -#include "llfloater.h" -#include "llmultifloater.h" -#include "llfloaterreglistener.h" -#include "lluiusage.h" - -//******************************************************* - -//static -LLFloaterReg::instance_list_t LLFloaterReg::sNullInstanceList; -LLFloaterReg::instance_map_t LLFloaterReg::sInstanceMap; -LLFloaterReg::build_map_t LLFloaterReg::sBuildMap; -std::map LLFloaterReg::sGroupMap; -bool LLFloaterReg::sBlockShowFloaters = false; -std::set LLFloaterReg::sAlwaysShowableList; - -static LLFloaterRegListener sFloaterRegListener; - -//******************************************************* - -//static -void LLFloaterReg::add(const std::string& name, const std::string& filename, const LLFloaterBuildFunc& func, const std::string& groupname) -{ - sBuildMap[name].mFunc = func; - sBuildMap[name].mFile = filename; - sGroupMap[name] = groupname.empty() ? name : groupname; - sGroupMap[groupname] = groupname; // for referencing directly by group name -} - -//static -bool LLFloaterReg::isRegistered(const std::string& name) -{ - return sBuildMap.find(name) != sBuildMap.end(); -} - -//static -LLFloater* LLFloaterReg::getLastFloaterInGroup(const std::string& name) -{ - const std::string& groupname = sGroupMap[name]; - if (!groupname.empty()) - { - instance_list_t& list = sInstanceMap[groupname]; - if (!list.empty()) - { - for (instance_list_t::reverse_iterator iter = list.rbegin(); iter != list.rend(); ++iter) - { - LLFloater* inst = *iter; - - if (inst->getVisible() && !inst->isMinimized()) - { - return inst; - } - } - } - } - return NULL; -} - -LLFloater* LLFloaterReg::getLastFloaterCascading() -{ - LLRect candidate_rect; - candidate_rect.mTop = 100000; - LLFloater* candidate_floater = NULL; - - std::map::const_iterator it = sGroupMap.begin(), it_end = sGroupMap.end(); - for( ; it != it_end; ++it) - { - const std::string& group_name = it->second; - - instance_list_t& instances = sInstanceMap[group_name]; - - for (instance_list_t::const_iterator iter = instances.begin(); iter != instances.end(); ++iter) - { - LLFloater* inst = *iter; - - if (inst->getVisible() - && (inst->isPositioning(LLFloaterEnums::POSITIONING_CASCADING) - || inst->isPositioning(LLFloaterEnums::POSITIONING_CASCADE_GROUP))) - { - if (candidate_rect.mTop > inst->getRect().mTop) - { - candidate_floater = inst; - candidate_rect = inst->getRect(); - } - } - } - } - - return candidate_floater; -} - -//static -LLFloater* LLFloaterReg::findInstance(const std::string& name, const LLSD& key) -{ - LLFloater* res = NULL; - const std::string& groupname = sGroupMap[name]; - if (!groupname.empty()) - { - instance_list_t& list = sInstanceMap[groupname]; - for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter) - { - LLFloater* inst = *iter; - if (inst->matchesKey(key)) - { - res = inst; - break; - } - } - } - return res; -} - -//static -LLFloater* LLFloaterReg::getInstance(const std::string& name, const LLSD& key) -{ - LLFloater* res = findInstance(name, key); - if (!res) - { - const LLFloaterBuildFunc& build_func = sBuildMap[name].mFunc; - const std::string& xui_file = sBuildMap[name].mFile; - if (build_func) - { - const std::string& groupname = sGroupMap[name]; - if (!groupname.empty()) - { - instance_list_t& list = sInstanceMap[groupname]; - - res = build_func(key); - if (!res) - { - LL_WARNS() << "Failed to build floater type: '" << name << "'." << LL_ENDL; - return NULL; - } - bool success = res->buildFromFile(xui_file); - if (!success) - { - LL_WARNS() << "Failed to build floater type: '" << name << "'." << LL_ENDL; - return NULL; - } - - // Note: key should eventually be a non optional LLFloater arg; for now, set mKey to be safe - if (res->mKey.isUndefined()) - { - res->mKey = key; - } - res->setInstanceName(name); - - LLFloater *last_floater = (list.empty() ? NULL : list.back()); - - res->applyControlsAndPosition(last_floater); - - gFloaterView->adjustToFitScreen(res, false); - - list.push_back(res); - } - } - if (!res) - { - LL_WARNS() << "Floater type: '" << name << "' not registered." << LL_ENDL; - } - } - return res; -} - -//static -LLFloater* LLFloaterReg::removeInstance(const std::string& name, const LLSD& key) -{ - LLFloater* res = NULL; - const std::string& groupname = sGroupMap[name]; - if (!groupname.empty()) - { - instance_list_t& list = sInstanceMap[groupname]; - for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter) - { - LLFloater* inst = *iter; - if (inst->matchesKey(key)) - { - res = inst; - list.erase(iter); - break; - } - } - } - return res; -} - -//static -// returns true if the instance existed -bool LLFloaterReg::destroyInstance(const std::string& name, const LLSD& key) -{ - LLFloater* inst = removeInstance(name, key); - if (inst) - { - delete inst; - return true; - } - else - { - return false; - } -} - -// Iterators -//static -LLFloaterReg::const_instance_list_t& LLFloaterReg::getFloaterList(const std::string& name) -{ - instance_map_t::iterator iter = sInstanceMap.find(name); - if (iter != sInstanceMap.end()) - { - return iter->second; - } - else - { - return sNullInstanceList; - } -} - -// Visibility Management - -//static -LLFloater* LLFloaterReg::showInstance(const std::string& name, const LLSD& key, bool focus) -{ - if( sBlockShowFloaters - // see EXT-7090 - && sAlwaysShowableList.find(name) == sAlwaysShowableList.end()) - return 0;// - LLFloater* instance = getInstance(name, key); - if (instance) - { - instance->openFloater(key); - if (focus) - instance->setFocus(true); - } - return instance; -} - -//static -// returns true if the instance exists -bool LLFloaterReg::hideInstance(const std::string& name, const LLSD& key) -{ - LLFloater* instance = findInstance(name, key); - if (instance) - { - instance->closeHostedFloater(); - } - return (instance != NULL); -} - -//static -// returns true if the instance is visible when completed -bool LLFloaterReg::toggleInstance(const std::string& name, const LLSD& key) -{ - LLFloater* instance = findInstance(name, key); - if (instance && instance->isShown()) - { - instance->closeHostedFloater(); - return false; - } - - instance = showInstance(name, key, true); - - return instance != nullptr; -} - -//static -// returns true if the instance exists and is visible (doesnt matter minimized or not) -bool LLFloaterReg::instanceVisible(const std::string& name, const LLSD& key) -{ - LLFloater* instance = findInstance(name, key); - return LLFloater::isVisible(instance); -} - -//static -void LLFloaterReg::showInitialVisibleInstances() -{ - // Iterate through alll registered instance names and show any with a save visible state - for (build_map_t::iterator iter = sBuildMap.begin(); iter != sBuildMap.end(); ++iter) - { - const std::string& name = iter->first; - std::string controlname = getVisibilityControlName(name); - if (LLFloater::getControlGroup()->controlExists(controlname)) - { - bool isvis = LLFloater::getControlGroup()->getBOOL(controlname); - if (isvis) - { - showInstance(name, LLSD()); // keyed floaters shouldn't set save_vis to true - } - } - } -} - -//static -void LLFloaterReg::hideVisibleInstances(const std::set& exceptions) -{ - // Iterate through alll active instances and hide them - for (instance_map_t::iterator iter = sInstanceMap.begin(); iter != sInstanceMap.end(); ++iter) - { - const std::string& name = iter->first; - if (exceptions.find(name) != exceptions.end()) - continue; - instance_list_t& list = iter->second; - for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter) - { - LLFloater* floater = *iter; - floater->pushVisible(false); - } - } -} - -//static -void LLFloaterReg::restoreVisibleInstances() -{ - // Iterate through all active instances and restore visibility - for (instance_map_t::iterator iter = sInstanceMap.begin(); iter != sInstanceMap.end(); ++iter) - { - instance_list_t& list = iter->second; - for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter) - { - LLFloater* floater = *iter; - floater->popVisible(); - } - } -} - -//static -std::string LLFloaterReg::getRectControlName(const std::string& name) -{ - return std::string("floater_rect_") + getBaseControlName(name); -} - -//static -std::string LLFloaterReg::declareRectControl(const std::string& name) -{ - std::string controlname = getRectControlName(name); - LLFloater::getControlGroup()->declareRect(controlname, LLRect(), - llformat("Window Size for %s", name.c_str()), - LLControlVariable::PERSIST_NONDFT); - return controlname; -} - -std::string LLFloaterReg::declarePosXControl(const std::string& name) -{ - std::string controlname = std::string("floater_pos_") + getBaseControlName(name) + "_x"; - LLFloater::getControlGroup()->declareF32(controlname, - 10.f, - llformat("Window X Position for %s", name.c_str()), - LLControlVariable::PERSIST_NONDFT); - return controlname; -} - -std::string LLFloaterReg::declarePosYControl(const std::string& name) -{ - std::string controlname = std::string("floater_pos_") + getBaseControlName(name) + "_y"; - LLFloater::getControlGroup()->declareF32(controlname, - 10.f, - llformat("Window Y Position for %s", name.c_str()), - LLControlVariable::PERSIST_NONDFT); - - return controlname; -} - - -//static -std::string LLFloaterReg::getVisibilityControlName(const std::string& name) -{ - return std::string("floater_vis_") + getBaseControlName(name); -} - -//static -std::string LLFloaterReg::getBaseControlName(const std::string& name) -{ - std::string res(name); - LLStringUtil::replaceChar( res, ' ', '_' ); - return res; -} - - -//static -std::string LLFloaterReg::declareVisibilityControl(const std::string& name) -{ - std::string controlname = getVisibilityControlName(name); - LLFloater::getControlGroup()->declareBOOL(controlname, false, - llformat("Window Visibility for %s", name.c_str()), - LLControlVariable::PERSIST_NONDFT); - return controlname; -} - -//static -std::string LLFloaterReg::declareDockStateControl(const std::string& name) -{ - std::string controlname = getDockStateControlName(name); - LLFloater::getControlGroup()->declareBOOL(controlname, true, - llformat("Window Docking state for %s", name.c_str()), - LLControlVariable::PERSIST_NONDFT); - return controlname; - -} - -//static -std::string LLFloaterReg::getDockStateControlName(const std::string& name) -{ - std::string res = std::string("floater_dock_") + name; - LLStringUtil::replaceChar( res, ' ', '_' ); - return res; -} - - -//static -void LLFloaterReg::registerControlVariables() -{ - // Iterate through alll registered instance names and register rect and visibility control variables - for (build_map_t::iterator iter = sBuildMap.begin(); iter != sBuildMap.end(); ++iter) - { - const std::string& name = iter->first; - if (LLFloater::getControlGroup()->controlExists(getRectControlName(name))) - { - declareRectControl(name); - } - if (LLFloater::getControlGroup()->controlExists(getVisibilityControlName(name))) - { - declareVisibilityControl(name); - } - } - - const LLSD& exclude_list = LLUI::getInstance()->mSettingGroups["config"]->getLLSD("always_showable_floaters"); - for (LLSD::array_const_iterator iter = exclude_list.beginArray(); - iter != exclude_list.endArray(); - iter++) - { - sAlwaysShowableList.insert(iter->asString()); - } -} - -//static -void LLFloaterReg::toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& key) -{ - // - // Floaters controlled by the toolbar behave a bit differently from others. - // Namely they have 3-4 states as defined in the design wiki page here: - // https://wiki.lindenlab.com/wiki/FUI_Button_states - // - // The basic idea is this: - // * If the target floater is minimized, this button press will un-minimize it. - // * Else if the target floater is closed open it. - // * Else if the target floater does not have focus, give it focus. - // * Also, if it is not on top, bring it forward when focus is given. - // * Else the target floater is open, close it. - // - std::string name = sdname.asString(); - LLFloater* instance = getInstance(name, key); - - if (!instance) - { - LL_DEBUGS() << "Unable to get instance of floater '" << name << "'" << LL_ENDL; - return; - } - - // If hosted, we need to take that into account - LLFloater* host = instance->getHost(); - - if (host) - { - if (host->isMinimized() || !host->isShown() || !host->isFrontmost()) - { - host->setMinimized(false); - instance->openFloater(key); - instance->setVisibleAndFrontmost(true, key); - } - else if (!instance->getVisible()) - { - instance->openFloater(key); - instance->setVisibleAndFrontmost(true, key); - instance->setFocus(true); - } - else - { - instance->closeHostedFloater(); - } - } - else - { - if (instance->isMinimized()) - { - instance->setMinimized(false); - instance->setVisibleAndFrontmost(true, key); - } - else if (!instance->isShown()) - { - instance->openFloater(key); - instance->setVisibleAndFrontmost(true, key); - } - else if (!instance->isFrontmost()) - { - instance->setVisibleAndFrontmost(true, key); - } - else - { - instance->closeHostedFloater(); - } - } -} - -// static -// Same as toggleInstanceOrBringToFront but does not close floater. -// unlike showInstance() does not trigger onOpen() if already open -void LLFloaterReg::showInstanceOrBringToFront(const LLSD& sdname, const LLSD& key) -{ - std::string name = sdname.asString(); - LLFloater* instance = getInstance(name, key); - - - if (!instance) - { - LL_DEBUGS() << "Unable to get instance of floater '" << name << "'" << LL_ENDL; - return; - } - - // If hosted, we need to take that into account - LLFloater* host = instance->getHost(); - - if (host) - { - if (host->isMinimized() || !host->isShown() || !host->isFrontmost()) - { - host->setMinimized(false); - instance->openFloater(key); - instance->setVisibleAndFrontmost(true, key); - } - else if (!instance->getVisible()) - { - instance->openFloater(key); - instance->setVisibleAndFrontmost(true, key); - instance->setFocus(true); - } - } - else - { - if (instance->isMinimized()) - { - instance->setMinimized(false); - instance->setVisibleAndFrontmost(true, key); - } - else if (!instance->isShown()) - { - instance->openFloater(key); - instance->setVisibleAndFrontmost(true, key); - } - else if (!instance->isFrontmost()) - { - instance->setVisibleAndFrontmost(true, key); - } - } -} - -// static -U32 LLFloaterReg::getVisibleFloaterInstanceCount() -{ - U32 count = 0; - - std::map::const_iterator it = sGroupMap.begin(), it_end = sGroupMap.end(); - for( ; it != it_end; ++it) - { - const std::string& group_name = it->second; - - instance_list_t& instances = sInstanceMap[group_name]; - - for (instance_list_t::const_iterator iter = instances.begin(); iter != instances.end(); ++iter) - { - LLFloater* inst = *iter; - - if (inst->getVisible() && !inst->isMinimized()) - { - count++; - } - } - } - - return count; -} +/** + * @file llfloaterreg.cpp + * @brief LLFloaterReg Floater Registration Class + * + * $LicenseInfo:firstyear=2002&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 "llfloaterreg.h" + +//#include "llagent.h" +#include "llfloater.h" +#include "llmultifloater.h" +#include "llfloaterreglistener.h" +#include "lluiusage.h" + +//******************************************************* + +//static +LLFloaterReg::instance_list_t LLFloaterReg::sNullInstanceList; +LLFloaterReg::instance_map_t LLFloaterReg::sInstanceMap; +LLFloaterReg::build_map_t LLFloaterReg::sBuildMap; +std::map LLFloaterReg::sGroupMap; +bool LLFloaterReg::sBlockShowFloaters = false; +std::set LLFloaterReg::sAlwaysShowableList; + +static LLFloaterRegListener sFloaterRegListener; + +//******************************************************* + +//static +void LLFloaterReg::add(const std::string& name, const std::string& filename, const LLFloaterBuildFunc& func, const std::string& groupname) +{ + sBuildMap[name].mFunc = func; + sBuildMap[name].mFile = filename; + sGroupMap[name] = groupname.empty() ? name : groupname; + sGroupMap[groupname] = groupname; // for referencing directly by group name +} + +//static +bool LLFloaterReg::isRegistered(const std::string& name) +{ + return sBuildMap.find(name) != sBuildMap.end(); +} + +//static +LLFloater* LLFloaterReg::getLastFloaterInGroup(const std::string& name) +{ + const std::string& groupname = sGroupMap[name]; + if (!groupname.empty()) + { + instance_list_t& list = sInstanceMap[groupname]; + if (!list.empty()) + { + for (instance_list_t::reverse_iterator iter = list.rbegin(); iter != list.rend(); ++iter) + { + LLFloater* inst = *iter; + + if (inst->getVisible() && !inst->isMinimized()) + { + return inst; + } + } + } + } + return NULL; +} + +LLFloater* LLFloaterReg::getLastFloaterCascading() +{ + LLRect candidate_rect; + candidate_rect.mTop = 100000; + LLFloater* candidate_floater = NULL; + + std::map::const_iterator it = sGroupMap.begin(), it_end = sGroupMap.end(); + for( ; it != it_end; ++it) + { + const std::string& group_name = it->second; + + instance_list_t& instances = sInstanceMap[group_name]; + + for (instance_list_t::const_iterator iter = instances.begin(); iter != instances.end(); ++iter) + { + LLFloater* inst = *iter; + + if (inst->getVisible() + && (inst->isPositioning(LLFloaterEnums::POSITIONING_CASCADING) + || inst->isPositioning(LLFloaterEnums::POSITIONING_CASCADE_GROUP))) + { + if (candidate_rect.mTop > inst->getRect().mTop) + { + candidate_floater = inst; + candidate_rect = inst->getRect(); + } + } + } + } + + return candidate_floater; +} + +//static +LLFloater* LLFloaterReg::findInstance(const std::string& name, const LLSD& key) +{ + LLFloater* res = NULL; + const std::string& groupname = sGroupMap[name]; + if (!groupname.empty()) + { + instance_list_t& list = sInstanceMap[groupname]; + for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter) + { + LLFloater* inst = *iter; + if (inst->matchesKey(key)) + { + res = inst; + break; + } + } + } + return res; +} + +//static +LLFloater* LLFloaterReg::getInstance(const std::string& name, const LLSD& key) +{ + LLFloater* res = findInstance(name, key); + if (!res) + { + const LLFloaterBuildFunc& build_func = sBuildMap[name].mFunc; + const std::string& xui_file = sBuildMap[name].mFile; + if (build_func) + { + const std::string& groupname = sGroupMap[name]; + if (!groupname.empty()) + { + instance_list_t& list = sInstanceMap[groupname]; + + res = build_func(key); + if (!res) + { + LL_WARNS() << "Failed to build floater type: '" << name << "'." << LL_ENDL; + return NULL; + } + bool success = res->buildFromFile(xui_file); + if (!success) + { + LL_WARNS() << "Failed to build floater type: '" << name << "'." << LL_ENDL; + return NULL; + } + + // Note: key should eventually be a non optional LLFloater arg; for now, set mKey to be safe + if (res->mKey.isUndefined()) + { + res->mKey = key; + } + res->setInstanceName(name); + + LLFloater *last_floater = (list.empty() ? NULL : list.back()); + + res->applyControlsAndPosition(last_floater); + + gFloaterView->adjustToFitScreen(res, false); + + list.push_back(res); + } + } + if (!res) + { + LL_WARNS() << "Floater type: '" << name << "' not registered." << LL_ENDL; + } + } + return res; +} + +//static +LLFloater* LLFloaterReg::removeInstance(const std::string& name, const LLSD& key) +{ + LLFloater* res = NULL; + const std::string& groupname = sGroupMap[name]; + if (!groupname.empty()) + { + instance_list_t& list = sInstanceMap[groupname]; + for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter) + { + LLFloater* inst = *iter; + if (inst->matchesKey(key)) + { + res = inst; + list.erase(iter); + break; + } + } + } + return res; +} + +//static +// returns true if the instance existed +bool LLFloaterReg::destroyInstance(const std::string& name, const LLSD& key) +{ + LLFloater* inst = removeInstance(name, key); + if (inst) + { + delete inst; + return true; + } + else + { + return false; + } +} + +// Iterators +//static +LLFloaterReg::const_instance_list_t& LLFloaterReg::getFloaterList(const std::string& name) +{ + instance_map_t::iterator iter = sInstanceMap.find(name); + if (iter != sInstanceMap.end()) + { + return iter->second; + } + else + { + return sNullInstanceList; + } +} + +// Visibility Management + +//static +LLFloater* LLFloaterReg::showInstance(const std::string& name, const LLSD& key, bool focus) +{ + if( sBlockShowFloaters + // see EXT-7090 + && sAlwaysShowableList.find(name) == sAlwaysShowableList.end()) + return 0;// + LLFloater* instance = getInstance(name, key); + if (instance) + { + instance->openFloater(key); + if (focus) + instance->setFocus(true); + } + return instance; +} + +//static +// returns true if the instance exists +bool LLFloaterReg::hideInstance(const std::string& name, const LLSD& key) +{ + LLFloater* instance = findInstance(name, key); + if (instance) + { + instance->closeHostedFloater(); + } + return (instance != NULL); +} + +//static +// returns true if the instance is visible when completed +bool LLFloaterReg::toggleInstance(const std::string& name, const LLSD& key) +{ + LLFloater* instance = findInstance(name, key); + if (instance && instance->isShown()) + { + instance->closeHostedFloater(); + return false; + } + + instance = showInstance(name, key, true); + + return instance != nullptr; +} + +//static +// returns true if the instance exists and is visible (doesnt matter minimized or not) +bool LLFloaterReg::instanceVisible(const std::string& name, const LLSD& key) +{ + LLFloater* instance = findInstance(name, key); + return LLFloater::isVisible(instance); +} + +//static +void LLFloaterReg::showInitialVisibleInstances() +{ + // Iterate through alll registered instance names and show any with a save visible state + for (build_map_t::iterator iter = sBuildMap.begin(); iter != sBuildMap.end(); ++iter) + { + const std::string& name = iter->first; + std::string controlname = getVisibilityControlName(name); + if (LLFloater::getControlGroup()->controlExists(controlname)) + { + bool isvis = LLFloater::getControlGroup()->getBOOL(controlname); + if (isvis) + { + showInstance(name, LLSD()); // keyed floaters shouldn't set save_vis to true + } + } + } +} + +//static +void LLFloaterReg::hideVisibleInstances(const std::set& exceptions) +{ + // Iterate through alll active instances and hide them + for (instance_map_t::iterator iter = sInstanceMap.begin(); iter != sInstanceMap.end(); ++iter) + { + const std::string& name = iter->first; + if (exceptions.find(name) != exceptions.end()) + continue; + instance_list_t& list = iter->second; + for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter) + { + LLFloater* floater = *iter; + floater->pushVisible(false); + } + } +} + +//static +void LLFloaterReg::restoreVisibleInstances() +{ + // Iterate through all active instances and restore visibility + for (instance_map_t::iterator iter = sInstanceMap.begin(); iter != sInstanceMap.end(); ++iter) + { + instance_list_t& list = iter->second; + for (instance_list_t::iterator iter = list.begin(); iter != list.end(); ++iter) + { + LLFloater* floater = *iter; + floater->popVisible(); + } + } +} + +//static +std::string LLFloaterReg::getRectControlName(const std::string& name) +{ + return std::string("floater_rect_") + getBaseControlName(name); +} + +//static +std::string LLFloaterReg::declareRectControl(const std::string& name) +{ + std::string controlname = getRectControlName(name); + LLFloater::getControlGroup()->declareRect(controlname, LLRect(), + llformat("Window Size for %s", name.c_str()), + LLControlVariable::PERSIST_NONDFT); + return controlname; +} + +std::string LLFloaterReg::declarePosXControl(const std::string& name) +{ + std::string controlname = std::string("floater_pos_") + getBaseControlName(name) + "_x"; + LLFloater::getControlGroup()->declareF32(controlname, + 10.f, + llformat("Window X Position for %s", name.c_str()), + LLControlVariable::PERSIST_NONDFT); + return controlname; +} + +std::string LLFloaterReg::declarePosYControl(const std::string& name) +{ + std::string controlname = std::string("floater_pos_") + getBaseControlName(name) + "_y"; + LLFloater::getControlGroup()->declareF32(controlname, + 10.f, + llformat("Window Y Position for %s", name.c_str()), + LLControlVariable::PERSIST_NONDFT); + + return controlname; +} + + +//static +std::string LLFloaterReg::getVisibilityControlName(const std::string& name) +{ + return std::string("floater_vis_") + getBaseControlName(name); +} + +//static +std::string LLFloaterReg::getBaseControlName(const std::string& name) +{ + std::string res(name); + LLStringUtil::replaceChar( res, ' ', '_' ); + return res; +} + + +//static +std::string LLFloaterReg::declareVisibilityControl(const std::string& name) +{ + std::string controlname = getVisibilityControlName(name); + LLFloater::getControlGroup()->declareBOOL(controlname, false, + llformat("Window Visibility for %s", name.c_str()), + LLControlVariable::PERSIST_NONDFT); + return controlname; +} + +//static +std::string LLFloaterReg::declareDockStateControl(const std::string& name) +{ + std::string controlname = getDockStateControlName(name); + LLFloater::getControlGroup()->declareBOOL(controlname, true, + llformat("Window Docking state for %s", name.c_str()), + LLControlVariable::PERSIST_NONDFT); + return controlname; + +} + +//static +std::string LLFloaterReg::getDockStateControlName(const std::string& name) +{ + std::string res = std::string("floater_dock_") + name; + LLStringUtil::replaceChar( res, ' ', '_' ); + return res; +} + + +//static +void LLFloaterReg::registerControlVariables() +{ + // Iterate through alll registered instance names and register rect and visibility control variables + for (build_map_t::iterator iter = sBuildMap.begin(); iter != sBuildMap.end(); ++iter) + { + const std::string& name = iter->first; + if (LLFloater::getControlGroup()->controlExists(getRectControlName(name))) + { + declareRectControl(name); + } + if (LLFloater::getControlGroup()->controlExists(getVisibilityControlName(name))) + { + declareVisibilityControl(name); + } + } + + const LLSD& exclude_list = LLUI::getInstance()->mSettingGroups["config"]->getLLSD("always_showable_floaters"); + for (LLSD::array_const_iterator iter = exclude_list.beginArray(); + iter != exclude_list.endArray(); + iter++) + { + sAlwaysShowableList.insert(iter->asString()); + } +} + +//static +void LLFloaterReg::toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& key) +{ + // + // Floaters controlled by the toolbar behave a bit differently from others. + // Namely they have 3-4 states as defined in the design wiki page here: + // https://wiki.lindenlab.com/wiki/FUI_Button_states + // + // The basic idea is this: + // * If the target floater is minimized, this button press will un-minimize it. + // * Else if the target floater is closed open it. + // * Else if the target floater does not have focus, give it focus. + // * Also, if it is not on top, bring it forward when focus is given. + // * Else the target floater is open, close it. + // + std::string name = sdname.asString(); + LLFloater* instance = getInstance(name, key); + + if (!instance) + { + LL_DEBUGS() << "Unable to get instance of floater '" << name << "'" << LL_ENDL; + return; + } + + // If hosted, we need to take that into account + LLFloater* host = instance->getHost(); + + if (host) + { + if (host->isMinimized() || !host->isShown() || !host->isFrontmost()) + { + host->setMinimized(false); + instance->openFloater(key); + instance->setVisibleAndFrontmost(true, key); + } + else if (!instance->getVisible()) + { + instance->openFloater(key); + instance->setVisibleAndFrontmost(true, key); + instance->setFocus(true); + } + else + { + instance->closeHostedFloater(); + } + } + else + { + if (instance->isMinimized()) + { + instance->setMinimized(false); + instance->setVisibleAndFrontmost(true, key); + } + else if (!instance->isShown()) + { + instance->openFloater(key); + instance->setVisibleAndFrontmost(true, key); + } + else if (!instance->isFrontmost()) + { + instance->setVisibleAndFrontmost(true, key); + } + else + { + instance->closeHostedFloater(); + } + } +} + +// static +// Same as toggleInstanceOrBringToFront but does not close floater. +// unlike showInstance() does not trigger onOpen() if already open +void LLFloaterReg::showInstanceOrBringToFront(const LLSD& sdname, const LLSD& key) +{ + std::string name = sdname.asString(); + LLFloater* instance = getInstance(name, key); + + + if (!instance) + { + LL_DEBUGS() << "Unable to get instance of floater '" << name << "'" << LL_ENDL; + return; + } + + // If hosted, we need to take that into account + LLFloater* host = instance->getHost(); + + if (host) + { + if (host->isMinimized() || !host->isShown() || !host->isFrontmost()) + { + host->setMinimized(false); + instance->openFloater(key); + instance->setVisibleAndFrontmost(true, key); + } + else if (!instance->getVisible()) + { + instance->openFloater(key); + instance->setVisibleAndFrontmost(true, key); + instance->setFocus(true); + } + } + else + { + if (instance->isMinimized()) + { + instance->setMinimized(false); + instance->setVisibleAndFrontmost(true, key); + } + else if (!instance->isShown()) + { + instance->openFloater(key); + instance->setVisibleAndFrontmost(true, key); + } + else if (!instance->isFrontmost()) + { + instance->setVisibleAndFrontmost(true, key); + } + } +} + +// static +U32 LLFloaterReg::getVisibleFloaterInstanceCount() +{ + U32 count = 0; + + std::map::const_iterator it = sGroupMap.begin(), it_end = sGroupMap.end(); + for( ; it != it_end; ++it) + { + const std::string& group_name = it->second; + + instance_list_t& instances = sInstanceMap[group_name]; + + for (instance_list_t::const_iterator iter = instances.begin(); iter != instances.end(); ++iter) + { + LLFloater* inst = *iter; + + if (inst->getVisible() && !inst->isMinimized()) + { + count++; + } + } + } + + return count; +} diff --git a/indra/llui/llfloaterreg.h b/indra/llui/llfloaterreg.h index 988372e8fe..6a642cbb27 100644 --- a/indra/llui/llfloaterreg.h +++ b/indra/llui/llfloaterreg.h @@ -1,158 +1,158 @@ -/** - * @file llfloaterreg.h - * @brief LLFloaterReg Floater Registration Class - * - * $LicenseInfo:firstyear=2002&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$ - */ -#ifndef LLFLOATERREG_H -#define LLFLOATERREG_H - -/// llcommon -#include "llrect.h" -#include "llsd.h" - -#include -#include - -//******************************************************* -// -// Floater Class Registry -// - -class LLFloater; -class LLUICtrl; - -typedef boost::function LLFloaterBuildFunc; - -class LLFloaterReg -{ -public: - // We use a list of LLFloater's instead of a set for two reasons: - // 1) With a list we have a predictable ordering, useful for finding the last opened floater of a given type. - // 2) We can change the key of a floater without altering the list. - typedef std::list instance_list_t; - typedef const instance_list_t const_instance_list_t; - typedef std::map instance_map_t; - - struct BuildData - { - LLFloaterBuildFunc mFunc; - std::string mFile; - }; - typedef std::map build_map_t; - -private: - friend class LLFloaterRegListener; - static instance_list_t sNullInstanceList; - static instance_map_t sInstanceMap; - static build_map_t sBuildMap; - static std::map sGroupMap; - static bool sBlockShowFloaters; - /** - * Defines list of floater names that can be shown despite state of sBlockShowFloaters. - */ - static std::set sAlwaysShowableList; - -public: - // Registration - - // usage: LLFloaterClassRegistry::add("foo", (LLFloaterBuildFunc)&LLFloaterClassRegistry::build); - template - static LLFloater* build(const LLSD& key) - { - T* floater = new T(key); - return floater; - } - - static void add(const std::string& name, const std::string& file, const LLFloaterBuildFunc& func, - const std::string& groupname = LLStringUtil::null); - static bool isRegistered(const std::string& name); - - // Helpers - static LLFloater* getLastFloaterInGroup(const std::string& name); - static LLFloater* getLastFloaterCascading(); - - // Find / get (create) / remove / destroy - static LLFloater* findInstance(const std::string& name, const LLSD& key = LLSD()); - static LLFloater* getInstance(const std::string& name, const LLSD& key = LLSD()); - static LLFloater* removeInstance(const std::string& name, const LLSD& key = LLSD()); - static bool destroyInstance(const std::string& name, const LLSD& key = LLSD()); - - // Iterators - static const_instance_list_t& getFloaterList(const std::string& name); - - // Visibility Management - // return NULL if instance not found or can't create instance (no builder) - static LLFloater* showInstance(const std::string& name, const LLSD& key = LLSD(), bool focus = false); - // Close a floater (may destroy or set invisible) - // return false if can't find instance - static bool hideInstance(const std::string& name, const LLSD& key = LLSD()); - // return true if instance is visible: - static bool toggleInstance(const std::string& name, const LLSD& key = LLSD()); - static bool instanceVisible(const std::string& name, const LLSD& key = LLSD()); - - static void showInitialVisibleInstances(); - static void hideVisibleInstances(const std::set& exceptions = std::set()); - static void restoreVisibleInstances(); - - // Control Variables - static std::string getRectControlName(const std::string& name); - static std::string declareRectControl(const std::string& name); - static std::string declarePosXControl(const std::string& name); - static std::string declarePosYControl(const std::string& name); - static std::string getVisibilityControlName(const std::string& name); - static std::string declareVisibilityControl(const std::string& name); - static std::string getBaseControlName(const std::string& name); - static std::string declareDockStateControl(const std::string& name); - static std::string getDockStateControlName(const std::string& name); - - static void registerControlVariables(); - - // Callback wrappers - static void toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& key = LLSD()); - static void showInstanceOrBringToFront(const LLSD& sdname, const LLSD& key = LLSD()); - - // Typed find / get / show - template - static T* findTypedInstance(const std::string& name, const LLSD& key = LLSD()) - { - return dynamic_cast(findInstance(name, key)); - } - - template - static T* getTypedInstance(const std::string& name, const LLSD& key = LLSD()) - { - return dynamic_cast(getInstance(name, key)); - } - - template - static T* showTypedInstance(const std::string& name, const LLSD& key = LLSD(), bool focus = false) - { - return dynamic_cast(showInstance(name, key, focus)); - } - - static void blockShowFloaters(bool value) { sBlockShowFloaters = value;} - - static U32 getVisibleFloaterInstanceCount(); -}; - -#endif +/** + * @file llfloaterreg.h + * @brief LLFloaterReg Floater Registration Class + * + * $LicenseInfo:firstyear=2002&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$ + */ +#ifndef LLFLOATERREG_H +#define LLFLOATERREG_H + +/// llcommon +#include "llrect.h" +#include "llsd.h" + +#include +#include + +//******************************************************* +// +// Floater Class Registry +// + +class LLFloater; +class LLUICtrl; + +typedef boost::function LLFloaterBuildFunc; + +class LLFloaterReg +{ +public: + // We use a list of LLFloater's instead of a set for two reasons: + // 1) With a list we have a predictable ordering, useful for finding the last opened floater of a given type. + // 2) We can change the key of a floater without altering the list. + typedef std::list instance_list_t; + typedef const instance_list_t const_instance_list_t; + typedef std::map instance_map_t; + + struct BuildData + { + LLFloaterBuildFunc mFunc; + std::string mFile; + }; + typedef std::map build_map_t; + +private: + friend class LLFloaterRegListener; + static instance_list_t sNullInstanceList; + static instance_map_t sInstanceMap; + static build_map_t sBuildMap; + static std::map sGroupMap; + static bool sBlockShowFloaters; + /** + * Defines list of floater names that can be shown despite state of sBlockShowFloaters. + */ + static std::set sAlwaysShowableList; + +public: + // Registration + + // usage: LLFloaterClassRegistry::add("foo", (LLFloaterBuildFunc)&LLFloaterClassRegistry::build); + template + static LLFloater* build(const LLSD& key) + { + T* floater = new T(key); + return floater; + } + + static void add(const std::string& name, const std::string& file, const LLFloaterBuildFunc& func, + const std::string& groupname = LLStringUtil::null); + static bool isRegistered(const std::string& name); + + // Helpers + static LLFloater* getLastFloaterInGroup(const std::string& name); + static LLFloater* getLastFloaterCascading(); + + // Find / get (create) / remove / destroy + static LLFloater* findInstance(const std::string& name, const LLSD& key = LLSD()); + static LLFloater* getInstance(const std::string& name, const LLSD& key = LLSD()); + static LLFloater* removeInstance(const std::string& name, const LLSD& key = LLSD()); + static bool destroyInstance(const std::string& name, const LLSD& key = LLSD()); + + // Iterators + static const_instance_list_t& getFloaterList(const std::string& name); + + // Visibility Management + // return NULL if instance not found or can't create instance (no builder) + static LLFloater* showInstance(const std::string& name, const LLSD& key = LLSD(), bool focus = false); + // Close a floater (may destroy or set invisible) + // return false if can't find instance + static bool hideInstance(const std::string& name, const LLSD& key = LLSD()); + // return true if instance is visible: + static bool toggleInstance(const std::string& name, const LLSD& key = LLSD()); + static bool instanceVisible(const std::string& name, const LLSD& key = LLSD()); + + static void showInitialVisibleInstances(); + static void hideVisibleInstances(const std::set& exceptions = std::set()); + static void restoreVisibleInstances(); + + // Control Variables + static std::string getRectControlName(const std::string& name); + static std::string declareRectControl(const std::string& name); + static std::string declarePosXControl(const std::string& name); + static std::string declarePosYControl(const std::string& name); + static std::string getVisibilityControlName(const std::string& name); + static std::string declareVisibilityControl(const std::string& name); + static std::string getBaseControlName(const std::string& name); + static std::string declareDockStateControl(const std::string& name); + static std::string getDockStateControlName(const std::string& name); + + static void registerControlVariables(); + + // Callback wrappers + static void toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& key = LLSD()); + static void showInstanceOrBringToFront(const LLSD& sdname, const LLSD& key = LLSD()); + + // Typed find / get / show + template + static T* findTypedInstance(const std::string& name, const LLSD& key = LLSD()) + { + return dynamic_cast(findInstance(name, key)); + } + + template + static T* getTypedInstance(const std::string& name, const LLSD& key = LLSD()) + { + return dynamic_cast(getInstance(name, key)); + } + + template + static T* showTypedInstance(const std::string& name, const LLSD& key = LLSD(), bool focus = false) + { + return dynamic_cast(showInstance(name, key, focus)); + } + + static void blockShowFloaters(bool value) { sBlockShowFloaters = value;} + + static U32 getVisibleFloaterInstanceCount(); +}; + +#endif diff --git a/indra/llui/llflyoutbutton.cpp b/indra/llui/llflyoutbutton.cpp index 1682e195df..2e198e8dbc 100644 --- a/indra/llui/llflyoutbutton.cpp +++ b/indra/llui/llflyoutbutton.cpp @@ -1,77 +1,77 @@ -/** - * @file llflyoutbutton.cpp - * @brief LLFlyoutButton base 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" - -// file includes -#include "llflyoutbutton.h" - -//static LLDefaultChildRegistry::Register r2("flyout_button"); - -const S32 FLYOUT_BUTTON_ARROW_WIDTH = 24; - -LLFlyoutButton::LLFlyoutButton(const Params& p) -: LLComboBox(p), - mToggleState(false), - mActionButton(NULL) -{ - // Always use text box - // Text label button - LLButton::Params bp(p.action_button); - bp.name(p.label); - bp.label(p.label); - bp.rect.left(0).bottom(0).width(getRect().getWidth() - FLYOUT_BUTTON_ARROW_WIDTH).height(getRect().getHeight()); - bp.click_callback.function(boost::bind(&LLFlyoutButton::onActionButtonClick, this, _2)); - bp.follows.flags(FOLLOWS_ALL); - - mActionButton = LLUICtrlFactory::create(bp); - addChild(mActionButton); -} - -void LLFlyoutButton::onActionButtonClick(const LLSD& data) -{ - // remember last list selection? - mList->deselect(); - onCommit(); -} - -void LLFlyoutButton::draw() -{ - mActionButton->setToggleState(mToggleState); - mButton->setToggleState(mToggleState); - - //FIXME: this should be an attribute of comboboxes, whether they have a distinct label or - // the label reflects the last selected item, for now we have to manually remove the label - setLabel(LLStringUtil::null); - LLComboBox::draw(); -} - -void LLFlyoutButton::setToggleState(bool state) -{ - mToggleState = state; -} - - +/** + * @file llflyoutbutton.cpp + * @brief LLFlyoutButton base 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" + +// file includes +#include "llflyoutbutton.h" + +//static LLDefaultChildRegistry::Register r2("flyout_button"); + +const S32 FLYOUT_BUTTON_ARROW_WIDTH = 24; + +LLFlyoutButton::LLFlyoutButton(const Params& p) +: LLComboBox(p), + mToggleState(false), + mActionButton(NULL) +{ + // Always use text box + // Text label button + LLButton::Params bp(p.action_button); + bp.name(p.label); + bp.label(p.label); + bp.rect.left(0).bottom(0).width(getRect().getWidth() - FLYOUT_BUTTON_ARROW_WIDTH).height(getRect().getHeight()); + bp.click_callback.function(boost::bind(&LLFlyoutButton::onActionButtonClick, this, _2)); + bp.follows.flags(FOLLOWS_ALL); + + mActionButton = LLUICtrlFactory::create(bp); + addChild(mActionButton); +} + +void LLFlyoutButton::onActionButtonClick(const LLSD& data) +{ + // remember last list selection? + mList->deselect(); + onCommit(); +} + +void LLFlyoutButton::draw() +{ + mActionButton->setToggleState(mToggleState); + mButton->setToggleState(mToggleState); + + //FIXME: this should be an attribute of comboboxes, whether they have a distinct label or + // the label reflects the last selected item, for now we have to manually remove the label + setLabel(LLStringUtil::null); + LLComboBox::draw(); +} + +void LLFlyoutButton::setToggleState(bool state) +{ + mToggleState = state; +} + + diff --git a/indra/llui/llflyoutbutton.h b/indra/llui/llflyoutbutton.h index 3992bb1e01..7a49501318 100644 --- a/indra/llui/llflyoutbutton.h +++ b/indra/llui/llflyoutbutton.h @@ -1,68 +1,68 @@ -/** - * @file llflyoutbutton.h - * @brief LLFlyoutButton base 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$ - */ - -// A control that displays the name of the chosen item, which when clicked -// shows a scrolling box of choices. - -#ifndef LL_LLFLYOUTBUTTON_H -#define LL_LLFLYOUTBUTTON_H - -#include "llcombobox.h" - -// Classes - -class LLFlyoutButton : public LLComboBox -{ -public: - struct Params : public LLInitParam::Block - { - Optional action_button; - Deprecated allow_text_entry; - - Params() - : action_button("action_button"), - allow_text_entry("allow_text_entry") - { - changeDefault(LLComboBox::Params::allow_text_entry, false); - } - - }; -protected: - LLFlyoutButton(const Params&); - friend class LLUICtrlFactory; -public: - virtual void draw(); - - void setToggleState(bool state); - - void onActionButtonClick(const LLSD& data); - -protected: - LLButton* mActionButton; - bool mToggleState; -}; - -#endif // LL_LLFLYOUTBUTTON_H +/** + * @file llflyoutbutton.h + * @brief LLFlyoutButton base 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$ + */ + +// A control that displays the name of the chosen item, which when clicked +// shows a scrolling box of choices. + +#ifndef LL_LLFLYOUTBUTTON_H +#define LL_LLFLYOUTBUTTON_H + +#include "llcombobox.h" + +// Classes + +class LLFlyoutButton : public LLComboBox +{ +public: + struct Params : public LLInitParam::Block + { + Optional action_button; + Deprecated allow_text_entry; + + Params() + : action_button("action_button"), + allow_text_entry("allow_text_entry") + { + changeDefault(LLComboBox::Params::allow_text_entry, false); + } + + }; +protected: + LLFlyoutButton(const Params&); + friend class LLUICtrlFactory; +public: + virtual void draw(); + + void setToggleState(bool state); + + void onActionButtonClick(const LLSD& data); + +protected: + LLButton* mActionButton; + bool mToggleState; +}; + +#endif // LL_LLFLYOUTBUTTON_H diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index 02d02a15f4..937dde4def 100644 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -1,509 +1,509 @@ -/** - * @file llfocusmgr.cpp - * @brief LLFocusMgr base class - * - * $LicenseInfo:firstyear=2002&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 "llfocusmgr.h" -#include "lluictrl.h" -#include "v4color.h" - -const F32 FOCUS_FADE_TIME = 0.3f; - -LLFocusableElement::LLFocusableElement() -: mFocusLostCallback(NULL), - mFocusReceivedCallback(NULL), - mFocusChangedCallback(NULL), - mTopLostCallback(NULL) -{ -} - -// virtual -bool LLFocusableElement::handleKey(KEY key, MASK mask, bool called_from_parent) -{ - return false; -} - -// virtual -bool LLFocusableElement::handleKeyUp(KEY key, MASK mask, bool called_from_parent) -{ - return false; -} - -// virtual -bool LLFocusableElement::handleUnicodeChar(llwchar uni_char, bool called_from_parent) -{ - return false; -} - -// virtual -bool LLFocusableElement::wantsKeyUpKeyDown() const -{ - return false; -} - -//virtual -bool LLFocusableElement::wantsReturnKey() const -{ - return false; -} - -// virtual -LLFocusableElement::~LLFocusableElement() -{ - delete mFocusLostCallback; - delete mFocusReceivedCallback; - delete mFocusChangedCallback; - delete mTopLostCallback; -} - -void LLFocusableElement::onFocusReceived() -{ - if (mFocusReceivedCallback) (*mFocusReceivedCallback)(this); - if (mFocusChangedCallback) (*mFocusChangedCallback)(this); -} - -void LLFocusableElement::onFocusLost() -{ - if (mFocusLostCallback) (*mFocusLostCallback)(this); - if (mFocusChangedCallback) (*mFocusChangedCallback)(this); -} - -void LLFocusableElement::onTopLost() -{ - if (mTopLostCallback) (*mTopLostCallback)(this); -} - -bool LLFocusableElement::hasFocus() const -{ - return gFocusMgr.getKeyboardFocus() == this; -} - -void LLFocusableElement::setFocus(bool b) -{ -} - -boost::signals2::connection LLFocusableElement::setFocusLostCallback( const focus_signal_t::slot_type& cb) -{ - if (!mFocusLostCallback) mFocusLostCallback = new focus_signal_t(); - return mFocusLostCallback->connect(cb); -} - -boost::signals2::connection LLFocusableElement::setFocusReceivedCallback(const focus_signal_t::slot_type& cb) -{ - if (!mFocusReceivedCallback) mFocusReceivedCallback = new focus_signal_t(); - return mFocusReceivedCallback->connect(cb); -} - -boost::signals2::connection LLFocusableElement::setFocusChangedCallback(const focus_signal_t::slot_type& cb) -{ - if (!mFocusChangedCallback) mFocusChangedCallback = new focus_signal_t(); - return mFocusChangedCallback->connect(cb); -} - -boost::signals2::connection LLFocusableElement::setTopLostCallback(const focus_signal_t::slot_type& cb) -{ - if (!mTopLostCallback) mTopLostCallback = new focus_signal_t(); - return mTopLostCallback->connect(cb); -} - - - -typedef std::list > view_handle_list_t; -typedef std::map, LLHandle > focus_history_map_t; -struct LLFocusMgr::Impl -{ - // caching list of keyboard focus ancestors for calling onFocusReceived and onFocusLost - view_handle_list_t mCachedKeyboardFocusList; - - focus_history_map_t mFocusHistory; -}; - -LLFocusMgr gFocusMgr; - -LLFocusMgr::LLFocusMgr() -: mLockedView( NULL ), - mMouseCaptor( NULL ), - mKeyboardFocus( NULL ), - mLastKeyboardFocus( NULL ), - mDefaultKeyboardFocus( NULL ), - mKeystrokesOnly(false), - mTopCtrl( NULL ), - mAppHasFocus(true), // Macs don't seem to notify us that we've gotten focus, so default to true - mImpl(new LLFocusMgr::Impl) -{ -} - -LLFocusMgr::~LLFocusMgr() -{ - mImpl->mFocusHistory.clear(); - delete mImpl; - mImpl = NULL; -} - -void LLFocusMgr::releaseFocusIfNeeded( LLView* view ) -{ - if( childHasMouseCapture( view ) ) - { - setMouseCapture( NULL ); - } - - if( childHasKeyboardFocus( view )) - { - if (view == mLockedView) - { - mLockedView = NULL; - setKeyboardFocus( NULL ); - } - else - { - setKeyboardFocus( mLockedView ); - } - } - - LLUI::getInstance()->removePopup(view); -} - -void LLFocusMgr::setKeyboardFocus(LLFocusableElement* new_focus, bool lock, bool keystrokes_only) -{ - // notes if keyboard focus is changed again (by onFocusLost/onFocusReceived) - // making the rest of our processing unnecessary since it will already be - // handled by the recursive call - static bool focus_dirty; - focus_dirty = false; - - if (mLockedView && - (new_focus == NULL || - (new_focus != mLockedView - && dynamic_cast(new_focus) - && !dynamic_cast(new_focus)->hasAncestor(mLockedView)))) - { - // don't allow focus to go to anything that is not the locked focus - // or one of its descendants - return; - } - - mKeystrokesOnly = keystrokes_only; - - if( new_focus != mKeyboardFocus ) - { - mLastKeyboardFocus = mKeyboardFocus; - mKeyboardFocus = new_focus; - - // list of the focus and it's ancestors - view_handle_list_t old_focus_list = mImpl->mCachedKeyboardFocusList; - view_handle_list_t new_focus_list; - - // walk up the tree to root and add all views to the new_focus_list - for (LLView* ctrl = dynamic_cast(mKeyboardFocus); ctrl; ctrl = ctrl->getParent()) - { - new_focus_list.push_back(ctrl->getHandle()); - } - - // remove all common ancestors since their focus is unchanged - while (!new_focus_list.empty() && - !old_focus_list.empty() && - new_focus_list.back() == old_focus_list.back()) - { - new_focus_list.pop_back(); - old_focus_list.pop_back(); - } - - // walk up the old focus branch calling onFocusLost - // we bubble up the tree to release focus, and back down to add - for (view_handle_list_t::iterator old_focus_iter = old_focus_list.begin(); - old_focus_iter != old_focus_list.end() && !focus_dirty; - old_focus_iter++) - { - LLView* old_focus_view = old_focus_iter->get(); - if (old_focus_view) - { - mImpl->mCachedKeyboardFocusList.pop_front(); - old_focus_view->onFocusLost(); - } - } - - // walk down the new focus branch calling onFocusReceived - for (view_handle_list_t::reverse_iterator new_focus_riter = new_focus_list.rbegin(); - new_focus_riter != new_focus_list.rend() && !focus_dirty; - new_focus_riter++) - { - LLView* new_focus_view = new_focus_riter->get(); - if (new_focus_view) - { - mImpl->mCachedKeyboardFocusList.push_front(new_focus_view->getHandle()); - new_focus_view->onFocusReceived(); - } - } - - // if focus was changed as part of an onFocusLost or onFocusReceived call - // stop iterating on current list since it is now invalid - if (focus_dirty) - { - return; - } - - // If we've got a default keyboard focus, and the caller is - // releasing keyboard focus, move to the default. - if (mDefaultKeyboardFocus != NULL && mKeyboardFocus == NULL) - { - mDefaultKeyboardFocus->setFocus(true); - } - - LLView* focus_subtree = dynamic_cast(mKeyboardFocus); - LLView* viewp = dynamic_cast(mKeyboardFocus); - // find root-most focus root - while(viewp) - { - if (viewp->isFocusRoot()) - { - focus_subtree = viewp; - } - viewp = viewp->getParent(); - } - - - if (focus_subtree) - { - LLView* focused_view = dynamic_cast(mKeyboardFocus); - mImpl->mFocusHistory[focus_subtree->getHandle()] = focused_view ? focused_view->getHandle() : LLHandle(); - } - } - - if (lock) - { - lockFocus(); - } - - focus_dirty = true; -} - - -// Returns true is parent or any descedent of parent has keyboard focus. -bool LLFocusMgr::childHasKeyboardFocus(const LLView* parent ) const -{ - LLView* focus_view = dynamic_cast(mKeyboardFocus); - while( focus_view ) - { - if( focus_view == parent ) - { - return true; - } - focus_view = focus_view->getParent(); - } - return false; -} - -// Returns true is parent or any descedent of parent is the mouse captor. -bool LLFocusMgr::childHasMouseCapture( const LLView* parent ) const -{ - if( mMouseCaptor && dynamic_cast(mMouseCaptor) != NULL ) - { - LLView* captor_view = (LLView*)mMouseCaptor; - while( captor_view ) - { - if( captor_view == parent ) - { - return true; - } - captor_view = captor_view->getParent(); - } - } - return false; -} - -void LLFocusMgr::removeKeyboardFocusWithoutCallback( const LLFocusableElement* focus ) -{ - // should be ok to unlock here, as you have to know the locked view - // in order to unlock it - if (focus == mLockedView) - { - mLockedView = NULL; - } - - if( mKeyboardFocus == focus ) - { - mKeyboardFocus = NULL; - } -} - -bool LLFocusMgr::keyboardFocusHasAccelerators() const -{ - LLView* focus_view = dynamic_cast(mKeyboardFocus); - while( focus_view ) - { - if(focus_view->hasAccelerators()) - { - return true; - } - - focus_view = focus_view->getParent(); - } - return false; -} - -void LLFocusMgr::setMouseCapture( LLMouseHandler* new_captor ) -{ - if( new_captor != mMouseCaptor ) - { - LLMouseHandler* old_captor = mMouseCaptor; - mMouseCaptor = new_captor; - - if (LLView::sDebugMouseHandling) - { - if (new_captor) - { - LL_INFOS() << "New mouse captor: " << new_captor->getName() << LL_ENDL; - } - else - { - LL_INFOS() << "New mouse captor: NULL" << LL_ENDL; - } - } - - if( old_captor ) - { - old_captor->onMouseCaptureLost(); - } - - } -} - -void LLFocusMgr::removeMouseCaptureWithoutCallback( const LLMouseHandler* captor ) -{ - if( mMouseCaptor == captor ) - { - mMouseCaptor = NULL; - } -} - - -bool LLFocusMgr::childIsTopCtrl( const LLView* parent ) const -{ - LLView* top_view = (LLView*)mTopCtrl; - while( top_view ) - { - if( top_view == parent ) - { - return true; - } - top_view = top_view->getParent(); - } - return false; -} - - - -// set new_top = NULL to release top_view. -void LLFocusMgr::setTopCtrl( LLUICtrl* new_top ) -{ - LLUICtrl* old_top = mTopCtrl; - if( new_top != old_top ) - { - mTopCtrl = new_top; - - if (old_top) - { - old_top->onTopLost(); - } - } -} - -void LLFocusMgr::removeTopCtrlWithoutCallback( const LLUICtrl* top_view ) -{ - if( mTopCtrl == top_view ) - { - mTopCtrl = NULL; - } -} - -void LLFocusMgr::lockFocus() -{ - mLockedView = dynamic_cast(mKeyboardFocus); -} - -void LLFocusMgr::unlockFocus() -{ - mLockedView = NULL; -} - -F32 LLFocusMgr::getFocusFlashAmt() const -{ - return clamp_rescale(mFocusFlashTimer.getElapsedTimeF32(), 0.f, FOCUS_FADE_TIME, 1.f, 0.f); -} - -LLColor4 LLFocusMgr::getFocusColor() const -{ - static LLUIColor focus_color_cached = LLUIColorTable::instance().getColor("FocusColor"); - LLColor4 focus_color = lerp(focus_color_cached, LLColor4::white, getFocusFlashAmt()); - // de-emphasize keyboard focus when app has lost focus (to avoid typing into wrong window problem) - if (!mAppHasFocus) - { - focus_color.mV[VALPHA] *= 0.4f; - } - return focus_color; -} - -void LLFocusMgr::triggerFocusFlash() -{ - mFocusFlashTimer.reset(); -} - -void LLFocusMgr::setAppHasFocus(bool focus) -{ - if (!mAppHasFocus && focus) - { - triggerFocusFlash(); - } - - // release focus from "top ctrl"s, which generally hides them - if (!focus) - { - LLUI::getInstance()->clearPopups(); - } - mAppHasFocus = focus; -} - -LLView* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const -{ - if (subtree_root) - { - focus_history_map_t::const_iterator found_it = mImpl->mFocusHistory.find(subtree_root->getHandle()); - if (found_it != mImpl->mFocusHistory.end()) - { - // found last focus for this subtree - return found_it->second.get(); - } - } - return NULL; -} - -void LLFocusMgr::clearLastFocusForGroup(LLView* subtree_root) -{ - if (subtree_root) - { - mImpl->mFocusHistory.erase(subtree_root->getHandle()); - } -} +/** + * @file llfocusmgr.cpp + * @brief LLFocusMgr base class + * + * $LicenseInfo:firstyear=2002&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 "llfocusmgr.h" +#include "lluictrl.h" +#include "v4color.h" + +const F32 FOCUS_FADE_TIME = 0.3f; + +LLFocusableElement::LLFocusableElement() +: mFocusLostCallback(NULL), + mFocusReceivedCallback(NULL), + mFocusChangedCallback(NULL), + mTopLostCallback(NULL) +{ +} + +// virtual +bool LLFocusableElement::handleKey(KEY key, MASK mask, bool called_from_parent) +{ + return false; +} + +// virtual +bool LLFocusableElement::handleKeyUp(KEY key, MASK mask, bool called_from_parent) +{ + return false; +} + +// virtual +bool LLFocusableElement::handleUnicodeChar(llwchar uni_char, bool called_from_parent) +{ + return false; +} + +// virtual +bool LLFocusableElement::wantsKeyUpKeyDown() const +{ + return false; +} + +//virtual +bool LLFocusableElement::wantsReturnKey() const +{ + return false; +} + +// virtual +LLFocusableElement::~LLFocusableElement() +{ + delete mFocusLostCallback; + delete mFocusReceivedCallback; + delete mFocusChangedCallback; + delete mTopLostCallback; +} + +void LLFocusableElement::onFocusReceived() +{ + if (mFocusReceivedCallback) (*mFocusReceivedCallback)(this); + if (mFocusChangedCallback) (*mFocusChangedCallback)(this); +} + +void LLFocusableElement::onFocusLost() +{ + if (mFocusLostCallback) (*mFocusLostCallback)(this); + if (mFocusChangedCallback) (*mFocusChangedCallback)(this); +} + +void LLFocusableElement::onTopLost() +{ + if (mTopLostCallback) (*mTopLostCallback)(this); +} + +bool LLFocusableElement::hasFocus() const +{ + return gFocusMgr.getKeyboardFocus() == this; +} + +void LLFocusableElement::setFocus(bool b) +{ +} + +boost::signals2::connection LLFocusableElement::setFocusLostCallback( const focus_signal_t::slot_type& cb) +{ + if (!mFocusLostCallback) mFocusLostCallback = new focus_signal_t(); + return mFocusLostCallback->connect(cb); +} + +boost::signals2::connection LLFocusableElement::setFocusReceivedCallback(const focus_signal_t::slot_type& cb) +{ + if (!mFocusReceivedCallback) mFocusReceivedCallback = new focus_signal_t(); + return mFocusReceivedCallback->connect(cb); +} + +boost::signals2::connection LLFocusableElement::setFocusChangedCallback(const focus_signal_t::slot_type& cb) +{ + if (!mFocusChangedCallback) mFocusChangedCallback = new focus_signal_t(); + return mFocusChangedCallback->connect(cb); +} + +boost::signals2::connection LLFocusableElement::setTopLostCallback(const focus_signal_t::slot_type& cb) +{ + if (!mTopLostCallback) mTopLostCallback = new focus_signal_t(); + return mTopLostCallback->connect(cb); +} + + + +typedef std::list > view_handle_list_t; +typedef std::map, LLHandle > focus_history_map_t; +struct LLFocusMgr::Impl +{ + // caching list of keyboard focus ancestors for calling onFocusReceived and onFocusLost + view_handle_list_t mCachedKeyboardFocusList; + + focus_history_map_t mFocusHistory; +}; + +LLFocusMgr gFocusMgr; + +LLFocusMgr::LLFocusMgr() +: mLockedView( NULL ), + mMouseCaptor( NULL ), + mKeyboardFocus( NULL ), + mLastKeyboardFocus( NULL ), + mDefaultKeyboardFocus( NULL ), + mKeystrokesOnly(false), + mTopCtrl( NULL ), + mAppHasFocus(true), // Macs don't seem to notify us that we've gotten focus, so default to true + mImpl(new LLFocusMgr::Impl) +{ +} + +LLFocusMgr::~LLFocusMgr() +{ + mImpl->mFocusHistory.clear(); + delete mImpl; + mImpl = NULL; +} + +void LLFocusMgr::releaseFocusIfNeeded( LLView* view ) +{ + if( childHasMouseCapture( view ) ) + { + setMouseCapture( NULL ); + } + + if( childHasKeyboardFocus( view )) + { + if (view == mLockedView) + { + mLockedView = NULL; + setKeyboardFocus( NULL ); + } + else + { + setKeyboardFocus( mLockedView ); + } + } + + LLUI::getInstance()->removePopup(view); +} + +void LLFocusMgr::setKeyboardFocus(LLFocusableElement* new_focus, bool lock, bool keystrokes_only) +{ + // notes if keyboard focus is changed again (by onFocusLost/onFocusReceived) + // making the rest of our processing unnecessary since it will already be + // handled by the recursive call + static bool focus_dirty; + focus_dirty = false; + + if (mLockedView && + (new_focus == NULL || + (new_focus != mLockedView + && dynamic_cast(new_focus) + && !dynamic_cast(new_focus)->hasAncestor(mLockedView)))) + { + // don't allow focus to go to anything that is not the locked focus + // or one of its descendants + return; + } + + mKeystrokesOnly = keystrokes_only; + + if( new_focus != mKeyboardFocus ) + { + mLastKeyboardFocus = mKeyboardFocus; + mKeyboardFocus = new_focus; + + // list of the focus and it's ancestors + view_handle_list_t old_focus_list = mImpl->mCachedKeyboardFocusList; + view_handle_list_t new_focus_list; + + // walk up the tree to root and add all views to the new_focus_list + for (LLView* ctrl = dynamic_cast(mKeyboardFocus); ctrl; ctrl = ctrl->getParent()) + { + new_focus_list.push_back(ctrl->getHandle()); + } + + // remove all common ancestors since their focus is unchanged + while (!new_focus_list.empty() && + !old_focus_list.empty() && + new_focus_list.back() == old_focus_list.back()) + { + new_focus_list.pop_back(); + old_focus_list.pop_back(); + } + + // walk up the old focus branch calling onFocusLost + // we bubble up the tree to release focus, and back down to add + for (view_handle_list_t::iterator old_focus_iter = old_focus_list.begin(); + old_focus_iter != old_focus_list.end() && !focus_dirty; + old_focus_iter++) + { + LLView* old_focus_view = old_focus_iter->get(); + if (old_focus_view) + { + mImpl->mCachedKeyboardFocusList.pop_front(); + old_focus_view->onFocusLost(); + } + } + + // walk down the new focus branch calling onFocusReceived + for (view_handle_list_t::reverse_iterator new_focus_riter = new_focus_list.rbegin(); + new_focus_riter != new_focus_list.rend() && !focus_dirty; + new_focus_riter++) + { + LLView* new_focus_view = new_focus_riter->get(); + if (new_focus_view) + { + mImpl->mCachedKeyboardFocusList.push_front(new_focus_view->getHandle()); + new_focus_view->onFocusReceived(); + } + } + + // if focus was changed as part of an onFocusLost or onFocusReceived call + // stop iterating on current list since it is now invalid + if (focus_dirty) + { + return; + } + + // If we've got a default keyboard focus, and the caller is + // releasing keyboard focus, move to the default. + if (mDefaultKeyboardFocus != NULL && mKeyboardFocus == NULL) + { + mDefaultKeyboardFocus->setFocus(true); + } + + LLView* focus_subtree = dynamic_cast(mKeyboardFocus); + LLView* viewp = dynamic_cast(mKeyboardFocus); + // find root-most focus root + while(viewp) + { + if (viewp->isFocusRoot()) + { + focus_subtree = viewp; + } + viewp = viewp->getParent(); + } + + + if (focus_subtree) + { + LLView* focused_view = dynamic_cast(mKeyboardFocus); + mImpl->mFocusHistory[focus_subtree->getHandle()] = focused_view ? focused_view->getHandle() : LLHandle(); + } + } + + if (lock) + { + lockFocus(); + } + + focus_dirty = true; +} + + +// Returns true is parent or any descedent of parent has keyboard focus. +bool LLFocusMgr::childHasKeyboardFocus(const LLView* parent ) const +{ + LLView* focus_view = dynamic_cast(mKeyboardFocus); + while( focus_view ) + { + if( focus_view == parent ) + { + return true; + } + focus_view = focus_view->getParent(); + } + return false; +} + +// Returns true is parent or any descedent of parent is the mouse captor. +bool LLFocusMgr::childHasMouseCapture( const LLView* parent ) const +{ + if( mMouseCaptor && dynamic_cast(mMouseCaptor) != NULL ) + { + LLView* captor_view = (LLView*)mMouseCaptor; + while( captor_view ) + { + if( captor_view == parent ) + { + return true; + } + captor_view = captor_view->getParent(); + } + } + return false; +} + +void LLFocusMgr::removeKeyboardFocusWithoutCallback( const LLFocusableElement* focus ) +{ + // should be ok to unlock here, as you have to know the locked view + // in order to unlock it + if (focus == mLockedView) + { + mLockedView = NULL; + } + + if( mKeyboardFocus == focus ) + { + mKeyboardFocus = NULL; + } +} + +bool LLFocusMgr::keyboardFocusHasAccelerators() const +{ + LLView* focus_view = dynamic_cast(mKeyboardFocus); + while( focus_view ) + { + if(focus_view->hasAccelerators()) + { + return true; + } + + focus_view = focus_view->getParent(); + } + return false; +} + +void LLFocusMgr::setMouseCapture( LLMouseHandler* new_captor ) +{ + if( new_captor != mMouseCaptor ) + { + LLMouseHandler* old_captor = mMouseCaptor; + mMouseCaptor = new_captor; + + if (LLView::sDebugMouseHandling) + { + if (new_captor) + { + LL_INFOS() << "New mouse captor: " << new_captor->getName() << LL_ENDL; + } + else + { + LL_INFOS() << "New mouse captor: NULL" << LL_ENDL; + } + } + + if( old_captor ) + { + old_captor->onMouseCaptureLost(); + } + + } +} + +void LLFocusMgr::removeMouseCaptureWithoutCallback( const LLMouseHandler* captor ) +{ + if( mMouseCaptor == captor ) + { + mMouseCaptor = NULL; + } +} + + +bool LLFocusMgr::childIsTopCtrl( const LLView* parent ) const +{ + LLView* top_view = (LLView*)mTopCtrl; + while( top_view ) + { + if( top_view == parent ) + { + return true; + } + top_view = top_view->getParent(); + } + return false; +} + + + +// set new_top = NULL to release top_view. +void LLFocusMgr::setTopCtrl( LLUICtrl* new_top ) +{ + LLUICtrl* old_top = mTopCtrl; + if( new_top != old_top ) + { + mTopCtrl = new_top; + + if (old_top) + { + old_top->onTopLost(); + } + } +} + +void LLFocusMgr::removeTopCtrlWithoutCallback( const LLUICtrl* top_view ) +{ + if( mTopCtrl == top_view ) + { + mTopCtrl = NULL; + } +} + +void LLFocusMgr::lockFocus() +{ + mLockedView = dynamic_cast(mKeyboardFocus); +} + +void LLFocusMgr::unlockFocus() +{ + mLockedView = NULL; +} + +F32 LLFocusMgr::getFocusFlashAmt() const +{ + return clamp_rescale(mFocusFlashTimer.getElapsedTimeF32(), 0.f, FOCUS_FADE_TIME, 1.f, 0.f); +} + +LLColor4 LLFocusMgr::getFocusColor() const +{ + static LLUIColor focus_color_cached = LLUIColorTable::instance().getColor("FocusColor"); + LLColor4 focus_color = lerp(focus_color_cached, LLColor4::white, getFocusFlashAmt()); + // de-emphasize keyboard focus when app has lost focus (to avoid typing into wrong window problem) + if (!mAppHasFocus) + { + focus_color.mV[VALPHA] *= 0.4f; + } + return focus_color; +} + +void LLFocusMgr::triggerFocusFlash() +{ + mFocusFlashTimer.reset(); +} + +void LLFocusMgr::setAppHasFocus(bool focus) +{ + if (!mAppHasFocus && focus) + { + triggerFocusFlash(); + } + + // release focus from "top ctrl"s, which generally hides them + if (!focus) + { + LLUI::getInstance()->clearPopups(); + } + mAppHasFocus = focus; +} + +LLView* LLFocusMgr::getLastFocusForGroup(LLView* subtree_root) const +{ + if (subtree_root) + { + focus_history_map_t::const_iterator found_it = mImpl->mFocusHistory.find(subtree_root->getHandle()); + if (found_it != mImpl->mFocusHistory.end()) + { + // found last focus for this subtree + return found_it->second.get(); + } + } + return NULL; +} + +void LLFocusMgr::clearLastFocusForGroup(LLView* subtree_root) +{ + if (subtree_root) + { + mImpl->mFocusHistory.erase(subtree_root->getHandle()); + } +} diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h index 6bdd3e8e9a..1fa0ac137e 100644 --- a/indra/llui/llfocusmgr.h +++ b/indra/llui/llfocusmgr.h @@ -1,160 +1,160 @@ -/** - * @file llfocusmgr.h - * @brief LLFocusMgr base class - * - * $LicenseInfo:firstyear=2002&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$ - */ - -// Singleton that manages keyboard and mouse focus - -#ifndef LL_LLFOCUSMGR_H -#define LL_LLFOCUSMGR_H - -#include "llstring.h" -#include "llframetimer.h" -#include "llui.h" - -class LLUICtrl; -class LLMouseHandler; -class LLView; - -// NOTE: the LLFocusableElement class declaration has been moved here from lluictrl.h. -class LLFocusableElement -{ - friend class LLFocusMgr; // allow access to focus change handlers -public: - LLFocusableElement(); - virtual ~LLFocusableElement(); - - virtual void setFocus( bool b ); - virtual bool hasFocus() const; - - typedef boost::signals2::signal focus_signal_t; - - boost::signals2::connection setFocusLostCallback( const focus_signal_t::slot_type& cb); - boost::signals2::connection setFocusReceivedCallback(const focus_signal_t::slot_type& cb); - boost::signals2::connection setFocusChangedCallback(const focus_signal_t::slot_type& cb); - boost::signals2::connection setTopLostCallback(const focus_signal_t::slot_type& cb); - - // These were brought up the hierarchy from LLView so that we don't have to use dynamic_cast when dealing with keyboard focus. - virtual bool handleKey(KEY key, MASK mask, bool called_from_parent); - virtual bool handleKeyUp(KEY key, MASK mask, bool called_from_parent); - virtual bool handleUnicodeChar(llwchar uni_char, bool called_from_parent); - - /** - * If true this LLFocusableElement wants to receive KEYUP and KEYDOWN messages - * even for normal character strokes. - * Default implementation returns false. - */ - virtual bool wantsKeyUpKeyDown() const; - virtual bool wantsReturnKey() const; - - virtual void onTopLost(); // called when registered as top ctrl and user clicks elsewhere -protected: - virtual void onFocusReceived(); - virtual void onFocusLost(); - focus_signal_t* mFocusLostCallback; - focus_signal_t* mFocusReceivedCallback; - focus_signal_t* mFocusChangedCallback; - focus_signal_t* mTopLostCallback; -}; - - -class LLFocusMgr -{ -public: - LLFocusMgr(); - ~LLFocusMgr(); - - // Mouse Captor - void setMouseCapture(LLMouseHandler* new_captor); // new_captor = NULL to release the mouse. - LLMouseHandler* getMouseCapture() const { return mMouseCaptor; } - void removeMouseCaptureWithoutCallback( const LLMouseHandler* captor ); - bool childHasMouseCapture( const LLView* parent ) const; - - // Keyboard Focus - void setKeyboardFocus(LLFocusableElement* new_focus, bool lock = false, bool keystrokes_only = false); // new_focus = NULL to release the focus. - LLFocusableElement* getKeyboardFocus() const { return mKeyboardFocus; } - LLFocusableElement* getLastKeyboardFocus() const { return mLastKeyboardFocus; } - bool childHasKeyboardFocus( const LLView* parent ) const; - void removeKeyboardFocusWithoutCallback( const LLFocusableElement* focus ); - bool getKeystrokesOnly() { return mKeystrokesOnly; } - void setKeystrokesOnly(bool keystrokes_only) { mKeystrokesOnly = keystrokes_only; } - - F32 getFocusFlashAmt() const; - S32 getFocusFlashWidth() const { return ll_round(lerp(1.f, 3.f, getFocusFlashAmt())); } - LLColor4 getFocusColor() const; - void triggerFocusFlash(); - bool getAppHasFocus() const { return mAppHasFocus; } - void setAppHasFocus(bool focus); - LLView* getLastFocusForGroup(LLView* subtree_root) const; - void clearLastFocusForGroup(LLView* subtree_root); - - // If setKeyboardFocus(NULL) is called, and there is a non-NULL default - // keyboard focus view, focus goes there. JC - void setDefaultKeyboardFocus(LLFocusableElement* default_focus) { mDefaultKeyboardFocus = default_focus; } - LLFocusableElement* getDefaultKeyboardFocus() const { return mDefaultKeyboardFocus; } - - - // Top View - void setTopCtrl(LLUICtrl* new_top); - LLUICtrl* getTopCtrl() const { return mTopCtrl; } - void removeTopCtrlWithoutCallback( const LLUICtrl* top_view ); - bool childIsTopCtrl( const LLView* parent ) const; - - // All Three - void releaseFocusIfNeeded( LLView* top_view ); - void lockFocus(); - void unlockFocus(); - bool focusLocked() const { return mLockedView != NULL; } - - bool keyboardFocusHasAccelerators() const; - - struct Impl; - -private: - LLUICtrl* mLockedView; - - // Mouse Captor - LLMouseHandler* mMouseCaptor; // Mouse events are premptively routed to this object - - // Keyboard Focus - LLFocusableElement* mKeyboardFocus; // Keyboard events are preemptively routed to this object - LLFocusableElement* mLastKeyboardFocus; // who last had focus - LLFocusableElement* mDefaultKeyboardFocus; - bool mKeystrokesOnly; - - // Top View - LLUICtrl* mTopCtrl; - - LLFrameTimer mFocusFlashTimer; - - bool mAppHasFocus; - - Impl * mImpl; -}; - -extern LLFocusMgr gFocusMgr; - -#endif // LL_LLFOCUSMGR_H - - +/** + * @file llfocusmgr.h + * @brief LLFocusMgr base class + * + * $LicenseInfo:firstyear=2002&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$ + */ + +// Singleton that manages keyboard and mouse focus + +#ifndef LL_LLFOCUSMGR_H +#define LL_LLFOCUSMGR_H + +#include "llstring.h" +#include "llframetimer.h" +#include "llui.h" + +class LLUICtrl; +class LLMouseHandler; +class LLView; + +// NOTE: the LLFocusableElement class declaration has been moved here from lluictrl.h. +class LLFocusableElement +{ + friend class LLFocusMgr; // allow access to focus change handlers +public: + LLFocusableElement(); + virtual ~LLFocusableElement(); + + virtual void setFocus( bool b ); + virtual bool hasFocus() const; + + typedef boost::signals2::signal focus_signal_t; + + boost::signals2::connection setFocusLostCallback( const focus_signal_t::slot_type& cb); + boost::signals2::connection setFocusReceivedCallback(const focus_signal_t::slot_type& cb); + boost::signals2::connection setFocusChangedCallback(const focus_signal_t::slot_type& cb); + boost::signals2::connection setTopLostCallback(const focus_signal_t::slot_type& cb); + + // These were brought up the hierarchy from LLView so that we don't have to use dynamic_cast when dealing with keyboard focus. + virtual bool handleKey(KEY key, MASK mask, bool called_from_parent); + virtual bool handleKeyUp(KEY key, MASK mask, bool called_from_parent); + virtual bool handleUnicodeChar(llwchar uni_char, bool called_from_parent); + + /** + * If true this LLFocusableElement wants to receive KEYUP and KEYDOWN messages + * even for normal character strokes. + * Default implementation returns false. + */ + virtual bool wantsKeyUpKeyDown() const; + virtual bool wantsReturnKey() const; + + virtual void onTopLost(); // called when registered as top ctrl and user clicks elsewhere +protected: + virtual void onFocusReceived(); + virtual void onFocusLost(); + focus_signal_t* mFocusLostCallback; + focus_signal_t* mFocusReceivedCallback; + focus_signal_t* mFocusChangedCallback; + focus_signal_t* mTopLostCallback; +}; + + +class LLFocusMgr +{ +public: + LLFocusMgr(); + ~LLFocusMgr(); + + // Mouse Captor + void setMouseCapture(LLMouseHandler* new_captor); // new_captor = NULL to release the mouse. + LLMouseHandler* getMouseCapture() const { return mMouseCaptor; } + void removeMouseCaptureWithoutCallback( const LLMouseHandler* captor ); + bool childHasMouseCapture( const LLView* parent ) const; + + // Keyboard Focus + void setKeyboardFocus(LLFocusableElement* new_focus, bool lock = false, bool keystrokes_only = false); // new_focus = NULL to release the focus. + LLFocusableElement* getKeyboardFocus() const { return mKeyboardFocus; } + LLFocusableElement* getLastKeyboardFocus() const { return mLastKeyboardFocus; } + bool childHasKeyboardFocus( const LLView* parent ) const; + void removeKeyboardFocusWithoutCallback( const LLFocusableElement* focus ); + bool getKeystrokesOnly() { return mKeystrokesOnly; } + void setKeystrokesOnly(bool keystrokes_only) { mKeystrokesOnly = keystrokes_only; } + + F32 getFocusFlashAmt() const; + S32 getFocusFlashWidth() const { return ll_round(lerp(1.f, 3.f, getFocusFlashAmt())); } + LLColor4 getFocusColor() const; + void triggerFocusFlash(); + bool getAppHasFocus() const { return mAppHasFocus; } + void setAppHasFocus(bool focus); + LLView* getLastFocusForGroup(LLView* subtree_root) const; + void clearLastFocusForGroup(LLView* subtree_root); + + // If setKeyboardFocus(NULL) is called, and there is a non-NULL default + // keyboard focus view, focus goes there. JC + void setDefaultKeyboardFocus(LLFocusableElement* default_focus) { mDefaultKeyboardFocus = default_focus; } + LLFocusableElement* getDefaultKeyboardFocus() const { return mDefaultKeyboardFocus; } + + + // Top View + void setTopCtrl(LLUICtrl* new_top); + LLUICtrl* getTopCtrl() const { return mTopCtrl; } + void removeTopCtrlWithoutCallback( const LLUICtrl* top_view ); + bool childIsTopCtrl( const LLView* parent ) const; + + // All Three + void releaseFocusIfNeeded( LLView* top_view ); + void lockFocus(); + void unlockFocus(); + bool focusLocked() const { return mLockedView != NULL; } + + bool keyboardFocusHasAccelerators() const; + + struct Impl; + +private: + LLUICtrl* mLockedView; + + // Mouse Captor + LLMouseHandler* mMouseCaptor; // Mouse events are premptively routed to this object + + // Keyboard Focus + LLFocusableElement* mKeyboardFocus; // Keyboard events are preemptively routed to this object + LLFocusableElement* mLastKeyboardFocus; // who last had focus + LLFocusableElement* mDefaultKeyboardFocus; + bool mKeystrokesOnly; + + // Top View + LLUICtrl* mTopCtrl; + + LLFrameTimer mFocusFlashTimer; + + bool mAppHasFocus; + + Impl * mImpl; +}; + +extern LLFocusMgr gFocusMgr; + +#endif // LL_LLFOCUSMGR_H + + diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index c01e811477..47821e38ca 100644 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -1,2129 +1,2129 @@ -/** - * @file llfolderview.cpp - * @brief Implementation of the folder view collection of classes. - * - * $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 "llfolderview.h" -#include "llfolderviewmodel.h" -#include "llclipboard.h" // *TODO: remove this once hack below gone. -#include "llkeyboard.h" -#include "lllineeditor.h" -#include "llmenugl.h" -#include "llpanel.h" -#include "llscrollcontainer.h" // hack to allow scrolling -#include "lltextbox.h" -#include "lltrans.h" -#include "llui.h" -#include "lluictrlfactory.h" - -// Linden library includes -#include "lldbstrings.h" -#include "llfocusmgr.h" -#include "llfontgl.h" -#include "llgl.h" -#include "llrender.h" - -// Third-party library includes -#include - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -const S32 RENAME_HEIGHT_PAD = 1; -const S32 AUTO_OPEN_STACK_DEPTH = 16; - -const S32 MINIMUM_RENAMER_WIDTH = 80; - -// *TODO: move in params in xml if necessary. Requires modification of LLFolderView & LLInventoryPanel Params. -const S32 STATUS_TEXT_HPAD = 6; -const S32 STATUS_TEXT_VPAD = 8; - -enum { - SIGNAL_NO_KEYBOARD_FOCUS = 1, - SIGNAL_KEYBOARD_FOCUS = 2 -}; - -F32 LLFolderView::sAutoOpenTime = 1.f; - -//--------------------------------------------------------------------------- - -// Tells all folders in a folderview to close themselves -// For efficiency, calls setOpenArrangeRecursively(). -// The calling function must then call: -// LLFolderView* root = getRoot(); -// if( root ) -// { -// root->arrange( NULL, NULL ); -// root->scrollToShowSelection(); -// } -// to patch things up. -class LLCloseAllFoldersFunctor : public LLFolderViewFunctor -{ -public: - LLCloseAllFoldersFunctor(bool close) { mOpen = !close; } - virtual ~LLCloseAllFoldersFunctor() {} - virtual void doFolder(LLFolderViewFolder* folder); - virtual void doItem(LLFolderViewItem* item); - - bool mOpen; -}; - - -void LLCloseAllFoldersFunctor::doFolder(LLFolderViewFolder* folder) -{ - folder->setOpenArrangeRecursively(mOpen); -} - -// Do nothing. -void LLCloseAllFoldersFunctor::doItem(LLFolderViewItem* item) -{ } - -//--------------------------------------------------------------------------- - -void LLAllDescendentsPassedFilter::doFolder(LLFolderViewFolder* folder) -{ - mAllDescendentsPassedFilter &= (folder) && (folder->passedFilter()) && (folder->descendantsPassedFilter()); -} - -void LLAllDescendentsPassedFilter::doItem(LLFolderViewItem* item) -{ - mAllDescendentsPassedFilter &= (item) && (item->passedFilter()); -} - -///---------------------------------------------------------------------------- -/// Class LLFolderViewScrollContainer -///---------------------------------------------------------------------------- - -// virtual -const LLRect LLFolderViewScrollContainer::getScrolledViewRect() const -{ - LLRect rect = LLRect::null; - if (mScrolledView) - { - LLFolderView* folder_view = dynamic_cast(mScrolledView); - if (folder_view) - { - S32 height = folder_view->getRect().getHeight(); - - rect = mScrolledView->getRect(); - rect.setLeftTopAndSize(rect.mLeft, rect.mTop, rect.getWidth(), height); - } - } - - return rect; -} - -LLFolderViewScrollContainer::LLFolderViewScrollContainer(const LLScrollContainer::Params& p) -: LLScrollContainer(p) -{} - -///---------------------------------------------------------------------------- -/// Class LLFolderView -///---------------------------------------------------------------------------- -LLFolderView::Params::Params() -: title("title"), - use_label_suffix("use_label_suffix"), - allow_multiselect("allow_multiselect", true), - allow_drag("allow_drag", true), - show_empty_message("show_empty_message", true), - suppress_folder_menu("suppress_folder_menu", false), - use_ellipses("use_ellipses", false), - options_menu("options_menu", "") -{ - folder_indentation = -4; -} - - -// Default constructor -LLFolderView::LLFolderView(const Params& p) -: LLFolderViewFolder(p), - mScrollContainer( NULL ), - mPopupMenuHandle(), - mMenuFileName(p.options_menu), - mAllowMultiSelect(p.allow_multiselect), - mAllowDrag(p.allow_drag), - mShowEmptyMessage(p.show_empty_message), - mShowFolderHierarchy(false), - mRenameItem( NULL ), - mNeedsScroll( false ), - mUseLabelSuffix(p.use_label_suffix), - mSuppressFolderMenu(p.suppress_folder_menu), - mPinningSelectedItem(false), - mNeedsAutoSelect( false ), - mAutoSelectOverride(false), - mNeedsAutoRename(false), - mShowSelectionContext(false), - mShowSingleSelection(false), - mArrangeGeneration(0), - mSignalSelectCallback(0), - mMinWidth(0), - mDragAndDropThisFrame(false), - mCallbackRegistrar(NULL), - mEnableRegistrar(NULL), - mUseEllipses(p.use_ellipses), - mDraggingOverItem(NULL), - mStatusTextBox(NULL), - mShowItemLinkOverlays(p.show_item_link_overlays), - mViewModel(p.view_model), - mGroupedItemModel(p.grouped_item_model), - mForceArrange(false), - mSingleFolderMode(false) -{ - LLPanel* panel = p.parent_panel; - mParentPanel = panel->getHandle(); - mViewModel->setFolderView(this); - mRoot = this; - - LLRect rect = p.rect; - LLRect new_rect(rect.mLeft, rect.mBottom + getRect().getHeight(), rect.mLeft + getRect().getWidth(), rect.mBottom); - setRect( rect ); - reshape(rect.getWidth(), rect.getHeight()); - mAutoOpenItems.setDepth(AUTO_OPEN_STACK_DEPTH); - mAutoOpenCandidate = NULL; - mAutoOpenTimer.stop(); - mKeyboardSelection = false; - mIndentation = getParentFolder() ? getParentFolder()->getIndentation() + mLocalIndentation : 0; - - //clear label - // go ahead and render root folder as usual - // just make sure the label ("Inventory Folder") never shows up - mLabel = LLStringUtil::null; - - // Escape is handled by reverting the rename, not commiting it (default behavior) - LLLineEditor::Params params; - params.name("ren"); - params.rect(rect); - params.font(getLabelFontForStyle(LLFontGL::NORMAL)); - params.max_length.bytes(DB_INV_ITEM_NAME_STR_LEN); - params.commit_callback.function(boost::bind(&LLFolderView::commitRename, this, _2)); - params.prevalidator(&LLTextValidate::validateASCIIPrintableNoPipe); - params.commit_on_focus_lost(true); - params.visible(false); - mRenamer = LLUICtrlFactory::create (params); - addChild(mRenamer); - - // Textbox - LLTextBox::Params text_p; - LLFontGL* font = getLabelFontForStyle(mLabelStyle); - //mIconPad, mTextPad are set in folder_view_item.xml - LLRect new_r = LLRect(rect.mLeft + mIconPad, - rect.mTop - mTextPad, - rect.mRight, - rect.mTop - mTextPad - font->getLineHeight()); - text_p.rect(new_r); - text_p.name(std::string(p.name)); - text_p.font(font); - text_p.visible(false); - text_p.parse_urls(true); - text_p.wrap(true); // allow multiline text. See EXT-7564, EXT-7047 - // set text padding the same as in People panel. EXT-7047, EXT-4837 - text_p.h_pad(STATUS_TEXT_HPAD); - text_p.v_pad(STATUS_TEXT_VPAD); - mStatusTextBox = LLUICtrlFactory::create (text_p); - mStatusTextBox->setFollowsLeft(); - mStatusTextBox->setFollowsTop(); - addChild(mStatusTextBox); - - mViewModelItem->openItem(); - - mAreChildrenInited = true; // root folder is a special case due to not being loaded normally, assume that it's inited. -} - -// Destroys the object -LLFolderView::~LLFolderView( void ) -{ - mRenamerTopLostSignalConnection.disconnect(); - if (mRenamer) - { - // instead of using closeRenamer remove it directly, - // since it might already be hidden - LLUI::getInstance()->removePopup(mRenamer); - } - - // The release focus call can potentially call the - // scrollcontainer, which can potentially be called with a partly - // destroyed scollcontainer. Just null it out here, and no worries - // about calling into the invalid scroll container. - // Same with the renamer. - mScrollContainer = NULL; - mRenameItem = NULL; - mRenamer = NULL; - mStatusTextBox = NULL; - - if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die(); - mPopupMenuHandle.markDead(); - - mAutoOpenItems.removeAllNodes(); - clearSelection(); - mItems.clear(); - mFolders.clear(); - - //mViewModel->setFolderView(NULL); - mViewModel = NULL; -} - -bool LLFolderView::canFocusChildren() const -{ - return false; -} - -void LLFolderView::addFolder( LLFolderViewFolder* folder) -{ - LLFolderViewFolder::addFolder(folder); -} - -void LLFolderView::closeAllFolders() -{ - // Close all the folders - setOpenArrangeRecursively(false, LLFolderViewFolder::RECURSE_DOWN); - arrangeAll(); -} - -void LLFolderView::openTopLevelFolders() -{ - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();) - { - folders_t::iterator fit = iter++; - (*fit)->setOpen(true); - } -} - -// This view grows and shrinks to enclose all of its children items and folders. -// *width should be 0 -// conform show folder state works -S32 LLFolderView::arrange( S32* unused_width, S32* unused_height ) - { - mMinWidth = 0; - S32 target_height; - - LLFolderViewFolder::arrange(&mMinWidth, &target_height); - - LLRect scroll_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); - reshape( llmax(scroll_rect.getWidth(), mMinWidth), ll_round(mCurHeight) ); - - LLRect new_scroll_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); - if (new_scroll_rect.getWidth() != scroll_rect.getWidth()) - { - reshape( llmax(scroll_rect.getWidth(), mMinWidth), ll_round(mCurHeight) ); - } - - // move item renamer text field to item's new position - updateRenamerPosition(); - - return ll_round(mTargetHeight); -} - -void LLFolderView::filter( LLFolderViewFilter& filter ) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - const S32 TIME_VISIBLE = 10; // in milliseconds - const S32 TIME_INVISIBLE = 1; - filter.resetTime(llclamp((mParentPanel.get()->getVisible() ? TIME_VISIBLE : TIME_INVISIBLE), 1, 100)); - - // Note: we filter the model, not the view - getViewModelItem()->filter(filter); -} - -void LLFolderView::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLRect scroll_rect; - if (mScrollContainer) - { - LLView::reshape(width, height, called_from_parent); - scroll_rect = mScrollContainer->getContentWindowRect(); - } - width = llmax(mMinWidth, scroll_rect.getWidth()); - height = llmax(ll_round(mCurHeight), scroll_rect.getHeight()); - - // Restrict width within scroll container's width - if (mUseEllipses && mScrollContainer) - { - width = scroll_rect.getWidth(); - } - LLView::reshape(width, height, called_from_parent); - mReshapeSignal(mSelectedItems, false); -} - -void LLFolderView::addToSelectionList(LLFolderViewItem* item) -{ - if (item->isSelected()) - { - removeFromSelectionList(item); - } - if (mSelectedItems.size()) - { - mSelectedItems.back()->setIsCurSelection(false); - } - item->setIsCurSelection(true); - mSelectedItems.push_back(item); -} - -void LLFolderView::removeFromSelectionList(LLFolderViewItem* item) -{ - if (mSelectedItems.size()) - { - mSelectedItems.back()->setIsCurSelection(false); - } - - selected_items_t::iterator item_iter; - for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end();) - { - if (*item_iter == item) - { - item_iter = mSelectedItems.erase(item_iter); - } - else - { - ++item_iter; - } - } - if (mSelectedItems.size()) - { - mSelectedItems.back()->setIsCurSelection(true); - } -} - -LLFolderViewItem* LLFolderView::getCurSelectedItem( void ) -{ - if(mSelectedItems.size()) - { - LLFolderViewItem* itemp = mSelectedItems.back(); - llassert(itemp->getIsCurSelection()); - return itemp; - } - return NULL; -} - -LLFolderView::selected_items_t& LLFolderView::getSelectedItems( void ) -{ - return mSelectedItems; -} - -// Record the selected item and pass it down the hierachy. -bool LLFolderView::setSelection(LLFolderViewItem* selection, bool openitem, - bool take_keyboard_focus) -{ - mSignalSelectCallback = take_keyboard_focus ? SIGNAL_KEYBOARD_FOCUS : SIGNAL_NO_KEYBOARD_FOCUS; - - if( selection == this ) - { - return false; - } - - if( selection && take_keyboard_focus) - { - mParentPanel.get()->setFocus(true); - } - - // clear selection down here because change of keyboard focus can potentially - // affect selection - clearSelection(); - - if(selection) - { - addToSelectionList(selection); - } - - bool rv = LLFolderViewFolder::setSelection(selection, openitem, take_keyboard_focus); - if(openitem && selection) - { - selection->getParentFolder()->requestArrange(); - } - - llassert(mSelectedItems.size() <= 1); - - return rv; -} - -bool LLFolderView::changeSelection(LLFolderViewItem* selection, bool selected) -{ - bool rv = false; - - // can't select root folder - if(!selection || selection == this) - { - return false; - } - - if (!mAllowMultiSelect) - { - clearSelection(); - } - - selected_items_t::iterator item_iter; - for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter) - { - if (*item_iter == selection) - { - break; - } - } - - bool on_list = (item_iter != mSelectedItems.end()); - - if(selected && !on_list) - { - addToSelectionList(selection); - } - if(!selected && on_list) - { - removeFromSelectionList(selection); - } - - rv = LLFolderViewFolder::changeSelection(selection, selected); - - mSignalSelectCallback = SIGNAL_KEYBOARD_FOCUS; - - return rv; -} - -void LLFolderView::sanitizeSelection() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - // store off current item in case it is automatically deselected - // and we want to preserve context - LLFolderViewItem* original_selected_item = getCurSelectedItem(); - - std::vector items_to_remove; - selected_items_t::iterator item_iter; - for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter) - { - LLFolderViewItem* item = *item_iter; - - // ensure that each ancestor is open and potentially passes filtering - bool visible = false; - if(item->getViewModelItem() != NULL) - { - visible = item->getViewModelItem()->potentiallyVisible(); // initialize from filter state for this item - } - // modify with parent open and filters states - LLFolderViewFolder* parent_folder = item->getParentFolder(); - // Move up through parent folders and see what's visible - while(parent_folder) - { - visible = visible && parent_folder->isOpen() && parent_folder->getViewModelItem()->potentiallyVisible(); - parent_folder = parent_folder->getParentFolder(); - } - - // deselect item if any ancestor is closed or didn't pass filter requirements. - if (!visible) - { - items_to_remove.push_back(item); - } - - // disallow nested selections (i.e. folder items plus one or more ancestors) - // could check cached mum selections count and only iterate if there are any - // but that may be a premature optimization. - selected_items_t::iterator other_item_iter; - for (other_item_iter = mSelectedItems.begin(); other_item_iter != mSelectedItems.end(); ++other_item_iter) - { - LLFolderViewItem* other_item = *other_item_iter; - for( parent_folder = other_item->getParentFolder(); parent_folder; parent_folder = parent_folder->getParentFolder()) - { - if (parent_folder == item) - { - // this is a descendent of the current folder, remove from list - items_to_remove.push_back(other_item); - break; - } - } - } - - // Don't allow invisible items (such as root folders) to be selected. - if (item == getRoot()) - { - items_to_remove.push_back(item); - } - } - - std::vector::iterator item_it; - for (item_it = items_to_remove.begin(); item_it != items_to_remove.end(); ++item_it ) - { - changeSelection(*item_it, false); // toggle selection (also removes from list) - } - - // if nothing selected after prior constraints... - if (mSelectedItems.empty()) - { - // ...select first available parent of original selection - LLFolderViewItem* new_selection = NULL; - if (original_selected_item) - { - for(LLFolderViewFolder* parent_folder = original_selected_item->getParentFolder(); - parent_folder; - parent_folder = parent_folder->getParentFolder()) - { - if (parent_folder->getViewModelItem() && parent_folder->getViewModelItem()->potentiallyVisible()) - { - // give initial selection to first ancestor folder that potentially passes the filter - if (!new_selection) - { - new_selection = parent_folder; - } - - // if any ancestor folder of original item is closed, move the selection up - // to the highest closed - if (!parent_folder->isOpen()) - { - new_selection = parent_folder; - } - } - } - } - else - { - new_selection = NULL; - } - - if (new_selection) - { - setSelection(new_selection, false, false); - } - } -} - -void LLFolderView::clearSelection() -{ - for (selected_items_t::const_iterator item_it = mSelectedItems.begin(); - item_it != mSelectedItems.end(); - ++item_it) - { - (*item_it)->setUnselected(); - } - - mSelectedItems.clear(); - mNeedsScroll = false; -} - -std::set LLFolderView::getSelectionList() const -{ - std::set selection; - std::copy(mSelectedItems.begin(), mSelectedItems.end(), std::inserter(selection, selection.begin())); - return selection; -} - -bool LLFolderView::startDrag() -{ - std::vector selected_items; - selected_items_t::iterator item_it; - - if (!mSelectedItems.empty()) - { - for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) - { - selected_items.push_back((*item_it)->getViewModelItem()); - } - - return getFolderViewModel()->startDrag(selected_items); - } - return false; -} - -void LLFolderView::commitRename( const LLSD& data ) -{ - finishRenamingItem(); - arrange( NULL, NULL ); - -} - -void LLFolderView::draw() -{ - //LLFontGL* font = getLabelFontForStyle(mLabelStyle); - - // if cursor has moved off of me during drag and drop - // close all auto opened folders - if (!mDragAndDropThisFrame) - { - closeAutoOpenedFolders(); - } - - static LLCachedControl type_ahead_timeout(*LLUI::getInstance()->mSettingGroups["config"], "TypeAheadTimeout", 1.5f); - if (mSearchTimer.getElapsedTimeF32() > type_ahead_timeout || !mSearchString.size()) - { - mSearchString.clear(); - } - - if (hasVisibleChildren()) - { - mStatusTextBox->setVisible( false ); - } - else if (mShowEmptyMessage) - { - mStatusTextBox->setValue(getFolderViewModel()->getStatusText(mItems.empty() && mFolders.empty())); - mStatusTextBox->setVisible( true ); - - // firstly reshape message textbox with current size. This is necessary to - // LLTextBox::getTextPixelHeight works properly - const LLRect local_rect = getLocalRect(); - mStatusTextBox->setShape(local_rect); - - // get preferable text height... - S32 pixel_height = mStatusTextBox->getTextPixelHeight(); - bool height_changed = (local_rect.getHeight() < pixel_height); - if (height_changed) - { - // ... if it does not match current height, lets rearrange current view. - // This will indirectly call ::arrange and reshape of the status textbox. - // We should call this method to also notify parent about required rect. - // See EXT-7564, EXT-7047. - S32 height = 0; - S32 width = 0; - S32 total_height = arrange( &width, &height ); - notifyParent(LLSD().with("action", "size_changes").with("height", total_height)); - - LLUI::popMatrix(); - LLUI::pushMatrix(); - LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom); - } - } - - if (mRenameItem - && mRenamer - && mRenamer->getVisible() - && !getVisibleRect().overlaps(mRenamer->getRect())) - { - // renamer is not connected to the item we are renaming in any form so manage it manually - // TODO: consider stopping on any scroll action instead of when out of visible area - LL_DEBUGS("Inventory") << "Renamer out of bounds, hiding" << LL_ENDL; - finishRenamingItem(); - } - - // skip over LLFolderViewFolder::draw since we don't want the folder icon, label, - // and arrow for the root folder - LLView::draw(); - - mDragAndDropThisFrame = false; -} - -void LLFolderView::finishRenamingItem( void ) -{ - if(!mRenamer) - { - return; - } - if( mRenameItem ) - { - mRenameItem->rename( mRenamer->getText() ); - } - - closeRenamer(); - - // This is moved to an inventory observer in llinventorybridge.cpp, to handle updating after operation completed in AISv3 (SH-4611). - // List is re-sorted alphabetically, so scroll to make sure the selected item is visible. - //scrollToShowSelection(); -} - -void LLFolderView::closeRenamer( void ) -{ - if (mRenamer && mRenamer->getVisible()) - { - // Triggers onRenamerLost() that actually closes the renamer. - LLUI::getInstance()->removePopup(mRenamer); - } -} - -void LLFolderView::removeSelectedItems() -{ - if(getVisible() && getEnabled()) - { - // just in case we're removing the renaming item. - mRenameItem = NULL; - - // create a temporary structure which we will use to remove - // items, since the removal will futz with internal data - // structures. - std::vector items; - S32 count = mSelectedItems.size(); - if(count <= 0) return; - LLFolderViewItem* item = NULL; - selected_items_t::iterator item_it; - for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) - { - item = *item_it; - if (item && item->isRemovable()) - { - items.push_back(item); - } - else - { - LL_DEBUGS() << "Cannot delete " << item->getName() << LL_ENDL; - return; - } - } - - // iterate through the new container. - count = items.size(); - LLUUID new_selection_id; - LLFolderViewItem* item_to_select = getNextUnselectedItem(); - - if(count == 1) - { - LLFolderViewItem* item_to_delete = items[0]; - LLFolderViewFolder* parent = item_to_delete->getParentFolder(); - if(parent) - { - if (item_to_delete->remove()) - { - // change selection on successful delete - setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus()); - } - } - arrangeAll(); - } - else if (count > 1) - { - std::vector listeners; - LLFolderViewModelItem* listener; - - setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus()); - - listeners.reserve(count); - for(S32 i = 0; i < count; ++i) - { - listener = items[i]->getViewModelItem(); - if(listener && (std::find(listeners.begin(), listeners.end(), listener) == listeners.end())) - { - listeners.push_back(listener); - } - } - listener = static_cast(listeners.at(0)); - if(listener) - { - listener->removeBatch(listeners); - } - } - arrangeAll(); - scrollToShowSelection(); - } -} - -void LLFolderView::autoOpenItem( LLFolderViewFolder* item ) -{ - if ((mAutoOpenItems.check() == item) || - (mAutoOpenItems.getDepth() >= (U32)AUTO_OPEN_STACK_DEPTH) || - item->isOpen()) - { - return; - } - - // close auto-opened folders - LLFolderViewFolder* close_item = mAutoOpenItems.check(); - while (close_item && close_item != item->getParentFolder()) - { - mAutoOpenItems.pop(); - close_item->setOpenArrangeRecursively(false); - close_item = mAutoOpenItems.check(); - } - - item->requestArrange(); - - mAutoOpenItems.push(item); - - item->setOpen(true); - if(!item->isSingleFolderMode()) - { - LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); - LLRect constraint_rect(0,content_rect.getHeight(), content_rect.getWidth(), 0); - scrollToShowItem(item, constraint_rect); - } -} - -void LLFolderView::closeAutoOpenedFolders() -{ - while (mAutoOpenItems.check()) - { - LLFolderViewFolder* close_item = mAutoOpenItems.pop(); - close_item->setOpen(false); - } - - if (mAutoOpenCandidate) - { - mAutoOpenCandidate->setAutoOpenCountdown(0.f); - } - mAutoOpenCandidate = NULL; - mAutoOpenTimer.stop(); -} - -bool LLFolderView::autoOpenTest(LLFolderViewFolder* folder) -{ - if (folder && mAutoOpenCandidate == folder) - { - if (mAutoOpenTimer.getStarted()) - { - if (!mAutoOpenCandidate->isOpen()) - { - mAutoOpenCandidate->setAutoOpenCountdown(clamp_rescale(mAutoOpenTimer.getElapsedTimeF32(), 0.f, sAutoOpenTime, 0.f, 1.f)); - } - if (mAutoOpenTimer.getElapsedTimeF32() > sAutoOpenTime) - { - autoOpenItem(folder); - mAutoOpenTimer.stop(); - return true; - } - } - return false; - } - - // otherwise new candidate, restart timer - if (mAutoOpenCandidate) - { - mAutoOpenCandidate->setAutoOpenCountdown(0.f); - } - mAutoOpenCandidate = folder; - mAutoOpenTimer.start(); - return false; -} - -bool LLFolderView::canCopy() const -{ - if (!(getVisible() && getEnabled() && (mSelectedItems.size() > 0))) - { - return false; - } - - for (selected_items_t::const_iterator selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it) - { - const LLFolderViewItem* item = *selected_it; - if (!item->getViewModelItem()->isItemCopyable()) - { - return false; - } - } - return true; -} - -// copy selected item -void LLFolderView::copy() -{ - // *NOTE: total hack to clear the inventory clipboard - LLClipboard::instance().reset(); - S32 count = mSelectedItems.size(); - if(getVisible() && getEnabled() && (count > 0)) - { - LLFolderViewModelItem* listener = NULL; - selected_items_t::iterator item_it; - for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) - { - listener = (*item_it)->getViewModelItem(); - if(listener) - { - listener->copyToClipboard(); - } - } - } - mSearchString.clear(); -} - -bool LLFolderView::canCut() const -{ - if (!(getVisible() && getEnabled() && (mSelectedItems.size() > 0))) - { - return false; - } - - for (selected_items_t::const_iterator selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it) - { - const LLFolderViewItem* item = *selected_it; - const LLFolderViewModelItem* listener = item->getViewModelItem(); - - if (!listener || !listener->isItemRemovable()) - { - return false; - } - } - return true; -} - -void LLFolderView::cut() -{ - // clear the inventory clipboard - LLClipboard::instance().reset(); - if(getVisible() && getEnabled() && (mSelectedItems.size() > 0)) - { - // Find out which item will be selected once the selection will be cut - LLFolderViewItem* item_to_select = getNextUnselectedItem(); - - // Get the selection: removeItem() modified mSelectedItems and makes iterating on it unwise - std::set inventory_selected = getSelectionList(); - - // Move each item to the clipboard and out of their folder - for (std::set::iterator item_it = inventory_selected.begin(); item_it != inventory_selected.end(); ++item_it) - { - LLFolderViewItem* item_to_cut = *item_it; - LLFolderViewModelItem* listener = item_to_cut->getViewModelItem(); - if (listener) - { - listener->cutToClipboard(); - } - } - - // Update the selection - setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus()); - } - mSearchString.clear(); -} - -bool LLFolderView::canPaste() const -{ - if (mSelectedItems.empty()) - { - return false; - } - - if(getVisible() && getEnabled()) - { - for (selected_items_t::const_iterator item_it = mSelectedItems.begin(); - item_it != mSelectedItems.end(); ++item_it) - { - // *TODO: only check folders and parent folders of items - const LLFolderViewItem* item = (*item_it); - const LLFolderViewModelItem* listener = item->getViewModelItem(); - if(!listener || !listener->isClipboardPasteable()) - { - const LLFolderViewFolder* folderp = item->getParentFolder(); - listener = folderp->getViewModelItem(); - if (!listener || !listener->isClipboardPasteable()) - { - return false; - } - } - } - return true; - } - return false; -} - -// paste selected item -void LLFolderView::paste() -{ - if(getVisible() && getEnabled()) - { - // find set of unique folders to paste into - std::set folder_set; - - selected_items_t::iterator selected_it; - for (selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it) - { - LLFolderViewItem* item = *selected_it; - LLFolderViewFolder* folder = dynamic_cast(item); - if (folder == NULL) - { - folder = item->getParentFolder(); - } - folder_set.insert(folder); - } - - std::set::iterator set_iter; - for(set_iter = folder_set.begin(); set_iter != folder_set.end(); ++set_iter) - { - LLFolderViewModelItem* listener = (*set_iter)->getViewModelItem(); - if(listener && listener->isClipboardPasteable()) - { - listener->pasteFromClipboard(); - } - } - } - mSearchString.clear(); -} - -// public rename functionality - can only start the process -void LLFolderView::startRenamingSelectedItem( void ) -{ - LL_DEBUGS("Inventory") << "Starting inventory renamer" << LL_ENDL; - - // make sure selection is visible - scrollToShowSelection(); - - S32 count = mSelectedItems.size(); - LLFolderViewItem* item = NULL; - if(count > 0) - { - item = mSelectedItems.front(); - } - if(getVisible() && getEnabled() && (count == 1) && item && item->getViewModelItem() && - item->getViewModelItem()->isItemRenameable()) - { - mRenameItem = item; - - updateRenamerPosition(); - - - mRenamer->setText(item->getName()); - mRenamer->selectAll(); - mRenamer->setVisible( true ); - // set focus will fail unless item is visible - mRenamer->setFocus( true ); - if (!mRenamerTopLostSignalConnection.connected()) - { - mRenamerTopLostSignalConnection = mRenamer->setTopLostCallback(boost::bind(&LLFolderView::onRenamerLost, this)); - } - LLUI::getInstance()->addPopup(mRenamer); - } -} - -bool LLFolderView::handleKeyHere( KEY key, MASK mask ) -{ - bool handled = false; - - // SL-51858: Key presses are not being passed to the Popup menu. - // A proper fix is non-trivial so instead just close the menu. - LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); - if (menu && menu->isOpen()) - { - LLMenuGL::sMenuContainer->hideMenus(); - } - - switch( key ) - { - case KEY_F2: - mSearchString.clear(); - startRenamingSelectedItem(); - handled = true; - break; - - case KEY_RETURN: - if (mask == MASK_NONE) - { - if( mRenameItem && mRenamer->getVisible() ) - { - finishRenamingItem(); - mSearchString.clear(); - handled = true; - } - } - break; - - case KEY_ESCAPE: - if( mRenameItem && mRenamer->getVisible() ) - { - closeRenamer(); - handled = true; - } - mSearchString.clear(); - break; - - case KEY_PAGE_UP: - mSearchString.clear(); - if (mScrollContainer) - { - mScrollContainer->pageUp(30); - } - handled = true; - break; - - case KEY_PAGE_DOWN: - mSearchString.clear(); - if (mScrollContainer) - { - mScrollContainer->pageDown(30); - } - handled = true; - break; - - case KEY_HOME: - mSearchString.clear(); - if (mScrollContainer) - { - mScrollContainer->goToTop(); - } - handled = true; - break; - - case KEY_END: - mSearchString.clear(); - if (mScrollContainer) - { - mScrollContainer->goToBottom(); - } - break; - - case KEY_DOWN: - if((mSelectedItems.size() > 0) && mScrollContainer) - { - LLFolderViewItem* last_selected = getCurSelectedItem(); - bool shift_select = mask & MASK_SHIFT; - // don't shift select down to children of folders (they are implicitly selected through parent) - LLFolderViewItem* next = last_selected->getNextOpenNode(!shift_select); - - if (!mKeyboardSelection || (!shift_select && (!next || next == last_selected))) - { - setSelection(last_selected, false, true); - mKeyboardSelection = true; - } - - if (shift_select) - { - if (next) - { - if (next->isSelected()) - { - // shrink selection - changeSelection(last_selected, false); - } - else if (last_selected->getParentFolder() == next->getParentFolder()) - { - // grow selection - changeSelection(next, true); - } - } - } - else - { - if( next ) - { - if (next == last_selected) - { - //special case for LLAccordionCtrl - if(notifyParent(LLSD().with("action","select_next")) > 0 )//message was processed - { - clearSelection(); - return true; - } - return false; - } - setSelection( next, false, true ); - } - else - { - //special case for LLAccordionCtrl - if(notifyParent(LLSD().with("action","select_next")) > 0 )//message was processed - { - clearSelection(); - return true; - } - return false; - } - } - scrollToShowSelection(); - mSearchString.clear(); - handled = true; - } - break; - - case KEY_UP: - if((mSelectedItems.size() > 0) && mScrollContainer) - { - LLFolderViewItem* last_selected = mSelectedItems.back(); - bool shift_select = mask & MASK_SHIFT; - // don't shift select down to children of folders (they are implicitly selected through parent) - LLFolderViewItem* prev = last_selected->getPreviousOpenNode(!shift_select); - - if (!mKeyboardSelection || (!shift_select && prev == this)) - { - setSelection(last_selected, false, true); - mKeyboardSelection = true; - } - - if (shift_select) - { - if (prev) - { - if (prev->isSelected()) - { - // shrink selection - changeSelection(last_selected, false); - } - else if (last_selected->getParentFolder() == prev->getParentFolder()) - { - // grow selection - changeSelection(prev, true); - } - } - } - else - { - if( prev ) - { - if (prev == this) - { - // If case we are in accordion tab notify parent to go to the previous accordion - if(notifyParent(LLSD().with("action","select_prev")) > 0 )//message was processed - { - clearSelection(); - return true; - } - - return false; - } - setSelection( prev, false, true ); - } - } - scrollToShowSelection(); - mSearchString.clear(); - - handled = true; - } - break; - - case KEY_RIGHT: - if(mSelectedItems.size()) - { - LLFolderViewItem* last_selected = getCurSelectedItem(); - last_selected->setOpen( true ); - mSearchString.clear(); - handled = true; - } - break; - - case KEY_LEFT: - if(mSelectedItems.size()) - { - LLFolderViewItem* last_selected = getCurSelectedItem(); - if(last_selected && last_selected->isSingleFolderMode()) - { - handled = false; - break; - } - LLFolderViewItem* parent_folder = last_selected->getParentFolder(); - if (!last_selected->isOpen() && parent_folder && parent_folder->getParentFolder()) - { - setSelection(parent_folder, false, true); - } - else - { - last_selected->setOpen( false ); - } - mSearchString.clear(); - scrollToShowSelection(); - handled = true; - } - break; - } - - return handled; -} - - -bool LLFolderView::handleUnicodeCharHere(llwchar uni_char) -{ - if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL - { - return false; - } - - if (uni_char > 0x7f) - { - LL_WARNS() << "LLFolderView::handleUnicodeCharHere - Don't handle non-ascii yet, aborting" << LL_ENDL; - return false; - } - - bool handled = false; - if (mParentPanel.get()->hasFocus()) - { - // SL-51858: Key presses are not being passed to the Popup menu. - // A proper fix is non-trivial so instead just close the menu. - LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); - if (menu && menu->isOpen()) - { - LLMenuGL::sMenuContainer->hideMenus(); - } - - //do text search - if (mSearchTimer.getElapsedTimeF32() > LLUI::getInstance()->mSettingGroups["config"]->getF32("TypeAheadTimeout")) - { - mSearchString.clear(); - } - mSearchTimer.reset(); - if (mSearchString.size() < 128) - { - mSearchString += uni_char; - } - search(getCurSelectedItem(), mSearchString, false); - - handled = true; - } - - return handled; -} - - -bool LLFolderView::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - mKeyboardSelection = false; - mSearchString.clear(); - - mParentPanel.get()->setFocus(true); - - LLEditMenuHandler::gEditMenuHandler = this; - - return LLView::handleMouseDown( x, y, mask ); -} - -bool LLFolderView::search(LLFolderViewItem* first_item, const std::string &search_string, bool backward) -{ - // get first selected item - LLFolderViewItem* search_item = first_item; - - // make sure search string is upper case - std::string upper_case_string = search_string; - LLStringUtil::toUpper(upper_case_string); - - // if nothing selected, select first item in folder - if (!search_item) - { - // start from first item - search_item = getNextFromChild(NULL); - } - - // search over all open nodes for first substring match (with wrapping) - bool found = false; - LLFolderViewItem* original_search_item = search_item; - do - { - // wrap at end - if (!search_item) - { - if (backward) - { - search_item = getPreviousFromChild(NULL); - } - else - { - search_item = getNextFromChild(NULL); - } - if (!search_item || search_item == original_search_item) - { - break; - } - } - - std::string current_item_label(search_item->getViewModelItem()->getSearchableName()); - LLStringUtil::toUpper(current_item_label); - S32 search_string_length = llmin(upper_case_string.size(), current_item_label.size()); - if (!current_item_label.compare(0, search_string_length, upper_case_string)) - { - found = true; - break; - } - if (backward) - { - search_item = search_item->getPreviousOpenNode(); - } - else - { - search_item = search_item->getNextOpenNode(); - } - - } while(search_item != original_search_item); - - - if (found) - { - setSelection(search_item, false, true); - scrollToShowSelection(); - } - - return found; -} - -bool LLFolderView::handleDoubleClick( S32 x, S32 y, MASK mask ) -{ - // skip LLFolderViewFolder::handleDoubleClick() - return LLView::handleDoubleClick( x, y, mask ); -} - -bool LLFolderView::handleRightMouseDown( S32 x, S32 y, MASK mask ) -{ - // all user operations move keyboard focus to inventory - // this way, we know when to stop auto-updating a search - mParentPanel.get()->setFocus(true); - - bool handled = childrenHandleRightMouseDown(x, y, mask) != NULL; - S32 count = mSelectedItems.size(); - - LLMenuGL* menu = static_cast(mPopupMenuHandle.get()); - if (!menu) - { - if (mCallbackRegistrar) - { - mCallbackRegistrar->pushScope(); - } - if (mEnableRegistrar) - { - mEnableRegistrar->pushScope(); - } - llassert(LLMenuGL::sMenuContainer != NULL); - menu = LLUICtrlFactory::getInstance()->createFromFile(mMenuFileName, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); - if (!menu) - { - menu = LLUICtrlFactory::getDefaultWidget("inventory_menu"); - } - menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor")); - mPopupMenuHandle = menu->getHandle(); - if (mEnableRegistrar) - { - mEnableRegistrar->popScope(); - } - if (mCallbackRegistrar) - { - mCallbackRegistrar->popScope(); - } - } - - bool item_clicked{ false }; - for (const auto item : mSelectedItems) - { - item_clicked |= item->getRect().pointInRect(x, y); - } - if(!item_clicked && mSingleFolderMode) - { - clearSelection(); - } - bool hide_folder_menu = mSuppressFolderMenu && isFolderSelected(); - if (menu && (mSingleFolderMode || (handled - && ( count > 0 && (hasVisibleChildren()) ))) && // show menu only if selected items are visible - !hide_folder_menu) - { - if (mCallbackRegistrar) - { - mCallbackRegistrar->pushScope(); - } - if (mEnableRegistrar) - { - mEnableRegistrar->pushScope(); - } - - updateMenuOptions(menu); - - menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(this, menu, x, y); - if (mEnableRegistrar) - { - mEnableRegistrar->popScope(); - } - if (mCallbackRegistrar) - { - mCallbackRegistrar->popScope(); - } - } - else - { - if (menu && menu->getVisible()) - { - menu->setVisible(false); - } - setSelection(NULL, false, true); - } - return handled; -} - -// Add "--no options--" if the menu is completely blank. -bool LLFolderView::addNoOptions(LLMenuGL* menu) const -{ - const std::string nooptions_str = "--no options--"; - LLView *nooptions_item = NULL; - - const LLView::child_list_t *list = menu->getChildList(); - for (LLView::child_list_t::const_iterator itor = list->begin(); - itor != list->end(); - ++itor) - { - LLView *menu_item = (*itor); - if (menu_item->getVisible()) - { - return false; - } - std::string name = menu_item->getName(); - if (menu_item->getName() == nooptions_str) - { - nooptions_item = menu_item; - } - } - if (nooptions_item) - { - nooptions_item->setVisible(true); - nooptions_item->setEnabled(false); - return true; - } - return false; -} - -bool LLFolderView::handleHover( S32 x, S32 y, MASK mask ) -{ - return LLView::handleHover( x, y, mask ); -} - -LLFolderViewItem* LLFolderView::getHoveredItem() const -{ - return dynamic_cast(mHoveredItem.get()); -} - -void LLFolderView::setHoveredItem(LLFolderViewItem* itemp) -{ - if (mHoveredItem.get() != itemp) - { - if (itemp) - mHoveredItem = itemp->getHandle(); - else - mHoveredItem.markDead(); - } -} - -bool LLFolderView::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - mDragAndDropThisFrame = true; - // have children handle it first - bool handled = LLView::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, - accept, tooltip_msg); - - // when drop is not handled by child, it should be handled - // by the folder which is the hierarchy root. - if (!handled) - { - handled = LLFolderViewFolder::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - } - - return handled; -} - -void LLFolderView::deleteAllChildren() -{ - mRenamerTopLostSignalConnection.disconnect(); - if (mRenamer) - { - LLUI::getInstance()->removePopup(mRenamer); - } - if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die(); - mPopupMenuHandle.markDead(); - mScrollContainer = NULL; - mRenameItem = NULL; - mRenamer = NULL; - mStatusTextBox = NULL; - - clearSelection(); - LLView::deleteAllChildren(); -} - -void LLFolderView::scrollToShowSelection() -{ - if ( mSelectedItems.size() ) - { - mNeedsScroll = true; - } -} - -// If the parent is scroll container, scroll it to make the selection -// is maximally visible. -void LLFolderView::scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect) -{ - if (!mScrollContainer) return; - - // don't scroll to items when mouse is being used to scroll/drag and drop - if (gFocusMgr.childHasMouseCapture(mScrollContainer)) - { - mNeedsScroll = false; - return; - } - - // if item exists and is in visible portion of parent folder... - if(item) - { - LLRect local_rect = item->getLocalRect(); - S32 icon_height = mIcon.isNull() ? 0 : mIcon->getHeight(); - S32 label_height = getLabelFontForStyle(mLabelStyle)->getLineHeight(); - // when navigating with keyboard, only move top of opened folder on screen, otherwise show whole folder - S32 max_height_to_show = item->isOpen() && mScrollContainer->hasFocus() ? (llmax( icon_height, label_height ) + item->getIconPad()) : local_rect.getHeight(); - - // get portion of item that we want to see... - LLRect item_local_rect = LLRect(item->getIndentation(), - local_rect.getHeight(), - //+40 is supposed to include few first characters - llmin(item->getLabelXPos() - item->getIndentation() + 40, local_rect.getWidth()), - llmax(0, local_rect.getHeight() - max_height_to_show)); - - LLRect item_doc_rect; - - item->localRectToOtherView(item_local_rect, &item_doc_rect, this); - - mScrollContainer->scrollToShowRect( item_doc_rect, constraint_rect ); - - } -} - -LLRect LLFolderView::getVisibleRect() -{ - S32 visible_height = (mScrollContainer ? mScrollContainer->getRect().getHeight() : 0); - S32 visible_width = (mScrollContainer ? mScrollContainer->getRect().getWidth() : 0); - LLRect visible_rect; - visible_rect.setLeftTopAndSize(-getRect().mLeft, visible_height - getRect().mBottom, visible_width, visible_height); - return visible_rect; -} - -bool LLFolderView::getShowSelectionContext() -{ - if (mShowSelectionContext) - { - return true; - } - LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); - if (menu && menu->getVisible()) - { - return true; - } - return false; -} - -void LLFolderView::setShowSingleSelection(bool show) -{ - if (show != mShowSingleSelection) - { - mMultiSelectionFadeTimer.reset(); - mShowSingleSelection = show; - } -} - -static LLTrace::BlockTimerStatHandle FTM_INVENTORY("Inventory"); - -// Main idle routine -void LLFolderView::update() -{ - // If this is associated with the user's inventory, don't do anything - // until that inventory is loaded up. - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(FTM_INVENTORY); - - // If there's no model, the view is in suspended state (being deleted) and shouldn't be updated - if (getFolderViewModel() == NULL) - { - return; - } - - LLFolderViewFilter& filter_object = getFolderViewModel()->getFilter(); - - if (filter_object.isModified() && filter_object.isNotDefault() && mParentPanel.get()->getVisible()) - { - mNeedsAutoSelect = true; - } - - // Filter to determine visibility before arranging - filter(filter_object); - - // Clear the modified setting on the filter only if the filter finished after running the filter process - // Note: if the filter count has timed out, that means the filter halted before completing the entire set of items - bool filter_modified = filter_object.isModified(); - if (filter_modified && (!filter_object.isTimedOut())) - { - filter_object.clearModified(); - } - - // automatically show matching items, and select first one if we had a selection - if (mNeedsAutoSelect) - { - // select new item only if a filtered item not currently selected and there was a selection - LLFolderViewItem* selected_itemp = mSelectedItems.empty() ? NULL : mSelectedItems.back(); - if (!mAutoSelectOverride && selected_itemp && !selected_itemp->getViewModelItem()->potentiallyVisible()) - { - // these are named variables to get around gcc not binding non-const references to rvalues - // and functor application is inherently non-const to allow for stateful functors - LLSelectFirstFilteredItem functor; - applyFunctorRecursively(functor); - } - - // Open filtered folders for folder views with mAutoSelectOverride=true. - // Used by LLPlacesFolderView. - if (filter_object.showAllResults()) - { - // these are named variables to get around gcc not binding non-const references to rvalues - // and functor application is inherently non-const to allow for stateful functors - LLOpenFilteredFolders functor; - applyFunctorRecursively(functor); - } - - scrollToShowSelection(); - } - - bool filter_finished = mViewModel->contentsReady() - && (getViewModelItem()->passedFilter() - || ( getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration() - && !filter_modified)); - if (filter_finished - || gFocusMgr.childHasKeyboardFocus(mParentPanel.get()) - || gFocusMgr.childHasMouseCapture(mParentPanel.get())) - { - // finishing the filter process, giving focus to the folder view, or dragging the scrollbar all stop the auto select process - mNeedsAutoSelect = false; - } - - bool is_visible = isInVisibleChain() || mForceArrange; - - //Puts folders/items in proper positions - // arrange() takes the model filter flag into account and call sort() if necessary (CHUI-849) - // It also handles the open/close folder animation - if ( is_visible ) - { - sanitizeSelection(); - if( needsArrange() ) - { - S32 height = 0; - S32 width = 0; - S32 total_height = arrange( &width, &height ); - notifyParent(LLSD().with("action", "size_changes").with("height", total_height)); - } - } - - // during filtering process, try to pin selected item's location on screen - // this will happen when searching your inventory and when new items arrive - if (!filter_finished) - { - // calculate rectangle to pin item to at start of animated rearrange - if (!mPinningSelectedItem && !mSelectedItems.empty()) - { - // lets pin it! - mPinningSelectedItem = true; - - //Computes visible area - const LLRect visible_content_rect = (mScrollContainer ? mScrollContainer->getVisibleContentRect() : LLRect()); - LLFolderViewItem* selected_item = mSelectedItems.back(); - - //Computes location of selected content, content outside visible area will be scrolled to using below code - LLRect item_rect; - selected_item->localRectToOtherView(selected_item->getLocalRect(), &item_rect, this); - - //Computes intersected region of the selected content and visible area - LLRect overlap_rect(item_rect); - overlap_rect.intersectWith(visible_content_rect); - - //Don't scroll when the selected content exists within the visible area - if (overlap_rect.getHeight() >= selected_item->getItemHeight()) - { - // then attempt to keep it in same place on screen - mScrollConstraintRect = item_rect; - mScrollConstraintRect.translate(-visible_content_rect.mLeft, -visible_content_rect.mBottom); - } - //Scroll because the selected content is outside the visible area - else - { - // otherwise we just want it onscreen somewhere - LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); - mScrollConstraintRect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); - } - } - } - else - { - // stop pinning selected item after folders stop rearranging - if (!needsArrange()) - { - mPinningSelectedItem = false; - } - } - - LLRect constraint_rect; - if (mPinningSelectedItem) - { - // use last known constraint rect for pinned item - constraint_rect = mScrollConstraintRect; - } - else - { - // during normal use (page up/page down, etc), just try to fit item on screen - LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); - constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); - } - - if (mSelectedItems.size() && mNeedsScroll) - { - LLFolderViewItem* scroll_to_item = mSelectedItems.back(); - scrollToShowItem(scroll_to_item, constraint_rect); - // continue scrolling until animated layout change is done - bool selected_filter_finished = getRoot()->getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration(); - if (selected_filter_finished && scroll_to_item && scroll_to_item->getViewModelItem()) - { - selected_filter_finished = scroll_to_item->getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration(); - } - if (filter_finished && selected_filter_finished) - { - bool needs_arrange = needsArrange() || getRoot()->needsArrange(); - if (mParentFolder) - { - needs_arrange |= (bool)mParentFolder->needsArrange(); - } - if (!needs_arrange || !is_visible) - { - mNeedsScroll = false; - } - } - } - - if (mSelectedItems.size()) - { - LLFolderViewItem* item = mSelectedItems.back(); - // If the goal is to show renamer, don't callback untill - // item is visible or is no longer being scrolled to. - // Otherwise renamer will be instantly closed - // Todo: consider moving renamer out of selection callback - if (!mNeedsAutoRename || !mNeedsScroll || item->getVisible()) - { - if (mSignalSelectCallback) - { - //RN: we use keyboard focus as a proxy for user-explicit actions - bool take_keyboard_focus = (mSignalSelectCallback == SIGNAL_KEYBOARD_FOCUS); - mSelectSignal(mSelectedItems, take_keyboard_focus); - } - mSignalSelectCallback = false; - } - } - else - { - mSignalSelectCallback = false; - } -} - -void LLFolderView::dumpSelectionInformation() -{ - LL_INFOS() << "LLFolderView::dumpSelectionInformation()" << LL_NEWLINE - << "****************************************" << LL_ENDL; - selected_items_t::iterator item_it; - for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) - { - LL_INFOS() << " " << (*item_it)->getName() << LL_ENDL; - } - LL_INFOS() << "****************************************" << LL_ENDL; -} - -void LLFolderView::updateRenamerPosition() -{ - if(mRenameItem) - { - // See also LLFolderViewItem::draw() - S32 x = mRenameItem->getLabelXPos(); - S32 y = mRenameItem->getRect().getHeight() - mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD; - mRenameItem->localPointToScreen( x, y, &x, &y ); - screenPointToLocal( x, y, &x, &y ); - mRenamer->setOrigin( x, y ); - - LLRect scroller_rect(0, 0, (S32)LLUI::getInstance()->getWindowSize().mV[VX], 0); - if (mScrollContainer) - { - scroller_rect = mScrollContainer->getContentWindowRect(); - } - - S32 width = llmax(llmin(mRenameItem->getRect().getWidth() - x, scroller_rect.getWidth() - x - getRect().mLeft), MINIMUM_RENAMER_WIDTH); - S32 height = mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD; - mRenamer->reshape( width, height, true ); - } -} - -// Update visibility and availability (i.e. enabled/disabled) of context menu items. -void LLFolderView::updateMenuOptions(LLMenuGL* menu) -{ - const LLView::child_list_t *list = menu->getChildList(); - - LLView::child_list_t::const_iterator menu_itor; - for (menu_itor = list->begin(); menu_itor != list->end(); ++menu_itor) - { - (*menu_itor)->setVisible(false); - (*menu_itor)->pushVisible(true); - (*menu_itor)->setEnabled(true); - } - - // Successively filter out invalid options - U32 multi_select_flag = (mSelectedItems.size() > 1 ? ITEM_IN_MULTI_SELECTION : 0x0); - U32 flags = multi_select_flag | FIRST_SELECTED_ITEM; - for (selected_items_t::iterator item_itor = mSelectedItems.begin(); - item_itor != mSelectedItems.end(); - ++item_itor) - { - LLFolderViewItem* selected_item = (*item_itor); - selected_item->buildContextMenu(*menu, flags); - flags = multi_select_flag; - } - - if(mSingleFolderMode && (mSelectedItems.size() == 0)) - { - buildContextMenu(*menu, flags); - } - - // This adds a check for restrictions based on the entire - // selection set - for example, any one wearable may not push you - // over the limit, but all wearables together still might. - if (getFolderViewGroupedItemModel()) - { - getFolderViewGroupedItemModel()->groupFilterContextMenu(mSelectedItems,*menu); - } - - addNoOptions(menu); -} - -// Refresh the context menu (that is already shown). -void LLFolderView::updateMenu() -{ - LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); - if (menu && menu->getVisible()) - { - updateMenuOptions(menu); - menu->needsArrange(); // update menu height if needed - } -} - -bool LLFolderView::isFolderSelected() -{ - selected_items_t::iterator item_iter; - for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter) - { - LLFolderViewFolder* folder = dynamic_cast(*item_iter); - if (folder != NULL) - { - return true; - } - } - return false; -} - -bool LLFolderView::selectFirstItem() -{ - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();++iter) - { - LLFolderViewFolder* folder = (*iter ); - if (folder->getVisible()) - { - LLFolderViewItem* itemp = folder->getNextFromChild(0,true); - if(itemp) - setSelection(itemp,false,true); - return true; - } - - } - for(items_t::iterator iit = mItems.begin(); - iit != mItems.end(); ++iit) - { - LLFolderViewItem* itemp = (*iit); - if (itemp->getVisible()) - { - setSelection(itemp,false,true); - return true; - } - } - return false; -} -bool LLFolderView::selectLastItem() -{ - for(items_t::reverse_iterator iit = mItems.rbegin(); - iit != mItems.rend(); ++iit) - { - LLFolderViewItem* itemp = (*iit); - if (itemp->getVisible()) - { - setSelection(itemp,false,true); - return true; - } - } - for (folders_t::reverse_iterator iter = mFolders.rbegin(); - iter != mFolders.rend();++iter) - { - LLFolderViewFolder* folder = (*iter); - if (folder->getVisible()) - { - LLFolderViewItem* itemp = folder->getPreviousFromChild(0,true); - if(itemp) - setSelection(itemp,false,true); - return true; - } - } - return false; -} - - -S32 LLFolderView::notify(const LLSD& info) -{ - if(info.has("action")) - { - std::string str_action = info["action"]; - if(str_action == "select_first") - { - setFocus(true); - selectFirstItem(); - scrollToShowSelection(); - return 1; - - } - else if(str_action == "select_last") - { - setFocus(true); - selectLastItem(); - scrollToShowSelection(); - return 1; - } - } - return 0; -} - - -///---------------------------------------------------------------------------- -/// Local function definitions -///---------------------------------------------------------------------------- - -void LLFolderView::onRenamerLost() -{ - if (mRenamer && mRenamer->getVisible()) - { - mRenamer->setVisible(false); - - // will commit current name (which could be same as original name) - mRenamer->setFocus(false); - } - - if( mRenameItem ) - { - setSelection( mRenameItem, true ); - mRenameItem = NULL; - } -} - -LLFolderViewItem* LLFolderView::getNextUnselectedItem() -{ - LLFolderViewItem* last_item = *mSelectedItems.rbegin(); - LLFolderViewItem* new_selection = last_item->getNextOpenNode(false); - while(new_selection && new_selection->isSelected()) - { - new_selection = new_selection->getNextOpenNode(false); - } - if (!new_selection) - { - new_selection = last_item->getPreviousOpenNode(false); - while (new_selection && (new_selection->isInSelection())) - { - new_selection = new_selection->getPreviousOpenNode(false); - } - } - return new_selection; -} - -S32 LLFolderView::getItemHeight() const -{ - if(!hasVisibleChildren()) -{ - //We need to display status textbox, let's reserve some place for it - return llmax(0, mStatusTextBox->getTextPixelHeight()); -} - return 0; -} +/** + * @file llfolderview.cpp + * @brief Implementation of the folder view collection of classes. + * + * $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 "llfolderview.h" +#include "llfolderviewmodel.h" +#include "llclipboard.h" // *TODO: remove this once hack below gone. +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llmenugl.h" +#include "llpanel.h" +#include "llscrollcontainer.h" // hack to allow scrolling +#include "lltextbox.h" +#include "lltrans.h" +#include "llui.h" +#include "lluictrlfactory.h" + +// Linden library includes +#include "lldbstrings.h" +#include "llfocusmgr.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llrender.h" + +// Third-party library includes +#include + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +const S32 RENAME_HEIGHT_PAD = 1; +const S32 AUTO_OPEN_STACK_DEPTH = 16; + +const S32 MINIMUM_RENAMER_WIDTH = 80; + +// *TODO: move in params in xml if necessary. Requires modification of LLFolderView & LLInventoryPanel Params. +const S32 STATUS_TEXT_HPAD = 6; +const S32 STATUS_TEXT_VPAD = 8; + +enum { + SIGNAL_NO_KEYBOARD_FOCUS = 1, + SIGNAL_KEYBOARD_FOCUS = 2 +}; + +F32 LLFolderView::sAutoOpenTime = 1.f; + +//--------------------------------------------------------------------------- + +// Tells all folders in a folderview to close themselves +// For efficiency, calls setOpenArrangeRecursively(). +// The calling function must then call: +// LLFolderView* root = getRoot(); +// if( root ) +// { +// root->arrange( NULL, NULL ); +// root->scrollToShowSelection(); +// } +// to patch things up. +class LLCloseAllFoldersFunctor : public LLFolderViewFunctor +{ +public: + LLCloseAllFoldersFunctor(bool close) { mOpen = !close; } + virtual ~LLCloseAllFoldersFunctor() {} + virtual void doFolder(LLFolderViewFolder* folder); + virtual void doItem(LLFolderViewItem* item); + + bool mOpen; +}; + + +void LLCloseAllFoldersFunctor::doFolder(LLFolderViewFolder* folder) +{ + folder->setOpenArrangeRecursively(mOpen); +} + +// Do nothing. +void LLCloseAllFoldersFunctor::doItem(LLFolderViewItem* item) +{ } + +//--------------------------------------------------------------------------- + +void LLAllDescendentsPassedFilter::doFolder(LLFolderViewFolder* folder) +{ + mAllDescendentsPassedFilter &= (folder) && (folder->passedFilter()) && (folder->descendantsPassedFilter()); +} + +void LLAllDescendentsPassedFilter::doItem(LLFolderViewItem* item) +{ + mAllDescendentsPassedFilter &= (item) && (item->passedFilter()); +} + +///---------------------------------------------------------------------------- +/// Class LLFolderViewScrollContainer +///---------------------------------------------------------------------------- + +// virtual +const LLRect LLFolderViewScrollContainer::getScrolledViewRect() const +{ + LLRect rect = LLRect::null; + if (mScrolledView) + { + LLFolderView* folder_view = dynamic_cast(mScrolledView); + if (folder_view) + { + S32 height = folder_view->getRect().getHeight(); + + rect = mScrolledView->getRect(); + rect.setLeftTopAndSize(rect.mLeft, rect.mTop, rect.getWidth(), height); + } + } + + return rect; +} + +LLFolderViewScrollContainer::LLFolderViewScrollContainer(const LLScrollContainer::Params& p) +: LLScrollContainer(p) +{} + +///---------------------------------------------------------------------------- +/// Class LLFolderView +///---------------------------------------------------------------------------- +LLFolderView::Params::Params() +: title("title"), + use_label_suffix("use_label_suffix"), + allow_multiselect("allow_multiselect", true), + allow_drag("allow_drag", true), + show_empty_message("show_empty_message", true), + suppress_folder_menu("suppress_folder_menu", false), + use_ellipses("use_ellipses", false), + options_menu("options_menu", "") +{ + folder_indentation = -4; +} + + +// Default constructor +LLFolderView::LLFolderView(const Params& p) +: LLFolderViewFolder(p), + mScrollContainer( NULL ), + mPopupMenuHandle(), + mMenuFileName(p.options_menu), + mAllowMultiSelect(p.allow_multiselect), + mAllowDrag(p.allow_drag), + mShowEmptyMessage(p.show_empty_message), + mShowFolderHierarchy(false), + mRenameItem( NULL ), + mNeedsScroll( false ), + mUseLabelSuffix(p.use_label_suffix), + mSuppressFolderMenu(p.suppress_folder_menu), + mPinningSelectedItem(false), + mNeedsAutoSelect( false ), + mAutoSelectOverride(false), + mNeedsAutoRename(false), + mShowSelectionContext(false), + mShowSingleSelection(false), + mArrangeGeneration(0), + mSignalSelectCallback(0), + mMinWidth(0), + mDragAndDropThisFrame(false), + mCallbackRegistrar(NULL), + mEnableRegistrar(NULL), + mUseEllipses(p.use_ellipses), + mDraggingOverItem(NULL), + mStatusTextBox(NULL), + mShowItemLinkOverlays(p.show_item_link_overlays), + mViewModel(p.view_model), + mGroupedItemModel(p.grouped_item_model), + mForceArrange(false), + mSingleFolderMode(false) +{ + LLPanel* panel = p.parent_panel; + mParentPanel = panel->getHandle(); + mViewModel->setFolderView(this); + mRoot = this; + + LLRect rect = p.rect; + LLRect new_rect(rect.mLeft, rect.mBottom + getRect().getHeight(), rect.mLeft + getRect().getWidth(), rect.mBottom); + setRect( rect ); + reshape(rect.getWidth(), rect.getHeight()); + mAutoOpenItems.setDepth(AUTO_OPEN_STACK_DEPTH); + mAutoOpenCandidate = NULL; + mAutoOpenTimer.stop(); + mKeyboardSelection = false; + mIndentation = getParentFolder() ? getParentFolder()->getIndentation() + mLocalIndentation : 0; + + //clear label + // go ahead and render root folder as usual + // just make sure the label ("Inventory Folder") never shows up + mLabel = LLStringUtil::null; + + // Escape is handled by reverting the rename, not commiting it (default behavior) + LLLineEditor::Params params; + params.name("ren"); + params.rect(rect); + params.font(getLabelFontForStyle(LLFontGL::NORMAL)); + params.max_length.bytes(DB_INV_ITEM_NAME_STR_LEN); + params.commit_callback.function(boost::bind(&LLFolderView::commitRename, this, _2)); + params.prevalidator(&LLTextValidate::validateASCIIPrintableNoPipe); + params.commit_on_focus_lost(true); + params.visible(false); + mRenamer = LLUICtrlFactory::create (params); + addChild(mRenamer); + + // Textbox + LLTextBox::Params text_p; + LLFontGL* font = getLabelFontForStyle(mLabelStyle); + //mIconPad, mTextPad are set in folder_view_item.xml + LLRect new_r = LLRect(rect.mLeft + mIconPad, + rect.mTop - mTextPad, + rect.mRight, + rect.mTop - mTextPad - font->getLineHeight()); + text_p.rect(new_r); + text_p.name(std::string(p.name)); + text_p.font(font); + text_p.visible(false); + text_p.parse_urls(true); + text_p.wrap(true); // allow multiline text. See EXT-7564, EXT-7047 + // set text padding the same as in People panel. EXT-7047, EXT-4837 + text_p.h_pad(STATUS_TEXT_HPAD); + text_p.v_pad(STATUS_TEXT_VPAD); + mStatusTextBox = LLUICtrlFactory::create (text_p); + mStatusTextBox->setFollowsLeft(); + mStatusTextBox->setFollowsTop(); + addChild(mStatusTextBox); + + mViewModelItem->openItem(); + + mAreChildrenInited = true; // root folder is a special case due to not being loaded normally, assume that it's inited. +} + +// Destroys the object +LLFolderView::~LLFolderView( void ) +{ + mRenamerTopLostSignalConnection.disconnect(); + if (mRenamer) + { + // instead of using closeRenamer remove it directly, + // since it might already be hidden + LLUI::getInstance()->removePopup(mRenamer); + } + + // The release focus call can potentially call the + // scrollcontainer, which can potentially be called with a partly + // destroyed scollcontainer. Just null it out here, and no worries + // about calling into the invalid scroll container. + // Same with the renamer. + mScrollContainer = NULL; + mRenameItem = NULL; + mRenamer = NULL; + mStatusTextBox = NULL; + + if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die(); + mPopupMenuHandle.markDead(); + + mAutoOpenItems.removeAllNodes(); + clearSelection(); + mItems.clear(); + mFolders.clear(); + + //mViewModel->setFolderView(NULL); + mViewModel = NULL; +} + +bool LLFolderView::canFocusChildren() const +{ + return false; +} + +void LLFolderView::addFolder( LLFolderViewFolder* folder) +{ + LLFolderViewFolder::addFolder(folder); +} + +void LLFolderView::closeAllFolders() +{ + // Close all the folders + setOpenArrangeRecursively(false, LLFolderViewFolder::RECURSE_DOWN); + arrangeAll(); +} + +void LLFolderView::openTopLevelFolders() +{ + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();) + { + folders_t::iterator fit = iter++; + (*fit)->setOpen(true); + } +} + +// This view grows and shrinks to enclose all of its children items and folders. +// *width should be 0 +// conform show folder state works +S32 LLFolderView::arrange( S32* unused_width, S32* unused_height ) + { + mMinWidth = 0; + S32 target_height; + + LLFolderViewFolder::arrange(&mMinWidth, &target_height); + + LLRect scroll_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); + reshape( llmax(scroll_rect.getWidth(), mMinWidth), ll_round(mCurHeight) ); + + LLRect new_scroll_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); + if (new_scroll_rect.getWidth() != scroll_rect.getWidth()) + { + reshape( llmax(scroll_rect.getWidth(), mMinWidth), ll_round(mCurHeight) ); + } + + // move item renamer text field to item's new position + updateRenamerPosition(); + + return ll_round(mTargetHeight); +} + +void LLFolderView::filter( LLFolderViewFilter& filter ) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + const S32 TIME_VISIBLE = 10; // in milliseconds + const S32 TIME_INVISIBLE = 1; + filter.resetTime(llclamp((mParentPanel.get()->getVisible() ? TIME_VISIBLE : TIME_INVISIBLE), 1, 100)); + + // Note: we filter the model, not the view + getViewModelItem()->filter(filter); +} + +void LLFolderView::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLRect scroll_rect; + if (mScrollContainer) + { + LLView::reshape(width, height, called_from_parent); + scroll_rect = mScrollContainer->getContentWindowRect(); + } + width = llmax(mMinWidth, scroll_rect.getWidth()); + height = llmax(ll_round(mCurHeight), scroll_rect.getHeight()); + + // Restrict width within scroll container's width + if (mUseEllipses && mScrollContainer) + { + width = scroll_rect.getWidth(); + } + LLView::reshape(width, height, called_from_parent); + mReshapeSignal(mSelectedItems, false); +} + +void LLFolderView::addToSelectionList(LLFolderViewItem* item) +{ + if (item->isSelected()) + { + removeFromSelectionList(item); + } + if (mSelectedItems.size()) + { + mSelectedItems.back()->setIsCurSelection(false); + } + item->setIsCurSelection(true); + mSelectedItems.push_back(item); +} + +void LLFolderView::removeFromSelectionList(LLFolderViewItem* item) +{ + if (mSelectedItems.size()) + { + mSelectedItems.back()->setIsCurSelection(false); + } + + selected_items_t::iterator item_iter; + for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end();) + { + if (*item_iter == item) + { + item_iter = mSelectedItems.erase(item_iter); + } + else + { + ++item_iter; + } + } + if (mSelectedItems.size()) + { + mSelectedItems.back()->setIsCurSelection(true); + } +} + +LLFolderViewItem* LLFolderView::getCurSelectedItem( void ) +{ + if(mSelectedItems.size()) + { + LLFolderViewItem* itemp = mSelectedItems.back(); + llassert(itemp->getIsCurSelection()); + return itemp; + } + return NULL; +} + +LLFolderView::selected_items_t& LLFolderView::getSelectedItems( void ) +{ + return mSelectedItems; +} + +// Record the selected item and pass it down the hierachy. +bool LLFolderView::setSelection(LLFolderViewItem* selection, bool openitem, + bool take_keyboard_focus) +{ + mSignalSelectCallback = take_keyboard_focus ? SIGNAL_KEYBOARD_FOCUS : SIGNAL_NO_KEYBOARD_FOCUS; + + if( selection == this ) + { + return false; + } + + if( selection && take_keyboard_focus) + { + mParentPanel.get()->setFocus(true); + } + + // clear selection down here because change of keyboard focus can potentially + // affect selection + clearSelection(); + + if(selection) + { + addToSelectionList(selection); + } + + bool rv = LLFolderViewFolder::setSelection(selection, openitem, take_keyboard_focus); + if(openitem && selection) + { + selection->getParentFolder()->requestArrange(); + } + + llassert(mSelectedItems.size() <= 1); + + return rv; +} + +bool LLFolderView::changeSelection(LLFolderViewItem* selection, bool selected) +{ + bool rv = false; + + // can't select root folder + if(!selection || selection == this) + { + return false; + } + + if (!mAllowMultiSelect) + { + clearSelection(); + } + + selected_items_t::iterator item_iter; + for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter) + { + if (*item_iter == selection) + { + break; + } + } + + bool on_list = (item_iter != mSelectedItems.end()); + + if(selected && !on_list) + { + addToSelectionList(selection); + } + if(!selected && on_list) + { + removeFromSelectionList(selection); + } + + rv = LLFolderViewFolder::changeSelection(selection, selected); + + mSignalSelectCallback = SIGNAL_KEYBOARD_FOCUS; + + return rv; +} + +void LLFolderView::sanitizeSelection() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + // store off current item in case it is automatically deselected + // and we want to preserve context + LLFolderViewItem* original_selected_item = getCurSelectedItem(); + + std::vector items_to_remove; + selected_items_t::iterator item_iter; + for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter) + { + LLFolderViewItem* item = *item_iter; + + // ensure that each ancestor is open and potentially passes filtering + bool visible = false; + if(item->getViewModelItem() != NULL) + { + visible = item->getViewModelItem()->potentiallyVisible(); // initialize from filter state for this item + } + // modify with parent open and filters states + LLFolderViewFolder* parent_folder = item->getParentFolder(); + // Move up through parent folders and see what's visible + while(parent_folder) + { + visible = visible && parent_folder->isOpen() && parent_folder->getViewModelItem()->potentiallyVisible(); + parent_folder = parent_folder->getParentFolder(); + } + + // deselect item if any ancestor is closed or didn't pass filter requirements. + if (!visible) + { + items_to_remove.push_back(item); + } + + // disallow nested selections (i.e. folder items plus one or more ancestors) + // could check cached mum selections count and only iterate if there are any + // but that may be a premature optimization. + selected_items_t::iterator other_item_iter; + for (other_item_iter = mSelectedItems.begin(); other_item_iter != mSelectedItems.end(); ++other_item_iter) + { + LLFolderViewItem* other_item = *other_item_iter; + for( parent_folder = other_item->getParentFolder(); parent_folder; parent_folder = parent_folder->getParentFolder()) + { + if (parent_folder == item) + { + // this is a descendent of the current folder, remove from list + items_to_remove.push_back(other_item); + break; + } + } + } + + // Don't allow invisible items (such as root folders) to be selected. + if (item == getRoot()) + { + items_to_remove.push_back(item); + } + } + + std::vector::iterator item_it; + for (item_it = items_to_remove.begin(); item_it != items_to_remove.end(); ++item_it ) + { + changeSelection(*item_it, false); // toggle selection (also removes from list) + } + + // if nothing selected after prior constraints... + if (mSelectedItems.empty()) + { + // ...select first available parent of original selection + LLFolderViewItem* new_selection = NULL; + if (original_selected_item) + { + for(LLFolderViewFolder* parent_folder = original_selected_item->getParentFolder(); + parent_folder; + parent_folder = parent_folder->getParentFolder()) + { + if (parent_folder->getViewModelItem() && parent_folder->getViewModelItem()->potentiallyVisible()) + { + // give initial selection to first ancestor folder that potentially passes the filter + if (!new_selection) + { + new_selection = parent_folder; + } + + // if any ancestor folder of original item is closed, move the selection up + // to the highest closed + if (!parent_folder->isOpen()) + { + new_selection = parent_folder; + } + } + } + } + else + { + new_selection = NULL; + } + + if (new_selection) + { + setSelection(new_selection, false, false); + } + } +} + +void LLFolderView::clearSelection() +{ + for (selected_items_t::const_iterator item_it = mSelectedItems.begin(); + item_it != mSelectedItems.end(); + ++item_it) + { + (*item_it)->setUnselected(); + } + + mSelectedItems.clear(); + mNeedsScroll = false; +} + +std::set LLFolderView::getSelectionList() const +{ + std::set selection; + std::copy(mSelectedItems.begin(), mSelectedItems.end(), std::inserter(selection, selection.begin())); + return selection; +} + +bool LLFolderView::startDrag() +{ + std::vector selected_items; + selected_items_t::iterator item_it; + + if (!mSelectedItems.empty()) + { + for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) + { + selected_items.push_back((*item_it)->getViewModelItem()); + } + + return getFolderViewModel()->startDrag(selected_items); + } + return false; +} + +void LLFolderView::commitRename( const LLSD& data ) +{ + finishRenamingItem(); + arrange( NULL, NULL ); + +} + +void LLFolderView::draw() +{ + //LLFontGL* font = getLabelFontForStyle(mLabelStyle); + + // if cursor has moved off of me during drag and drop + // close all auto opened folders + if (!mDragAndDropThisFrame) + { + closeAutoOpenedFolders(); + } + + static LLCachedControl type_ahead_timeout(*LLUI::getInstance()->mSettingGroups["config"], "TypeAheadTimeout", 1.5f); + if (mSearchTimer.getElapsedTimeF32() > type_ahead_timeout || !mSearchString.size()) + { + mSearchString.clear(); + } + + if (hasVisibleChildren()) + { + mStatusTextBox->setVisible( false ); + } + else if (mShowEmptyMessage) + { + mStatusTextBox->setValue(getFolderViewModel()->getStatusText(mItems.empty() && mFolders.empty())); + mStatusTextBox->setVisible( true ); + + // firstly reshape message textbox with current size. This is necessary to + // LLTextBox::getTextPixelHeight works properly + const LLRect local_rect = getLocalRect(); + mStatusTextBox->setShape(local_rect); + + // get preferable text height... + S32 pixel_height = mStatusTextBox->getTextPixelHeight(); + bool height_changed = (local_rect.getHeight() < pixel_height); + if (height_changed) + { + // ... if it does not match current height, lets rearrange current view. + // This will indirectly call ::arrange and reshape of the status textbox. + // We should call this method to also notify parent about required rect. + // See EXT-7564, EXT-7047. + S32 height = 0; + S32 width = 0; + S32 total_height = arrange( &width, &height ); + notifyParent(LLSD().with("action", "size_changes").with("height", total_height)); + + LLUI::popMatrix(); + LLUI::pushMatrix(); + LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom); + } + } + + if (mRenameItem + && mRenamer + && mRenamer->getVisible() + && !getVisibleRect().overlaps(mRenamer->getRect())) + { + // renamer is not connected to the item we are renaming in any form so manage it manually + // TODO: consider stopping on any scroll action instead of when out of visible area + LL_DEBUGS("Inventory") << "Renamer out of bounds, hiding" << LL_ENDL; + finishRenamingItem(); + } + + // skip over LLFolderViewFolder::draw since we don't want the folder icon, label, + // and arrow for the root folder + LLView::draw(); + + mDragAndDropThisFrame = false; +} + +void LLFolderView::finishRenamingItem( void ) +{ + if(!mRenamer) + { + return; + } + if( mRenameItem ) + { + mRenameItem->rename( mRenamer->getText() ); + } + + closeRenamer(); + + // This is moved to an inventory observer in llinventorybridge.cpp, to handle updating after operation completed in AISv3 (SH-4611). + // List is re-sorted alphabetically, so scroll to make sure the selected item is visible. + //scrollToShowSelection(); +} + +void LLFolderView::closeRenamer( void ) +{ + if (mRenamer && mRenamer->getVisible()) + { + // Triggers onRenamerLost() that actually closes the renamer. + LLUI::getInstance()->removePopup(mRenamer); + } +} + +void LLFolderView::removeSelectedItems() +{ + if(getVisible() && getEnabled()) + { + // just in case we're removing the renaming item. + mRenameItem = NULL; + + // create a temporary structure which we will use to remove + // items, since the removal will futz with internal data + // structures. + std::vector items; + S32 count = mSelectedItems.size(); + if(count <= 0) return; + LLFolderViewItem* item = NULL; + selected_items_t::iterator item_it; + for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) + { + item = *item_it; + if (item && item->isRemovable()) + { + items.push_back(item); + } + else + { + LL_DEBUGS() << "Cannot delete " << item->getName() << LL_ENDL; + return; + } + } + + // iterate through the new container. + count = items.size(); + LLUUID new_selection_id; + LLFolderViewItem* item_to_select = getNextUnselectedItem(); + + if(count == 1) + { + LLFolderViewItem* item_to_delete = items[0]; + LLFolderViewFolder* parent = item_to_delete->getParentFolder(); + if(parent) + { + if (item_to_delete->remove()) + { + // change selection on successful delete + setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus()); + } + } + arrangeAll(); + } + else if (count > 1) + { + std::vector listeners; + LLFolderViewModelItem* listener; + + setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus()); + + listeners.reserve(count); + for(S32 i = 0; i < count; ++i) + { + listener = items[i]->getViewModelItem(); + if(listener && (std::find(listeners.begin(), listeners.end(), listener) == listeners.end())) + { + listeners.push_back(listener); + } + } + listener = static_cast(listeners.at(0)); + if(listener) + { + listener->removeBatch(listeners); + } + } + arrangeAll(); + scrollToShowSelection(); + } +} + +void LLFolderView::autoOpenItem( LLFolderViewFolder* item ) +{ + if ((mAutoOpenItems.check() == item) || + (mAutoOpenItems.getDepth() >= (U32)AUTO_OPEN_STACK_DEPTH) || + item->isOpen()) + { + return; + } + + // close auto-opened folders + LLFolderViewFolder* close_item = mAutoOpenItems.check(); + while (close_item && close_item != item->getParentFolder()) + { + mAutoOpenItems.pop(); + close_item->setOpenArrangeRecursively(false); + close_item = mAutoOpenItems.check(); + } + + item->requestArrange(); + + mAutoOpenItems.push(item); + + item->setOpen(true); + if(!item->isSingleFolderMode()) + { + LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); + LLRect constraint_rect(0,content_rect.getHeight(), content_rect.getWidth(), 0); + scrollToShowItem(item, constraint_rect); + } +} + +void LLFolderView::closeAutoOpenedFolders() +{ + while (mAutoOpenItems.check()) + { + LLFolderViewFolder* close_item = mAutoOpenItems.pop(); + close_item->setOpen(false); + } + + if (mAutoOpenCandidate) + { + mAutoOpenCandidate->setAutoOpenCountdown(0.f); + } + mAutoOpenCandidate = NULL; + mAutoOpenTimer.stop(); +} + +bool LLFolderView::autoOpenTest(LLFolderViewFolder* folder) +{ + if (folder && mAutoOpenCandidate == folder) + { + if (mAutoOpenTimer.getStarted()) + { + if (!mAutoOpenCandidate->isOpen()) + { + mAutoOpenCandidate->setAutoOpenCountdown(clamp_rescale(mAutoOpenTimer.getElapsedTimeF32(), 0.f, sAutoOpenTime, 0.f, 1.f)); + } + if (mAutoOpenTimer.getElapsedTimeF32() > sAutoOpenTime) + { + autoOpenItem(folder); + mAutoOpenTimer.stop(); + return true; + } + } + return false; + } + + // otherwise new candidate, restart timer + if (mAutoOpenCandidate) + { + mAutoOpenCandidate->setAutoOpenCountdown(0.f); + } + mAutoOpenCandidate = folder; + mAutoOpenTimer.start(); + return false; +} + +bool LLFolderView::canCopy() const +{ + if (!(getVisible() && getEnabled() && (mSelectedItems.size() > 0))) + { + return false; + } + + for (selected_items_t::const_iterator selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it) + { + const LLFolderViewItem* item = *selected_it; + if (!item->getViewModelItem()->isItemCopyable()) + { + return false; + } + } + return true; +} + +// copy selected item +void LLFolderView::copy() +{ + // *NOTE: total hack to clear the inventory clipboard + LLClipboard::instance().reset(); + S32 count = mSelectedItems.size(); + if(getVisible() && getEnabled() && (count > 0)) + { + LLFolderViewModelItem* listener = NULL; + selected_items_t::iterator item_it; + for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) + { + listener = (*item_it)->getViewModelItem(); + if(listener) + { + listener->copyToClipboard(); + } + } + } + mSearchString.clear(); +} + +bool LLFolderView::canCut() const +{ + if (!(getVisible() && getEnabled() && (mSelectedItems.size() > 0))) + { + return false; + } + + for (selected_items_t::const_iterator selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it) + { + const LLFolderViewItem* item = *selected_it; + const LLFolderViewModelItem* listener = item->getViewModelItem(); + + if (!listener || !listener->isItemRemovable()) + { + return false; + } + } + return true; +} + +void LLFolderView::cut() +{ + // clear the inventory clipboard + LLClipboard::instance().reset(); + if(getVisible() && getEnabled() && (mSelectedItems.size() > 0)) + { + // Find out which item will be selected once the selection will be cut + LLFolderViewItem* item_to_select = getNextUnselectedItem(); + + // Get the selection: removeItem() modified mSelectedItems and makes iterating on it unwise + std::set inventory_selected = getSelectionList(); + + // Move each item to the clipboard and out of their folder + for (std::set::iterator item_it = inventory_selected.begin(); item_it != inventory_selected.end(); ++item_it) + { + LLFolderViewItem* item_to_cut = *item_it; + LLFolderViewModelItem* listener = item_to_cut->getViewModelItem(); + if (listener) + { + listener->cutToClipboard(); + } + } + + // Update the selection + setSelection(item_to_select, item_to_select ? item_to_select->isOpen() : false, mParentPanel.get()->hasFocus()); + } + mSearchString.clear(); +} + +bool LLFolderView::canPaste() const +{ + if (mSelectedItems.empty()) + { + return false; + } + + if(getVisible() && getEnabled()) + { + for (selected_items_t::const_iterator item_it = mSelectedItems.begin(); + item_it != mSelectedItems.end(); ++item_it) + { + // *TODO: only check folders and parent folders of items + const LLFolderViewItem* item = (*item_it); + const LLFolderViewModelItem* listener = item->getViewModelItem(); + if(!listener || !listener->isClipboardPasteable()) + { + const LLFolderViewFolder* folderp = item->getParentFolder(); + listener = folderp->getViewModelItem(); + if (!listener || !listener->isClipboardPasteable()) + { + return false; + } + } + } + return true; + } + return false; +} + +// paste selected item +void LLFolderView::paste() +{ + if(getVisible() && getEnabled()) + { + // find set of unique folders to paste into + std::set folder_set; + + selected_items_t::iterator selected_it; + for (selected_it = mSelectedItems.begin(); selected_it != mSelectedItems.end(); ++selected_it) + { + LLFolderViewItem* item = *selected_it; + LLFolderViewFolder* folder = dynamic_cast(item); + if (folder == NULL) + { + folder = item->getParentFolder(); + } + folder_set.insert(folder); + } + + std::set::iterator set_iter; + for(set_iter = folder_set.begin(); set_iter != folder_set.end(); ++set_iter) + { + LLFolderViewModelItem* listener = (*set_iter)->getViewModelItem(); + if(listener && listener->isClipboardPasteable()) + { + listener->pasteFromClipboard(); + } + } + } + mSearchString.clear(); +} + +// public rename functionality - can only start the process +void LLFolderView::startRenamingSelectedItem( void ) +{ + LL_DEBUGS("Inventory") << "Starting inventory renamer" << LL_ENDL; + + // make sure selection is visible + scrollToShowSelection(); + + S32 count = mSelectedItems.size(); + LLFolderViewItem* item = NULL; + if(count > 0) + { + item = mSelectedItems.front(); + } + if(getVisible() && getEnabled() && (count == 1) && item && item->getViewModelItem() && + item->getViewModelItem()->isItemRenameable()) + { + mRenameItem = item; + + updateRenamerPosition(); + + + mRenamer->setText(item->getName()); + mRenamer->selectAll(); + mRenamer->setVisible( true ); + // set focus will fail unless item is visible + mRenamer->setFocus( true ); + if (!mRenamerTopLostSignalConnection.connected()) + { + mRenamerTopLostSignalConnection = mRenamer->setTopLostCallback(boost::bind(&LLFolderView::onRenamerLost, this)); + } + LLUI::getInstance()->addPopup(mRenamer); + } +} + +bool LLFolderView::handleKeyHere( KEY key, MASK mask ) +{ + bool handled = false; + + // SL-51858: Key presses are not being passed to the Popup menu. + // A proper fix is non-trivial so instead just close the menu. + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu && menu->isOpen()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + + switch( key ) + { + case KEY_F2: + mSearchString.clear(); + startRenamingSelectedItem(); + handled = true; + break; + + case KEY_RETURN: + if (mask == MASK_NONE) + { + if( mRenameItem && mRenamer->getVisible() ) + { + finishRenamingItem(); + mSearchString.clear(); + handled = true; + } + } + break; + + case KEY_ESCAPE: + if( mRenameItem && mRenamer->getVisible() ) + { + closeRenamer(); + handled = true; + } + mSearchString.clear(); + break; + + case KEY_PAGE_UP: + mSearchString.clear(); + if (mScrollContainer) + { + mScrollContainer->pageUp(30); + } + handled = true; + break; + + case KEY_PAGE_DOWN: + mSearchString.clear(); + if (mScrollContainer) + { + mScrollContainer->pageDown(30); + } + handled = true; + break; + + case KEY_HOME: + mSearchString.clear(); + if (mScrollContainer) + { + mScrollContainer->goToTop(); + } + handled = true; + break; + + case KEY_END: + mSearchString.clear(); + if (mScrollContainer) + { + mScrollContainer->goToBottom(); + } + break; + + case KEY_DOWN: + if((mSelectedItems.size() > 0) && mScrollContainer) + { + LLFolderViewItem* last_selected = getCurSelectedItem(); + bool shift_select = mask & MASK_SHIFT; + // don't shift select down to children of folders (they are implicitly selected through parent) + LLFolderViewItem* next = last_selected->getNextOpenNode(!shift_select); + + if (!mKeyboardSelection || (!shift_select && (!next || next == last_selected))) + { + setSelection(last_selected, false, true); + mKeyboardSelection = true; + } + + if (shift_select) + { + if (next) + { + if (next->isSelected()) + { + // shrink selection + changeSelection(last_selected, false); + } + else if (last_selected->getParentFolder() == next->getParentFolder()) + { + // grow selection + changeSelection(next, true); + } + } + } + else + { + if( next ) + { + if (next == last_selected) + { + //special case for LLAccordionCtrl + if(notifyParent(LLSD().with("action","select_next")) > 0 )//message was processed + { + clearSelection(); + return true; + } + return false; + } + setSelection( next, false, true ); + } + else + { + //special case for LLAccordionCtrl + if(notifyParent(LLSD().with("action","select_next")) > 0 )//message was processed + { + clearSelection(); + return true; + } + return false; + } + } + scrollToShowSelection(); + mSearchString.clear(); + handled = true; + } + break; + + case KEY_UP: + if((mSelectedItems.size() > 0) && mScrollContainer) + { + LLFolderViewItem* last_selected = mSelectedItems.back(); + bool shift_select = mask & MASK_SHIFT; + // don't shift select down to children of folders (they are implicitly selected through parent) + LLFolderViewItem* prev = last_selected->getPreviousOpenNode(!shift_select); + + if (!mKeyboardSelection || (!shift_select && prev == this)) + { + setSelection(last_selected, false, true); + mKeyboardSelection = true; + } + + if (shift_select) + { + if (prev) + { + if (prev->isSelected()) + { + // shrink selection + changeSelection(last_selected, false); + } + else if (last_selected->getParentFolder() == prev->getParentFolder()) + { + // grow selection + changeSelection(prev, true); + } + } + } + else + { + if( prev ) + { + if (prev == this) + { + // If case we are in accordion tab notify parent to go to the previous accordion + if(notifyParent(LLSD().with("action","select_prev")) > 0 )//message was processed + { + clearSelection(); + return true; + } + + return false; + } + setSelection( prev, false, true ); + } + } + scrollToShowSelection(); + mSearchString.clear(); + + handled = true; + } + break; + + case KEY_RIGHT: + if(mSelectedItems.size()) + { + LLFolderViewItem* last_selected = getCurSelectedItem(); + last_selected->setOpen( true ); + mSearchString.clear(); + handled = true; + } + break; + + case KEY_LEFT: + if(mSelectedItems.size()) + { + LLFolderViewItem* last_selected = getCurSelectedItem(); + if(last_selected && last_selected->isSingleFolderMode()) + { + handled = false; + break; + } + LLFolderViewItem* parent_folder = last_selected->getParentFolder(); + if (!last_selected->isOpen() && parent_folder && parent_folder->getParentFolder()) + { + setSelection(parent_folder, false, true); + } + else + { + last_selected->setOpen( false ); + } + mSearchString.clear(); + scrollToShowSelection(); + handled = true; + } + break; + } + + return handled; +} + + +bool LLFolderView::handleUnicodeCharHere(llwchar uni_char) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return false; + } + + if (uni_char > 0x7f) + { + LL_WARNS() << "LLFolderView::handleUnicodeCharHere - Don't handle non-ascii yet, aborting" << LL_ENDL; + return false; + } + + bool handled = false; + if (mParentPanel.get()->hasFocus()) + { + // SL-51858: Key presses are not being passed to the Popup menu. + // A proper fix is non-trivial so instead just close the menu. + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu && menu->isOpen()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + + //do text search + if (mSearchTimer.getElapsedTimeF32() > LLUI::getInstance()->mSettingGroups["config"]->getF32("TypeAheadTimeout")) + { + mSearchString.clear(); + } + mSearchTimer.reset(); + if (mSearchString.size() < 128) + { + mSearchString += uni_char; + } + search(getCurSelectedItem(), mSearchString, false); + + handled = true; + } + + return handled; +} + + +bool LLFolderView::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + mKeyboardSelection = false; + mSearchString.clear(); + + mParentPanel.get()->setFocus(true); + + LLEditMenuHandler::gEditMenuHandler = this; + + return LLView::handleMouseDown( x, y, mask ); +} + +bool LLFolderView::search(LLFolderViewItem* first_item, const std::string &search_string, bool backward) +{ + // get first selected item + LLFolderViewItem* search_item = first_item; + + // make sure search string is upper case + std::string upper_case_string = search_string; + LLStringUtil::toUpper(upper_case_string); + + // if nothing selected, select first item in folder + if (!search_item) + { + // start from first item + search_item = getNextFromChild(NULL); + } + + // search over all open nodes for first substring match (with wrapping) + bool found = false; + LLFolderViewItem* original_search_item = search_item; + do + { + // wrap at end + if (!search_item) + { + if (backward) + { + search_item = getPreviousFromChild(NULL); + } + else + { + search_item = getNextFromChild(NULL); + } + if (!search_item || search_item == original_search_item) + { + break; + } + } + + std::string current_item_label(search_item->getViewModelItem()->getSearchableName()); + LLStringUtil::toUpper(current_item_label); + S32 search_string_length = llmin(upper_case_string.size(), current_item_label.size()); + if (!current_item_label.compare(0, search_string_length, upper_case_string)) + { + found = true; + break; + } + if (backward) + { + search_item = search_item->getPreviousOpenNode(); + } + else + { + search_item = search_item->getNextOpenNode(); + } + + } while(search_item != original_search_item); + + + if (found) + { + setSelection(search_item, false, true); + scrollToShowSelection(); + } + + return found; +} + +bool LLFolderView::handleDoubleClick( S32 x, S32 y, MASK mask ) +{ + // skip LLFolderViewFolder::handleDoubleClick() + return LLView::handleDoubleClick( x, y, mask ); +} + +bool LLFolderView::handleRightMouseDown( S32 x, S32 y, MASK mask ) +{ + // all user operations move keyboard focus to inventory + // this way, we know when to stop auto-updating a search + mParentPanel.get()->setFocus(true); + + bool handled = childrenHandleRightMouseDown(x, y, mask) != NULL; + S32 count = mSelectedItems.size(); + + LLMenuGL* menu = static_cast(mPopupMenuHandle.get()); + if (!menu) + { + if (mCallbackRegistrar) + { + mCallbackRegistrar->pushScope(); + } + if (mEnableRegistrar) + { + mEnableRegistrar->pushScope(); + } + llassert(LLMenuGL::sMenuContainer != NULL); + menu = LLUICtrlFactory::getInstance()->createFromFile(mMenuFileName, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); + if (!menu) + { + menu = LLUICtrlFactory::getDefaultWidget("inventory_menu"); + } + menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor")); + mPopupMenuHandle = menu->getHandle(); + if (mEnableRegistrar) + { + mEnableRegistrar->popScope(); + } + if (mCallbackRegistrar) + { + mCallbackRegistrar->popScope(); + } + } + + bool item_clicked{ false }; + for (const auto item : mSelectedItems) + { + item_clicked |= item->getRect().pointInRect(x, y); + } + if(!item_clicked && mSingleFolderMode) + { + clearSelection(); + } + bool hide_folder_menu = mSuppressFolderMenu && isFolderSelected(); + if (menu && (mSingleFolderMode || (handled + && ( count > 0 && (hasVisibleChildren()) ))) && // show menu only if selected items are visible + !hide_folder_menu) + { + if (mCallbackRegistrar) + { + mCallbackRegistrar->pushScope(); + } + if (mEnableRegistrar) + { + mEnableRegistrar->pushScope(); + } + + updateMenuOptions(menu); + + menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, menu, x, y); + if (mEnableRegistrar) + { + mEnableRegistrar->popScope(); + } + if (mCallbackRegistrar) + { + mCallbackRegistrar->popScope(); + } + } + else + { + if (menu && menu->getVisible()) + { + menu->setVisible(false); + } + setSelection(NULL, false, true); + } + return handled; +} + +// Add "--no options--" if the menu is completely blank. +bool LLFolderView::addNoOptions(LLMenuGL* menu) const +{ + const std::string nooptions_str = "--no options--"; + LLView *nooptions_item = NULL; + + const LLView::child_list_t *list = menu->getChildList(); + for (LLView::child_list_t::const_iterator itor = list->begin(); + itor != list->end(); + ++itor) + { + LLView *menu_item = (*itor); + if (menu_item->getVisible()) + { + return false; + } + std::string name = menu_item->getName(); + if (menu_item->getName() == nooptions_str) + { + nooptions_item = menu_item; + } + } + if (nooptions_item) + { + nooptions_item->setVisible(true); + nooptions_item->setEnabled(false); + return true; + } + return false; +} + +bool LLFolderView::handleHover( S32 x, S32 y, MASK mask ) +{ + return LLView::handleHover( x, y, mask ); +} + +LLFolderViewItem* LLFolderView::getHoveredItem() const +{ + return dynamic_cast(mHoveredItem.get()); +} + +void LLFolderView::setHoveredItem(LLFolderViewItem* itemp) +{ + if (mHoveredItem.get() != itemp) + { + if (itemp) + mHoveredItem = itemp->getHandle(); + else + mHoveredItem.markDead(); + } +} + +bool LLFolderView::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + mDragAndDropThisFrame = true; + // have children handle it first + bool handled = LLView::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, + accept, tooltip_msg); + + // when drop is not handled by child, it should be handled + // by the folder which is the hierarchy root. + if (!handled) + { + handled = LLFolderViewFolder::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + + return handled; +} + +void LLFolderView::deleteAllChildren() +{ + mRenamerTopLostSignalConnection.disconnect(); + if (mRenamer) + { + LLUI::getInstance()->removePopup(mRenamer); + } + if (mPopupMenuHandle.get()) mPopupMenuHandle.get()->die(); + mPopupMenuHandle.markDead(); + mScrollContainer = NULL; + mRenameItem = NULL; + mRenamer = NULL; + mStatusTextBox = NULL; + + clearSelection(); + LLView::deleteAllChildren(); +} + +void LLFolderView::scrollToShowSelection() +{ + if ( mSelectedItems.size() ) + { + mNeedsScroll = true; + } +} + +// If the parent is scroll container, scroll it to make the selection +// is maximally visible. +void LLFolderView::scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect) +{ + if (!mScrollContainer) return; + + // don't scroll to items when mouse is being used to scroll/drag and drop + if (gFocusMgr.childHasMouseCapture(mScrollContainer)) + { + mNeedsScroll = false; + return; + } + + // if item exists and is in visible portion of parent folder... + if(item) + { + LLRect local_rect = item->getLocalRect(); + S32 icon_height = mIcon.isNull() ? 0 : mIcon->getHeight(); + S32 label_height = getLabelFontForStyle(mLabelStyle)->getLineHeight(); + // when navigating with keyboard, only move top of opened folder on screen, otherwise show whole folder + S32 max_height_to_show = item->isOpen() && mScrollContainer->hasFocus() ? (llmax( icon_height, label_height ) + item->getIconPad()) : local_rect.getHeight(); + + // get portion of item that we want to see... + LLRect item_local_rect = LLRect(item->getIndentation(), + local_rect.getHeight(), + //+40 is supposed to include few first characters + llmin(item->getLabelXPos() - item->getIndentation() + 40, local_rect.getWidth()), + llmax(0, local_rect.getHeight() - max_height_to_show)); + + LLRect item_doc_rect; + + item->localRectToOtherView(item_local_rect, &item_doc_rect, this); + + mScrollContainer->scrollToShowRect( item_doc_rect, constraint_rect ); + + } +} + +LLRect LLFolderView::getVisibleRect() +{ + S32 visible_height = (mScrollContainer ? mScrollContainer->getRect().getHeight() : 0); + S32 visible_width = (mScrollContainer ? mScrollContainer->getRect().getWidth() : 0); + LLRect visible_rect; + visible_rect.setLeftTopAndSize(-getRect().mLeft, visible_height - getRect().mBottom, visible_width, visible_height); + return visible_rect; +} + +bool LLFolderView::getShowSelectionContext() +{ + if (mShowSelectionContext) + { + return true; + } + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu && menu->getVisible()) + { + return true; + } + return false; +} + +void LLFolderView::setShowSingleSelection(bool show) +{ + if (show != mShowSingleSelection) + { + mMultiSelectionFadeTimer.reset(); + mShowSingleSelection = show; + } +} + +static LLTrace::BlockTimerStatHandle FTM_INVENTORY("Inventory"); + +// Main idle routine +void LLFolderView::update() +{ + // If this is associated with the user's inventory, don't do anything + // until that inventory is loaded up. + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(FTM_INVENTORY); + + // If there's no model, the view is in suspended state (being deleted) and shouldn't be updated + if (getFolderViewModel() == NULL) + { + return; + } + + LLFolderViewFilter& filter_object = getFolderViewModel()->getFilter(); + + if (filter_object.isModified() && filter_object.isNotDefault() && mParentPanel.get()->getVisible()) + { + mNeedsAutoSelect = true; + } + + // Filter to determine visibility before arranging + filter(filter_object); + + // Clear the modified setting on the filter only if the filter finished after running the filter process + // Note: if the filter count has timed out, that means the filter halted before completing the entire set of items + bool filter_modified = filter_object.isModified(); + if (filter_modified && (!filter_object.isTimedOut())) + { + filter_object.clearModified(); + } + + // automatically show matching items, and select first one if we had a selection + if (mNeedsAutoSelect) + { + // select new item only if a filtered item not currently selected and there was a selection + LLFolderViewItem* selected_itemp = mSelectedItems.empty() ? NULL : mSelectedItems.back(); + if (!mAutoSelectOverride && selected_itemp && !selected_itemp->getViewModelItem()->potentiallyVisible()) + { + // these are named variables to get around gcc not binding non-const references to rvalues + // and functor application is inherently non-const to allow for stateful functors + LLSelectFirstFilteredItem functor; + applyFunctorRecursively(functor); + } + + // Open filtered folders for folder views with mAutoSelectOverride=true. + // Used by LLPlacesFolderView. + if (filter_object.showAllResults()) + { + // these are named variables to get around gcc not binding non-const references to rvalues + // and functor application is inherently non-const to allow for stateful functors + LLOpenFilteredFolders functor; + applyFunctorRecursively(functor); + } + + scrollToShowSelection(); + } + + bool filter_finished = mViewModel->contentsReady() + && (getViewModelItem()->passedFilter() + || ( getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration() + && !filter_modified)); + if (filter_finished + || gFocusMgr.childHasKeyboardFocus(mParentPanel.get()) + || gFocusMgr.childHasMouseCapture(mParentPanel.get())) + { + // finishing the filter process, giving focus to the folder view, or dragging the scrollbar all stop the auto select process + mNeedsAutoSelect = false; + } + + bool is_visible = isInVisibleChain() || mForceArrange; + + //Puts folders/items in proper positions + // arrange() takes the model filter flag into account and call sort() if necessary (CHUI-849) + // It also handles the open/close folder animation + if ( is_visible ) + { + sanitizeSelection(); + if( needsArrange() ) + { + S32 height = 0; + S32 width = 0; + S32 total_height = arrange( &width, &height ); + notifyParent(LLSD().with("action", "size_changes").with("height", total_height)); + } + } + + // during filtering process, try to pin selected item's location on screen + // this will happen when searching your inventory and when new items arrive + if (!filter_finished) + { + // calculate rectangle to pin item to at start of animated rearrange + if (!mPinningSelectedItem && !mSelectedItems.empty()) + { + // lets pin it! + mPinningSelectedItem = true; + + //Computes visible area + const LLRect visible_content_rect = (mScrollContainer ? mScrollContainer->getVisibleContentRect() : LLRect()); + LLFolderViewItem* selected_item = mSelectedItems.back(); + + //Computes location of selected content, content outside visible area will be scrolled to using below code + LLRect item_rect; + selected_item->localRectToOtherView(selected_item->getLocalRect(), &item_rect, this); + + //Computes intersected region of the selected content and visible area + LLRect overlap_rect(item_rect); + overlap_rect.intersectWith(visible_content_rect); + + //Don't scroll when the selected content exists within the visible area + if (overlap_rect.getHeight() >= selected_item->getItemHeight()) + { + // then attempt to keep it in same place on screen + mScrollConstraintRect = item_rect; + mScrollConstraintRect.translate(-visible_content_rect.mLeft, -visible_content_rect.mBottom); + } + //Scroll because the selected content is outside the visible area + else + { + // otherwise we just want it onscreen somewhere + LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); + mScrollConstraintRect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); + } + } + } + else + { + // stop pinning selected item after folders stop rearranging + if (!needsArrange()) + { + mPinningSelectedItem = false; + } + } + + LLRect constraint_rect; + if (mPinningSelectedItem) + { + // use last known constraint rect for pinned item + constraint_rect = mScrollConstraintRect; + } + else + { + // during normal use (page up/page down, etc), just try to fit item on screen + LLRect content_rect = (mScrollContainer ? mScrollContainer->getContentWindowRect() : LLRect()); + constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); + } + + if (mSelectedItems.size() && mNeedsScroll) + { + LLFolderViewItem* scroll_to_item = mSelectedItems.back(); + scrollToShowItem(scroll_to_item, constraint_rect); + // continue scrolling until animated layout change is done + bool selected_filter_finished = getRoot()->getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration(); + if (selected_filter_finished && scroll_to_item && scroll_to_item->getViewModelItem()) + { + selected_filter_finished = scroll_to_item->getViewModelItem()->getLastFilterGeneration() >= filter_object.getFirstSuccessGeneration(); + } + if (filter_finished && selected_filter_finished) + { + bool needs_arrange = needsArrange() || getRoot()->needsArrange(); + if (mParentFolder) + { + needs_arrange |= (bool)mParentFolder->needsArrange(); + } + if (!needs_arrange || !is_visible) + { + mNeedsScroll = false; + } + } + } + + if (mSelectedItems.size()) + { + LLFolderViewItem* item = mSelectedItems.back(); + // If the goal is to show renamer, don't callback untill + // item is visible or is no longer being scrolled to. + // Otherwise renamer will be instantly closed + // Todo: consider moving renamer out of selection callback + if (!mNeedsAutoRename || !mNeedsScroll || item->getVisible()) + { + if (mSignalSelectCallback) + { + //RN: we use keyboard focus as a proxy for user-explicit actions + bool take_keyboard_focus = (mSignalSelectCallback == SIGNAL_KEYBOARD_FOCUS); + mSelectSignal(mSelectedItems, take_keyboard_focus); + } + mSignalSelectCallback = false; + } + } + else + { + mSignalSelectCallback = false; + } +} + +void LLFolderView::dumpSelectionInformation() +{ + LL_INFOS() << "LLFolderView::dumpSelectionInformation()" << LL_NEWLINE + << "****************************************" << LL_ENDL; + selected_items_t::iterator item_it; + for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) + { + LL_INFOS() << " " << (*item_it)->getName() << LL_ENDL; + } + LL_INFOS() << "****************************************" << LL_ENDL; +} + +void LLFolderView::updateRenamerPosition() +{ + if(mRenameItem) + { + // See also LLFolderViewItem::draw() + S32 x = mRenameItem->getLabelXPos(); + S32 y = mRenameItem->getRect().getHeight() - mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD; + mRenameItem->localPointToScreen( x, y, &x, &y ); + screenPointToLocal( x, y, &x, &y ); + mRenamer->setOrigin( x, y ); + + LLRect scroller_rect(0, 0, (S32)LLUI::getInstance()->getWindowSize().mV[VX], 0); + if (mScrollContainer) + { + scroller_rect = mScrollContainer->getContentWindowRect(); + } + + S32 width = llmax(llmin(mRenameItem->getRect().getWidth() - x, scroller_rect.getWidth() - x - getRect().mLeft), MINIMUM_RENAMER_WIDTH); + S32 height = mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD; + mRenamer->reshape( width, height, true ); + } +} + +// Update visibility and availability (i.e. enabled/disabled) of context menu items. +void LLFolderView::updateMenuOptions(LLMenuGL* menu) +{ + const LLView::child_list_t *list = menu->getChildList(); + + LLView::child_list_t::const_iterator menu_itor; + for (menu_itor = list->begin(); menu_itor != list->end(); ++menu_itor) + { + (*menu_itor)->setVisible(false); + (*menu_itor)->pushVisible(true); + (*menu_itor)->setEnabled(true); + } + + // Successively filter out invalid options + U32 multi_select_flag = (mSelectedItems.size() > 1 ? ITEM_IN_MULTI_SELECTION : 0x0); + U32 flags = multi_select_flag | FIRST_SELECTED_ITEM; + for (selected_items_t::iterator item_itor = mSelectedItems.begin(); + item_itor != mSelectedItems.end(); + ++item_itor) + { + LLFolderViewItem* selected_item = (*item_itor); + selected_item->buildContextMenu(*menu, flags); + flags = multi_select_flag; + } + + if(mSingleFolderMode && (mSelectedItems.size() == 0)) + { + buildContextMenu(*menu, flags); + } + + // This adds a check for restrictions based on the entire + // selection set - for example, any one wearable may not push you + // over the limit, but all wearables together still might. + if (getFolderViewGroupedItemModel()) + { + getFolderViewGroupedItemModel()->groupFilterContextMenu(mSelectedItems,*menu); + } + + addNoOptions(menu); +} + +// Refresh the context menu (that is already shown). +void LLFolderView::updateMenu() +{ + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu && menu->getVisible()) + { + updateMenuOptions(menu); + menu->needsArrange(); // update menu height if needed + } +} + +bool LLFolderView::isFolderSelected() +{ + selected_items_t::iterator item_iter; + for (item_iter = mSelectedItems.begin(); item_iter != mSelectedItems.end(); ++item_iter) + { + LLFolderViewFolder* folder = dynamic_cast(*item_iter); + if (folder != NULL) + { + return true; + } + } + return false; +} + +bool LLFolderView::selectFirstItem() +{ + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();++iter) + { + LLFolderViewFolder* folder = (*iter ); + if (folder->getVisible()) + { + LLFolderViewItem* itemp = folder->getNextFromChild(0,true); + if(itemp) + setSelection(itemp,false,true); + return true; + } + + } + for(items_t::iterator iit = mItems.begin(); + iit != mItems.end(); ++iit) + { + LLFolderViewItem* itemp = (*iit); + if (itemp->getVisible()) + { + setSelection(itemp,false,true); + return true; + } + } + return false; +} +bool LLFolderView::selectLastItem() +{ + for(items_t::reverse_iterator iit = mItems.rbegin(); + iit != mItems.rend(); ++iit) + { + LLFolderViewItem* itemp = (*iit); + if (itemp->getVisible()) + { + setSelection(itemp,false,true); + return true; + } + } + for (folders_t::reverse_iterator iter = mFolders.rbegin(); + iter != mFolders.rend();++iter) + { + LLFolderViewFolder* folder = (*iter); + if (folder->getVisible()) + { + LLFolderViewItem* itemp = folder->getPreviousFromChild(0,true); + if(itemp) + setSelection(itemp,false,true); + return true; + } + } + return false; +} + + +S32 LLFolderView::notify(const LLSD& info) +{ + if(info.has("action")) + { + std::string str_action = info["action"]; + if(str_action == "select_first") + { + setFocus(true); + selectFirstItem(); + scrollToShowSelection(); + return 1; + + } + else if(str_action == "select_last") + { + setFocus(true); + selectLastItem(); + scrollToShowSelection(); + return 1; + } + } + return 0; +} + + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- + +void LLFolderView::onRenamerLost() +{ + if (mRenamer && mRenamer->getVisible()) + { + mRenamer->setVisible(false); + + // will commit current name (which could be same as original name) + mRenamer->setFocus(false); + } + + if( mRenameItem ) + { + setSelection( mRenameItem, true ); + mRenameItem = NULL; + } +} + +LLFolderViewItem* LLFolderView::getNextUnselectedItem() +{ + LLFolderViewItem* last_item = *mSelectedItems.rbegin(); + LLFolderViewItem* new_selection = last_item->getNextOpenNode(false); + while(new_selection && new_selection->isSelected()) + { + new_selection = new_selection->getNextOpenNode(false); + } + if (!new_selection) + { + new_selection = last_item->getPreviousOpenNode(false); + while (new_selection && (new_selection->isInSelection())) + { + new_selection = new_selection->getPreviousOpenNode(false); + } + } + return new_selection; +} + +S32 LLFolderView::getItemHeight() const +{ + if(!hasVisibleChildren()) +{ + //We need to display status textbox, let's reserve some place for it + return llmax(0, mStatusTextBox->getTextPixelHeight()); +} + return 0; +} diff --git a/indra/llui/llfolderview.h b/indra/llui/llfolderview.h index 7c3167c156..62ef2a0626 100644 --- a/indra/llui/llfolderview.h +++ b/indra/llui/llfolderview.h @@ -1,448 +1,448 @@ -/** - * @file llfolderview.h - * @brief Definition of the folder view collection of classes. - * - * $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$ - */ - -/** - * - * The folder view collection of classes provides an interface for - * making a 'folder view' similar to the way the a single pane file - * folder interface works. - * - */ - -#ifndef LL_LLFOLDERVIEW_H -#define LL_LLFOLDERVIEW_H - -#include "llfolderviewitem.h" // because LLFolderView is-a LLFolderViewFolder - -#include "lluictrl.h" -#include "v4color.h" -#include "lldepthstack.h" -#include "lleditmenuhandler.h" -#include "llfontgl.h" -#include "llscrollcontainer.h" - -class LLFolderViewModelInterface; -class LLFolderViewGroupedItemModel; -class LLFolderViewFolder; -class LLFolderViewItem; -class LLFolderViewFilter; -class LLPanel; -class LLLineEditor; -class LLMenuGL; -class LLUICtrl; -class LLTextBox; - -/** - * Class LLFolderViewScrollContainer - * - * A scroll container which provides the information about the height - * of currently displayed folder view contents. - * Used for updating vertical scroll bar visibility in inventory panel. - * See LLScrollContainer::calcVisibleSize(). - */ -class LLFolderViewScrollContainer : public LLScrollContainer -{ -public: - /*virtual*/ ~LLFolderViewScrollContainer() {}; - /*virtual*/ const LLRect getScrolledViewRect() const; - -protected: - LLFolderViewScrollContainer(const LLScrollContainer::Params& p); - friend class LLUICtrlFactory; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFolderView -// -// The LLFolderView represents the root level folder view object. -// It manages the screen region of the folder view. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLFolderView : public LLFolderViewFolder, public LLEditMenuHandler -{ -public: - struct Params : public LLInitParam::Block - { - Mandatory parent_panel; - Optional title; - Optional use_label_suffix, - allow_multiselect, - allow_drag, - show_empty_message, - use_ellipses, - show_item_link_overlays, - suppress_folder_menu; - Mandatory view_model; - Optional grouped_item_model; - Mandatory options_menu; - - - Params(); - }; - - friend class LLFolderViewScrollContainer; - typedef folder_view_item_deque selected_items_t; - - LLFolderView(const Params&); - virtual ~LLFolderView( void ); - - virtual bool canFocusChildren() const; - - virtual const LLFolderView* getRoot() const { return this; } - virtual LLFolderView* getRoot() { return this; } - - LLFolderViewModelInterface* getFolderViewModel() { return mViewModel; } - const LLFolderViewModelInterface* getFolderViewModel() const { return mViewModel; } - - LLFolderViewGroupedItemModel* getFolderViewGroupedItemModel() { return mGroupedItemModel; } - const LLFolderViewGroupedItemModel* getFolderViewGroupedItemModel() const { return mGroupedItemModel; } - - typedef boost::signals2::signal& items, bool user_action)> signal_t; - void setSelectCallback(const signal_t::slot_type& cb) { mSelectSignal.connect(cb); } - void setReshapeCallback(const signal_t::slot_type& cb) { mReshapeSignal.connect(cb); } - - bool getAllowMultiSelect() { return mAllowMultiSelect; } - bool getAllowDrag() { return mAllowDrag; } - - void setSingleFolderMode(bool is_single_mode) { mSingleFolderMode = is_single_mode; } - bool isSingleFolderMode() { return mSingleFolderMode; } - - // Close all folders in the view - void closeAllFolders(); - void openTopLevelFolders(); - - virtual void addFolder( LLFolderViewFolder* folder); - - // Find width and height of this object and its children. Also - // makes sure that this view and its children are the right size. - virtual S32 arrange( S32* width, S32* height ); - virtual S32 getItemHeight() const; - - void arrangeAll() { mArrangeGeneration++; } - S32 getArrangeGeneration() { return mArrangeGeneration; } - - // applies filters to control visibility of items - virtual void filter( LLFolderViewFilter& filter); - - void clearHoveredItem() { setHoveredItem(nullptr); } - LLFolderViewItem* getHoveredItem() const; - void setHoveredItem(LLFolderViewItem* itemp); - - // Get the last selected item - virtual LLFolderViewItem* getCurSelectedItem( void ); - selected_items_t& getSelectedItems( void ); - - // Record the selected item and pass it down the hierarchy. - virtual bool setSelection(LLFolderViewItem* selection, bool openitem, - bool take_keyboard_focus = true); - - // This method is used to toggle the selection of an item. Walks - // children, and keeps track of selected objects. - virtual bool changeSelection(LLFolderViewItem* selection, bool selected); - - virtual std::set getSelectionList() const; - - // Make sure if ancestor is selected, descendants are not - void sanitizeSelection(); - virtual void clearSelection(); - void addToSelectionList(LLFolderViewItem* item); - void removeFromSelectionList(LLFolderViewItem* item); - - bool startDrag(); - void setDragAndDropThisFrame() { mDragAndDropThisFrame = true; } - void setDraggingOverItem(LLFolderViewItem* item) { mDraggingOverItem = item; } - LLFolderViewItem* getDraggingOverItem() { return mDraggingOverItem; } - - // Deletion functionality - void removeSelectedItems(); - - void autoOpenItem(LLFolderViewFolder* item); - void closeAutoOpenedFolders(); - bool autoOpenTest(LLFolderViewFolder* item); - bool isOpen() const { return true; } // root folder always open - - // Copy & paste - virtual bool canCopy() const; - virtual void copy(); - - virtual bool canCut() const; - virtual void cut(); - - virtual bool canPaste() const; - virtual void paste(); - - LLFolderViewItem* getNextUnselectedItem(); - - // Public rename functionality - can only start the process - void startRenamingSelectedItem( void ); - - // LLView functionality - ///*virtual*/ bool handleKey( KEY key, MASK mask, bool called_from_parent ); - /*virtual*/ bool handleKeyHere( KEY key, MASK mask ); - /*virtual*/ bool handleUnicodeCharHere(llwchar uni_char); - /*virtual*/ bool handleMouseDown( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleDoubleClick( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleRightMouseDown( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleHover( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask) { setShowSelectionContext(false); } - virtual void draw(); - virtual void deleteAllChildren(); - - void stopAutoScollining() {mNeedsScroll = false;} - void scrollToShowSelection(); - void scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect); - void setScrollContainer( LLScrollContainer* parent ) { mScrollContainer = parent; } - LLRect getVisibleRect(); - - bool search(LLFolderViewItem* first_item, const std::string &search_string, bool backward); - void setShowSelectionContext(bool show) { mShowSelectionContext = show; } - bool getShowSelectionContext(); - void setShowSingleSelection(bool show); - bool getShowSingleSelection() { return mShowSingleSelection; } - F32 getSelectionFadeElapsedTime() { return mMultiSelectionFadeTimer.getElapsedTimeF32(); } - bool getUseEllipses() { return mUseEllipses; } - S32 getSelectedCount() { return (S32)mSelectedItems.size(); } - - void update(); // needs to be called periodically (e.g. once per frame) - - bool needsAutoSelect() { return mNeedsAutoSelect && !mAutoSelectOverride; } - bool needsAutoRename() { return mNeedsAutoRename; } - void setNeedsAutoRename(bool val) { mNeedsAutoRename = val; } - void setPinningSelectedItem(bool val) { mPinningSelectedItem = val; } - void setAutoSelectOverride(bool val) { mAutoSelectOverride = val; } - - bool showItemLinkOverlays() { return mShowItemLinkOverlays; } - - void setCallbackRegistrar(LLUICtrl::CommitCallbackRegistry::ScopedRegistrar* registrar) { mCallbackRegistrar = registrar; } - void setEnableRegistrar(LLUICtrl::EnableCallbackRegistry::ScopedRegistrar* registrar) { mEnableRegistrar = registrar; } - - void setForceArrange(bool force) { mForceArrange = force; } - - LLPanel* getParentPanel() { return mParentPanel.get(); } - // DEBUG only - void dumpSelectionInformation(); - - virtual S32 notify(const LLSD& info) ; - - void setShowEmptyMessage(bool show_msg) { mShowEmptyMessage = show_msg; } - - bool useLabelSuffix() { return mUseLabelSuffix; } - virtual void updateMenu(); - - void finishRenamingItem( void ); - - // Note: We may eventually have to move that method up the hierarchy to LLFolderViewItem. - LLHandle getHandle() const { return getDerivedHandle(); } - -private: - void updateMenuOptions(LLMenuGL* menu); - void updateRenamerPosition(); - -protected: - LLScrollContainer* mScrollContainer; // NULL if this is not a child of a scroll container. - - void commitRename( const LLSD& data ); - void onRenamerLost(); - - void closeRenamer( void ); - - bool isFolderSelected(); - - bool selectFirstItem(); - bool selectLastItem(); - - bool addNoOptions(LLMenuGL* menu) const; - - -protected: - LLHandle mPopupMenuHandle; - std::string mMenuFileName; - - LLHandle mHoveredItem; - selected_items_t mSelectedItems; - bool mKeyboardSelection, - mAllowMultiSelect, - mAllowDrag, - mShowEmptyMessage, - mShowFolderHierarchy, - mNeedsScroll, - mPinningSelectedItem, - mNeedsAutoSelect, - mAutoSelectOverride, - mNeedsAutoRename, - mUseLabelSuffix, - mDragAndDropThisFrame, - mShowItemLinkOverlays, - mShowSelectionContext, - mShowSingleSelection, - mSuppressFolderMenu, - mSingleFolderMode; - - // Renaming variables and methods - LLFolderViewItem* mRenameItem; // The item currently being renamed - LLLineEditor* mRenamer; - - LLRect mScrollConstraintRect; - - LLDepthStack mAutoOpenItems; - LLFolderViewFolder* mAutoOpenCandidate; - LLFrameTimer mAutoOpenTimer; - LLFrameTimer mSearchTimer; - std::string mSearchString; - LLFrameTimer mMultiSelectionFadeTimer; - S32 mArrangeGeneration; - - signal_t mSelectSignal; - signal_t mReshapeSignal; - S32 mSignalSelectCallback; - S32 mMinWidth; - - LLHandle mParentPanel; - - LLFolderViewModelInterface* mViewModel; - LLFolderViewGroupedItemModel* mGroupedItemModel; - - /** - * Is used to determine if we need to cut text In LLFolderViewItem to avoid horizontal scroll. - * NOTE: For now it's used only to cut LLFolderViewItem::mLabel text for Landmarks in Places Panel. - */ - bool mUseEllipses; // See EXT-719 - - /** - * Contains item under mouse pointer while dragging - */ - LLFolderViewItem* mDraggingOverItem; // See EXT-719 - - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar* mCallbackRegistrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar* mEnableRegistrar; - - boost::signals2::connection mRenamerTopLostSignalConnection; - - bool mForceArrange; - -public: - static F32 sAutoOpenTime; - LLTextBox* mStatusTextBox; - -}; - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFolderViewFunctor -// -// Simple abstract base class for applying a functor to folders and -// items in a folder view hierarchy. This is suboptimal for algorithms -// that only work folders or only work on items, but I'll worry about -// that later when it's determined to be too slow. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLFolderViewFunctor -{ -public: - virtual ~LLFolderViewFunctor() {} - virtual void doFolder(LLFolderViewFolder* folder) = 0; - virtual void doItem(LLFolderViewItem* item) = 0; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLSelectFirstFilteredItem -// -// This will select the first *item* found in the hierarchy. If no item can be -// selected, the first matching folder will. -// Since doFolder() is done first but we prioritize item selection, we let the -// first filtered folder set the selection and raise a folder flag. -// The selection might be overridden by the first filtered item in doItem() -// which checks an item flag. Since doFolder() checks the item flag too, the first -// item will still be selected if items were to be done first and folders second. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLSelectFirstFilteredItem : public LLFolderViewFunctor -{ -public: - LLSelectFirstFilteredItem() : mItemSelected(false), mFolderSelected(false) {} - virtual ~LLSelectFirstFilteredItem() {} - virtual void doFolder(LLFolderViewFolder* folder); - virtual void doItem(LLFolderViewItem* item); - bool wasItemSelected() { return mItemSelected || mFolderSelected; } -protected: - bool mItemSelected; - bool mFolderSelected; -}; - -class LLOpenFilteredFolders : public LLFolderViewFunctor -{ -public: - LLOpenFilteredFolders() {} - virtual ~LLOpenFilteredFolders() {} - virtual void doFolder(LLFolderViewFolder* folder); - virtual void doItem(LLFolderViewItem* item); -}; - -class LLSaveFolderState : public LLFolderViewFunctor -{ -public: - LLSaveFolderState() : mApply(false) {} - virtual ~LLSaveFolderState() {} - virtual void doFolder(LLFolderViewFolder* folder); - virtual void doItem(LLFolderViewItem* item) {} - void setApply(bool apply); - void clearOpenFolders() { mOpenFolders.clear(); } -protected: - std::set mOpenFolders; - bool mApply; -}; - -class LLOpenFoldersWithSelection : public LLFolderViewFunctor -{ -public: - LLOpenFoldersWithSelection() {} - virtual ~LLOpenFoldersWithSelection() {} - virtual void doFolder(LLFolderViewFolder* folder); - virtual void doItem(LLFolderViewItem* item); -}; - -class LLAllDescendentsPassedFilter : public LLFolderViewFunctor -{ -public: - LLAllDescendentsPassedFilter() : mAllDescendentsPassedFilter(true) {} - /*virtual*/ ~LLAllDescendentsPassedFilter() {} - /*virtual*/ void doFolder(LLFolderViewFolder* folder); - /*virtual*/ void doItem(LLFolderViewItem* item); - bool allDescendentsPassedFilter() const { return mAllDescendentsPassedFilter; } -protected: - bool mAllDescendentsPassedFilter; -}; - -// Flags for buildContextMenu() -const U32 SUPPRESS_OPEN_ITEM = 0x1; -const U32 FIRST_SELECTED_ITEM = 0x2; -const U32 ITEM_IN_MULTI_SELECTION = 0x4; - -#endif // LL_LLFOLDERVIEW_H +/** + * @file llfolderview.h + * @brief Definition of the folder view collection of classes. + * + * $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$ + */ + +/** + * + * The folder view collection of classes provides an interface for + * making a 'folder view' similar to the way the a single pane file + * folder interface works. + * + */ + +#ifndef LL_LLFOLDERVIEW_H +#define LL_LLFOLDERVIEW_H + +#include "llfolderviewitem.h" // because LLFolderView is-a LLFolderViewFolder + +#include "lluictrl.h" +#include "v4color.h" +#include "lldepthstack.h" +#include "lleditmenuhandler.h" +#include "llfontgl.h" +#include "llscrollcontainer.h" + +class LLFolderViewModelInterface; +class LLFolderViewGroupedItemModel; +class LLFolderViewFolder; +class LLFolderViewItem; +class LLFolderViewFilter; +class LLPanel; +class LLLineEditor; +class LLMenuGL; +class LLUICtrl; +class LLTextBox; + +/** + * Class LLFolderViewScrollContainer + * + * A scroll container which provides the information about the height + * of currently displayed folder view contents. + * Used for updating vertical scroll bar visibility in inventory panel. + * See LLScrollContainer::calcVisibleSize(). + */ +class LLFolderViewScrollContainer : public LLScrollContainer +{ +public: + /*virtual*/ ~LLFolderViewScrollContainer() {}; + /*virtual*/ const LLRect getScrolledViewRect() const; + +protected: + LLFolderViewScrollContainer(const LLScrollContainer::Params& p); + friend class LLUICtrlFactory; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFolderView +// +// The LLFolderView represents the root level folder view object. +// It manages the screen region of the folder view. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFolderView : public LLFolderViewFolder, public LLEditMenuHandler +{ +public: + struct Params : public LLInitParam::Block + { + Mandatory parent_panel; + Optional title; + Optional use_label_suffix, + allow_multiselect, + allow_drag, + show_empty_message, + use_ellipses, + show_item_link_overlays, + suppress_folder_menu; + Mandatory view_model; + Optional grouped_item_model; + Mandatory options_menu; + + + Params(); + }; + + friend class LLFolderViewScrollContainer; + typedef folder_view_item_deque selected_items_t; + + LLFolderView(const Params&); + virtual ~LLFolderView( void ); + + virtual bool canFocusChildren() const; + + virtual const LLFolderView* getRoot() const { return this; } + virtual LLFolderView* getRoot() { return this; } + + LLFolderViewModelInterface* getFolderViewModel() { return mViewModel; } + const LLFolderViewModelInterface* getFolderViewModel() const { return mViewModel; } + + LLFolderViewGroupedItemModel* getFolderViewGroupedItemModel() { return mGroupedItemModel; } + const LLFolderViewGroupedItemModel* getFolderViewGroupedItemModel() const { return mGroupedItemModel; } + + typedef boost::signals2::signal& items, bool user_action)> signal_t; + void setSelectCallback(const signal_t::slot_type& cb) { mSelectSignal.connect(cb); } + void setReshapeCallback(const signal_t::slot_type& cb) { mReshapeSignal.connect(cb); } + + bool getAllowMultiSelect() { return mAllowMultiSelect; } + bool getAllowDrag() { return mAllowDrag; } + + void setSingleFolderMode(bool is_single_mode) { mSingleFolderMode = is_single_mode; } + bool isSingleFolderMode() { return mSingleFolderMode; } + + // Close all folders in the view + void closeAllFolders(); + void openTopLevelFolders(); + + virtual void addFolder( LLFolderViewFolder* folder); + + // Find width and height of this object and its children. Also + // makes sure that this view and its children are the right size. + virtual S32 arrange( S32* width, S32* height ); + virtual S32 getItemHeight() const; + + void arrangeAll() { mArrangeGeneration++; } + S32 getArrangeGeneration() { return mArrangeGeneration; } + + // applies filters to control visibility of items + virtual void filter( LLFolderViewFilter& filter); + + void clearHoveredItem() { setHoveredItem(nullptr); } + LLFolderViewItem* getHoveredItem() const; + void setHoveredItem(LLFolderViewItem* itemp); + + // Get the last selected item + virtual LLFolderViewItem* getCurSelectedItem( void ); + selected_items_t& getSelectedItems( void ); + + // Record the selected item and pass it down the hierarchy. + virtual bool setSelection(LLFolderViewItem* selection, bool openitem, + bool take_keyboard_focus = true); + + // This method is used to toggle the selection of an item. Walks + // children, and keeps track of selected objects. + virtual bool changeSelection(LLFolderViewItem* selection, bool selected); + + virtual std::set getSelectionList() const; + + // Make sure if ancestor is selected, descendants are not + void sanitizeSelection(); + virtual void clearSelection(); + void addToSelectionList(LLFolderViewItem* item); + void removeFromSelectionList(LLFolderViewItem* item); + + bool startDrag(); + void setDragAndDropThisFrame() { mDragAndDropThisFrame = true; } + void setDraggingOverItem(LLFolderViewItem* item) { mDraggingOverItem = item; } + LLFolderViewItem* getDraggingOverItem() { return mDraggingOverItem; } + + // Deletion functionality + void removeSelectedItems(); + + void autoOpenItem(LLFolderViewFolder* item); + void closeAutoOpenedFolders(); + bool autoOpenTest(LLFolderViewFolder* item); + bool isOpen() const { return true; } // root folder always open + + // Copy & paste + virtual bool canCopy() const; + virtual void copy(); + + virtual bool canCut() const; + virtual void cut(); + + virtual bool canPaste() const; + virtual void paste(); + + LLFolderViewItem* getNextUnselectedItem(); + + // Public rename functionality - can only start the process + void startRenamingSelectedItem( void ); + + // LLView functionality + ///*virtual*/ bool handleKey( KEY key, MASK mask, bool called_from_parent ); + /*virtual*/ bool handleKeyHere( KEY key, MASK mask ); + /*virtual*/ bool handleUnicodeCharHere(llwchar uni_char); + /*virtual*/ bool handleMouseDown( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleDoubleClick( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleRightMouseDown( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleHover( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask) { setShowSelectionContext(false); } + virtual void draw(); + virtual void deleteAllChildren(); + + void stopAutoScollining() {mNeedsScroll = false;} + void scrollToShowSelection(); + void scrollToShowItem(LLFolderViewItem* item, const LLRect& constraint_rect); + void setScrollContainer( LLScrollContainer* parent ) { mScrollContainer = parent; } + LLRect getVisibleRect(); + + bool search(LLFolderViewItem* first_item, const std::string &search_string, bool backward); + void setShowSelectionContext(bool show) { mShowSelectionContext = show; } + bool getShowSelectionContext(); + void setShowSingleSelection(bool show); + bool getShowSingleSelection() { return mShowSingleSelection; } + F32 getSelectionFadeElapsedTime() { return mMultiSelectionFadeTimer.getElapsedTimeF32(); } + bool getUseEllipses() { return mUseEllipses; } + S32 getSelectedCount() { return (S32)mSelectedItems.size(); } + + void update(); // needs to be called periodically (e.g. once per frame) + + bool needsAutoSelect() { return mNeedsAutoSelect && !mAutoSelectOverride; } + bool needsAutoRename() { return mNeedsAutoRename; } + void setNeedsAutoRename(bool val) { mNeedsAutoRename = val; } + void setPinningSelectedItem(bool val) { mPinningSelectedItem = val; } + void setAutoSelectOverride(bool val) { mAutoSelectOverride = val; } + + bool showItemLinkOverlays() { return mShowItemLinkOverlays; } + + void setCallbackRegistrar(LLUICtrl::CommitCallbackRegistry::ScopedRegistrar* registrar) { mCallbackRegistrar = registrar; } + void setEnableRegistrar(LLUICtrl::EnableCallbackRegistry::ScopedRegistrar* registrar) { mEnableRegistrar = registrar; } + + void setForceArrange(bool force) { mForceArrange = force; } + + LLPanel* getParentPanel() { return mParentPanel.get(); } + // DEBUG only + void dumpSelectionInformation(); + + virtual S32 notify(const LLSD& info) ; + + void setShowEmptyMessage(bool show_msg) { mShowEmptyMessage = show_msg; } + + bool useLabelSuffix() { return mUseLabelSuffix; } + virtual void updateMenu(); + + void finishRenamingItem( void ); + + // Note: We may eventually have to move that method up the hierarchy to LLFolderViewItem. + LLHandle getHandle() const { return getDerivedHandle(); } + +private: + void updateMenuOptions(LLMenuGL* menu); + void updateRenamerPosition(); + +protected: + LLScrollContainer* mScrollContainer; // NULL if this is not a child of a scroll container. + + void commitRename( const LLSD& data ); + void onRenamerLost(); + + void closeRenamer( void ); + + bool isFolderSelected(); + + bool selectFirstItem(); + bool selectLastItem(); + + bool addNoOptions(LLMenuGL* menu) const; + + +protected: + LLHandle mPopupMenuHandle; + std::string mMenuFileName; + + LLHandle mHoveredItem; + selected_items_t mSelectedItems; + bool mKeyboardSelection, + mAllowMultiSelect, + mAllowDrag, + mShowEmptyMessage, + mShowFolderHierarchy, + mNeedsScroll, + mPinningSelectedItem, + mNeedsAutoSelect, + mAutoSelectOverride, + mNeedsAutoRename, + mUseLabelSuffix, + mDragAndDropThisFrame, + mShowItemLinkOverlays, + mShowSelectionContext, + mShowSingleSelection, + mSuppressFolderMenu, + mSingleFolderMode; + + // Renaming variables and methods + LLFolderViewItem* mRenameItem; // The item currently being renamed + LLLineEditor* mRenamer; + + LLRect mScrollConstraintRect; + + LLDepthStack mAutoOpenItems; + LLFolderViewFolder* mAutoOpenCandidate; + LLFrameTimer mAutoOpenTimer; + LLFrameTimer mSearchTimer; + std::string mSearchString; + LLFrameTimer mMultiSelectionFadeTimer; + S32 mArrangeGeneration; + + signal_t mSelectSignal; + signal_t mReshapeSignal; + S32 mSignalSelectCallback; + S32 mMinWidth; + + LLHandle mParentPanel; + + LLFolderViewModelInterface* mViewModel; + LLFolderViewGroupedItemModel* mGroupedItemModel; + + /** + * Is used to determine if we need to cut text In LLFolderViewItem to avoid horizontal scroll. + * NOTE: For now it's used only to cut LLFolderViewItem::mLabel text for Landmarks in Places Panel. + */ + bool mUseEllipses; // See EXT-719 + + /** + * Contains item under mouse pointer while dragging + */ + LLFolderViewItem* mDraggingOverItem; // See EXT-719 + + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar* mCallbackRegistrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar* mEnableRegistrar; + + boost::signals2::connection mRenamerTopLostSignalConnection; + + bool mForceArrange; + +public: + static F32 sAutoOpenTime; + LLTextBox* mStatusTextBox; + +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFolderViewFunctor +// +// Simple abstract base class for applying a functor to folders and +// items in a folder view hierarchy. This is suboptimal for algorithms +// that only work folders or only work on items, but I'll worry about +// that later when it's determined to be too slow. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFolderViewFunctor +{ +public: + virtual ~LLFolderViewFunctor() {} + virtual void doFolder(LLFolderViewFolder* folder) = 0; + virtual void doItem(LLFolderViewItem* item) = 0; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLSelectFirstFilteredItem +// +// This will select the first *item* found in the hierarchy. If no item can be +// selected, the first matching folder will. +// Since doFolder() is done first but we prioritize item selection, we let the +// first filtered folder set the selection and raise a folder flag. +// The selection might be overridden by the first filtered item in doItem() +// which checks an item flag. Since doFolder() checks the item flag too, the first +// item will still be selected if items were to be done first and folders second. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLSelectFirstFilteredItem : public LLFolderViewFunctor +{ +public: + LLSelectFirstFilteredItem() : mItemSelected(false), mFolderSelected(false) {} + virtual ~LLSelectFirstFilteredItem() {} + virtual void doFolder(LLFolderViewFolder* folder); + virtual void doItem(LLFolderViewItem* item); + bool wasItemSelected() { return mItemSelected || mFolderSelected; } +protected: + bool mItemSelected; + bool mFolderSelected; +}; + +class LLOpenFilteredFolders : public LLFolderViewFunctor +{ +public: + LLOpenFilteredFolders() {} + virtual ~LLOpenFilteredFolders() {} + virtual void doFolder(LLFolderViewFolder* folder); + virtual void doItem(LLFolderViewItem* item); +}; + +class LLSaveFolderState : public LLFolderViewFunctor +{ +public: + LLSaveFolderState() : mApply(false) {} + virtual ~LLSaveFolderState() {} + virtual void doFolder(LLFolderViewFolder* folder); + virtual void doItem(LLFolderViewItem* item) {} + void setApply(bool apply); + void clearOpenFolders() { mOpenFolders.clear(); } +protected: + std::set mOpenFolders; + bool mApply; +}; + +class LLOpenFoldersWithSelection : public LLFolderViewFunctor +{ +public: + LLOpenFoldersWithSelection() {} + virtual ~LLOpenFoldersWithSelection() {} + virtual void doFolder(LLFolderViewFolder* folder); + virtual void doItem(LLFolderViewItem* item); +}; + +class LLAllDescendentsPassedFilter : public LLFolderViewFunctor +{ +public: + LLAllDescendentsPassedFilter() : mAllDescendentsPassedFilter(true) {} + /*virtual*/ ~LLAllDescendentsPassedFilter() {} + /*virtual*/ void doFolder(LLFolderViewFolder* folder); + /*virtual*/ void doItem(LLFolderViewItem* item); + bool allDescendentsPassedFilter() const { return mAllDescendentsPassedFilter; } +protected: + bool mAllDescendentsPassedFilter; +}; + +// Flags for buildContextMenu() +const U32 SUPPRESS_OPEN_ITEM = 0x1; +const U32 FIRST_SELECTED_ITEM = 0x2; +const U32 ITEM_IN_MULTI_SELECTION = 0x4; + +#endif // LL_LLFOLDERVIEW_H diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 2bbf23c4bf..a0c7407b06 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -1,2368 +1,2368 @@ -/** -* @file llfolderviewitem.cpp -* @brief Items and folders that can appear in a hierarchical folder view -* -* $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 "../newview/llviewerprecompiledheaders.h" - -#include "llflashtimer.h" - -#include "linden_common.h" -#include "llfolderviewitem.h" -#include "llfolderview.h" -#include "llfolderviewmodel.h" -#include "llpanel.h" -#include "llcallbacklist.h" -#include "llcriticaldamp.h" -#include "llclipboard.h" -#include "llfocusmgr.h" // gFocusMgr -#include "lltrans.h" -#include "llwindow.h" - -///---------------------------------------------------------------------------- -/// Class LLFolderViewItem -///---------------------------------------------------------------------------- - -static LLDefaultChildRegistry::Register r("folder_view_item"); - -// statics -std::map LLFolderViewItem::sFonts; // map of styles to fonts - -bool LLFolderViewItem::sColorSetInitialized = false; -LLUIColor LLFolderViewItem::sFgColor; -LLUIColor LLFolderViewItem::sHighlightBgColor; -LLUIColor LLFolderViewItem::sFlashBgColor; -LLUIColor LLFolderViewItem::sFocusOutlineColor; -LLUIColor LLFolderViewItem::sMouseOverColor; -LLUIColor LLFolderViewItem::sFilterBGColor; -LLUIColor LLFolderViewItem::sFilterTextColor; -LLUIColor LLFolderViewItem::sSuffixColor; -LLUIColor LLFolderViewItem::sSearchStatusColor; - -// only integers can be initialized in header -const F32 LLFolderViewItem::FOLDER_CLOSE_TIME_CONSTANT = 0.02f; -const F32 LLFolderViewItem::FOLDER_OPEN_TIME_CONSTANT = 0.03f; - -const LLColor4U DEFAULT_WHITE(255, 255, 255); - - -//static -LLFontGL* LLFolderViewItem::getLabelFontForStyle(U8 style) -{ - LLFontGL* rtn = sFonts[style]; - if (!rtn) // grab label font with this style, lazily - { - LLFontDescriptor labelfontdesc("SansSerif", "Small", style); - rtn = LLFontGL::getFont(labelfontdesc); - if (!rtn) - { - rtn = LLFontGL::getFontDefault(); - } - sFonts[style] = rtn; - } - return rtn; -} - -//static -void LLFolderViewItem::initClass() -{ -} - -//static -void LLFolderViewItem::cleanupClass() -{ - sFonts.clear(); -} - - -// NOTE: Optimize this, we call it a *lot* when opening a large inventory -LLFolderViewItem::Params::Params() -: root(), - listener(), - folder_arrow_image("folder_arrow_image"), - folder_indentation("folder_indentation"), - selection_image("selection_image"), - item_height("item_height"), - item_top_pad("item_top_pad"), - creation_date(), - allow_wear("allow_wear", true), - allow_drop("allow_drop", true), - font_color("font_color"), - font_highlight_color("font_highlight_color"), - left_pad("left_pad", 0), - icon_pad("icon_pad", 0), - icon_width("icon_width", 0), - text_pad("text_pad", 0), - text_pad_right("text_pad_right", 0), - single_folder_mode("single_folder_mode", false), - double_click_override("double_click_override", false), - arrow_size("arrow_size", 0), - max_folder_item_overlap("max_folder_item_overlap", 0) -{ } - -// Default constructor -LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) -: LLView(p), - mLabelWidth(0), - mLabelWidthDirty(false), - mSuffixNeedsRefresh(false), - mLabelPaddingRight(DEFAULT_LABEL_PADDING_RIGHT), - mParentFolder( NULL ), - mIsSelected( false ), - mIsCurSelection( false ), - mSelectPending(false), - mIsItemCut(false), - mCutGeneration(0), - mLabelStyle( LLFontGL::NORMAL ), - mHasVisibleChildren(false), - mLocalIndentation(p.folder_indentation), - mIndentation(0), - mItemHeight(p.item_height), - mControlLabelRotation(0.f), - mDragAndDropTarget(false), - mLabel(p.name), - mRoot(p.root), - mViewModelItem(p.listener), - mIsMouseOverTitle(false), - mAllowWear(p.allow_wear), - mAllowDrop(p.allow_drop), - mFontColor(p.font_color), - mFontHighlightColor(p.font_highlight_color), - mLeftPad(p.left_pad), - mIconPad(p.icon_pad), - mIconWidth(p.icon_width), - mTextPad(p.text_pad), - mTextPadRight(p.text_pad_right), - mArrowSize(p.arrow_size), - mSingleFolderMode(p.single_folder_mode), - mMaxFolderItemOverlap(p.max_folder_item_overlap), - mDoubleClickOverride(p.double_click_override) -{ - if (!sColorSetInitialized) - { - sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); - sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE); - sFlashBgColor = LLUIColorTable::instance().getColor("MenuItemFlashBgColor", DEFAULT_WHITE); - sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE); - sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE); - sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE); - sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE); - sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); - sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE); - sColorSetInitialized = true; - } - - if (mViewModelItem) - { - mViewModelItem->setFolderViewItem(this); - } -} - -// Destroys the object -LLFolderViewItem::~LLFolderViewItem() -{ - mViewModelItem = NULL; - gFocusMgr.removeKeyboardFocusWithoutCallback(this); -} - -bool LLFolderViewItem::postBuild() -{ - LLFolderViewModelItem* vmi = getViewModelItem(); - llassert(vmi); // not supposed to happen, if happens, find out why and fix - if (vmi) - { - // getDisplayName() is expensive (due to internal getLabelSuffix() and name building) - // it also sets search strings so it requires a filter reset - mLabel = vmi->getDisplayName(); - setToolTip(vmi->getName()); - - // Dirty the filter flag of the model from the view (CHUI-849) - vmi->dirtyFilter(); - } - - // Don't do full refresh on constructor if it is possible to avoid - // it significantly slows down bulk view creation. - // Todo: Ideally we need to move getDisplayName() out of constructor as well. - // Like: make a logic that will let filter update search string, - // while LLFolderViewItem::arrange() updates visual part - mSuffixNeedsRefresh = true; - mLabelWidthDirty = true; - return true; -} - -LLFolderView* LLFolderViewItem::getRoot() -{ - return mRoot; -} - -const LLFolderView* LLFolderViewItem::getRoot() const -{ - return mRoot; -} -// Returns true if this object is a child (or grandchild, etc.) of potential_ancestor. -bool LLFolderViewItem::isDescendantOf( const LLFolderViewFolder* potential_ancestor ) -{ - LLFolderViewItem* root = this; - while( root->mParentFolder ) - { - if( root->mParentFolder == potential_ancestor ) - { - return true; - } - root = root->mParentFolder; - } - return false; -} - -LLFolderViewItem* LLFolderViewItem::getNextOpenNode(bool include_children) -{ - if (!mParentFolder) - { - return NULL; - } - - LLFolderViewItem* itemp = mParentFolder->getNextFromChild( this, include_children ); - while(itemp && !itemp->getVisible()) - { - LLFolderViewItem* next_itemp = itemp->mParentFolder->getNextFromChild( itemp, include_children ); - if (itemp == next_itemp) - { - // hit last item - return itemp->getVisible() ? itemp : this; - } - itemp = next_itemp; - } - - return itemp; -} - -LLFolderViewItem* LLFolderViewItem::getPreviousOpenNode(bool include_children) -{ - if (!mParentFolder) - { - return NULL; - } - - LLFolderViewItem* itemp = mParentFolder->getPreviousFromChild( this, include_children ); - - // Skip over items that are invisible or are hidden from the UI. - while(itemp && !itemp->getVisible()) - { - LLFolderViewItem* next_itemp = itemp->mParentFolder->getPreviousFromChild( itemp, include_children ); - if (itemp == next_itemp) - { - // hit first item - return itemp->getVisible() ? itemp : this; - } - itemp = next_itemp; - } - - return itemp; -} - -bool LLFolderViewItem::passedFilter(S32 filter_generation) -{ - return getViewModelItem()->passedFilter(filter_generation); -} - -bool LLFolderViewItem::isPotentiallyVisible(S32 filter_generation) -{ - if (filter_generation < 0) - { - filter_generation = getFolderViewModel()->getFilter().getFirstSuccessGeneration(); - } - LLFolderViewModelItem* model = getViewModelItem(); - bool visible = model->passedFilter(filter_generation); - if (model->getMarkedDirtyGeneration() >= filter_generation) - { - // unsure visibility state - // retaining previous visibility until item is updated or filter generation changes - visible |= getVisible(); - } - return visible; -} - -void LLFolderViewItem::refresh() -{ - LLFolderViewModelItem& vmi = *getViewModelItem(); - - mLabel = vmi.getDisplayName(); - setToolTip(vmi.getName()); - // icons are slightly expensive to get, can be optimized - // see LLInventoryIcon::getIcon() - mIcon = vmi.getIcon(); - mIconOpen = vmi.getIconOpen(); - mIconOverlay = vmi.getIconOverlay(); - - if (mRoot->useLabelSuffix()) - { - // Very Expensive! - // Can do a number of expensive checks, like checking active motions, wearables or friend list - mLabelStyle = vmi.getLabelStyle(); - mLabelSuffix = vmi.getLabelSuffix(); - } - - // Dirty the filter flag of the model from the view (CHUI-849) - vmi.dirtyFilter(); - - mLabelWidthDirty = true; - mSuffixNeedsRefresh = false; -} - -void LLFolderViewItem::refreshSuffix() -{ - LLFolderViewModelItem const* vmi = getViewModelItem(); - - // icons are slightly expensive to get, can be optimized - // see LLInventoryIcon::getIcon() - mIcon = vmi->getIcon(); - mIconOpen = vmi->getIconOpen(); - mIconOverlay = vmi->getIconOverlay(); - - if (mRoot->useLabelSuffix()) - { - // Very Expensive! - // Can do a number of expensive checks, like checking active motions, wearables or friend list - mLabelStyle = vmi->getLabelStyle(); - mLabelSuffix = vmi->getLabelSuffix(); - } - - mLabelWidthDirty = true; - mSuffixNeedsRefresh = false; -} - -// Utility function for LLFolderView -void LLFolderViewItem::arrangeAndSet(bool set_selection, - bool take_keyboard_focus) -{ - LLFolderView* root = getRoot(); - if (getParentFolder()) - { - getParentFolder()->requestArrange(); - } - if(set_selection) - { - getRoot()->setSelection(this, true, take_keyboard_focus); - if(root) - { - root->scrollToShowSelection(); - } - } -} - - -std::set LLFolderViewItem::getSelectionList() const -{ - std::set selection; - return selection; -} - -// addToFolder() returns true if it succeeds. false otherwise -void LLFolderViewItem::addToFolder(LLFolderViewFolder* folder) -{ - folder->addItem(this); - - // Compute indentation since parent folder changed - mIndentation = (getParentFolder()) - ? getParentFolder()->getIndentation() + mLocalIndentation - : 0; -} - - -// Finds width and height of this object and its children. Also -// makes sure that this view and its children are the right size. -S32 LLFolderViewItem::arrange( S32* width, S32* height ) -{ - // Only indent deeper items in hierarchy - mIndentation = (getParentFolder()) - ? getParentFolder()->getIndentation() + mLocalIndentation - : 0; - if (mLabelWidthDirty) - { - if (mSuffixNeedsRefresh) - { - // Expensive. But despite refreshing label, - // it is purely visual, so it is fine to do at our laisure - refreshSuffix(); - } - mLabelWidth = getLabelXPos() + getLabelFontForStyle(mLabelStyle)->getWidth(mLabel) + getLabelFontForStyle(LLFontGL::NORMAL)->getWidth(mLabelSuffix) + mLabelPaddingRight; - mLabelWidthDirty = false; - } - - *width = llmax(*width, mLabelWidth); - - // determine if we need to use ellipses to avoid horizontal scroll. EXT-719 - bool use_ellipses = getRoot()->getUseEllipses(); - if (use_ellipses) - { - // limit to set rect to avoid horizontal scrollbar - *width = llmin(*width, getRoot()->getRect().getWidth()); - } - *height = getItemHeight(); - return *height; -} - -S32 LLFolderViewItem::getItemHeight() const -{ - return mItemHeight; -} - -S32 LLFolderViewItem::getLabelXPos() -{ - return getIndentation() + mArrowSize + mTextPad + mIconWidth + mIconPad; -} - -S32 LLFolderViewItem::getIconPad() -{ - return mIconPad; -} - -S32 LLFolderViewItem::getTextPad() -{ - return mTextPad; -} - -// *TODO: This can be optimized a lot by simply recording that it is -// selected in the appropriate places, and assuming that set selection -// means 'deselect' for a leaf item. Do this optimization after -// multiple selection is implemented to make sure it all plays nice -// together. -bool LLFolderViewItem::setSelection(LLFolderViewItem* selection, bool openitem, bool take_keyboard_focus) -{ - if (selection == this && !mIsSelected) - { - selectItem(); - } - else if (mIsSelected) // Deselect everything else. - { - deselectItem(); - } - return mIsSelected; -} - -bool LLFolderViewItem::changeSelection(LLFolderViewItem* selection, bool selected) -{ - if (selection == this) - { - if (mIsSelected) - { - deselectItem(); - } - else - { - selectItem(); - } - return true; - } - return false; -} - -void LLFolderViewItem::deselectItem(void) -{ - mIsSelected = false; -} - -void LLFolderViewItem::selectItem(void) -{ - if (!mIsSelected) - { - mIsSelected = true; - getViewModelItem()->selectItem(); - } -} - -bool LLFolderViewItem::isMovable() -{ - return getViewModelItem()->isItemMovable(); -} - -bool LLFolderViewItem::isRemovable() -{ - return getViewModelItem()->isItemRemovable(); -} - -void LLFolderViewItem::destroyView() -{ - getRoot()->removeFromSelectionList(this); - - if (mParentFolder) - { - // removeView deletes me - mParentFolder->extractItem(this); - } - delete this; -} - -// Call through to the viewed object and return true if it can be -// removed. -//bool LLFolderViewItem::removeRecursively(bool single_item) -bool LLFolderViewItem::remove() -{ - if(!isRemovable()) - { - return false; - } - return getViewModelItem()->removeItem(); -} - -// Build an appropriate context menu for the item. -void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - getViewModelItem()->buildContextMenu(menu, flags); -} - -void LLFolderViewItem::openItem( void ) -{ - if (mAllowWear || !getViewModelItem()->isItemWearable()) - { - getViewModelItem()->openItem(); - } -} - -void LLFolderViewItem::rename(const std::string& new_name) -{ - if( !new_name.empty() ) - { - getViewModelItem()->renameItem(new_name); - } -} - -const std::string& LLFolderViewItem::getName( void ) const -{ - static const std::string noName(""); - return getViewModelItem() ? getViewModelItem()->getName() : noName; -} - -// LLView functionality -bool LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask ) -{ - if(!mIsSelected) - { - getRoot()->setSelection(this, false); - } - make_ui_sound("UISndClick"); - return true; -} - -bool LLFolderViewItem::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - if (LLView::childrenHandleMouseDown(x, y, mask)) - { - return true; - } - - // No handler needed for focus lost since this class has no - // state that depends on it. - gFocusMgr.setMouseCapture( this ); - - if (!mIsSelected) - { - if(mask & MASK_CONTROL) - { - getRoot()->changeSelection(this, !mIsSelected); - } - else if (mask & MASK_SHIFT) - { - getParentFolder()->extendSelectionTo(this); - } - else - { - getRoot()->setSelection(this, false); - } - make_ui_sound("UISndClick"); - } - else - { - // If selected, we reserve the decision of deselecting/reselecting to the mouse up moment. - // This is necessary so we maintain selection consistent when starting a drag. - mSelectPending = true; - } - - mDragStartX = x; - mDragStartY = y; - return true; -} - -bool LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask ) -{ - mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight)); - - if( hasMouseCapture() && isMovable() ) - { - LLFolderView* root = getRoot(); - - if( (x - mDragStartX) * (x - mDragStartX) + (y - mDragStartY) * (y - mDragStartY) > DRAG_N_DROP_DISTANCE_THRESHOLD * DRAG_N_DROP_DISTANCE_THRESHOLD - && root->getAllowDrag() - && root->getCurSelectedItem() - && root->startDrag()) - { - // RN: when starting drag and drop, clear out last auto-open - root->autoOpenTest(NULL); - root->setShowSelectionContext(true); - - // Release keyboard focus, so that if stuff is dropped into the - // world, pressing the delete key won't blow away the inventory - // item. - gFocusMgr.setKeyboardFocus(NULL); - - getWindow()->setCursor(UI_CURSOR_ARROW); - } - else if (x != mDragStartX || y != mDragStartY) - { - getWindow()->setCursor(UI_CURSOR_NOLOCKED); - } - - root->clearHoveredItem(); - return true; - } - else - { - LLFolderView* pRoot = getRoot(); - pRoot->setHoveredItem(this); - pRoot->setShowSelectionContext(false); - getWindow()->setCursor(UI_CURSOR_ARROW); - // let parent handle this then... - return false; - } -} - - -bool LLFolderViewItem::handleDoubleClick( S32 x, S32 y, MASK mask ) -{ - openItem(); - return true; -} - -bool LLFolderViewItem::handleMouseUp( S32 x, S32 y, MASK mask ) -{ - if (LLView::childrenHandleMouseUp(x, y, mask)) - { - return true; - } - - // if mouse hasn't moved since mouse down... - if ( pointInView(x, y) && mSelectPending ) - { - //...then select - if(mask & MASK_CONTROL) - { - getRoot()->changeSelection(this, !mIsSelected); - } - else if (mask & MASK_SHIFT) - { - getParentFolder()->extendSelectionTo(this); - } - else - { - getRoot()->setSelection(this, false); - } - } - - mSelectPending = false; - - if( hasMouseCapture() ) - { - if (getRoot()) - { - getRoot()->setShowSelectionContext(false); - } - gFocusMgr.setMouseCapture( NULL ); - } - return true; -} - -void LLFolderViewItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - mIsMouseOverTitle = false; - - // NOTE: LLViewerWindow::updateUI() calls "enter" before "leave"; if the mouse moved to another item, we can't just outright clear it - LLFolderView* pRoot = getRoot(); - if (this == pRoot->getHoveredItem()) - { - pRoot->clearHoveredItem(); - } -} - -bool LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - bool handled = false; - bool accepted = getViewModelItem()->dragOrDrop(mask,drop,cargo_type,cargo_data, tooltip_msg); - handled = accepted; - if (accepted) - { - mDragAndDropTarget = true; - *accept = ACCEPT_YES_MULTI; - } - else - { - *accept = ACCEPT_NO; - } - if(mParentFolder && !handled) - { - // store this item to get it in LLFolderBridge::dragItemIntoFolder on drop event. - mRoot->setDraggingOverItem(this); - handled = mParentFolder->handleDragAndDropFromChild(mask,drop,cargo_type,cargo_data,accept,tooltip_msg); - mRoot->setDraggingOverItem(NULL); - } - if (handled) - { - LL_DEBUGS("UserInput") << "dragAndDrop handled by LLFolderViewItem" << LL_ENDL; - } - - return handled; -} - -void LLFolderViewItem::drawOpenFolderArrow(const Params& default_params, const LLUIColor& fg_color) -{ - //--------------------------------------------------------------------------------// - // Draw open folder arrow - // - const S32 TOP_PAD = default_params.item_top_pad; - - if (hasVisibleChildren() || !isFolderComplete()) - { - LLUIImage* arrow_image = default_params.folder_arrow_image; - gl_draw_scaled_rotated_image( - mIndentation, getRect().getHeight() - mArrowSize - mTextPad - TOP_PAD, - mArrowSize, mArrowSize, mControlLabelRotation, arrow_image->getImage(), fg_color); - } -} - -/*virtual*/ bool LLFolderViewItem::isHighlightAllowed() -{ - return mIsSelected; -} - -/*virtual*/ bool LLFolderViewItem::isHighlightActive() -{ - return mIsCurSelection; -} - -/*virtual*/ bool LLFolderViewItem::isFadeItem() -{ - LLClipboard& clipboard = LLClipboard::instance(); - if (mCutGeneration != clipboard.getGeneration()) - { - mCutGeneration = clipboard.getGeneration(); - mIsItemCut = clipboard.isCutMode() - && ((getParentFolder() && getParentFolder()->isFadeItem()) - || getViewModelItem()->isCutToClipboard()); - } - return mIsItemCut; -} - -void LLFolderViewItem::drawHighlight(const bool showContent, const bool hasKeyboardFocus, const LLUIColor &selectColor, const LLUIColor &flashColor, - const LLUIColor &focusOutlineColor, const LLUIColor &mouseOverColor) -{ - const S32 focus_top = getRect().getHeight(); - const S32 focus_bottom = getRect().getHeight() - mItemHeight; - const bool folder_open = (getRect().getHeight() > mItemHeight + 4); - const S32 FOCUS_LEFT = 1; - - // Determine which background color to use for highlighting - LLUIColor bgColor = (isFlashing() ? flashColor : selectColor); - - //--------------------------------------------------------------------------------// - // Draw highlight for selected items - // Note: Always render "current" item or flashing item, only render other selected - // items if mShowSingleSelection is false. - // - if (isHighlightAllowed()) - - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - // Highlight for selected but not current items - if (!isHighlightActive() && !isFlashing()) - { - LLColor4 bg_color = bgColor; - // do time-based fade of extra objects - F32 fade_time = (getRoot() ? getRoot()->getSelectionFadeElapsedTime() : 0.0f); - if (getRoot() && getRoot()->getShowSingleSelection()) - { - // fading out - bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, bg_color.mV[VALPHA], 0.f); - } - else - { - // fading in - bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, 0.f, bg_color.mV[VALPHA]); - } - gl_rect_2d(FOCUS_LEFT, - focus_top, - getRect().getWidth() - 2, - focus_bottom, - bg_color, hasKeyboardFocus); - } - - // Highlight for currently selected or flashing item - if (isHighlightActive()) - { - // Background - gl_rect_2d(FOCUS_LEFT, - focus_top, - getRect().getWidth() - 2, - focus_bottom, - bgColor, hasKeyboardFocus); - // Outline - gl_rect_2d(FOCUS_LEFT, - focus_top, - getRect().getWidth() - 2, - focus_bottom, - focusOutlineColor, false); - } - - if (folder_open) - { - gl_rect_2d(FOCUS_LEFT, - focus_bottom + 1, // overlap with bottom edge of above rect - getRect().getWidth() - 2, - 0, - focusOutlineColor, false); - if (showContent && !isFlashing()) - { - gl_rect_2d(FOCUS_LEFT, - focus_bottom + 1, - getRect().getWidth() - 2, - 0, - bgColor, true); - } - } - } - else if (mIsMouseOverTitle) - { - gl_rect_2d(FOCUS_LEFT, - focus_top, - getRect().getWidth() - 2, - focus_bottom, - mouseOverColor, false); - } - - //--------------------------------------------------------------------------------// - // Draw DragNDrop highlight - // - if (mDragAndDropTarget) - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gl_rect_2d(FOCUS_LEFT, - focus_top, - getRect().getWidth() - 2, - focus_bottom, - bgColor, false); - if (folder_open) - { - gl_rect_2d(FOCUS_LEFT, - focus_bottom + 1, // overlap with bottom edge of above rect - getRect().getWidth() - 2, - 0, - bgColor, false); - } - mDragAndDropTarget = false; - } -} - -void LLFolderViewItem::drawLabel(const LLFontGL * font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x) -{ - //--------------------------------------------------------------------------------// - // Draw the actual label text - // - font->renderUTF8(mLabel, 0, x, y, color, - LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, /*use_ellipses*/true); -} - -void LLFolderViewItem::draw() -{ - const bool show_context = (getRoot() ? getRoot()->getShowSelectionContext() : false); - const bool filled = show_context || (getRoot() ? getRoot()->getParentPanel()->hasFocus() : false); // If we have keyboard focus, draw selection filled - - const Params& default_params = LLUICtrlFactory::getDefaultParams(); - const S32 TOP_PAD = default_params.item_top_pad; - - const LLFontGL* font = getLabelFontForStyle(mLabelStyle); - - getViewModelItem()->update(); - - if(!mSingleFolderMode) - { - drawOpenFolderArrow(default_params, sFgColor); - } - - drawHighlight(show_context, filled, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); - - //--------------------------------------------------------------------------------// - // Draw open icon - // - const S32 icon_x = mIndentation + mArrowSize + mTextPad; - if (!mIconOpen.isNull() && (llabs(mControlLabelRotation) > 80)) // For open folders - { - mIconOpen->draw(icon_x, getRect().getHeight() - mIconOpen->getHeight() - TOP_PAD + 1); - } - else if (mIcon) - { - mIcon->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1); - } - - if (mIconOverlay && getRoot()->showItemLinkOverlays()) - { - mIconOverlay->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1); - } - - //--------------------------------------------------------------------------------// - // Exit if no label to draw - // - if (mLabel.empty()) - { - return; - } - - std::string::size_type filter_string_length = mViewModelItem->hasFilterStringMatch() ? mViewModelItem->getFilterStringSize() : 0; - F32 right_x = 0; - F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; - F32 text_left = (F32)getLabelXPos(); - std::string combined_string = mLabel + mLabelSuffix; - - const LLFontGL* suffix_font = getLabelFontForStyle(LLFontGL::NORMAL); - S32 filter_offset = mViewModelItem->getFilterStringOffset(); - if (filter_string_length > 0) - { - S32 bottom = llfloor(getRect().getHeight() - font->getLineHeight() - 3 - TOP_PAD); - S32 top = getRect().getHeight() - TOP_PAD; - if(mLabelSuffix.empty() || (font == suffix_font)) - { - S32 left = ll_round(text_left) + font->getWidth(combined_string, 0, mViewModelItem->getFilterStringOffset()) - 2; - S32 right = left + font->getWidth(combined_string, mViewModelItem->getFilterStringOffset(), filter_string_length) + 2; - - LLUIImage* box_image = default_params.selection_image; - LLRect box_rect(left, top, right, bottom); - box_image->draw(box_rect, sFilterBGColor); - } - else - { - S32 label_filter_length = llmin((S32)mLabel.size() - filter_offset, (S32)filter_string_length); - if(label_filter_length > 0) - { - S32 left = ll_round(text_left) + font->getWidthF32(mLabel, 0, llmin(filter_offset, (S32)mLabel.size())) - 2; - S32 right = left + font->getWidthF32(mLabel, filter_offset, label_filter_length) + 2; - LLUIImage* box_image = default_params.selection_image; - LLRect box_rect(left, top, right, bottom); - box_image->draw(box_rect, sFilterBGColor); - } - S32 suffix_filter_length = label_filter_length > 0 ? filter_string_length - label_filter_length : filter_string_length; - if(suffix_filter_length > 0) - { - S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); - S32 left = ll_round(text_left) + font->getWidthF32(mLabel, 0, mLabel.size()) + suffix_font->getWidthF32(mLabelSuffix, 0, suffix_offset) - 2; - S32 right = left + suffix_font->getWidthF32(mLabelSuffix, suffix_offset, suffix_filter_length) + 2; - LLUIImage* box_image = default_params.selection_image; - LLRect box_rect(left, top, right, bottom); - box_image->draw(box_rect, sFilterBGColor); - } - } - } - - LLColor4 color = (mIsSelected && filled) ? mFontHighlightColor : mFontColor; - - if (isFadeItem()) - { - // Fade out item color to indicate it's being cut - color.mV[VALPHA] *= 0.5f; - } - drawLabel(font, text_left, y, color, right_x); - - //--------------------------------------------------------------------------------// - // Draw label suffix - // - if (!mLabelSuffix.empty()) - { - suffix_font->renderUTF8( mLabelSuffix, 0, right_x, y, isFadeItem() ? color : (LLColor4)sSuffixColor, - LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - S32_MAX, S32_MAX, &right_x); - } - - //--------------------------------------------------------------------------------// - // Highlight string match - // - if (filter_string_length > 0) - { - if(mLabelSuffix.empty() || (font == suffix_font)) - { - F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string, filter_offset, filter_string_length); - F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; - font->renderUTF8(combined_string, filter_offset, match_string_left, yy, - sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - filter_string_length, S32_MAX, &right_x); - } - else - { - S32 label_filter_length = llmin((S32)mLabel.size() - filter_offset, (S32)filter_string_length); - if(label_filter_length > 0) - { - F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, filter_offset + label_filter_length) - font->getWidthF32(mLabel, filter_offset, label_filter_length); - F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; - font->renderUTF8(mLabel, filter_offset, match_string_left, yy, - sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - label_filter_length, S32_MAX, &right_x); - } - - S32 suffix_filter_length = label_filter_length > 0 ? filter_string_length - label_filter_length : filter_string_length; - if(suffix_filter_length > 0) - { - S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); - F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, mLabel.size()) + suffix_font->getWidthF32(mLabelSuffix, 0, suffix_offset + suffix_filter_length) - suffix_font->getWidthF32(mLabelSuffix, suffix_offset, suffix_filter_length); - F32 yy = (F32)getRect().getHeight() - suffix_font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; - suffix_font->renderUTF8(mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, - LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - suffix_filter_length, S32_MAX, &right_x); - } - } - - } - - //Gilbert Linden 9-20-2012: Although this should be legal, removing it because it causes the mLabelSuffix rendering to - //be distorted...oddly. I initially added this in but didn't need it after all. So removing to prevent unnecessary bug. - //LLView::draw(); -} - -const LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void ) const -{ - return getRoot()->getFolderViewModel(); -} - -LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void ) -{ - return getRoot()->getFolderViewModel(); -} - -bool LLFolderViewItem::isInSelection() const -{ - return mIsSelected || (mParentFolder && mParentFolder->isInSelection()); -} - - - -///---------------------------------------------------------------------------- -/// Class LLFolderViewFolder -///---------------------------------------------------------------------------- - -LLFolderViewFolder::LLFolderViewFolder( const LLFolderViewItem::Params& p ): - LLFolderViewItem( p ), - mIsOpen(false), - mExpanderHighlighted(false), - mCurHeight(0.f), - mTargetHeight(0.f), - mAutoOpenCountdown(0.f), - mIsFolderComplete(false), // folder might have children that are not loaded yet. - mAreChildrenInited(false), // folder might have children that are not built yet. - mLastArrangeGeneration( -1 ), - mLastCalculatedWidth(0) -{ -} - -void LLFolderViewFolder::updateLabelRotation() -{ - if (mAutoOpenCountdown != 0.f) - { - mControlLabelRotation = mAutoOpenCountdown * -90.f; - } - else if (isOpen()) - { - mControlLabelRotation = lerp(mControlLabelRotation, -90.f, LLSmoothInterpolation::getInterpolant(0.04f)); - } - else - { - mControlLabelRotation = lerp(mControlLabelRotation, 0.f, LLSmoothInterpolation::getInterpolant(0.025f)); - } -} - -// Destroys the object -LLFolderViewFolder::~LLFolderViewFolder( void ) -{ - // The LLView base class takes care of object destruction. make sure that we - // don't have mouse or keyboard focus - gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() -} - -// addToFolder() returns true if it succeeds. false otherwise -void LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder) -{ - folder->addFolder(this); - - // Compute indentation since parent folder changed - mIndentation = (getParentFolder()) - ? getParentFolder()->getIndentation() + mLocalIndentation - : 0; - - if(isOpen() && folder->isOpen()) - { - requestArrange(); - } -} - -static LLTrace::BlockTimerStatHandle FTM_ARRANGE("Arrange"); - -// Make everything right and in the right place ready for drawing (CHUI-849) -// * Sort everything correctly if necessary -// * Turn widgets visible/invisible according to their model filtering state -// * Takes animation state into account for opening/closing of folders (this makes widgets visible/invisible) -// * Reposition visible widgets so that they line up correctly with no gap -// * Compute the width and height of the current folder and its children -// * Makes sure that this view and its children are the right size -S32 LLFolderViewFolder::arrange( S32* width, S32* height ) -{ - // Sort before laying out contents - // Note that we sort from the root (CHUI-849) - if (mAreChildrenInited) - { - getRoot()->getFolderViewModel()->sort(this); - } - - LL_RECORD_BLOCK_TIME(FTM_ARRANGE); - - // evaluate mHasVisibleChildren - mHasVisibleChildren = false; - if (mAreChildrenInited && getViewModelItem()->descendantsPassedFilter()) - { - // We have to verify that there's at least one child that's not filtered out - bool found = false; - // Try the items first - for (items_t::iterator iit = mItems.begin(); iit != mItems.end(); ++iit) - { - LLFolderViewItem* itemp = (*iit); - found = itemp->isPotentiallyVisible(); - if (found) - break; - } - if (!found) - { - // If no item found, try the folders - for (folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit) - { - LLFolderViewFolder* folderp = (*fit); - found = folderp->isPotentiallyVisible(); - if (found) - break; - } - } - - mHasVisibleChildren = found; - } - if (!mIsFolderComplete && mAreChildrenInited) - { - mIsFolderComplete = getFolderViewModel()->isFolderComplete(this); - } - - - - // calculate height as a single item (without any children), and reshapes rectangle to match - LLFolderViewItem::arrange( width, height ); - - // clamp existing animated height so as to never get smaller than a single item - mCurHeight = llmax((F32)*height, mCurHeight); - - // initialize running height value as height of single item in case we have no children - F32 running_height = (F32)*height; - F32 target_height = (F32)*height; - - // are my children visible? - if (needsArrange()) - { - // set last arrange generation first, in case children are animating - // and need to be arranged again - mLastArrangeGeneration = getRoot()->getArrangeGeneration(); - if (isOpen()) - { - // Add sizes of children - S32 parent_item_height = getRect().getHeight(); - - for(folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit) - { - LLFolderViewFolder* folderp = (*fit); - folderp->setVisible(folderp->isPotentiallyVisible()); - - if (folderp->getVisible()) - { - S32 child_width = *width; - S32 child_height = 0; - S32 child_top = parent_item_height - ll_round(running_height); - - target_height += folderp->arrange( &child_width, &child_height ); - - running_height += (F32)child_height; - *width = llmax(*width, child_width); - folderp->setOrigin( 0, child_top - folderp->getRect().getHeight() ); - } - } - for(items_t::iterator iit = mItems.begin(); - iit != mItems.end(); ++iit) - { - LLFolderViewItem* itemp = (*iit); - itemp->setVisible(itemp->isPotentiallyVisible()); - - if (itemp->getVisible()) - { - S32 child_width = *width; - S32 child_height = 0; - S32 child_top = parent_item_height - ll_round(running_height); - - target_height += itemp->arrange( &child_width, &child_height ); - // don't change width, as this item is as wide as its parent folder by construction - itemp->reshape( itemp->getRect().getWidth(), child_height); - - running_height += (F32)child_height; - *width = llmax(*width, child_width); - itemp->setOrigin( 0, child_top - itemp->getRect().getHeight() ); - } - } - } - - mTargetHeight = target_height; - // cache this width so next time we can just return it - mLastCalculatedWidth = *width; - } - else - { - // just use existing width - *width = mLastCalculatedWidth; - } - - // animate current height towards target height - if (llabs(mCurHeight - mTargetHeight) > 1.f) - { - mCurHeight = lerp(mCurHeight, mTargetHeight, LLSmoothInterpolation::getInterpolant(isOpen() ? FOLDER_OPEN_TIME_CONSTANT : FOLDER_CLOSE_TIME_CONSTANT)); - - requestArrange(); - - // hide child elements that fall out of current animated height - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();) - { - folders_t::iterator fit = iter++; - // number of pixels that bottom of folder label is from top of parent folder - if (getRect().getHeight() - (*fit)->getRect().mTop + (*fit)->getItemHeight() - > ll_round(mCurHeight) + mMaxFolderItemOverlap) - { - // hide if beyond current folder height - (*fit)->setVisible(false); - } - } - - for (items_t::iterator iter = mItems.begin(); - iter != mItems.end();) - { - items_t::iterator iit = iter++; - // number of pixels that bottom of item label is from top of parent folder - if (getRect().getHeight() - (*iit)->getRect().mBottom - > ll_round(mCurHeight) + mMaxFolderItemOverlap) - { - (*iit)->setVisible(false); - } - } - } - else - { - mCurHeight = mTargetHeight; - } - - // don't change width as this item is already as wide as its parent folder - reshape(getRect().getWidth(),ll_round(mCurHeight)); - - // pass current height value back to parent - *height = ll_round(mCurHeight); - - return ll_round(mTargetHeight); -} - -bool LLFolderViewFolder::needsArrange() -{ - return mLastArrangeGeneration < getRoot()->getArrangeGeneration(); -} - -bool LLFolderViewFolder::descendantsPassedFilter(S32 filter_generation) -{ - return getViewModelItem()->descendantsPassedFilter(filter_generation); -} - -// Passes selection information on to children and record selection -// information if necessary. -bool LLFolderViewFolder::setSelection(LLFolderViewItem* selection, bool openitem, - bool take_keyboard_focus) -{ - bool rv = false; - if (selection == this) - { - if (!isSelected()) - { - selectItem(); - } - rv = true; - } - else - { - if (isSelected()) - { - deselectItem(); - } - rv = false; - } - bool child_selected = false; - - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();) - { - folders_t::iterator fit = iter++; - if((*fit)->setSelection(selection, openitem, take_keyboard_focus)) - { - rv = true; - child_selected = true; - } - } - for (items_t::iterator iter = mItems.begin(); - iter != mItems.end();) - { - items_t::iterator iit = iter++; - if((*iit)->setSelection(selection, openitem, take_keyboard_focus)) - { - rv = true; - child_selected = true; - } - } - if(openitem && child_selected && !mSingleFolderMode) - { - setOpenArrangeRecursively(true); - } - return rv; -} - -// This method is used to change the selection of an item. -// Recursively traverse all children; if 'selection' is 'this' then change -// the select status if necessary. -// Returns true if the selection state of this folder, or of a child, was changed. -bool LLFolderViewFolder::changeSelection(LLFolderViewItem* selection, bool selected) -{ - bool rv = false; - if(selection == this) - { - if (isSelected() != selected) - { - rv = true; - if (selected) - { - selectItem(); - } - else - { - deselectItem(); - } - } - } - - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();) - { - folders_t::iterator fit = iter++; - if((*fit)->changeSelection(selection, selected)) - { - rv = true; - } - } - for (items_t::iterator iter = mItems.begin(); - iter != mItems.end();) - { - items_t::iterator iit = iter++; - if((*iit)->changeSelection(selection, selected)) - { - rv = true; - } - } - return rv; -} - -LLFolderViewFolder* LLFolderViewFolder::getCommonAncestor(LLFolderViewItem* item_a, LLFolderViewItem* item_b, bool& reverse) -{ - if (!item_a->getParentFolder() || !item_b->getParentFolder()) return NULL; - - std::deque item_a_ancestors; - - LLFolderViewFolder* parent = item_a->getParentFolder(); - while(parent) - { - item_a_ancestors.push_back(parent); - parent = parent->getParentFolder(); - } - - std::deque item_b_ancestors; - - parent = item_b->getParentFolder(); - while(parent) - { - item_b_ancestors.push_back(parent); - parent = parent->getParentFolder(); - } - - LLFolderViewFolder* common_ancestor = item_a->getRoot(); - - while(item_a_ancestors.size() > item_b_ancestors.size()) - { - item_a = item_a_ancestors.front(); - item_a_ancestors.pop_front(); - } - - while(item_b_ancestors.size() > item_a_ancestors.size()) - { - item_b = item_b_ancestors.front(); - item_b_ancestors.pop_front(); - } - - while(item_a_ancestors.size()) - { - common_ancestor = item_a_ancestors.front(); - - if (item_a_ancestors.front() == item_b_ancestors.front()) - { - // which came first, sibling a or sibling b? - for (folders_t::iterator it = common_ancestor->mFolders.begin(), end_it = common_ancestor->mFolders.end(); - it != end_it; - ++it) - { - LLFolderViewItem* item = *it; - - if (item == item_a) - { - reverse = false; - return common_ancestor; - } - if (item == item_b) - { - reverse = true; - return common_ancestor; - } - } - - for (items_t::iterator it = common_ancestor->mItems.begin(), end_it = common_ancestor->mItems.end(); - it != end_it; - ++it) - { - LLFolderViewItem* item = *it; - - if (item == item_a) - { - reverse = false; - return common_ancestor; - } - if (item == item_b) - { - reverse = true; - return common_ancestor; - } - } - break; - } - - item_a = item_a_ancestors.front(); - item_a_ancestors.pop_front(); - item_b = item_b_ancestors.front(); - item_b_ancestors.pop_front(); - } - - return NULL; -} - -void LLFolderViewFolder::gatherChildRangeExclusive(LLFolderViewItem* start, LLFolderViewItem* end, bool reverse, std::vector& items) -{ - bool selecting = start == NULL; - if (reverse) - { - for (items_t::reverse_iterator it = mItems.rbegin(), end_it = mItems.rend(); - it != end_it; - ++it) - { - if (*it == end) - { - return; - } - if (selecting && (*it)->getVisible()) - { - items.push_back(*it); - } - - if (*it == start) - { - selecting = true; - } - } - for (folders_t::reverse_iterator it = mFolders.rbegin(), end_it = mFolders.rend(); - it != end_it; - ++it) - { - if (*it == end) - { - return; - } - - if (selecting && (*it)->getVisible()) - { - items.push_back(*it); - } - - if (*it == start) - { - selecting = true; - } - } - } - else - { - for (folders_t::iterator it = mFolders.begin(), end_it = mFolders.end(); - it != end_it; - ++it) - { - if (*it == end) - { - return; - } - - if (selecting && (*it)->getVisible()) - { - items.push_back(*it); - } - - if (*it == start) - { - selecting = true; - } - } - for (items_t::iterator it = mItems.begin(), end_it = mItems.end(); - it != end_it; - ++it) - { - if (*it == end) - { - return; - } - - if (selecting && (*it)->getVisible()) - { - items.push_back(*it); - } - - if (*it == start) - { - selecting = true; - } - } - } -} - -void LLFolderViewFolder::extendSelectionTo(LLFolderViewItem* new_selection) -{ - if (!getRoot()->getAllowMultiSelect()) - return; - - LLFolderViewItem* cur_selected_item = getRoot()->getCurSelectedItem(); - if (cur_selected_item == NULL) - { - cur_selected_item = new_selection; - } - - - bool reverse = false; - LLFolderViewFolder* common_ancestor = getCommonAncestor(cur_selected_item, new_selection, reverse); - if (!common_ancestor) - return; - - LLFolderViewItem* last_selected_item_from_cur = cur_selected_item; - LLFolderViewFolder* cur_folder = cur_selected_item->getParentFolder(); - - std::vector items_to_select_forward; - - while (cur_folder != common_ancestor) - { - cur_folder->gatherChildRangeExclusive(last_selected_item_from_cur, NULL, reverse, items_to_select_forward); - - last_selected_item_from_cur = cur_folder; - cur_folder = cur_folder->getParentFolder(); - } - - std::vector items_to_select_reverse; - - LLFolderViewItem* last_selected_item_from_new = new_selection; - cur_folder = new_selection->getParentFolder(); - while (cur_folder != common_ancestor) - { - cur_folder->gatherChildRangeExclusive(last_selected_item_from_new, NULL, !reverse, items_to_select_reverse); - - last_selected_item_from_new = cur_folder; - cur_folder = cur_folder->getParentFolder(); - } - - common_ancestor->gatherChildRangeExclusive(last_selected_item_from_cur, last_selected_item_from_new, reverse, items_to_select_forward); - - for (std::vector::reverse_iterator it = items_to_select_reverse.rbegin(), end_it = items_to_select_reverse.rend(); - it != end_it; - ++it) - { - items_to_select_forward.push_back(*it); - } - - LLFolderView* root = getRoot(); - - bool selection_reverse = new_selection->isSelected(); //indication that some elements are being deselected - - // array always go from 'will be selected' to ' will be unselected', iterate - // in opposite direction to simplify identification of 'point of origin' in - // case it is in the list we are working with - for (std::vector::reverse_iterator it = items_to_select_forward.rbegin(), end_it = items_to_select_forward.rend(); - it != end_it; - ++it) - { - LLFolderViewItem* item = *it; - bool selected = item->isSelected(); - if (!selection_reverse && selected) - { - // it is our 'point of origin' where we shift/expand from - // don't deselect it - selection_reverse = true; - } - else - { - root->changeSelection(item, !selected); - } - } - - if (selection_reverse) - { - // at some point we reversed selection, first element should be deselected - root->changeSelection(last_selected_item_from_cur, false); - } - - // element we expand to should always be selected - root->changeSelection(new_selection, true); -} - - -void LLFolderViewFolder::destroyView() -{ - while (!mItems.empty()) - { - LLFolderViewItem *itemp = mItems.back(); - mItems.pop_back(); - itemp->destroyView(); // LLFolderViewItem::destroyView() removes entry from mItems - } - - while (!mFolders.empty()) - { - LLFolderViewFolder *folderp = mFolders.back(); - mFolders.pop_back(); - folderp->destroyView(); // LLFolderVievFolder::destroyView() removes entry from mFolders - } - - LLFolderViewItem::destroyView(); -} - -// extractItem() removes the specified item from the folder, but -// doesn't delete it. -void LLFolderViewFolder::extractItem( LLFolderViewItem* item, bool deparent_model ) -{ - if (item->isSelected()) - getRoot()->clearSelection(); - items_t::iterator it = std::find(mItems.begin(), mItems.end(), item); - if(it == mItems.end()) - { - // This is an evil downcast. However, it's only doing - // pointer comparison to find if (which it should be ) the - // item is in the container, so it's pretty safe. - LLFolderViewFolder* f = static_cast(item); - folders_t::iterator ft; - ft = std::find(mFolders.begin(), mFolders.end(), f); - if (ft != mFolders.end()) - { - mFolders.erase(ft); - } - } - else - { - mItems.erase(it); - } - //item has been removed, need to update filter - if (deparent_model) - { - // in some cases model does not belong to parent view, is shared between views - getViewModelItem()->removeChild(item->getViewModelItem()); - } - //because an item is going away regardless of filter status, force rearrange - requestArrange(); - removeChild(item); -} - -bool LLFolderViewFolder::isMovable() -{ - if( !(getViewModelItem()->isItemMovable()) ) - { - return false; - } - - for (items_t::iterator iter = mItems.begin(); - iter != mItems.end();) - { - items_t::iterator iit = iter++; - if(!(*iit)->isMovable()) - { - return false; - } - } - - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();) - { - folders_t::iterator fit = iter++; - if(!(*fit)->isMovable()) - { - return false; - } - } - return true; -} - - -bool LLFolderViewFolder::isRemovable() -{ - if( !(getViewModelItem()->isItemRemovable()) ) - { - return false; - } - - for (items_t::iterator iter = mItems.begin(); - iter != mItems.end();) - { - items_t::iterator iit = iter++; - if(!(*iit)->isRemovable()) - { - return false; - } - } - - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();) - { - folders_t::iterator fit = iter++; - if(!(*fit)->isRemovable()) - { - return false; - } - } - return true; -} - -void LLFolderViewFolder::destroyRoot() -{ - delete this; -} - -// this is an internal method used for adding items to folders. -void LLFolderViewFolder::addItem(LLFolderViewItem* item) -{ - if (item->getParentFolder()) - { - item->getParentFolder()->extractItem(item); - } - item->setParentFolder(this); - - mItems.push_back(item); - - item->setRect(LLRect(0, 0, getRect().getWidth(), 0)); - item->setVisible(false); - - addChild(item); - - // When the model is already hooked into a hierarchy (i.e. has a parent), do not reparent it - // Note: this happens when models are created before views or shared between views - if (!item->getViewModelItem()->hasParent()) - { - getViewModelItem()->addChild(item->getViewModelItem()); - } -} - -// this is an internal method used for adding items to folders. -void LLFolderViewFolder::addFolder(LLFolderViewFolder* folder) -{ - if (folder->mParentFolder) - { - folder->mParentFolder->extractItem(folder); - } - folder->mParentFolder = this; - mFolders.push_back(folder); - folder->setOrigin(0, 0); - folder->reshape(getRect().getWidth(), 0); - folder->setVisible(false); - // rearrange all descendants too, as our indentation level might have changed - //folder->requestArrange(); - //requestSort(); - - addChild(folder); - - // When the model is already hooked into a hierarchy (i.e. has a parent), do not reparent it - // Note: this happens when models are created before views or shared between views - if (!folder->getViewModelItem()->hasParent()) - { - getViewModelItem()->addChild(folder->getViewModelItem()); - } -} - -void LLFolderViewFolder::requestArrange() -{ - mLastArrangeGeneration = -1; - // flag all items up to root - if (mParentFolder) - { - mParentFolder->requestArrange(); - } -} - -void LLFolderViewFolder::toggleOpen() -{ - setOpen(!isOpen()); -} - -// Force a folder open or closed -void LLFolderViewFolder::setOpen(bool openitem) -{ - if(mSingleFolderMode) - { - // navigateToFolder can destroy this view - // delay it in case setOpen was called from click or key processing - doOnIdleOneTime([this]() - { - getViewModelItem()->navigateToFolder(); - }); - } - else - { - setOpenArrangeRecursively(openitem); - } -} - -void LLFolderViewFolder::setOpenArrangeRecursively(bool openitem, ERecurseType recurse) -{ - bool was_open = isOpen(); - mIsOpen = openitem; - if(!was_open && openitem) - { - getViewModelItem()->openItem(); - // openItem() will request content, it won't be incomplete - mIsFolderComplete = true; - } - else if(was_open && !openitem) - { - getViewModelItem()->closeItem(); - } - - if (recurse == RECURSE_DOWN || recurse == RECURSE_UP_DOWN) - { - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();) - { - folders_t::iterator fit = iter++; - (*fit)->setOpenArrangeRecursively(openitem, RECURSE_DOWN); /* Flawfinder: ignore */ - } - } - if (mParentFolder - && (recurse == RECURSE_UP - || recurse == RECURSE_UP_DOWN)) - { - mParentFolder->setOpenArrangeRecursively(openitem, RECURSE_UP); - } - - if (was_open != isOpen()) - { - requestArrange(); - } -} - -bool LLFolderViewFolder::handleDragAndDropFromChild(MASK mask, - bool drop, - EDragAndDropType c_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - bool accepted = mViewModelItem->dragOrDrop(mask,drop,c_type,cargo_data, tooltip_msg); - if (accepted) - { - mDragAndDropTarget = true; - *accept = ACCEPT_YES_MULTI; - } - else - { - *accept = ACCEPT_NO; - } - - // drag and drop to child item, so clear pending auto-opens - getRoot()->autoOpenTest(NULL); - - return true; -} - -void LLFolderViewFolder::openItem( void ) -{ - toggleOpen(); -} - -void LLFolderViewFolder::applyFunctorToChildren(LLFolderViewFunctor& functor) -{ - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();) - { - folders_t::iterator fit = iter++; - functor.doItem((*fit)); - } - for (items_t::iterator iter = mItems.begin(); - iter != mItems.end();) - { - items_t::iterator iit = iter++; - functor.doItem((*iit)); - } -} - -void LLFolderViewFolder::applyFunctorRecursively(LLFolderViewFunctor& functor) -{ - functor.doFolder(this); - - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();) - { - folders_t::iterator fit = iter++; - (*fit)->applyFunctorRecursively(functor); - } - for (items_t::iterator iter = mItems.begin(); - iter != mItems.end();) - { - items_t::iterator iit = iter++; - functor.doItem((*iit)); - } -} - -// LLView functionality -bool LLFolderViewFolder::handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - bool handled = false; - - if (isOpen()) - { - handled = (childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) != NULL); - } - - if (!handled) - { - handleDragAndDropToThisFolder(mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - - LL_DEBUGS("UserInput") << "dragAndDrop handled by LLFolderViewFolder" << LL_ENDL; - } - - return true; -} - -bool LLFolderViewFolder::handleDragAndDropToThisFolder(MASK mask, - bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - if (!mAllowDrop) - { - *accept = ACCEPT_NO; - tooltip_msg = LLTrans::getString("TooltipOutboxCannotDropOnRoot"); - return true; - } - - bool accepted = getViewModelItem()->dragOrDrop(mask,drop,cargo_type,cargo_data, tooltip_msg); - - if (accepted) - { - mDragAndDropTarget = true; - *accept = ACCEPT_YES_MULTI; - } - else - { - *accept = ACCEPT_NO; - } - - if (!drop && accepted) - { - getRoot()->autoOpenTest(this); - } - - return true; -} - - -bool LLFolderViewFolder::handleRightMouseDown( S32 x, S32 y, MASK mask ) -{ - bool handled = false; - - if( isOpen() ) - { - handled = childrenHandleRightMouseDown( x, y, mask ) != NULL; - } - if (!handled) - { - handled = LLFolderViewItem::handleRightMouseDown( x, y, mask ); - } - return handled; -} - - -bool LLFolderViewFolder::handleHover(S32 x, S32 y, MASK mask) -{ - mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight)); - - bool handled = LLView::handleHover(x, y, mask); - - if (!handled) - { - // this doesn't do child processing - handled = LLFolderViewItem::handleHover(x, y, mask); - } - - return handled; -} - -bool LLFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - bool handled = false; - if( isOpen() ) - { - handled = childrenHandleMouseDown(x,y,mask) != NULL; - } - if( !handled ) - { - if((mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad) - && !mSingleFolderMode) - { - toggleOpen(); - handled = true; - } - else - { - // do normal selection logic - handled = LLFolderViewItem::handleMouseDown(x, y, mask); - } - } - - return handled; -} - -bool LLFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask ) -{ - bool handled = false; - if(mSingleFolderMode) - { - static LLUICachedControl double_click_new_window("SingleModeDoubleClickOpenWindow", false); - if (double_click_new_window) - { - getViewModelItem()->navigateToFolder(true); - } - else - { - // navigating is going to destroy views and change children - // delay it untill handleDoubleClick processing is complete - doOnIdleOneTime([this]() - { - getViewModelItem()->navigateToFolder(false); - }); - } - return true; - } - - if( isOpen() ) - { - handled = childrenHandleDoubleClick( x, y, mask ) != NULL; - } - if( !handled ) - { - if(mDoubleClickOverride) - { - static LLUICachedControl double_click_action("MultiModeDoubleClickFolder", false); - if (double_click_action == 1) - { - getViewModelItem()->navigateToFolder(true); - return true; - } - if (double_click_action == 2) - { - getViewModelItem()->navigateToFolder(false, true); - return true; - } - } - if(mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad) - { - // don't select when user double-clicks plus sign - // so as not to contradict single-click behavior - toggleOpen(); - } - else - { - getRoot()->setSelection(this, false); - toggleOpen(); - } - handled = true; - } - return handled; -} - -void LLFolderViewFolder::draw() -{ - updateLabelRotation(); - - LLFolderViewItem::draw(); - - // draw children if root folder, or any other folder that is open or animating to closed state - if( getRoot() == this || (isOpen() || mCurHeight != mTargetHeight )) - { - LLView::draw(); - } - - mExpanderHighlighted = false; -} - -// this does prefix traversal, as folders are listed above their contents -LLFolderViewItem* LLFolderViewFolder::getNextFromChild( LLFolderViewItem* item, bool include_children ) -{ - bool found_item = false; - - LLFolderViewItem* result = NULL; - // when not starting from a given item, start at beginning - if(item == NULL) - { - found_item = true; - } - - // find current item among children - folders_t::iterator fit = mFolders.begin(); - folders_t::iterator fend = mFolders.end(); - - items_t::iterator iit = mItems.begin(); - items_t::iterator iend = mItems.end(); - - // if not trivially starting at the beginning, we have to find the current item - if (!found_item) - { - // first, look among folders, since they are always above items - for(; fit != fend; ++fit) - { - if(item == (*fit)) - { - found_item = true; - // if we are on downwards traversal - if (include_children && (*fit)->isOpen()) - { - // look for first descendant - return (*fit)->getNextFromChild(NULL, true); - } - // otherwise advance to next folder - ++fit; - include_children = true; - break; - } - } - - // didn't find in folders? Check items... - if (!found_item) - { - for(; iit != iend; ++iit) - { - if(item == (*iit)) - { - found_item = true; - // point to next item - ++iit; - break; - } - } - } - } - - if (!found_item) - { - // you should never call this method with an item that isn't a child - // so we should always find something - llassert(false); - return NULL; - } - - // at this point, either iit or fit point to a candidate "next" item - // if both are out of range, we need to punt up to our parent - - // now, starting from found folder, continue through folders - // searching for next visible folder - while(fit != fend && !(*fit)->getVisible()) - { - // turn on downwards traversal for next folder - ++fit; - } - - if (fit != fend) - { - result = (*fit); - } - else - { - // otherwise, scan for next visible item - while(iit != iend && !(*iit)->getVisible()) - { - ++iit; - } - - // check to see if we have a valid item - if (iit != iend) - { - result = (*iit); - } - } - - if( !result && mParentFolder ) - { - // If there are no siblings or children to go to, recurse up one level in the tree - // and skip children for this folder, as we've already discounted them - result = mParentFolder->getNextFromChild(this, false); - } - - return result; -} - -// this does postfix traversal, as folders are listed above their contents -LLFolderViewItem* LLFolderViewFolder::getPreviousFromChild( LLFolderViewItem* item, bool include_children ) -{ - bool found_item = false; - - LLFolderViewItem* result = NULL; - // when not starting from a given item, start at end - if(item == NULL) - { - found_item = true; - } - - // find current item among children - folders_t::reverse_iterator fit = mFolders.rbegin(); - folders_t::reverse_iterator fend = mFolders.rend(); - - items_t::reverse_iterator iit = mItems.rbegin(); - items_t::reverse_iterator iend = mItems.rend(); - - // if not trivially starting at the end, we have to find the current item - if (!found_item) - { - // first, look among items, since they are always below the folders - for(; iit != iend; ++iit) - { - if(item == (*iit)) - { - found_item = true; - // point to next item - ++iit; - break; - } - } - - // didn't find in items? Check folders... - if (!found_item) - { - for(; fit != fend; ++fit) - { - if(item == (*fit)) - { - found_item = true; - // point to next folder - ++fit; - break; - } - } - } - } - - if (!found_item) - { - // you should never call this method with an item that isn't a child - // so we should always find something - llassert(false); - return NULL; - } - - // at this point, either iit or fit point to a candidate "next" item - // if both are out of range, we need to punt up to our parent - - // now, starting from found item, continue through items - // searching for next visible item - while(iit != iend && !(*iit)->getVisible()) - { - ++iit; - } - - if (iit != iend) - { - // we found an appropriate item - result = (*iit); - } - else - { - // otherwise, scan for next visible folder - while(fit != fend && !(*fit)->getVisible()) - { - ++fit; - } - - // check to see if we have a valid folder - if (fit != fend) - { - // try selecting child element of this folder - if ((*fit)->isOpen() && include_children) - { - result = (*fit)->getPreviousFromChild(NULL); - } - else - { - result = (*fit); - } - } - } - - if( !result ) - { - // If there are no siblings or children to go to, recurse up one level in the tree - // which gets back to this folder, which will only be visited if it is a valid, visible item - result = this; - } - - return result; -} - +/** +* @file llfolderviewitem.cpp +* @brief Items and folders that can appear in a hierarchical folder view +* +* $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 "../newview/llviewerprecompiledheaders.h" + +#include "llflashtimer.h" + +#include "linden_common.h" +#include "llfolderviewitem.h" +#include "llfolderview.h" +#include "llfolderviewmodel.h" +#include "llpanel.h" +#include "llcallbacklist.h" +#include "llcriticaldamp.h" +#include "llclipboard.h" +#include "llfocusmgr.h" // gFocusMgr +#include "lltrans.h" +#include "llwindow.h" + +///---------------------------------------------------------------------------- +/// Class LLFolderViewItem +///---------------------------------------------------------------------------- + +static LLDefaultChildRegistry::Register r("folder_view_item"); + +// statics +std::map LLFolderViewItem::sFonts; // map of styles to fonts + +bool LLFolderViewItem::sColorSetInitialized = false; +LLUIColor LLFolderViewItem::sFgColor; +LLUIColor LLFolderViewItem::sHighlightBgColor; +LLUIColor LLFolderViewItem::sFlashBgColor; +LLUIColor LLFolderViewItem::sFocusOutlineColor; +LLUIColor LLFolderViewItem::sMouseOverColor; +LLUIColor LLFolderViewItem::sFilterBGColor; +LLUIColor LLFolderViewItem::sFilterTextColor; +LLUIColor LLFolderViewItem::sSuffixColor; +LLUIColor LLFolderViewItem::sSearchStatusColor; + +// only integers can be initialized in header +const F32 LLFolderViewItem::FOLDER_CLOSE_TIME_CONSTANT = 0.02f; +const F32 LLFolderViewItem::FOLDER_OPEN_TIME_CONSTANT = 0.03f; + +const LLColor4U DEFAULT_WHITE(255, 255, 255); + + +//static +LLFontGL* LLFolderViewItem::getLabelFontForStyle(U8 style) +{ + LLFontGL* rtn = sFonts[style]; + if (!rtn) // grab label font with this style, lazily + { + LLFontDescriptor labelfontdesc("SansSerif", "Small", style); + rtn = LLFontGL::getFont(labelfontdesc); + if (!rtn) + { + rtn = LLFontGL::getFontDefault(); + } + sFonts[style] = rtn; + } + return rtn; +} + +//static +void LLFolderViewItem::initClass() +{ +} + +//static +void LLFolderViewItem::cleanupClass() +{ + sFonts.clear(); +} + + +// NOTE: Optimize this, we call it a *lot* when opening a large inventory +LLFolderViewItem::Params::Params() +: root(), + listener(), + folder_arrow_image("folder_arrow_image"), + folder_indentation("folder_indentation"), + selection_image("selection_image"), + item_height("item_height"), + item_top_pad("item_top_pad"), + creation_date(), + allow_wear("allow_wear", true), + allow_drop("allow_drop", true), + font_color("font_color"), + font_highlight_color("font_highlight_color"), + left_pad("left_pad", 0), + icon_pad("icon_pad", 0), + icon_width("icon_width", 0), + text_pad("text_pad", 0), + text_pad_right("text_pad_right", 0), + single_folder_mode("single_folder_mode", false), + double_click_override("double_click_override", false), + arrow_size("arrow_size", 0), + max_folder_item_overlap("max_folder_item_overlap", 0) +{ } + +// Default constructor +LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) +: LLView(p), + mLabelWidth(0), + mLabelWidthDirty(false), + mSuffixNeedsRefresh(false), + mLabelPaddingRight(DEFAULT_LABEL_PADDING_RIGHT), + mParentFolder( NULL ), + mIsSelected( false ), + mIsCurSelection( false ), + mSelectPending(false), + mIsItemCut(false), + mCutGeneration(0), + mLabelStyle( LLFontGL::NORMAL ), + mHasVisibleChildren(false), + mLocalIndentation(p.folder_indentation), + mIndentation(0), + mItemHeight(p.item_height), + mControlLabelRotation(0.f), + mDragAndDropTarget(false), + mLabel(p.name), + mRoot(p.root), + mViewModelItem(p.listener), + mIsMouseOverTitle(false), + mAllowWear(p.allow_wear), + mAllowDrop(p.allow_drop), + mFontColor(p.font_color), + mFontHighlightColor(p.font_highlight_color), + mLeftPad(p.left_pad), + mIconPad(p.icon_pad), + mIconWidth(p.icon_width), + mTextPad(p.text_pad), + mTextPadRight(p.text_pad_right), + mArrowSize(p.arrow_size), + mSingleFolderMode(p.single_folder_mode), + mMaxFolderItemOverlap(p.max_folder_item_overlap), + mDoubleClickOverride(p.double_click_override) +{ + if (!sColorSetInitialized) + { + sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE); + sFlashBgColor = LLUIColorTable::instance().getColor("MenuItemFlashBgColor", DEFAULT_WHITE); + sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE); + sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE); + sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE); + sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE); + sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); + sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE); + sColorSetInitialized = true; + } + + if (mViewModelItem) + { + mViewModelItem->setFolderViewItem(this); + } +} + +// Destroys the object +LLFolderViewItem::~LLFolderViewItem() +{ + mViewModelItem = NULL; + gFocusMgr.removeKeyboardFocusWithoutCallback(this); +} + +bool LLFolderViewItem::postBuild() +{ + LLFolderViewModelItem* vmi = getViewModelItem(); + llassert(vmi); // not supposed to happen, if happens, find out why and fix + if (vmi) + { + // getDisplayName() is expensive (due to internal getLabelSuffix() and name building) + // it also sets search strings so it requires a filter reset + mLabel = vmi->getDisplayName(); + setToolTip(vmi->getName()); + + // Dirty the filter flag of the model from the view (CHUI-849) + vmi->dirtyFilter(); + } + + // Don't do full refresh on constructor if it is possible to avoid + // it significantly slows down bulk view creation. + // Todo: Ideally we need to move getDisplayName() out of constructor as well. + // Like: make a logic that will let filter update search string, + // while LLFolderViewItem::arrange() updates visual part + mSuffixNeedsRefresh = true; + mLabelWidthDirty = true; + return true; +} + +LLFolderView* LLFolderViewItem::getRoot() +{ + return mRoot; +} + +const LLFolderView* LLFolderViewItem::getRoot() const +{ + return mRoot; +} +// Returns true if this object is a child (or grandchild, etc.) of potential_ancestor. +bool LLFolderViewItem::isDescendantOf( const LLFolderViewFolder* potential_ancestor ) +{ + LLFolderViewItem* root = this; + while( root->mParentFolder ) + { + if( root->mParentFolder == potential_ancestor ) + { + return true; + } + root = root->mParentFolder; + } + return false; +} + +LLFolderViewItem* LLFolderViewItem::getNextOpenNode(bool include_children) +{ + if (!mParentFolder) + { + return NULL; + } + + LLFolderViewItem* itemp = mParentFolder->getNextFromChild( this, include_children ); + while(itemp && !itemp->getVisible()) + { + LLFolderViewItem* next_itemp = itemp->mParentFolder->getNextFromChild( itemp, include_children ); + if (itemp == next_itemp) + { + // hit last item + return itemp->getVisible() ? itemp : this; + } + itemp = next_itemp; + } + + return itemp; +} + +LLFolderViewItem* LLFolderViewItem::getPreviousOpenNode(bool include_children) +{ + if (!mParentFolder) + { + return NULL; + } + + LLFolderViewItem* itemp = mParentFolder->getPreviousFromChild( this, include_children ); + + // Skip over items that are invisible or are hidden from the UI. + while(itemp && !itemp->getVisible()) + { + LLFolderViewItem* next_itemp = itemp->mParentFolder->getPreviousFromChild( itemp, include_children ); + if (itemp == next_itemp) + { + // hit first item + return itemp->getVisible() ? itemp : this; + } + itemp = next_itemp; + } + + return itemp; +} + +bool LLFolderViewItem::passedFilter(S32 filter_generation) +{ + return getViewModelItem()->passedFilter(filter_generation); +} + +bool LLFolderViewItem::isPotentiallyVisible(S32 filter_generation) +{ + if (filter_generation < 0) + { + filter_generation = getFolderViewModel()->getFilter().getFirstSuccessGeneration(); + } + LLFolderViewModelItem* model = getViewModelItem(); + bool visible = model->passedFilter(filter_generation); + if (model->getMarkedDirtyGeneration() >= filter_generation) + { + // unsure visibility state + // retaining previous visibility until item is updated or filter generation changes + visible |= getVisible(); + } + return visible; +} + +void LLFolderViewItem::refresh() +{ + LLFolderViewModelItem& vmi = *getViewModelItem(); + + mLabel = vmi.getDisplayName(); + setToolTip(vmi.getName()); + // icons are slightly expensive to get, can be optimized + // see LLInventoryIcon::getIcon() + mIcon = vmi.getIcon(); + mIconOpen = vmi.getIconOpen(); + mIconOverlay = vmi.getIconOverlay(); + + if (mRoot->useLabelSuffix()) + { + // Very Expensive! + // Can do a number of expensive checks, like checking active motions, wearables or friend list + mLabelStyle = vmi.getLabelStyle(); + mLabelSuffix = vmi.getLabelSuffix(); + } + + // Dirty the filter flag of the model from the view (CHUI-849) + vmi.dirtyFilter(); + + mLabelWidthDirty = true; + mSuffixNeedsRefresh = false; +} + +void LLFolderViewItem::refreshSuffix() +{ + LLFolderViewModelItem const* vmi = getViewModelItem(); + + // icons are slightly expensive to get, can be optimized + // see LLInventoryIcon::getIcon() + mIcon = vmi->getIcon(); + mIconOpen = vmi->getIconOpen(); + mIconOverlay = vmi->getIconOverlay(); + + if (mRoot->useLabelSuffix()) + { + // Very Expensive! + // Can do a number of expensive checks, like checking active motions, wearables or friend list + mLabelStyle = vmi->getLabelStyle(); + mLabelSuffix = vmi->getLabelSuffix(); + } + + mLabelWidthDirty = true; + mSuffixNeedsRefresh = false; +} + +// Utility function for LLFolderView +void LLFolderViewItem::arrangeAndSet(bool set_selection, + bool take_keyboard_focus) +{ + LLFolderView* root = getRoot(); + if (getParentFolder()) + { + getParentFolder()->requestArrange(); + } + if(set_selection) + { + getRoot()->setSelection(this, true, take_keyboard_focus); + if(root) + { + root->scrollToShowSelection(); + } + } +} + + +std::set LLFolderViewItem::getSelectionList() const +{ + std::set selection; + return selection; +} + +// addToFolder() returns true if it succeeds. false otherwise +void LLFolderViewItem::addToFolder(LLFolderViewFolder* folder) +{ + folder->addItem(this); + + // Compute indentation since parent folder changed + mIndentation = (getParentFolder()) + ? getParentFolder()->getIndentation() + mLocalIndentation + : 0; +} + + +// Finds width and height of this object and its children. Also +// makes sure that this view and its children are the right size. +S32 LLFolderViewItem::arrange( S32* width, S32* height ) +{ + // Only indent deeper items in hierarchy + mIndentation = (getParentFolder()) + ? getParentFolder()->getIndentation() + mLocalIndentation + : 0; + if (mLabelWidthDirty) + { + if (mSuffixNeedsRefresh) + { + // Expensive. But despite refreshing label, + // it is purely visual, so it is fine to do at our laisure + refreshSuffix(); + } + mLabelWidth = getLabelXPos() + getLabelFontForStyle(mLabelStyle)->getWidth(mLabel) + getLabelFontForStyle(LLFontGL::NORMAL)->getWidth(mLabelSuffix) + mLabelPaddingRight; + mLabelWidthDirty = false; + } + + *width = llmax(*width, mLabelWidth); + + // determine if we need to use ellipses to avoid horizontal scroll. EXT-719 + bool use_ellipses = getRoot()->getUseEllipses(); + if (use_ellipses) + { + // limit to set rect to avoid horizontal scrollbar + *width = llmin(*width, getRoot()->getRect().getWidth()); + } + *height = getItemHeight(); + return *height; +} + +S32 LLFolderViewItem::getItemHeight() const +{ + return mItemHeight; +} + +S32 LLFolderViewItem::getLabelXPos() +{ + return getIndentation() + mArrowSize + mTextPad + mIconWidth + mIconPad; +} + +S32 LLFolderViewItem::getIconPad() +{ + return mIconPad; +} + +S32 LLFolderViewItem::getTextPad() +{ + return mTextPad; +} + +// *TODO: This can be optimized a lot by simply recording that it is +// selected in the appropriate places, and assuming that set selection +// means 'deselect' for a leaf item. Do this optimization after +// multiple selection is implemented to make sure it all plays nice +// together. +bool LLFolderViewItem::setSelection(LLFolderViewItem* selection, bool openitem, bool take_keyboard_focus) +{ + if (selection == this && !mIsSelected) + { + selectItem(); + } + else if (mIsSelected) // Deselect everything else. + { + deselectItem(); + } + return mIsSelected; +} + +bool LLFolderViewItem::changeSelection(LLFolderViewItem* selection, bool selected) +{ + if (selection == this) + { + if (mIsSelected) + { + deselectItem(); + } + else + { + selectItem(); + } + return true; + } + return false; +} + +void LLFolderViewItem::deselectItem(void) +{ + mIsSelected = false; +} + +void LLFolderViewItem::selectItem(void) +{ + if (!mIsSelected) + { + mIsSelected = true; + getViewModelItem()->selectItem(); + } +} + +bool LLFolderViewItem::isMovable() +{ + return getViewModelItem()->isItemMovable(); +} + +bool LLFolderViewItem::isRemovable() +{ + return getViewModelItem()->isItemRemovable(); +} + +void LLFolderViewItem::destroyView() +{ + getRoot()->removeFromSelectionList(this); + + if (mParentFolder) + { + // removeView deletes me + mParentFolder->extractItem(this); + } + delete this; +} + +// Call through to the viewed object and return true if it can be +// removed. +//bool LLFolderViewItem::removeRecursively(bool single_item) +bool LLFolderViewItem::remove() +{ + if(!isRemovable()) + { + return false; + } + return getViewModelItem()->removeItem(); +} + +// Build an appropriate context menu for the item. +void LLFolderViewItem::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + getViewModelItem()->buildContextMenu(menu, flags); +} + +void LLFolderViewItem::openItem( void ) +{ + if (mAllowWear || !getViewModelItem()->isItemWearable()) + { + getViewModelItem()->openItem(); + } +} + +void LLFolderViewItem::rename(const std::string& new_name) +{ + if( !new_name.empty() ) + { + getViewModelItem()->renameItem(new_name); + } +} + +const std::string& LLFolderViewItem::getName( void ) const +{ + static const std::string noName(""); + return getViewModelItem() ? getViewModelItem()->getName() : noName; +} + +// LLView functionality +bool LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask ) +{ + if(!mIsSelected) + { + getRoot()->setSelection(this, false); + } + make_ui_sound("UISndClick"); + return true; +} + +bool LLFolderViewItem::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + if (LLView::childrenHandleMouseDown(x, y, mask)) + { + return true; + } + + // No handler needed for focus lost since this class has no + // state that depends on it. + gFocusMgr.setMouseCapture( this ); + + if (!mIsSelected) + { + if(mask & MASK_CONTROL) + { + getRoot()->changeSelection(this, !mIsSelected); + } + else if (mask & MASK_SHIFT) + { + getParentFolder()->extendSelectionTo(this); + } + else + { + getRoot()->setSelection(this, false); + } + make_ui_sound("UISndClick"); + } + else + { + // If selected, we reserve the decision of deselecting/reselecting to the mouse up moment. + // This is necessary so we maintain selection consistent when starting a drag. + mSelectPending = true; + } + + mDragStartX = x; + mDragStartY = y; + return true; +} + +bool LLFolderViewItem::handleHover( S32 x, S32 y, MASK mask ) +{ + mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight)); + + if( hasMouseCapture() && isMovable() ) + { + LLFolderView* root = getRoot(); + + if( (x - mDragStartX) * (x - mDragStartX) + (y - mDragStartY) * (y - mDragStartY) > DRAG_N_DROP_DISTANCE_THRESHOLD * DRAG_N_DROP_DISTANCE_THRESHOLD + && root->getAllowDrag() + && root->getCurSelectedItem() + && root->startDrag()) + { + // RN: when starting drag and drop, clear out last auto-open + root->autoOpenTest(NULL); + root->setShowSelectionContext(true); + + // Release keyboard focus, so that if stuff is dropped into the + // world, pressing the delete key won't blow away the inventory + // item. + gFocusMgr.setKeyboardFocus(NULL); + + getWindow()->setCursor(UI_CURSOR_ARROW); + } + else if (x != mDragStartX || y != mDragStartY) + { + getWindow()->setCursor(UI_CURSOR_NOLOCKED); + } + + root->clearHoveredItem(); + return true; + } + else + { + LLFolderView* pRoot = getRoot(); + pRoot->setHoveredItem(this); + pRoot->setShowSelectionContext(false); + getWindow()->setCursor(UI_CURSOR_ARROW); + // let parent handle this then... + return false; + } +} + + +bool LLFolderViewItem::handleDoubleClick( S32 x, S32 y, MASK mask ) +{ + openItem(); + return true; +} + +bool LLFolderViewItem::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + if (LLView::childrenHandleMouseUp(x, y, mask)) + { + return true; + } + + // if mouse hasn't moved since mouse down... + if ( pointInView(x, y) && mSelectPending ) + { + //...then select + if(mask & MASK_CONTROL) + { + getRoot()->changeSelection(this, !mIsSelected); + } + else if (mask & MASK_SHIFT) + { + getParentFolder()->extendSelectionTo(this); + } + else + { + getRoot()->setSelection(this, false); + } + } + + mSelectPending = false; + + if( hasMouseCapture() ) + { + if (getRoot()) + { + getRoot()->setShowSelectionContext(false); + } + gFocusMgr.setMouseCapture( NULL ); + } + return true; +} + +void LLFolderViewItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + mIsMouseOverTitle = false; + + // NOTE: LLViewerWindow::updateUI() calls "enter" before "leave"; if the mouse moved to another item, we can't just outright clear it + LLFolderView* pRoot = getRoot(); + if (this == pRoot->getHoveredItem()) + { + pRoot->clearHoveredItem(); + } +} + +bool LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + bool handled = false; + bool accepted = getViewModelItem()->dragOrDrop(mask,drop,cargo_type,cargo_data, tooltip_msg); + handled = accepted; + if (accepted) + { + mDragAndDropTarget = true; + *accept = ACCEPT_YES_MULTI; + } + else + { + *accept = ACCEPT_NO; + } + if(mParentFolder && !handled) + { + // store this item to get it in LLFolderBridge::dragItemIntoFolder on drop event. + mRoot->setDraggingOverItem(this); + handled = mParentFolder->handleDragAndDropFromChild(mask,drop,cargo_type,cargo_data,accept,tooltip_msg); + mRoot->setDraggingOverItem(NULL); + } + if (handled) + { + LL_DEBUGS("UserInput") << "dragAndDrop handled by LLFolderViewItem" << LL_ENDL; + } + + return handled; +} + +void LLFolderViewItem::drawOpenFolderArrow(const Params& default_params, const LLUIColor& fg_color) +{ + //--------------------------------------------------------------------------------// + // Draw open folder arrow + // + const S32 TOP_PAD = default_params.item_top_pad; + + if (hasVisibleChildren() || !isFolderComplete()) + { + LLUIImage* arrow_image = default_params.folder_arrow_image; + gl_draw_scaled_rotated_image( + mIndentation, getRect().getHeight() - mArrowSize - mTextPad - TOP_PAD, + mArrowSize, mArrowSize, mControlLabelRotation, arrow_image->getImage(), fg_color); + } +} + +/*virtual*/ bool LLFolderViewItem::isHighlightAllowed() +{ + return mIsSelected; +} + +/*virtual*/ bool LLFolderViewItem::isHighlightActive() +{ + return mIsCurSelection; +} + +/*virtual*/ bool LLFolderViewItem::isFadeItem() +{ + LLClipboard& clipboard = LLClipboard::instance(); + if (mCutGeneration != clipboard.getGeneration()) + { + mCutGeneration = clipboard.getGeneration(); + mIsItemCut = clipboard.isCutMode() + && ((getParentFolder() && getParentFolder()->isFadeItem()) + || getViewModelItem()->isCutToClipboard()); + } + return mIsItemCut; +} + +void LLFolderViewItem::drawHighlight(const bool showContent, const bool hasKeyboardFocus, const LLUIColor &selectColor, const LLUIColor &flashColor, + const LLUIColor &focusOutlineColor, const LLUIColor &mouseOverColor) +{ + const S32 focus_top = getRect().getHeight(); + const S32 focus_bottom = getRect().getHeight() - mItemHeight; + const bool folder_open = (getRect().getHeight() > mItemHeight + 4); + const S32 FOCUS_LEFT = 1; + + // Determine which background color to use for highlighting + LLUIColor bgColor = (isFlashing() ? flashColor : selectColor); + + //--------------------------------------------------------------------------------// + // Draw highlight for selected items + // Note: Always render "current" item or flashing item, only render other selected + // items if mShowSingleSelection is false. + // + if (isHighlightAllowed()) + + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + // Highlight for selected but not current items + if (!isHighlightActive() && !isFlashing()) + { + LLColor4 bg_color = bgColor; + // do time-based fade of extra objects + F32 fade_time = (getRoot() ? getRoot()->getSelectionFadeElapsedTime() : 0.0f); + if (getRoot() && getRoot()->getShowSingleSelection()) + { + // fading out + bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, bg_color.mV[VALPHA], 0.f); + } + else + { + // fading in + bg_color.mV[VALPHA] = clamp_rescale(fade_time, 0.f, 0.4f, 0.f, bg_color.mV[VALPHA]); + } + gl_rect_2d(FOCUS_LEFT, + focus_top, + getRect().getWidth() - 2, + focus_bottom, + bg_color, hasKeyboardFocus); + } + + // Highlight for currently selected or flashing item + if (isHighlightActive()) + { + // Background + gl_rect_2d(FOCUS_LEFT, + focus_top, + getRect().getWidth() - 2, + focus_bottom, + bgColor, hasKeyboardFocus); + // Outline + gl_rect_2d(FOCUS_LEFT, + focus_top, + getRect().getWidth() - 2, + focus_bottom, + focusOutlineColor, false); + } + + if (folder_open) + { + gl_rect_2d(FOCUS_LEFT, + focus_bottom + 1, // overlap with bottom edge of above rect + getRect().getWidth() - 2, + 0, + focusOutlineColor, false); + if (showContent && !isFlashing()) + { + gl_rect_2d(FOCUS_LEFT, + focus_bottom + 1, + getRect().getWidth() - 2, + 0, + bgColor, true); + } + } + } + else if (mIsMouseOverTitle) + { + gl_rect_2d(FOCUS_LEFT, + focus_top, + getRect().getWidth() - 2, + focus_bottom, + mouseOverColor, false); + } + + //--------------------------------------------------------------------------------// + // Draw DragNDrop highlight + // + if (mDragAndDropTarget) + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gl_rect_2d(FOCUS_LEFT, + focus_top, + getRect().getWidth() - 2, + focus_bottom, + bgColor, false); + if (folder_open) + { + gl_rect_2d(FOCUS_LEFT, + focus_bottom + 1, // overlap with bottom edge of above rect + getRect().getWidth() - 2, + 0, + bgColor, false); + } + mDragAndDropTarget = false; + } +} + +void LLFolderViewItem::drawLabel(const LLFontGL * font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x) +{ + //--------------------------------------------------------------------------------// + // Draw the actual label text + // + font->renderUTF8(mLabel, 0, x, y, color, + LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, + S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, /*use_ellipses*/true); +} + +void LLFolderViewItem::draw() +{ + const bool show_context = (getRoot() ? getRoot()->getShowSelectionContext() : false); + const bool filled = show_context || (getRoot() ? getRoot()->getParentPanel()->hasFocus() : false); // If we have keyboard focus, draw selection filled + + const Params& default_params = LLUICtrlFactory::getDefaultParams(); + const S32 TOP_PAD = default_params.item_top_pad; + + const LLFontGL* font = getLabelFontForStyle(mLabelStyle); + + getViewModelItem()->update(); + + if(!mSingleFolderMode) + { + drawOpenFolderArrow(default_params, sFgColor); + } + + drawHighlight(show_context, filled, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); + + //--------------------------------------------------------------------------------// + // Draw open icon + // + const S32 icon_x = mIndentation + mArrowSize + mTextPad; + if (!mIconOpen.isNull() && (llabs(mControlLabelRotation) > 80)) // For open folders + { + mIconOpen->draw(icon_x, getRect().getHeight() - mIconOpen->getHeight() - TOP_PAD + 1); + } + else if (mIcon) + { + mIcon->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1); + } + + if (mIconOverlay && getRoot()->showItemLinkOverlays()) + { + mIconOverlay->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1); + } + + //--------------------------------------------------------------------------------// + // Exit if no label to draw + // + if (mLabel.empty()) + { + return; + } + + std::string::size_type filter_string_length = mViewModelItem->hasFilterStringMatch() ? mViewModelItem->getFilterStringSize() : 0; + F32 right_x = 0; + F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; + F32 text_left = (F32)getLabelXPos(); + std::string combined_string = mLabel + mLabelSuffix; + + const LLFontGL* suffix_font = getLabelFontForStyle(LLFontGL::NORMAL); + S32 filter_offset = mViewModelItem->getFilterStringOffset(); + if (filter_string_length > 0) + { + S32 bottom = llfloor(getRect().getHeight() - font->getLineHeight() - 3 - TOP_PAD); + S32 top = getRect().getHeight() - TOP_PAD; + if(mLabelSuffix.empty() || (font == suffix_font)) + { + S32 left = ll_round(text_left) + font->getWidth(combined_string, 0, mViewModelItem->getFilterStringOffset()) - 2; + S32 right = left + font->getWidth(combined_string, mViewModelItem->getFilterStringOffset(), filter_string_length) + 2; + + LLUIImage* box_image = default_params.selection_image; + LLRect box_rect(left, top, right, bottom); + box_image->draw(box_rect, sFilterBGColor); + } + else + { + S32 label_filter_length = llmin((S32)mLabel.size() - filter_offset, (S32)filter_string_length); + if(label_filter_length > 0) + { + S32 left = ll_round(text_left) + font->getWidthF32(mLabel, 0, llmin(filter_offset, (S32)mLabel.size())) - 2; + S32 right = left + font->getWidthF32(mLabel, filter_offset, label_filter_length) + 2; + LLUIImage* box_image = default_params.selection_image; + LLRect box_rect(left, top, right, bottom); + box_image->draw(box_rect, sFilterBGColor); + } + S32 suffix_filter_length = label_filter_length > 0 ? filter_string_length - label_filter_length : filter_string_length; + if(suffix_filter_length > 0) + { + S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); + S32 left = ll_round(text_left) + font->getWidthF32(mLabel, 0, mLabel.size()) + suffix_font->getWidthF32(mLabelSuffix, 0, suffix_offset) - 2; + S32 right = left + suffix_font->getWidthF32(mLabelSuffix, suffix_offset, suffix_filter_length) + 2; + LLUIImage* box_image = default_params.selection_image; + LLRect box_rect(left, top, right, bottom); + box_image->draw(box_rect, sFilterBGColor); + } + } + } + + LLColor4 color = (mIsSelected && filled) ? mFontHighlightColor : mFontColor; + + if (isFadeItem()) + { + // Fade out item color to indicate it's being cut + color.mV[VALPHA] *= 0.5f; + } + drawLabel(font, text_left, y, color, right_x); + + //--------------------------------------------------------------------------------// + // Draw label suffix + // + if (!mLabelSuffix.empty()) + { + suffix_font->renderUTF8( mLabelSuffix, 0, right_x, y, isFadeItem() ? color : (LLColor4)sSuffixColor, + LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, + S32_MAX, S32_MAX, &right_x); + } + + //--------------------------------------------------------------------------------// + // Highlight string match + // + if (filter_string_length > 0) + { + if(mLabelSuffix.empty() || (font == suffix_font)) + { + F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string, filter_offset, filter_string_length); + F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; + font->renderUTF8(combined_string, filter_offset, match_string_left, yy, + sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, + filter_string_length, S32_MAX, &right_x); + } + else + { + S32 label_filter_length = llmin((S32)mLabel.size() - filter_offset, (S32)filter_string_length); + if(label_filter_length > 0) + { + F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, filter_offset + label_filter_length) - font->getWidthF32(mLabel, filter_offset, label_filter_length); + F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; + font->renderUTF8(mLabel, filter_offset, match_string_left, yy, + sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, + label_filter_length, S32_MAX, &right_x); + } + + S32 suffix_filter_length = label_filter_length > 0 ? filter_string_length - label_filter_length : filter_string_length; + if(suffix_filter_length > 0) + { + S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); + F32 match_string_left = text_left + font->getWidthF32(mLabel, 0, mLabel.size()) + suffix_font->getWidthF32(mLabelSuffix, 0, suffix_offset + suffix_filter_length) - suffix_font->getWidthF32(mLabelSuffix, suffix_offset, suffix_filter_length); + F32 yy = (F32)getRect().getHeight() - suffix_font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; + suffix_font->renderUTF8(mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, + LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, + suffix_filter_length, S32_MAX, &right_x); + } + } + + } + + //Gilbert Linden 9-20-2012: Although this should be legal, removing it because it causes the mLabelSuffix rendering to + //be distorted...oddly. I initially added this in but didn't need it after all. So removing to prevent unnecessary bug. + //LLView::draw(); +} + +const LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void ) const +{ + return getRoot()->getFolderViewModel(); +} + +LLFolderViewModelInterface* LLFolderViewItem::getFolderViewModel( void ) +{ + return getRoot()->getFolderViewModel(); +} + +bool LLFolderViewItem::isInSelection() const +{ + return mIsSelected || (mParentFolder && mParentFolder->isInSelection()); +} + + + +///---------------------------------------------------------------------------- +/// Class LLFolderViewFolder +///---------------------------------------------------------------------------- + +LLFolderViewFolder::LLFolderViewFolder( const LLFolderViewItem::Params& p ): + LLFolderViewItem( p ), + mIsOpen(false), + mExpanderHighlighted(false), + mCurHeight(0.f), + mTargetHeight(0.f), + mAutoOpenCountdown(0.f), + mIsFolderComplete(false), // folder might have children that are not loaded yet. + mAreChildrenInited(false), // folder might have children that are not built yet. + mLastArrangeGeneration( -1 ), + mLastCalculatedWidth(0) +{ +} + +void LLFolderViewFolder::updateLabelRotation() +{ + if (mAutoOpenCountdown != 0.f) + { + mControlLabelRotation = mAutoOpenCountdown * -90.f; + } + else if (isOpen()) + { + mControlLabelRotation = lerp(mControlLabelRotation, -90.f, LLSmoothInterpolation::getInterpolant(0.04f)); + } + else + { + mControlLabelRotation = lerp(mControlLabelRotation, 0.f, LLSmoothInterpolation::getInterpolant(0.025f)); + } +} + +// Destroys the object +LLFolderViewFolder::~LLFolderViewFolder( void ) +{ + // The LLView base class takes care of object destruction. make sure that we + // don't have mouse or keyboard focus + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() +} + +// addToFolder() returns true if it succeeds. false otherwise +void LLFolderViewFolder::addToFolder(LLFolderViewFolder* folder) +{ + folder->addFolder(this); + + // Compute indentation since parent folder changed + mIndentation = (getParentFolder()) + ? getParentFolder()->getIndentation() + mLocalIndentation + : 0; + + if(isOpen() && folder->isOpen()) + { + requestArrange(); + } +} + +static LLTrace::BlockTimerStatHandle FTM_ARRANGE("Arrange"); + +// Make everything right and in the right place ready for drawing (CHUI-849) +// * Sort everything correctly if necessary +// * Turn widgets visible/invisible according to their model filtering state +// * Takes animation state into account for opening/closing of folders (this makes widgets visible/invisible) +// * Reposition visible widgets so that they line up correctly with no gap +// * Compute the width and height of the current folder and its children +// * Makes sure that this view and its children are the right size +S32 LLFolderViewFolder::arrange( S32* width, S32* height ) +{ + // Sort before laying out contents + // Note that we sort from the root (CHUI-849) + if (mAreChildrenInited) + { + getRoot()->getFolderViewModel()->sort(this); + } + + LL_RECORD_BLOCK_TIME(FTM_ARRANGE); + + // evaluate mHasVisibleChildren + mHasVisibleChildren = false; + if (mAreChildrenInited && getViewModelItem()->descendantsPassedFilter()) + { + // We have to verify that there's at least one child that's not filtered out + bool found = false; + // Try the items first + for (items_t::iterator iit = mItems.begin(); iit != mItems.end(); ++iit) + { + LLFolderViewItem* itemp = (*iit); + found = itemp->isPotentiallyVisible(); + if (found) + break; + } + if (!found) + { + // If no item found, try the folders + for (folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit) + { + LLFolderViewFolder* folderp = (*fit); + found = folderp->isPotentiallyVisible(); + if (found) + break; + } + } + + mHasVisibleChildren = found; + } + if (!mIsFolderComplete && mAreChildrenInited) + { + mIsFolderComplete = getFolderViewModel()->isFolderComplete(this); + } + + + + // calculate height as a single item (without any children), and reshapes rectangle to match + LLFolderViewItem::arrange( width, height ); + + // clamp existing animated height so as to never get smaller than a single item + mCurHeight = llmax((F32)*height, mCurHeight); + + // initialize running height value as height of single item in case we have no children + F32 running_height = (F32)*height; + F32 target_height = (F32)*height; + + // are my children visible? + if (needsArrange()) + { + // set last arrange generation first, in case children are animating + // and need to be arranged again + mLastArrangeGeneration = getRoot()->getArrangeGeneration(); + if (isOpen()) + { + // Add sizes of children + S32 parent_item_height = getRect().getHeight(); + + for(folders_t::iterator fit = mFolders.begin(); fit != mFolders.end(); ++fit) + { + LLFolderViewFolder* folderp = (*fit); + folderp->setVisible(folderp->isPotentiallyVisible()); + + if (folderp->getVisible()) + { + S32 child_width = *width; + S32 child_height = 0; + S32 child_top = parent_item_height - ll_round(running_height); + + target_height += folderp->arrange( &child_width, &child_height ); + + running_height += (F32)child_height; + *width = llmax(*width, child_width); + folderp->setOrigin( 0, child_top - folderp->getRect().getHeight() ); + } + } + for(items_t::iterator iit = mItems.begin(); + iit != mItems.end(); ++iit) + { + LLFolderViewItem* itemp = (*iit); + itemp->setVisible(itemp->isPotentiallyVisible()); + + if (itemp->getVisible()) + { + S32 child_width = *width; + S32 child_height = 0; + S32 child_top = parent_item_height - ll_round(running_height); + + target_height += itemp->arrange( &child_width, &child_height ); + // don't change width, as this item is as wide as its parent folder by construction + itemp->reshape( itemp->getRect().getWidth(), child_height); + + running_height += (F32)child_height; + *width = llmax(*width, child_width); + itemp->setOrigin( 0, child_top - itemp->getRect().getHeight() ); + } + } + } + + mTargetHeight = target_height; + // cache this width so next time we can just return it + mLastCalculatedWidth = *width; + } + else + { + // just use existing width + *width = mLastCalculatedWidth; + } + + // animate current height towards target height + if (llabs(mCurHeight - mTargetHeight) > 1.f) + { + mCurHeight = lerp(mCurHeight, mTargetHeight, LLSmoothInterpolation::getInterpolant(isOpen() ? FOLDER_OPEN_TIME_CONSTANT : FOLDER_CLOSE_TIME_CONSTANT)); + + requestArrange(); + + // hide child elements that fall out of current animated height + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();) + { + folders_t::iterator fit = iter++; + // number of pixels that bottom of folder label is from top of parent folder + if (getRect().getHeight() - (*fit)->getRect().mTop + (*fit)->getItemHeight() + > ll_round(mCurHeight) + mMaxFolderItemOverlap) + { + // hide if beyond current folder height + (*fit)->setVisible(false); + } + } + + for (items_t::iterator iter = mItems.begin(); + iter != mItems.end();) + { + items_t::iterator iit = iter++; + // number of pixels that bottom of item label is from top of parent folder + if (getRect().getHeight() - (*iit)->getRect().mBottom + > ll_round(mCurHeight) + mMaxFolderItemOverlap) + { + (*iit)->setVisible(false); + } + } + } + else + { + mCurHeight = mTargetHeight; + } + + // don't change width as this item is already as wide as its parent folder + reshape(getRect().getWidth(),ll_round(mCurHeight)); + + // pass current height value back to parent + *height = ll_round(mCurHeight); + + return ll_round(mTargetHeight); +} + +bool LLFolderViewFolder::needsArrange() +{ + return mLastArrangeGeneration < getRoot()->getArrangeGeneration(); +} + +bool LLFolderViewFolder::descendantsPassedFilter(S32 filter_generation) +{ + return getViewModelItem()->descendantsPassedFilter(filter_generation); +} + +// Passes selection information on to children and record selection +// information if necessary. +bool LLFolderViewFolder::setSelection(LLFolderViewItem* selection, bool openitem, + bool take_keyboard_focus) +{ + bool rv = false; + if (selection == this) + { + if (!isSelected()) + { + selectItem(); + } + rv = true; + } + else + { + if (isSelected()) + { + deselectItem(); + } + rv = false; + } + bool child_selected = false; + + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();) + { + folders_t::iterator fit = iter++; + if((*fit)->setSelection(selection, openitem, take_keyboard_focus)) + { + rv = true; + child_selected = true; + } + } + for (items_t::iterator iter = mItems.begin(); + iter != mItems.end();) + { + items_t::iterator iit = iter++; + if((*iit)->setSelection(selection, openitem, take_keyboard_focus)) + { + rv = true; + child_selected = true; + } + } + if(openitem && child_selected && !mSingleFolderMode) + { + setOpenArrangeRecursively(true); + } + return rv; +} + +// This method is used to change the selection of an item. +// Recursively traverse all children; if 'selection' is 'this' then change +// the select status if necessary. +// Returns true if the selection state of this folder, or of a child, was changed. +bool LLFolderViewFolder::changeSelection(LLFolderViewItem* selection, bool selected) +{ + bool rv = false; + if(selection == this) + { + if (isSelected() != selected) + { + rv = true; + if (selected) + { + selectItem(); + } + else + { + deselectItem(); + } + } + } + + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();) + { + folders_t::iterator fit = iter++; + if((*fit)->changeSelection(selection, selected)) + { + rv = true; + } + } + for (items_t::iterator iter = mItems.begin(); + iter != mItems.end();) + { + items_t::iterator iit = iter++; + if((*iit)->changeSelection(selection, selected)) + { + rv = true; + } + } + return rv; +} + +LLFolderViewFolder* LLFolderViewFolder::getCommonAncestor(LLFolderViewItem* item_a, LLFolderViewItem* item_b, bool& reverse) +{ + if (!item_a->getParentFolder() || !item_b->getParentFolder()) return NULL; + + std::deque item_a_ancestors; + + LLFolderViewFolder* parent = item_a->getParentFolder(); + while(parent) + { + item_a_ancestors.push_back(parent); + parent = parent->getParentFolder(); + } + + std::deque item_b_ancestors; + + parent = item_b->getParentFolder(); + while(parent) + { + item_b_ancestors.push_back(parent); + parent = parent->getParentFolder(); + } + + LLFolderViewFolder* common_ancestor = item_a->getRoot(); + + while(item_a_ancestors.size() > item_b_ancestors.size()) + { + item_a = item_a_ancestors.front(); + item_a_ancestors.pop_front(); + } + + while(item_b_ancestors.size() > item_a_ancestors.size()) + { + item_b = item_b_ancestors.front(); + item_b_ancestors.pop_front(); + } + + while(item_a_ancestors.size()) + { + common_ancestor = item_a_ancestors.front(); + + if (item_a_ancestors.front() == item_b_ancestors.front()) + { + // which came first, sibling a or sibling b? + for (folders_t::iterator it = common_ancestor->mFolders.begin(), end_it = common_ancestor->mFolders.end(); + it != end_it; + ++it) + { + LLFolderViewItem* item = *it; + + if (item == item_a) + { + reverse = false; + return common_ancestor; + } + if (item == item_b) + { + reverse = true; + return common_ancestor; + } + } + + for (items_t::iterator it = common_ancestor->mItems.begin(), end_it = common_ancestor->mItems.end(); + it != end_it; + ++it) + { + LLFolderViewItem* item = *it; + + if (item == item_a) + { + reverse = false; + return common_ancestor; + } + if (item == item_b) + { + reverse = true; + return common_ancestor; + } + } + break; + } + + item_a = item_a_ancestors.front(); + item_a_ancestors.pop_front(); + item_b = item_b_ancestors.front(); + item_b_ancestors.pop_front(); + } + + return NULL; +} + +void LLFolderViewFolder::gatherChildRangeExclusive(LLFolderViewItem* start, LLFolderViewItem* end, bool reverse, std::vector& items) +{ + bool selecting = start == NULL; + if (reverse) + { + for (items_t::reverse_iterator it = mItems.rbegin(), end_it = mItems.rend(); + it != end_it; + ++it) + { + if (*it == end) + { + return; + } + if (selecting && (*it)->getVisible()) + { + items.push_back(*it); + } + + if (*it == start) + { + selecting = true; + } + } + for (folders_t::reverse_iterator it = mFolders.rbegin(), end_it = mFolders.rend(); + it != end_it; + ++it) + { + if (*it == end) + { + return; + } + + if (selecting && (*it)->getVisible()) + { + items.push_back(*it); + } + + if (*it == start) + { + selecting = true; + } + } + } + else + { + for (folders_t::iterator it = mFolders.begin(), end_it = mFolders.end(); + it != end_it; + ++it) + { + if (*it == end) + { + return; + } + + if (selecting && (*it)->getVisible()) + { + items.push_back(*it); + } + + if (*it == start) + { + selecting = true; + } + } + for (items_t::iterator it = mItems.begin(), end_it = mItems.end(); + it != end_it; + ++it) + { + if (*it == end) + { + return; + } + + if (selecting && (*it)->getVisible()) + { + items.push_back(*it); + } + + if (*it == start) + { + selecting = true; + } + } + } +} + +void LLFolderViewFolder::extendSelectionTo(LLFolderViewItem* new_selection) +{ + if (!getRoot()->getAllowMultiSelect()) + return; + + LLFolderViewItem* cur_selected_item = getRoot()->getCurSelectedItem(); + if (cur_selected_item == NULL) + { + cur_selected_item = new_selection; + } + + + bool reverse = false; + LLFolderViewFolder* common_ancestor = getCommonAncestor(cur_selected_item, new_selection, reverse); + if (!common_ancestor) + return; + + LLFolderViewItem* last_selected_item_from_cur = cur_selected_item; + LLFolderViewFolder* cur_folder = cur_selected_item->getParentFolder(); + + std::vector items_to_select_forward; + + while (cur_folder != common_ancestor) + { + cur_folder->gatherChildRangeExclusive(last_selected_item_from_cur, NULL, reverse, items_to_select_forward); + + last_selected_item_from_cur = cur_folder; + cur_folder = cur_folder->getParentFolder(); + } + + std::vector items_to_select_reverse; + + LLFolderViewItem* last_selected_item_from_new = new_selection; + cur_folder = new_selection->getParentFolder(); + while (cur_folder != common_ancestor) + { + cur_folder->gatherChildRangeExclusive(last_selected_item_from_new, NULL, !reverse, items_to_select_reverse); + + last_selected_item_from_new = cur_folder; + cur_folder = cur_folder->getParentFolder(); + } + + common_ancestor->gatherChildRangeExclusive(last_selected_item_from_cur, last_selected_item_from_new, reverse, items_to_select_forward); + + for (std::vector::reverse_iterator it = items_to_select_reverse.rbegin(), end_it = items_to_select_reverse.rend(); + it != end_it; + ++it) + { + items_to_select_forward.push_back(*it); + } + + LLFolderView* root = getRoot(); + + bool selection_reverse = new_selection->isSelected(); //indication that some elements are being deselected + + // array always go from 'will be selected' to ' will be unselected', iterate + // in opposite direction to simplify identification of 'point of origin' in + // case it is in the list we are working with + for (std::vector::reverse_iterator it = items_to_select_forward.rbegin(), end_it = items_to_select_forward.rend(); + it != end_it; + ++it) + { + LLFolderViewItem* item = *it; + bool selected = item->isSelected(); + if (!selection_reverse && selected) + { + // it is our 'point of origin' where we shift/expand from + // don't deselect it + selection_reverse = true; + } + else + { + root->changeSelection(item, !selected); + } + } + + if (selection_reverse) + { + // at some point we reversed selection, first element should be deselected + root->changeSelection(last_selected_item_from_cur, false); + } + + // element we expand to should always be selected + root->changeSelection(new_selection, true); +} + + +void LLFolderViewFolder::destroyView() +{ + while (!mItems.empty()) + { + LLFolderViewItem *itemp = mItems.back(); + mItems.pop_back(); + itemp->destroyView(); // LLFolderViewItem::destroyView() removes entry from mItems + } + + while (!mFolders.empty()) + { + LLFolderViewFolder *folderp = mFolders.back(); + mFolders.pop_back(); + folderp->destroyView(); // LLFolderVievFolder::destroyView() removes entry from mFolders + } + + LLFolderViewItem::destroyView(); +} + +// extractItem() removes the specified item from the folder, but +// doesn't delete it. +void LLFolderViewFolder::extractItem( LLFolderViewItem* item, bool deparent_model ) +{ + if (item->isSelected()) + getRoot()->clearSelection(); + items_t::iterator it = std::find(mItems.begin(), mItems.end(), item); + if(it == mItems.end()) + { + // This is an evil downcast. However, it's only doing + // pointer comparison to find if (which it should be ) the + // item is in the container, so it's pretty safe. + LLFolderViewFolder* f = static_cast(item); + folders_t::iterator ft; + ft = std::find(mFolders.begin(), mFolders.end(), f); + if (ft != mFolders.end()) + { + mFolders.erase(ft); + } + } + else + { + mItems.erase(it); + } + //item has been removed, need to update filter + if (deparent_model) + { + // in some cases model does not belong to parent view, is shared between views + getViewModelItem()->removeChild(item->getViewModelItem()); + } + //because an item is going away regardless of filter status, force rearrange + requestArrange(); + removeChild(item); +} + +bool LLFolderViewFolder::isMovable() +{ + if( !(getViewModelItem()->isItemMovable()) ) + { + return false; + } + + for (items_t::iterator iter = mItems.begin(); + iter != mItems.end();) + { + items_t::iterator iit = iter++; + if(!(*iit)->isMovable()) + { + return false; + } + } + + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();) + { + folders_t::iterator fit = iter++; + if(!(*fit)->isMovable()) + { + return false; + } + } + return true; +} + + +bool LLFolderViewFolder::isRemovable() +{ + if( !(getViewModelItem()->isItemRemovable()) ) + { + return false; + } + + for (items_t::iterator iter = mItems.begin(); + iter != mItems.end();) + { + items_t::iterator iit = iter++; + if(!(*iit)->isRemovable()) + { + return false; + } + } + + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();) + { + folders_t::iterator fit = iter++; + if(!(*fit)->isRemovable()) + { + return false; + } + } + return true; +} + +void LLFolderViewFolder::destroyRoot() +{ + delete this; +} + +// this is an internal method used for adding items to folders. +void LLFolderViewFolder::addItem(LLFolderViewItem* item) +{ + if (item->getParentFolder()) + { + item->getParentFolder()->extractItem(item); + } + item->setParentFolder(this); + + mItems.push_back(item); + + item->setRect(LLRect(0, 0, getRect().getWidth(), 0)); + item->setVisible(false); + + addChild(item); + + // When the model is already hooked into a hierarchy (i.e. has a parent), do not reparent it + // Note: this happens when models are created before views or shared between views + if (!item->getViewModelItem()->hasParent()) + { + getViewModelItem()->addChild(item->getViewModelItem()); + } +} + +// this is an internal method used for adding items to folders. +void LLFolderViewFolder::addFolder(LLFolderViewFolder* folder) +{ + if (folder->mParentFolder) + { + folder->mParentFolder->extractItem(folder); + } + folder->mParentFolder = this; + mFolders.push_back(folder); + folder->setOrigin(0, 0); + folder->reshape(getRect().getWidth(), 0); + folder->setVisible(false); + // rearrange all descendants too, as our indentation level might have changed + //folder->requestArrange(); + //requestSort(); + + addChild(folder); + + // When the model is already hooked into a hierarchy (i.e. has a parent), do not reparent it + // Note: this happens when models are created before views or shared between views + if (!folder->getViewModelItem()->hasParent()) + { + getViewModelItem()->addChild(folder->getViewModelItem()); + } +} + +void LLFolderViewFolder::requestArrange() +{ + mLastArrangeGeneration = -1; + // flag all items up to root + if (mParentFolder) + { + mParentFolder->requestArrange(); + } +} + +void LLFolderViewFolder::toggleOpen() +{ + setOpen(!isOpen()); +} + +// Force a folder open or closed +void LLFolderViewFolder::setOpen(bool openitem) +{ + if(mSingleFolderMode) + { + // navigateToFolder can destroy this view + // delay it in case setOpen was called from click or key processing + doOnIdleOneTime([this]() + { + getViewModelItem()->navigateToFolder(); + }); + } + else + { + setOpenArrangeRecursively(openitem); + } +} + +void LLFolderViewFolder::setOpenArrangeRecursively(bool openitem, ERecurseType recurse) +{ + bool was_open = isOpen(); + mIsOpen = openitem; + if(!was_open && openitem) + { + getViewModelItem()->openItem(); + // openItem() will request content, it won't be incomplete + mIsFolderComplete = true; + } + else if(was_open && !openitem) + { + getViewModelItem()->closeItem(); + } + + if (recurse == RECURSE_DOWN || recurse == RECURSE_UP_DOWN) + { + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();) + { + folders_t::iterator fit = iter++; + (*fit)->setOpenArrangeRecursively(openitem, RECURSE_DOWN); /* Flawfinder: ignore */ + } + } + if (mParentFolder + && (recurse == RECURSE_UP + || recurse == RECURSE_UP_DOWN)) + { + mParentFolder->setOpenArrangeRecursively(openitem, RECURSE_UP); + } + + if (was_open != isOpen()) + { + requestArrange(); + } +} + +bool LLFolderViewFolder::handleDragAndDropFromChild(MASK mask, + bool drop, + EDragAndDropType c_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + bool accepted = mViewModelItem->dragOrDrop(mask,drop,c_type,cargo_data, tooltip_msg); + if (accepted) + { + mDragAndDropTarget = true; + *accept = ACCEPT_YES_MULTI; + } + else + { + *accept = ACCEPT_NO; + } + + // drag and drop to child item, so clear pending auto-opens + getRoot()->autoOpenTest(NULL); + + return true; +} + +void LLFolderViewFolder::openItem( void ) +{ + toggleOpen(); +} + +void LLFolderViewFolder::applyFunctorToChildren(LLFolderViewFunctor& functor) +{ + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();) + { + folders_t::iterator fit = iter++; + functor.doItem((*fit)); + } + for (items_t::iterator iter = mItems.begin(); + iter != mItems.end();) + { + items_t::iterator iit = iter++; + functor.doItem((*iit)); + } +} + +void LLFolderViewFolder::applyFunctorRecursively(LLFolderViewFunctor& functor) +{ + functor.doFolder(this); + + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();) + { + folders_t::iterator fit = iter++; + (*fit)->applyFunctorRecursively(functor); + } + for (items_t::iterator iter = mItems.begin(); + iter != mItems.end();) + { + items_t::iterator iit = iter++; + functor.doItem((*iit)); + } +} + +// LLView functionality +bool LLFolderViewFolder::handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + bool handled = false; + + if (isOpen()) + { + handled = (childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) != NULL); + } + + if (!handled) + { + handleDragAndDropToThisFolder(mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + + LL_DEBUGS("UserInput") << "dragAndDrop handled by LLFolderViewFolder" << LL_ENDL; + } + + return true; +} + +bool LLFolderViewFolder::handleDragAndDropToThisFolder(MASK mask, + bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + if (!mAllowDrop) + { + *accept = ACCEPT_NO; + tooltip_msg = LLTrans::getString("TooltipOutboxCannotDropOnRoot"); + return true; + } + + bool accepted = getViewModelItem()->dragOrDrop(mask,drop,cargo_type,cargo_data, tooltip_msg); + + if (accepted) + { + mDragAndDropTarget = true; + *accept = ACCEPT_YES_MULTI; + } + else + { + *accept = ACCEPT_NO; + } + + if (!drop && accepted) + { + getRoot()->autoOpenTest(this); + } + + return true; +} + + +bool LLFolderViewFolder::handleRightMouseDown( S32 x, S32 y, MASK mask ) +{ + bool handled = false; + + if( isOpen() ) + { + handled = childrenHandleRightMouseDown( x, y, mask ) != NULL; + } + if (!handled) + { + handled = LLFolderViewItem::handleRightMouseDown( x, y, mask ); + } + return handled; +} + + +bool LLFolderViewFolder::handleHover(S32 x, S32 y, MASK mask) +{ + mIsMouseOverTitle = (y > (getRect().getHeight() - mItemHeight)); + + bool handled = LLView::handleHover(x, y, mask); + + if (!handled) + { + // this doesn't do child processing + handled = LLFolderViewItem::handleHover(x, y, mask); + } + + return handled; +} + +bool LLFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + bool handled = false; + if( isOpen() ) + { + handled = childrenHandleMouseDown(x,y,mask) != NULL; + } + if( !handled ) + { + if((mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad) + && !mSingleFolderMode) + { + toggleOpen(); + handled = true; + } + else + { + // do normal selection logic + handled = LLFolderViewItem::handleMouseDown(x, y, mask); + } + } + + return handled; +} + +bool LLFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask ) +{ + bool handled = false; + if(mSingleFolderMode) + { + static LLUICachedControl double_click_new_window("SingleModeDoubleClickOpenWindow", false); + if (double_click_new_window) + { + getViewModelItem()->navigateToFolder(true); + } + else + { + // navigating is going to destroy views and change children + // delay it untill handleDoubleClick processing is complete + doOnIdleOneTime([this]() + { + getViewModelItem()->navigateToFolder(false); + }); + } + return true; + } + + if( isOpen() ) + { + handled = childrenHandleDoubleClick( x, y, mask ) != NULL; + } + if( !handled ) + { + if(mDoubleClickOverride) + { + static LLUICachedControl double_click_action("MultiModeDoubleClickFolder", false); + if (double_click_action == 1) + { + getViewModelItem()->navigateToFolder(true); + return true; + } + if (double_click_action == 2) + { + getViewModelItem()->navigateToFolder(false, true); + return true; + } + } + if(mIndentation < x && x < mIndentation + (isCollapsed() ? 0 : mArrowSize) + mTextPad) + { + // don't select when user double-clicks plus sign + // so as not to contradict single-click behavior + toggleOpen(); + } + else + { + getRoot()->setSelection(this, false); + toggleOpen(); + } + handled = true; + } + return handled; +} + +void LLFolderViewFolder::draw() +{ + updateLabelRotation(); + + LLFolderViewItem::draw(); + + // draw children if root folder, or any other folder that is open or animating to closed state + if( getRoot() == this || (isOpen() || mCurHeight != mTargetHeight )) + { + LLView::draw(); + } + + mExpanderHighlighted = false; +} + +// this does prefix traversal, as folders are listed above their contents +LLFolderViewItem* LLFolderViewFolder::getNextFromChild( LLFolderViewItem* item, bool include_children ) +{ + bool found_item = false; + + LLFolderViewItem* result = NULL; + // when not starting from a given item, start at beginning + if(item == NULL) + { + found_item = true; + } + + // find current item among children + folders_t::iterator fit = mFolders.begin(); + folders_t::iterator fend = mFolders.end(); + + items_t::iterator iit = mItems.begin(); + items_t::iterator iend = mItems.end(); + + // if not trivially starting at the beginning, we have to find the current item + if (!found_item) + { + // first, look among folders, since they are always above items + for(; fit != fend; ++fit) + { + if(item == (*fit)) + { + found_item = true; + // if we are on downwards traversal + if (include_children && (*fit)->isOpen()) + { + // look for first descendant + return (*fit)->getNextFromChild(NULL, true); + } + // otherwise advance to next folder + ++fit; + include_children = true; + break; + } + } + + // didn't find in folders? Check items... + if (!found_item) + { + for(; iit != iend; ++iit) + { + if(item == (*iit)) + { + found_item = true; + // point to next item + ++iit; + break; + } + } + } + } + + if (!found_item) + { + // you should never call this method with an item that isn't a child + // so we should always find something + llassert(false); + return NULL; + } + + // at this point, either iit or fit point to a candidate "next" item + // if both are out of range, we need to punt up to our parent + + // now, starting from found folder, continue through folders + // searching for next visible folder + while(fit != fend && !(*fit)->getVisible()) + { + // turn on downwards traversal for next folder + ++fit; + } + + if (fit != fend) + { + result = (*fit); + } + else + { + // otherwise, scan for next visible item + while(iit != iend && !(*iit)->getVisible()) + { + ++iit; + } + + // check to see if we have a valid item + if (iit != iend) + { + result = (*iit); + } + } + + if( !result && mParentFolder ) + { + // If there are no siblings or children to go to, recurse up one level in the tree + // and skip children for this folder, as we've already discounted them + result = mParentFolder->getNextFromChild(this, false); + } + + return result; +} + +// this does postfix traversal, as folders are listed above their contents +LLFolderViewItem* LLFolderViewFolder::getPreviousFromChild( LLFolderViewItem* item, bool include_children ) +{ + bool found_item = false; + + LLFolderViewItem* result = NULL; + // when not starting from a given item, start at end + if(item == NULL) + { + found_item = true; + } + + // find current item among children + folders_t::reverse_iterator fit = mFolders.rbegin(); + folders_t::reverse_iterator fend = mFolders.rend(); + + items_t::reverse_iterator iit = mItems.rbegin(); + items_t::reverse_iterator iend = mItems.rend(); + + // if not trivially starting at the end, we have to find the current item + if (!found_item) + { + // first, look among items, since they are always below the folders + for(; iit != iend; ++iit) + { + if(item == (*iit)) + { + found_item = true; + // point to next item + ++iit; + break; + } + } + + // didn't find in items? Check folders... + if (!found_item) + { + for(; fit != fend; ++fit) + { + if(item == (*fit)) + { + found_item = true; + // point to next folder + ++fit; + break; + } + } + } + } + + if (!found_item) + { + // you should never call this method with an item that isn't a child + // so we should always find something + llassert(false); + return NULL; + } + + // at this point, either iit or fit point to a candidate "next" item + // if both are out of range, we need to punt up to our parent + + // now, starting from found item, continue through items + // searching for next visible item + while(iit != iend && !(*iit)->getVisible()) + { + ++iit; + } + + if (iit != iend) + { + // we found an appropriate item + result = (*iit); + } + else + { + // otherwise, scan for next visible folder + while(fit != fend && !(*fit)->getVisible()) + { + ++fit; + } + + // check to see if we have a valid folder + if (fit != fend) + { + // try selecting child element of this folder + if ((*fit)->isOpen() && include_children) + { + result = (*fit)->getPreviousFromChild(NULL); + } + else + { + result = (*fit); + } + } + } + + if( !result ) + { + // If there are no siblings or children to go to, recurse up one level in the tree + // which gets back to this folder, which will only be visited if it is a valid, visible item + result = this; + } + + return result; +} + diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index d72b6804ad..f7ced81274 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -1,498 +1,498 @@ -/** -* @file llfolderviewitem.h -* @brief Items and folders that can appear in a hierarchical folder view -* -* $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$ -*/ -#ifndef LLFOLDERVIEWITEM_H -#define LLFOLDERVIEWITEM_H - -#include "llflashtimer.h" -#include "llview.h" -#include "lluiimage.h" - -class LLFolderView; -class LLFolderViewModelItem; -class LLFolderViewFolder; -class LLFolderViewFunctor; -class LLFolderViewFilter; -class LLFolderViewModelInterface; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFolderViewItem -// -// An instance of this class represents a single item in a folder view -// such as an inventory item or a file. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLFolderViewItem : public LLView -{ -public: - struct Params : public LLInitParam::Block - { - Optional folder_arrow_image, - selection_image; - Mandatory root; - Mandatory listener; - - Optional folder_indentation, // pixels - item_height, - item_top_pad; - - Optional creation_date; - Optional allow_wear; - Optional allow_drop; - - Optional font_color; - Optional font_highlight_color; - - Optional left_pad, - icon_pad, - icon_width, - text_pad, - text_pad_right, - arrow_size, - max_folder_item_overlap; - Optional single_folder_mode, - double_click_override; - Params(); - }; - - - static const S32 DEFAULT_LABEL_PADDING_RIGHT = 4; - // animation parameters - static const F32 FOLDER_CLOSE_TIME_CONSTANT, - FOLDER_OPEN_TIME_CONSTANT; - -protected: - friend class LLUICtrlFactory; - friend class LLFolderViewModelItem; - - LLFolderViewItem(const Params& p); - - std::string mLabel; - S32 mLabelWidth; - bool mLabelWidthDirty; - S32 mLabelPaddingRight; - LLFolderViewFolder* mParentFolder; - LLPointer mViewModelItem; - LLFontGL::StyleFlags mLabelStyle; - std::string mLabelSuffix; - bool mSuffixNeedsRefresh; //suffix and icons - LLUIImagePtr mIcon, - mIconOpen, - mIconOverlay; - S32 mLocalIndentation; - S32 mIndentation; - S32 mItemHeight; - S32 mDragStartX, - mDragStartY; - - S32 mLeftPad, - mIconPad, - mIconWidth, - mTextPad, - mTextPadRight, - mArrowSize, - mMaxFolderItemOverlap; - - F32 mControlLabelRotation; - LLFolderView* mRoot; - bool mHasVisibleChildren, - mIsCurSelection, - mDragAndDropTarget, - mIsMouseOverTitle, - mAllowWear, - mAllowDrop, - mSingleFolderMode, - mDoubleClickOverride, - mSelectPending, - mIsItemCut; - - S32 mCutGeneration; - - LLUIColor mFontColor; - LLUIColor mFontHighlightColor; - - // For now assuming all colors are the same in derived classes. - static bool sColorSetInitialized; - static LLUIColor sFgColor; - static LLUIColor sFgDisabledColor; - static LLUIColor sHighlightBgColor; - static LLUIColor sFlashBgColor; - static LLUIColor sFocusOutlineColor; - static LLUIColor sMouseOverColor; - static LLUIColor sFilterBGColor; - static LLUIColor sFilterTextColor; - static LLUIColor sSuffixColor; - static LLUIColor sSearchStatusColor; - - // this is an internal method used for adding items to folders. A - // no-op at this level, but reimplemented in derived classes. - virtual void addItem(LLFolderViewItem*) { } - virtual void addFolder(LLFolderViewFolder*) { } - virtual bool isHighlightAllowed(); - virtual bool isHighlightActive(); - virtual bool isFadeItem(); - virtual bool isFlashing() { return false; } - virtual void setFlashState(bool) { } - - static LLFontGL* getLabelFontForStyle(U8 style); - - bool mIsSelected; - -public: - static void initClass(); - static void cleanupClass(); - - bool postBuild(); - - virtual void openItem( void ); - - void arrangeAndSet(bool set_selection, bool take_keyboard_focus); - - virtual ~LLFolderViewItem( void ); - - // addToFolder() returns true if it succeeds. false otherwise - virtual void addToFolder(LLFolderViewFolder* folder); - - // Finds width and height of this object and it's children. Also - // makes sure that this view and it's children are the right size. - virtual S32 arrange( S32* width, S32* height ); - virtual S32 getItemHeight() const; - virtual S32 getLabelXPos(); - S32 getIconPad(); - S32 getTextPad(); - - // If 'selection' is 'this' then note that otherwise ignore. - // Returns true if this item ends up being selected. - virtual bool setSelection(LLFolderViewItem* selection, bool openitem, bool take_keyboard_focus); - - // This method is used to set the selection state of an item. - // If 'selection' is 'this' then note selection. - // Returns true if the selection state of this item was changed. - virtual bool changeSelection(LLFolderViewItem* selection, bool selected); - - // this method is used to deselect this element - void deselectItem(); - - // this method is used to select this element - virtual void selectItem(); - - // gets multiple-element selection - virtual std::set getSelectionList() const; - - // Returns true is this object and all of its children can be removed (deleted by user) - virtual bool isRemovable(); - - // Returns true is this object and all of its children can be moved - virtual bool isMovable(); - - // destroys this item recursively - virtual void destroyView(); - - bool isSelected() const { return mIsSelected; } - bool isInSelection() const; - - void setUnselected() { mIsSelected = false; } - - void setIsCurSelection(bool select) { mIsCurSelection = select; } - - bool getIsCurSelection() const { return mIsCurSelection; } - - bool hasVisibleChildren() const { return mHasVisibleChildren; } - - // true if object can't have children - virtual bool isFolderComplete() { return true; } - // true if object can't have children - virtual bool areChildrenInited() { return true; } - virtual void setChildrenInited(bool inited) { } - - // Call through to the viewed object and return true if it can be - // removed. Returns true if it's removed. - //virtual bool removeRecursively(bool single_item); - bool remove(); - - // Build an appropriate context menu for the item. Flags unused. - void buildContextMenu(class LLMenuGL& menu, U32 flags); - - // This method returns the actual name of the thing being - // viewed. This method will ask the viewed object itself. - const std::string& getName( void ) const; - - // This method returns the label displayed on the view. This - // method was primarily added to allow sorting on the folder - // contents possible before the entire view has been constructed. - const std::string& getLabel() const { return mLabel; } - - LLFolderViewFolder* getParentFolder( void ) { return mParentFolder; } - const LLFolderViewFolder* getParentFolder( void ) const { return mParentFolder; } - - void setParentFolder(LLFolderViewFolder* parent) { mParentFolder = parent; } - - LLFolderViewItem* getNextOpenNode( bool include_children = true ); - LLFolderViewItem* getPreviousOpenNode( bool include_children = true ); - - const LLFolderViewModelItem* getViewModelItem( void ) const { return mViewModelItem; } - LLFolderViewModelItem* getViewModelItem( void ) { return mViewModelItem; } - - const LLFolderViewModelInterface* getFolderViewModel( void ) const; - LLFolderViewModelInterface* getFolderViewModel( void ); - - // just rename the object. - void rename(const std::string& new_name); - - // Show children - virtual void setOpen(bool open = true) {}; - virtual bool isOpen() const { return false; } - - virtual LLFolderView* getRoot(); - virtual const LLFolderView* getRoot() const; - bool isDescendantOf( const LLFolderViewFolder* potential_ancestor ); - S32 getIndentation() const { return mIndentation; } - - virtual bool passedFilter(S32 filter_generation = -1); - virtual bool isPotentiallyVisible(S32 filter_generation = -1); - - // refresh information from the object being viewed. - // refreshes label, suffixes and sets icons. Expensive! - // Causes filter update - virtual void refresh(); - // refreshes suffixes and sets icons. Expensive! - // Does not need filter update - virtual void refreshSuffix(); - - bool isSingleFolderMode() { return mSingleFolderMode; } - - // LLView functionality - virtual bool handleRightMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleHover( S32 x, S32 y, MASK mask ); - virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); - virtual bool handleDoubleClick( S32 x, S32 y, MASK mask ); - - virtual void onMouseLeave(S32 x, S32 y, MASK mask); - - //virtual LLView* findChildView(const std::string& name, bool recurse) const { return LLView::findChildView(name, recurse); } - - // virtual void handleDropped(); - virtual void draw(); - void drawOpenFolderArrow(const Params& default_params, const LLUIColor& fg_color); - void drawHighlight(const bool showContent, const bool hasKeyboardFocus, const LLUIColor &selectColor, const LLUIColor &flashColor, const LLUIColor &outlineColor, const LLUIColor &mouseOverColor); - void drawLabel(const LLFontGL * font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x); - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - -private: - static std::map sFonts; // map of styles to fonts -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFolderViewFolder -// -// An instance of an LLFolderViewFolder represents a collection of -// more folders and items. This is used to build the hierarchy of -// items found in the folder view. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLFolderViewFolder : public LLFolderViewItem -{ -protected: - LLFolderViewFolder( const LLFolderViewItem::Params& ); - friend class LLUICtrlFactory; - - void updateLabelRotation(); - virtual bool isCollapsed() { return false; } - -public: - typedef std::list items_t; - typedef std::list folders_t; - -protected: - items_t mItems; - folders_t mFolders; - - bool mIsOpen; - bool mExpanderHighlighted; - F32 mCurHeight; - F32 mTargetHeight; - F32 mAutoOpenCountdown; - S32 mLastArrangeGeneration; - S32 mLastCalculatedWidth; - bool mIsFolderComplete; // indicates that some children were not loaded/added yet - bool mAreChildrenInited; // indicates that no children were initialized - -public: - typedef enum e_recurse_type - { - RECURSE_NO, - RECURSE_UP, - RECURSE_DOWN, - RECURSE_UP_DOWN - } ERecurseType; - - - virtual ~LLFolderViewFolder( void ); - - LLFolderViewItem* getNextFromChild( LLFolderViewItem*, bool include_children = true ); - LLFolderViewItem* getPreviousFromChild( LLFolderViewItem*, bool include_children = true ); - - // addToFolder() returns true if it succeeds. false otherwise - virtual void addToFolder(LLFolderViewFolder* folder); - - // Finds width and height of this object and it's children. Also - // makes sure that this view and it's children are the right size. - virtual S32 arrange( S32* width, S32* height ); - - bool needsArrange(); - - bool descendantsPassedFilter(S32 filter_generation = -1); - - // Passes selection information on to children and record - // selection information if necessary. - // Returns true if this object (or a child) ends up being selected. - // If 'openitem' is true then folders are opened up along the way to the selection. - virtual bool setSelection(LLFolderViewItem* selection, bool openitem, bool take_keyboard_focus = true); - - // This method is used to change the selection of an item. - // Recursively traverse all children; if 'selection' is 'this' then change - // the select status if necessary. - // Returns true if the selection state of this folder, or of a child, was changed. - virtual bool changeSelection(LLFolderViewItem* selection, bool selected); - - // this method is used to group select items - void extendSelectionTo(LLFolderViewItem* selection); - - // Returns true is this object and all of its children can be removed. - virtual bool isRemovable(); - - // Returns true is this object and all of its children can be moved - virtual bool isMovable(); - - // destroys this folder, and all children - virtual void destroyView(); - void destroyRoot(); - - // whether known children are fully loaded (arrange sets to true) - virtual bool isFolderComplete() { return mIsFolderComplete; } - - // whether known children are fully built - virtual bool areChildrenInited() { return mAreChildrenInited; } - virtual void setChildrenInited(bool inited) { mAreChildrenInited = inited; } - - // extractItem() removes the specified item from the folder, but - // doesn't delete it. - virtual void extractItem( LLFolderViewItem* item, bool deparent_model = true); - - // This function is called by a child that needs to be resorted. - void resort(LLFolderViewItem* item); - - void setAutoOpenCountdown(F32 countdown) { mAutoOpenCountdown = countdown; } - - // folders can be opened. This will usually be called by internal - // methods. - virtual void toggleOpen(); - - // Force a folder open or closed - virtual void setOpen(bool openitem = true); - - // Called when a child is refreshed. - virtual void requestArrange(); - - // internal method which doesn't update the entire view. This - // method was written because the list iterators destroy the state - // of other iterations, thus, we can't arrange while iterating - // through the children (such as when setting which is selected. - virtual void setOpenArrangeRecursively(bool openitem, ERecurseType recurse = RECURSE_NO); - - // Get the current state of the folder. - virtual bool isOpen() const { return mIsOpen; } - - // special case if an object is dropped on the child. - bool handleDragAndDropFromChild(MASK mask, - bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - - - // Just apply this functor to the folder's immediate children. - void applyFunctorToChildren(LLFolderViewFunctor& functor); - // apply this functor to the folder's descendants. - void applyFunctorRecursively(LLFolderViewFunctor& functor); - - virtual void openItem( void ); - - // LLView functionality - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleDoubleClick( S32 x, S32 y, MASK mask ); - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - bool handleDragAndDropToThisFolder(MASK mask, - bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - virtual void draw(); - - folders_t::iterator getFoldersBegin() { return mFolders.begin(); } - folders_t::iterator getFoldersEnd() { return mFolders.end(); } - folders_t::size_type getFoldersCount() const { return mFolders.size(); } - - items_t::const_iterator getItemsBegin() const { return mItems.begin(); } - items_t::const_iterator getItemsEnd() const { return mItems.end(); } - items_t::size_type getItemsCount() const { return mItems.size(); } - - LLFolderViewFolder* getCommonAncestor(LLFolderViewItem* item_a, LLFolderViewItem* item_b, bool& reverse); - void gatherChildRangeExclusive(LLFolderViewItem* start, LLFolderViewItem* end, bool reverse, std::vector& items); - - // internal functions for tracking folders and items separately - // use addToFolder() virtual method to ensure folders are always added to mFolders - // and not mItems - void addItem(LLFolderViewItem* item); - void addFolder( LLFolderViewFolder* folder); - - //WARNING: do not call directly...use the appropriate LLFolderViewModel-derived class instead - template void sortFolders(const SORT_FUNC& func) { mFolders.sort(func); } - template void sortItems(const SORT_FUNC& func) { mItems.sort(func); } -}; - -typedef std::deque folder_view_item_deque; - -class LLFolderViewGroupedItemModel: public LLRefCount -{ -public: - virtual void groupFilterContextMenu(folder_view_item_deque& selected_items, LLMenuGL& menu) = 0; -}; - -#endif // LLFOLDERVIEWITEM_H +/** +* @file llfolderviewitem.h +* @brief Items and folders that can appear in a hierarchical folder view +* +* $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$ +*/ +#ifndef LLFOLDERVIEWITEM_H +#define LLFOLDERVIEWITEM_H + +#include "llflashtimer.h" +#include "llview.h" +#include "lluiimage.h" + +class LLFolderView; +class LLFolderViewModelItem; +class LLFolderViewFolder; +class LLFolderViewFunctor; +class LLFolderViewFilter; +class LLFolderViewModelInterface; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFolderViewItem +// +// An instance of this class represents a single item in a folder view +// such as an inventory item or a file. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFolderViewItem : public LLView +{ +public: + struct Params : public LLInitParam::Block + { + Optional folder_arrow_image, + selection_image; + Mandatory root; + Mandatory listener; + + Optional folder_indentation, // pixels + item_height, + item_top_pad; + + Optional creation_date; + Optional allow_wear; + Optional allow_drop; + + Optional font_color; + Optional font_highlight_color; + + Optional left_pad, + icon_pad, + icon_width, + text_pad, + text_pad_right, + arrow_size, + max_folder_item_overlap; + Optional single_folder_mode, + double_click_override; + Params(); + }; + + + static const S32 DEFAULT_LABEL_PADDING_RIGHT = 4; + // animation parameters + static const F32 FOLDER_CLOSE_TIME_CONSTANT, + FOLDER_OPEN_TIME_CONSTANT; + +protected: + friend class LLUICtrlFactory; + friend class LLFolderViewModelItem; + + LLFolderViewItem(const Params& p); + + std::string mLabel; + S32 mLabelWidth; + bool mLabelWidthDirty; + S32 mLabelPaddingRight; + LLFolderViewFolder* mParentFolder; + LLPointer mViewModelItem; + LLFontGL::StyleFlags mLabelStyle; + std::string mLabelSuffix; + bool mSuffixNeedsRefresh; //suffix and icons + LLUIImagePtr mIcon, + mIconOpen, + mIconOverlay; + S32 mLocalIndentation; + S32 mIndentation; + S32 mItemHeight; + S32 mDragStartX, + mDragStartY; + + S32 mLeftPad, + mIconPad, + mIconWidth, + mTextPad, + mTextPadRight, + mArrowSize, + mMaxFolderItemOverlap; + + F32 mControlLabelRotation; + LLFolderView* mRoot; + bool mHasVisibleChildren, + mIsCurSelection, + mDragAndDropTarget, + mIsMouseOverTitle, + mAllowWear, + mAllowDrop, + mSingleFolderMode, + mDoubleClickOverride, + mSelectPending, + mIsItemCut; + + S32 mCutGeneration; + + LLUIColor mFontColor; + LLUIColor mFontHighlightColor; + + // For now assuming all colors are the same in derived classes. + static bool sColorSetInitialized; + static LLUIColor sFgColor; + static LLUIColor sFgDisabledColor; + static LLUIColor sHighlightBgColor; + static LLUIColor sFlashBgColor; + static LLUIColor sFocusOutlineColor; + static LLUIColor sMouseOverColor; + static LLUIColor sFilterBGColor; + static LLUIColor sFilterTextColor; + static LLUIColor sSuffixColor; + static LLUIColor sSearchStatusColor; + + // this is an internal method used for adding items to folders. A + // no-op at this level, but reimplemented in derived classes. + virtual void addItem(LLFolderViewItem*) { } + virtual void addFolder(LLFolderViewFolder*) { } + virtual bool isHighlightAllowed(); + virtual bool isHighlightActive(); + virtual bool isFadeItem(); + virtual bool isFlashing() { return false; } + virtual void setFlashState(bool) { } + + static LLFontGL* getLabelFontForStyle(U8 style); + + bool mIsSelected; + +public: + static void initClass(); + static void cleanupClass(); + + bool postBuild(); + + virtual void openItem( void ); + + void arrangeAndSet(bool set_selection, bool take_keyboard_focus); + + virtual ~LLFolderViewItem( void ); + + // addToFolder() returns true if it succeeds. false otherwise + virtual void addToFolder(LLFolderViewFolder* folder); + + // Finds width and height of this object and it's children. Also + // makes sure that this view and it's children are the right size. + virtual S32 arrange( S32* width, S32* height ); + virtual S32 getItemHeight() const; + virtual S32 getLabelXPos(); + S32 getIconPad(); + S32 getTextPad(); + + // If 'selection' is 'this' then note that otherwise ignore. + // Returns true if this item ends up being selected. + virtual bool setSelection(LLFolderViewItem* selection, bool openitem, bool take_keyboard_focus); + + // This method is used to set the selection state of an item. + // If 'selection' is 'this' then note selection. + // Returns true if the selection state of this item was changed. + virtual bool changeSelection(LLFolderViewItem* selection, bool selected); + + // this method is used to deselect this element + void deselectItem(); + + // this method is used to select this element + virtual void selectItem(); + + // gets multiple-element selection + virtual std::set getSelectionList() const; + + // Returns true is this object and all of its children can be removed (deleted by user) + virtual bool isRemovable(); + + // Returns true is this object and all of its children can be moved + virtual bool isMovable(); + + // destroys this item recursively + virtual void destroyView(); + + bool isSelected() const { return mIsSelected; } + bool isInSelection() const; + + void setUnselected() { mIsSelected = false; } + + void setIsCurSelection(bool select) { mIsCurSelection = select; } + + bool getIsCurSelection() const { return mIsCurSelection; } + + bool hasVisibleChildren() const { return mHasVisibleChildren; } + + // true if object can't have children + virtual bool isFolderComplete() { return true; } + // true if object can't have children + virtual bool areChildrenInited() { return true; } + virtual void setChildrenInited(bool inited) { } + + // Call through to the viewed object and return true if it can be + // removed. Returns true if it's removed. + //virtual bool removeRecursively(bool single_item); + bool remove(); + + // Build an appropriate context menu for the item. Flags unused. + void buildContextMenu(class LLMenuGL& menu, U32 flags); + + // This method returns the actual name of the thing being + // viewed. This method will ask the viewed object itself. + const std::string& getName( void ) const; + + // This method returns the label displayed on the view. This + // method was primarily added to allow sorting on the folder + // contents possible before the entire view has been constructed. + const std::string& getLabel() const { return mLabel; } + + LLFolderViewFolder* getParentFolder( void ) { return mParentFolder; } + const LLFolderViewFolder* getParentFolder( void ) const { return mParentFolder; } + + void setParentFolder(LLFolderViewFolder* parent) { mParentFolder = parent; } + + LLFolderViewItem* getNextOpenNode( bool include_children = true ); + LLFolderViewItem* getPreviousOpenNode( bool include_children = true ); + + const LLFolderViewModelItem* getViewModelItem( void ) const { return mViewModelItem; } + LLFolderViewModelItem* getViewModelItem( void ) { return mViewModelItem; } + + const LLFolderViewModelInterface* getFolderViewModel( void ) const; + LLFolderViewModelInterface* getFolderViewModel( void ); + + // just rename the object. + void rename(const std::string& new_name); + + // Show children + virtual void setOpen(bool open = true) {}; + virtual bool isOpen() const { return false; } + + virtual LLFolderView* getRoot(); + virtual const LLFolderView* getRoot() const; + bool isDescendantOf( const LLFolderViewFolder* potential_ancestor ); + S32 getIndentation() const { return mIndentation; } + + virtual bool passedFilter(S32 filter_generation = -1); + virtual bool isPotentiallyVisible(S32 filter_generation = -1); + + // refresh information from the object being viewed. + // refreshes label, suffixes and sets icons. Expensive! + // Causes filter update + virtual void refresh(); + // refreshes suffixes and sets icons. Expensive! + // Does not need filter update + virtual void refreshSuffix(); + + bool isSingleFolderMode() { return mSingleFolderMode; } + + // LLView functionality + virtual bool handleRightMouseDown( S32 x, S32 y, MASK mask ); + virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); + virtual bool handleHover( S32 x, S32 y, MASK mask ); + virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); + virtual bool handleDoubleClick( S32 x, S32 y, MASK mask ); + + virtual void onMouseLeave(S32 x, S32 y, MASK mask); + + //virtual LLView* findChildView(const std::string& name, bool recurse) const { return LLView::findChildView(name, recurse); } + + // virtual void handleDropped(); + virtual void draw(); + void drawOpenFolderArrow(const Params& default_params, const LLUIColor& fg_color); + void drawHighlight(const bool showContent, const bool hasKeyboardFocus, const LLUIColor &selectColor, const LLUIColor &flashColor, const LLUIColor &outlineColor, const LLUIColor &mouseOverColor); + void drawLabel(const LLFontGL * font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x); + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + +private: + static std::map sFonts; // map of styles to fonts +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFolderViewFolder +// +// An instance of an LLFolderViewFolder represents a collection of +// more folders and items. This is used to build the hierarchy of +// items found in the folder view. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFolderViewFolder : public LLFolderViewItem +{ +protected: + LLFolderViewFolder( const LLFolderViewItem::Params& ); + friend class LLUICtrlFactory; + + void updateLabelRotation(); + virtual bool isCollapsed() { return false; } + +public: + typedef std::list items_t; + typedef std::list folders_t; + +protected: + items_t mItems; + folders_t mFolders; + + bool mIsOpen; + bool mExpanderHighlighted; + F32 mCurHeight; + F32 mTargetHeight; + F32 mAutoOpenCountdown; + S32 mLastArrangeGeneration; + S32 mLastCalculatedWidth; + bool mIsFolderComplete; // indicates that some children were not loaded/added yet + bool mAreChildrenInited; // indicates that no children were initialized + +public: + typedef enum e_recurse_type + { + RECURSE_NO, + RECURSE_UP, + RECURSE_DOWN, + RECURSE_UP_DOWN + } ERecurseType; + + + virtual ~LLFolderViewFolder( void ); + + LLFolderViewItem* getNextFromChild( LLFolderViewItem*, bool include_children = true ); + LLFolderViewItem* getPreviousFromChild( LLFolderViewItem*, bool include_children = true ); + + // addToFolder() returns true if it succeeds. false otherwise + virtual void addToFolder(LLFolderViewFolder* folder); + + // Finds width and height of this object and it's children. Also + // makes sure that this view and it's children are the right size. + virtual S32 arrange( S32* width, S32* height ); + + bool needsArrange(); + + bool descendantsPassedFilter(S32 filter_generation = -1); + + // Passes selection information on to children and record + // selection information if necessary. + // Returns true if this object (or a child) ends up being selected. + // If 'openitem' is true then folders are opened up along the way to the selection. + virtual bool setSelection(LLFolderViewItem* selection, bool openitem, bool take_keyboard_focus = true); + + // This method is used to change the selection of an item. + // Recursively traverse all children; if 'selection' is 'this' then change + // the select status if necessary. + // Returns true if the selection state of this folder, or of a child, was changed. + virtual bool changeSelection(LLFolderViewItem* selection, bool selected); + + // this method is used to group select items + void extendSelectionTo(LLFolderViewItem* selection); + + // Returns true is this object and all of its children can be removed. + virtual bool isRemovable(); + + // Returns true is this object and all of its children can be moved + virtual bool isMovable(); + + // destroys this folder, and all children + virtual void destroyView(); + void destroyRoot(); + + // whether known children are fully loaded (arrange sets to true) + virtual bool isFolderComplete() { return mIsFolderComplete; } + + // whether known children are fully built + virtual bool areChildrenInited() { return mAreChildrenInited; } + virtual void setChildrenInited(bool inited) { mAreChildrenInited = inited; } + + // extractItem() removes the specified item from the folder, but + // doesn't delete it. + virtual void extractItem( LLFolderViewItem* item, bool deparent_model = true); + + // This function is called by a child that needs to be resorted. + void resort(LLFolderViewItem* item); + + void setAutoOpenCountdown(F32 countdown) { mAutoOpenCountdown = countdown; } + + // folders can be opened. This will usually be called by internal + // methods. + virtual void toggleOpen(); + + // Force a folder open or closed + virtual void setOpen(bool openitem = true); + + // Called when a child is refreshed. + virtual void requestArrange(); + + // internal method which doesn't update the entire view. This + // method was written because the list iterators destroy the state + // of other iterations, thus, we can't arrange while iterating + // through the children (such as when setting which is selected. + virtual void setOpenArrangeRecursively(bool openitem, ERecurseType recurse = RECURSE_NO); + + // Get the current state of the folder. + virtual bool isOpen() const { return mIsOpen; } + + // special case if an object is dropped on the child. + bool handleDragAndDropFromChild(MASK mask, + bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + + + // Just apply this functor to the folder's immediate children. + void applyFunctorToChildren(LLFolderViewFunctor& functor); + // apply this functor to the folder's descendants. + void applyFunctorRecursively(LLFolderViewFunctor& functor); + + virtual void openItem( void ); + + // LLView functionality + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseDown( S32 x, S32 y, MASK mask ); + virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); + virtual bool handleDoubleClick( S32 x, S32 y, MASK mask ); + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + bool handleDragAndDropToThisFolder(MASK mask, + bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + virtual void draw(); + + folders_t::iterator getFoldersBegin() { return mFolders.begin(); } + folders_t::iterator getFoldersEnd() { return mFolders.end(); } + folders_t::size_type getFoldersCount() const { return mFolders.size(); } + + items_t::const_iterator getItemsBegin() const { return mItems.begin(); } + items_t::const_iterator getItemsEnd() const { return mItems.end(); } + items_t::size_type getItemsCount() const { return mItems.size(); } + + LLFolderViewFolder* getCommonAncestor(LLFolderViewItem* item_a, LLFolderViewItem* item_b, bool& reverse); + void gatherChildRangeExclusive(LLFolderViewItem* start, LLFolderViewItem* end, bool reverse, std::vector& items); + + // internal functions for tracking folders and items separately + // use addToFolder() virtual method to ensure folders are always added to mFolders + // and not mItems + void addItem(LLFolderViewItem* item); + void addFolder( LLFolderViewFolder* folder); + + //WARNING: do not call directly...use the appropriate LLFolderViewModel-derived class instead + template void sortFolders(const SORT_FUNC& func) { mFolders.sort(func); } + template void sortItems(const SORT_FUNC& func) { mItems.sort(func); } +}; + +typedef std::deque folder_view_item_deque; + +class LLFolderViewGroupedItemModel: public LLRefCount +{ +public: + virtual void groupFilterContextMenu(folder_view_item_deque& selected_items, LLMenuGL& menu) = 0; +}; + +#endif // LLFOLDERVIEWITEM_H diff --git a/indra/llui/llfolderviewmodel.h b/indra/llui/llfolderviewmodel.h index 27d1f1dbe0..b8d6d89971 100644 --- a/indra/llui/llfolderviewmodel.h +++ b/indra/llui/llfolderviewmodel.h @@ -1,474 +1,474 @@ -/** - * @file llfolderviewmodel.h - * - * $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$ - */ -#ifndef LLFOLDERVIEWMODEL_H -#define LLFOLDERVIEWMODEL_H - -#include "llfontgl.h" // just for StyleFlags enum -#include "llfolderview.h" - -// These are grouping of inventory types. -// Order matters when sorting system folders to the top. -enum EInventorySortGroup -{ - SG_SYSTEM_FOLDER, - SG_TRASH_FOLDER, - SG_NORMAL_FOLDER, - SG_ITEM -}; - -class LLFontGL; -class LLInventoryModel; -class LLMenuGL; -class LLUIImage; -class LLUUID; -class LLFolderViewItem; -class LLFolderViewFolder; - -class LLFolderViewFilter -{ -public: - enum EFilterModified - { - FILTER_NONE, // nothing to do, already filtered - FILTER_RESTART, // restart filtering from scratch - FILTER_LESS_RESTRICTIVE, // existing filtered items will certainly pass this filter - FILTER_MORE_RESTRICTIVE // if you didn't pass the previous filter, you definitely won't pass this one - }; - -public: - - LLFolderViewFilter() {} - virtual ~LLFolderViewFilter() {} - - // +-------------------------------------------------------------------+ - // + Execution And Results - // +-------------------------------------------------------------------+ - virtual bool check(const LLFolderViewModelItem* item) = 0; - virtual bool checkFolder(const LLFolderViewModelItem* folder) const = 0; - - virtual void setEmptyLookupMessage(const std::string& message) = 0; - virtual std::string getEmptyLookupMessage(bool is_empty_folder = false) const = 0; - - virtual bool showAllResults() const = 0; - - virtual std::string::size_type getStringMatchOffset(LLFolderViewModelItem* item) const = 0; - virtual std::string::size_type getFilterStringSize() const = 0; - // +-------------------------------------------------------------------+ - // + Status - // +-------------------------------------------------------------------+ - virtual bool isActive() const = 0; - virtual bool isModified() const = 0; - virtual void clearModified() = 0; - virtual const std::string& getName() const = 0; - virtual const std::string& getFilterText() = 0; - //RN: this is public to allow system to externally force a global refilter - virtual void setModified(EFilterModified behavior = FILTER_RESTART) = 0; - - // +-------------------------------------------------------------------+ - // + Time - // +-------------------------------------------------------------------+ - virtual void resetTime(S32 timeout) = 0; - virtual bool isTimedOut() = 0; - - // +-------------------------------------------------------------------+ - // + Default - // +-------------------------------------------------------------------+ - virtual bool isDefault() const = 0; - virtual bool isNotDefault() const = 0; - virtual void markDefault() = 0; - virtual void resetDefault() = 0; - - // +-------------------------------------------------------------------+ - // + Generation - // +-------------------------------------------------------------------+ - virtual S32 getCurrentGeneration() const = 0; - virtual S32 getFirstSuccessGeneration() const = 0; - virtual S32 getFirstRequiredGeneration() const = 0; -}; - -class LLFolderViewModelInterface -{ -public: - LLFolderViewModelInterface() - {} - - virtual ~LLFolderViewModelInterface() {} - virtual void requestSortAll() = 0; - - virtual void sort(class LLFolderViewFolder*) = 0; - virtual void filter() = 0; - - virtual bool contentsReady() = 0; - virtual bool isFolderComplete(class LLFolderViewFolder*) = 0; - virtual void setFolderView(LLFolderView* folder_view) = 0; - virtual LLFolderViewFilter& getFilter() = 0; - virtual const LLFolderViewFilter& getFilter() const = 0; - virtual std::string getStatusText(bool is_empty_folder = false) = 0; - - virtual bool startDrag(std::vector& items) = 0; -}; - -// This is an abstract base class that users of the folderview classes -// would use to bridge the folder view with the underlying data -class LLFolderViewModelItem : public LLRefCount -{ -public: - LLFolderViewModelItem() - {} - - virtual ~LLFolderViewModelItem() { } - - virtual void update() {} //called when drawing - virtual const std::string& getName() const = 0; - virtual const std::string& getDisplayName() const = 0; - virtual const std::string& getSearchableName() const = 0; - - virtual std::string getSearchableDescription() const = 0; - virtual std::string getSearchableCreatorName()const = 0; - virtual std::string getSearchableUUIDString() const = 0; - - virtual LLPointer getIcon() const = 0; - virtual LLPointer getIconOpen() const { return getIcon(); } - virtual LLPointer getIconOverlay() const { return NULL; } - - virtual LLFontGL::StyleFlags getLabelStyle() const = 0; - virtual std::string getLabelSuffix() const = 0; - - virtual void openItem( void ) = 0; - virtual void closeItem( void ) = 0; - virtual void selectItem(void) = 0; - - virtual void navigateToFolder(bool new_window = false, bool change_mode = false) = 0; - - virtual bool isItemWearable() const { return false; } - - virtual bool isItemRenameable() const = 0; - virtual bool renameItem(const std::string& new_name) = 0; - - virtual bool isItemMovable( void ) const = 0; // Can be moved to another folder - virtual void move( LLFolderViewModelItem* parent_listener ) = 0; - - virtual bool isItemRemovable( bool check_worn = true ) const = 0; // Can be destroyed - virtual bool removeItem() = 0; - virtual void removeBatch(std::vector& batch) = 0; - - virtual bool isItemCopyable(bool can_copy_as_link = true) const = 0; - virtual bool copyToClipboard() const = 0; - virtual bool cutToClipboard() = 0; - virtual bool isCutToClipboard() { return false; }; - - virtual bool isClipboardPasteable() const = 0; - virtual void pasteFromClipboard() = 0; - virtual void pasteLinkFromClipboard() = 0; - - virtual void buildContextMenu(LLMenuGL& menu, U32 flags) = 0; - - virtual bool potentiallyVisible() = 0; // is the item definitely visible or we haven't made up our minds yet? - - virtual bool filter( LLFolderViewFilter& filter) = 0; - virtual bool passedFilter(S32 filter_generation = -1) = 0; - virtual bool descendantsPassedFilter(S32 filter_generation = -1) = 0; - virtual void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0) = 0; - virtual void setPassedFolderFilter(bool passed, S32 filter_generation) = 0; - virtual void dirtyFilter() = 0; - virtual void dirtyDescendantsFilter() = 0; - virtual bool hasFilterStringMatch() = 0; - virtual std::string::size_type getFilterStringOffset() = 0; - virtual std::string::size_type getFilterStringSize() = 0; - - virtual S32 getLastFilterGeneration() const = 0; - virtual S32 getMarkedDirtyGeneration() const = 0; - - virtual bool hasChildren() const = 0; - virtual void addChild(LLFolderViewModelItem* child) = 0; - virtual void removeChild(LLFolderViewModelItem* child) = 0; - virtual void clearChildren() = 0; - - // This method will be called to determine if a drop can be - // performed, and will set drop to true if a drop is - // requested. Returns true if a drop is possible/happened, - // otherwise false. - virtual bool dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg) = 0; - - virtual void requestSort() = 0; - virtual S32 getSortVersion() = 0; - virtual void setSortVersion(S32 version) = 0; - virtual void setParent(LLFolderViewModelItem* parent) = 0; - virtual bool hasParent() = 0; - -protected: - - friend class LLFolderViewItem; - virtual void setFolderViewItem(LLFolderViewItem* folder_view_item) = 0; - -}; - - -class LLFolderViewModelItemCommon : public LLFolderViewModelItem -{ -public: - LLFolderViewModelItemCommon(LLFolderViewModelInterface& root_view_model) - : mSortVersion(-1), - mPassedFilter(true), - mPassedFolderFilter(true), - mStringMatchOffsetFilter(std::string::npos), - mStringFilterSize(0), - mFolderViewItem(NULL), - mLastFilterGeneration(-1), - mLastFolderFilterGeneration(-1), - mMarkedDirtyGeneration(-1), - mMostFilteredDescendantGeneration(-1), - mParent(NULL), - mRootViewModel(root_view_model) - { - mChildren.clear(); - } - - void requestSort() { mSortVersion = -1; } - S32 getSortVersion() { return mSortVersion; } - void setSortVersion(S32 version) { mSortVersion = version;} - - S32 getLastFilterGeneration() const { return mLastFilterGeneration; } - S32 getLastFolderFilterGeneration() const { return mLastFolderFilterGeneration; } - S32 getMarkedDirtyGeneration() const { return mMarkedDirtyGeneration; } - void dirtyFilter() - { - if(mMarkedDirtyGeneration < 0) - { - mMarkedDirtyGeneration = mLastFilterGeneration; - } - mLastFilterGeneration = -1; - mLastFolderFilterGeneration = -1; - - // bubble up dirty flag all the way to root - if (mParent) - { - mParent->dirtyFilter(); - } - } - void dirtyDescendantsFilter() - { - mMostFilteredDescendantGeneration = -1; - if (mParent) - { - mParent->dirtyDescendantsFilter(); - } - } - bool hasFilterStringMatch(); - std::string::size_type getFilterStringOffset(); - std::string::size_type getFilterStringSize(); - - typedef std::list child_list_t; - - virtual void addChild(LLFolderViewModelItem* child) - { - mChildren.push_back(child); - child->setParent(this); - dirtyFilter(); - requestSort(); - } - virtual void removeChild(LLFolderViewModelItem* child) - { - mChildren.remove(child); - child->setParent(NULL); - dirtyDescendantsFilter(); - dirtyFilter(); - } - - virtual void clearChildren() - { - // We are working with models that belong to views as LLPointers, clean the list, let poiters handle the rest - std::for_each(mChildren.begin(), mChildren.end(), [](LLFolderViewModelItem* c) {c->setParent(NULL); }); - mChildren.clear(); - dirtyDescendantsFilter(); - dirtyFilter(); - } - - child_list_t::const_iterator getChildrenBegin() const { return mChildren.begin(); } - child_list_t::const_iterator getChildrenEnd() const { return mChildren.end(); } - child_list_t::size_type getChildrenCount() const { return mChildren.size(); } - - void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0) - { - mPassedFilter = passed; - mLastFilterGeneration = filter_generation; - mStringMatchOffsetFilter = string_offset; - mStringFilterSize = string_size; - mMarkedDirtyGeneration = -1; - } - - void setPassedFolderFilter(bool passed, S32 filter_generation) - { - mPassedFolderFilter = passed; - mLastFolderFilterGeneration = filter_generation; - } - - virtual bool potentiallyVisible() - { - return passedFilter() // we've passed the filter - || (getLastFilterGeneration() < mRootViewModel.getFilter().getFirstSuccessGeneration()) // or we don't know yet - || descendantsPassedFilter(); - } - - virtual bool passedFilter(S32 filter_generation = -1) - { - if (filter_generation < 0) - { - filter_generation = mRootViewModel.getFilter().getFirstSuccessGeneration(); - } - bool passed_folder_filter = mPassedFolderFilter && (mLastFolderFilterGeneration >= filter_generation); - bool passed_filter = mPassedFilter && (mLastFilterGeneration >= filter_generation); - return passed_folder_filter && (passed_filter || descendantsPassedFilter(filter_generation)); - } - - virtual bool descendantsPassedFilter(S32 filter_generation = -1) - { - if (filter_generation < 0) - { - filter_generation = mRootViewModel.getFilter().getFirstSuccessGeneration(); - } - return mMostFilteredDescendantGeneration >= filter_generation; - } - - -protected: - virtual void setParent(LLFolderViewModelItem* parent) { mParent = parent; } - virtual bool hasParent() { return mParent != NULL; } - - S32 mSortVersion; - bool mPassedFilter; - bool mPassedFolderFilter; - std::string::size_type mStringMatchOffsetFilter; - std::string::size_type mStringFilterSize; - - S32 mLastFilterGeneration, - mLastFolderFilterGeneration, - mMostFilteredDescendantGeneration, - mMarkedDirtyGeneration; - - child_list_t mChildren; - LLFolderViewModelItem* mParent; - LLFolderViewModelInterface& mRootViewModel; - - void setFolderViewItem(LLFolderViewItem* folder_view_item) { mFolderViewItem = folder_view_item;} - LLFolderViewItem* mFolderViewItem; -}; - - - -class LLFolderViewModelCommon : public LLFolderViewModelInterface -{ -public: - LLFolderViewModelCommon() - : mTargetSortVersion(0), - mFolderView(NULL) - {} - - virtual void requestSortAll() - { - // sort everything - mTargetSortVersion++; - } - virtual std::string getStatusText(bool is_empty_folder = false); - virtual void filter(); - - void setFolderView(LLFolderView* folder_view) { mFolderView = folder_view;} - -protected: - bool needsSort(class LLFolderViewModelItem* item); - - S32 mTargetSortVersion; - LLFolderView* mFolderView; - -}; - -template -class LLFolderViewModel : public LLFolderViewModelCommon -{ -public: - typedef SORT_TYPE SortType; - typedef ITEM_TYPE ItemType; - typedef FOLDER_TYPE FolderType; - typedef FILTER_TYPE FilterType; - - LLFolderViewModel(SortType* sorter, FilterType* filter) - : mSorter(sorter), - mFilter(filter) - {} - - virtual ~LLFolderViewModel() {} - - virtual SortType& getSorter() { return *mSorter; } - virtual const SortType& getSorter() const { return *mSorter; } - virtual void setSorter(const SortType& sorter) { mSorter.reset(new SortType(sorter)); requestSortAll(); } - - virtual FilterType& getFilter() { return *mFilter; } - virtual const FilterType& getFilter() const { return *mFilter; } - virtual void setFilter(const FilterType& filter) { mFilter.reset(new FilterType(filter)); } - - // By default, we assume the content is available. If a network fetch mechanism is implemented for the model, - // this method needs to be overloaded and return the relevant fetch status. - virtual bool contentsReady() { return true; } - virtual bool isFolderComplete(LLFolderViewFolder* folder) { return true; } - - struct ViewModelCompare - { - ViewModelCompare(const SortType& sorter) - : mSorter(sorter) - {} - - bool operator () (const LLFolderViewItem* a, const LLFolderViewItem* b) const - { - return mSorter(static_cast(a->getViewModelItem()), static_cast(b->getViewModelItem())); - } - - bool operator () (const LLFolderViewFolder* a, const LLFolderViewFolder* b) const - { - return mSorter(static_cast(a->getViewModelItem()), static_cast(b->getViewModelItem())); - } - - const SortType& mSorter; - }; - - void sort(LLFolderViewFolder* folder) - { - if (needsSort(folder->getViewModelItem())) - { - folder->sortFolders(ViewModelCompare(getSorter())); - folder->sortItems(ViewModelCompare(getSorter())); - folder->getViewModelItem()->setSortVersion(mTargetSortVersion); - folder->requestArrange(); - } - } - -protected: - std::unique_ptr mSorter; - std::unique_ptr mFilter; -}; - -#endif // LLFOLDERVIEWMODEL_H +/** + * @file llfolderviewmodel.h + * + * $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$ + */ +#ifndef LLFOLDERVIEWMODEL_H +#define LLFOLDERVIEWMODEL_H + +#include "llfontgl.h" // just for StyleFlags enum +#include "llfolderview.h" + +// These are grouping of inventory types. +// Order matters when sorting system folders to the top. +enum EInventorySortGroup +{ + SG_SYSTEM_FOLDER, + SG_TRASH_FOLDER, + SG_NORMAL_FOLDER, + SG_ITEM +}; + +class LLFontGL; +class LLInventoryModel; +class LLMenuGL; +class LLUIImage; +class LLUUID; +class LLFolderViewItem; +class LLFolderViewFolder; + +class LLFolderViewFilter +{ +public: + enum EFilterModified + { + FILTER_NONE, // nothing to do, already filtered + FILTER_RESTART, // restart filtering from scratch + FILTER_LESS_RESTRICTIVE, // existing filtered items will certainly pass this filter + FILTER_MORE_RESTRICTIVE // if you didn't pass the previous filter, you definitely won't pass this one + }; + +public: + + LLFolderViewFilter() {} + virtual ~LLFolderViewFilter() {} + + // +-------------------------------------------------------------------+ + // + Execution And Results + // +-------------------------------------------------------------------+ + virtual bool check(const LLFolderViewModelItem* item) = 0; + virtual bool checkFolder(const LLFolderViewModelItem* folder) const = 0; + + virtual void setEmptyLookupMessage(const std::string& message) = 0; + virtual std::string getEmptyLookupMessage(bool is_empty_folder = false) const = 0; + + virtual bool showAllResults() const = 0; + + virtual std::string::size_type getStringMatchOffset(LLFolderViewModelItem* item) const = 0; + virtual std::string::size_type getFilterStringSize() const = 0; + // +-------------------------------------------------------------------+ + // + Status + // +-------------------------------------------------------------------+ + virtual bool isActive() const = 0; + virtual bool isModified() const = 0; + virtual void clearModified() = 0; + virtual const std::string& getName() const = 0; + virtual const std::string& getFilterText() = 0; + //RN: this is public to allow system to externally force a global refilter + virtual void setModified(EFilterModified behavior = FILTER_RESTART) = 0; + + // +-------------------------------------------------------------------+ + // + Time + // +-------------------------------------------------------------------+ + virtual void resetTime(S32 timeout) = 0; + virtual bool isTimedOut() = 0; + + // +-------------------------------------------------------------------+ + // + Default + // +-------------------------------------------------------------------+ + virtual bool isDefault() const = 0; + virtual bool isNotDefault() const = 0; + virtual void markDefault() = 0; + virtual void resetDefault() = 0; + + // +-------------------------------------------------------------------+ + // + Generation + // +-------------------------------------------------------------------+ + virtual S32 getCurrentGeneration() const = 0; + virtual S32 getFirstSuccessGeneration() const = 0; + virtual S32 getFirstRequiredGeneration() const = 0; +}; + +class LLFolderViewModelInterface +{ +public: + LLFolderViewModelInterface() + {} + + virtual ~LLFolderViewModelInterface() {} + virtual void requestSortAll() = 0; + + virtual void sort(class LLFolderViewFolder*) = 0; + virtual void filter() = 0; + + virtual bool contentsReady() = 0; + virtual bool isFolderComplete(class LLFolderViewFolder*) = 0; + virtual void setFolderView(LLFolderView* folder_view) = 0; + virtual LLFolderViewFilter& getFilter() = 0; + virtual const LLFolderViewFilter& getFilter() const = 0; + virtual std::string getStatusText(bool is_empty_folder = false) = 0; + + virtual bool startDrag(std::vector& items) = 0; +}; + +// This is an abstract base class that users of the folderview classes +// would use to bridge the folder view with the underlying data +class LLFolderViewModelItem : public LLRefCount +{ +public: + LLFolderViewModelItem() + {} + + virtual ~LLFolderViewModelItem() { } + + virtual void update() {} //called when drawing + virtual const std::string& getName() const = 0; + virtual const std::string& getDisplayName() const = 0; + virtual const std::string& getSearchableName() const = 0; + + virtual std::string getSearchableDescription() const = 0; + virtual std::string getSearchableCreatorName()const = 0; + virtual std::string getSearchableUUIDString() const = 0; + + virtual LLPointer getIcon() const = 0; + virtual LLPointer getIconOpen() const { return getIcon(); } + virtual LLPointer getIconOverlay() const { return NULL; } + + virtual LLFontGL::StyleFlags getLabelStyle() const = 0; + virtual std::string getLabelSuffix() const = 0; + + virtual void openItem( void ) = 0; + virtual void closeItem( void ) = 0; + virtual void selectItem(void) = 0; + + virtual void navigateToFolder(bool new_window = false, bool change_mode = false) = 0; + + virtual bool isItemWearable() const { return false; } + + virtual bool isItemRenameable() const = 0; + virtual bool renameItem(const std::string& new_name) = 0; + + virtual bool isItemMovable( void ) const = 0; // Can be moved to another folder + virtual void move( LLFolderViewModelItem* parent_listener ) = 0; + + virtual bool isItemRemovable( bool check_worn = true ) const = 0; // Can be destroyed + virtual bool removeItem() = 0; + virtual void removeBatch(std::vector& batch) = 0; + + virtual bool isItemCopyable(bool can_copy_as_link = true) const = 0; + virtual bool copyToClipboard() const = 0; + virtual bool cutToClipboard() = 0; + virtual bool isCutToClipboard() { return false; }; + + virtual bool isClipboardPasteable() const = 0; + virtual void pasteFromClipboard() = 0; + virtual void pasteLinkFromClipboard() = 0; + + virtual void buildContextMenu(LLMenuGL& menu, U32 flags) = 0; + + virtual bool potentiallyVisible() = 0; // is the item definitely visible or we haven't made up our minds yet? + + virtual bool filter( LLFolderViewFilter& filter) = 0; + virtual bool passedFilter(S32 filter_generation = -1) = 0; + virtual bool descendantsPassedFilter(S32 filter_generation = -1) = 0; + virtual void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0) = 0; + virtual void setPassedFolderFilter(bool passed, S32 filter_generation) = 0; + virtual void dirtyFilter() = 0; + virtual void dirtyDescendantsFilter() = 0; + virtual bool hasFilterStringMatch() = 0; + virtual std::string::size_type getFilterStringOffset() = 0; + virtual std::string::size_type getFilterStringSize() = 0; + + virtual S32 getLastFilterGeneration() const = 0; + virtual S32 getMarkedDirtyGeneration() const = 0; + + virtual bool hasChildren() const = 0; + virtual void addChild(LLFolderViewModelItem* child) = 0; + virtual void removeChild(LLFolderViewModelItem* child) = 0; + virtual void clearChildren() = 0; + + // This method will be called to determine if a drop can be + // performed, and will set drop to true if a drop is + // requested. Returns true if a drop is possible/happened, + // otherwise false. + virtual bool dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg) = 0; + + virtual void requestSort() = 0; + virtual S32 getSortVersion() = 0; + virtual void setSortVersion(S32 version) = 0; + virtual void setParent(LLFolderViewModelItem* parent) = 0; + virtual bool hasParent() = 0; + +protected: + + friend class LLFolderViewItem; + virtual void setFolderViewItem(LLFolderViewItem* folder_view_item) = 0; + +}; + + +class LLFolderViewModelItemCommon : public LLFolderViewModelItem +{ +public: + LLFolderViewModelItemCommon(LLFolderViewModelInterface& root_view_model) + : mSortVersion(-1), + mPassedFilter(true), + mPassedFolderFilter(true), + mStringMatchOffsetFilter(std::string::npos), + mStringFilterSize(0), + mFolderViewItem(NULL), + mLastFilterGeneration(-1), + mLastFolderFilterGeneration(-1), + mMarkedDirtyGeneration(-1), + mMostFilteredDescendantGeneration(-1), + mParent(NULL), + mRootViewModel(root_view_model) + { + mChildren.clear(); + } + + void requestSort() { mSortVersion = -1; } + S32 getSortVersion() { return mSortVersion; } + void setSortVersion(S32 version) { mSortVersion = version;} + + S32 getLastFilterGeneration() const { return mLastFilterGeneration; } + S32 getLastFolderFilterGeneration() const { return mLastFolderFilterGeneration; } + S32 getMarkedDirtyGeneration() const { return mMarkedDirtyGeneration; } + void dirtyFilter() + { + if(mMarkedDirtyGeneration < 0) + { + mMarkedDirtyGeneration = mLastFilterGeneration; + } + mLastFilterGeneration = -1; + mLastFolderFilterGeneration = -1; + + // bubble up dirty flag all the way to root + if (mParent) + { + mParent->dirtyFilter(); + } + } + void dirtyDescendantsFilter() + { + mMostFilteredDescendantGeneration = -1; + if (mParent) + { + mParent->dirtyDescendantsFilter(); + } + } + bool hasFilterStringMatch(); + std::string::size_type getFilterStringOffset(); + std::string::size_type getFilterStringSize(); + + typedef std::list child_list_t; + + virtual void addChild(LLFolderViewModelItem* child) + { + mChildren.push_back(child); + child->setParent(this); + dirtyFilter(); + requestSort(); + } + virtual void removeChild(LLFolderViewModelItem* child) + { + mChildren.remove(child); + child->setParent(NULL); + dirtyDescendantsFilter(); + dirtyFilter(); + } + + virtual void clearChildren() + { + // We are working with models that belong to views as LLPointers, clean the list, let poiters handle the rest + std::for_each(mChildren.begin(), mChildren.end(), [](LLFolderViewModelItem* c) {c->setParent(NULL); }); + mChildren.clear(); + dirtyDescendantsFilter(); + dirtyFilter(); + } + + child_list_t::const_iterator getChildrenBegin() const { return mChildren.begin(); } + child_list_t::const_iterator getChildrenEnd() const { return mChildren.end(); } + child_list_t::size_type getChildrenCount() const { return mChildren.size(); } + + void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0) + { + mPassedFilter = passed; + mLastFilterGeneration = filter_generation; + mStringMatchOffsetFilter = string_offset; + mStringFilterSize = string_size; + mMarkedDirtyGeneration = -1; + } + + void setPassedFolderFilter(bool passed, S32 filter_generation) + { + mPassedFolderFilter = passed; + mLastFolderFilterGeneration = filter_generation; + } + + virtual bool potentiallyVisible() + { + return passedFilter() // we've passed the filter + || (getLastFilterGeneration() < mRootViewModel.getFilter().getFirstSuccessGeneration()) // or we don't know yet + || descendantsPassedFilter(); + } + + virtual bool passedFilter(S32 filter_generation = -1) + { + if (filter_generation < 0) + { + filter_generation = mRootViewModel.getFilter().getFirstSuccessGeneration(); + } + bool passed_folder_filter = mPassedFolderFilter && (mLastFolderFilterGeneration >= filter_generation); + bool passed_filter = mPassedFilter && (mLastFilterGeneration >= filter_generation); + return passed_folder_filter && (passed_filter || descendantsPassedFilter(filter_generation)); + } + + virtual bool descendantsPassedFilter(S32 filter_generation = -1) + { + if (filter_generation < 0) + { + filter_generation = mRootViewModel.getFilter().getFirstSuccessGeneration(); + } + return mMostFilteredDescendantGeneration >= filter_generation; + } + + +protected: + virtual void setParent(LLFolderViewModelItem* parent) { mParent = parent; } + virtual bool hasParent() { return mParent != NULL; } + + S32 mSortVersion; + bool mPassedFilter; + bool mPassedFolderFilter; + std::string::size_type mStringMatchOffsetFilter; + std::string::size_type mStringFilterSize; + + S32 mLastFilterGeneration, + mLastFolderFilterGeneration, + mMostFilteredDescendantGeneration, + mMarkedDirtyGeneration; + + child_list_t mChildren; + LLFolderViewModelItem* mParent; + LLFolderViewModelInterface& mRootViewModel; + + void setFolderViewItem(LLFolderViewItem* folder_view_item) { mFolderViewItem = folder_view_item;} + LLFolderViewItem* mFolderViewItem; +}; + + + +class LLFolderViewModelCommon : public LLFolderViewModelInterface +{ +public: + LLFolderViewModelCommon() + : mTargetSortVersion(0), + mFolderView(NULL) + {} + + virtual void requestSortAll() + { + // sort everything + mTargetSortVersion++; + } + virtual std::string getStatusText(bool is_empty_folder = false); + virtual void filter(); + + void setFolderView(LLFolderView* folder_view) { mFolderView = folder_view;} + +protected: + bool needsSort(class LLFolderViewModelItem* item); + + S32 mTargetSortVersion; + LLFolderView* mFolderView; + +}; + +template +class LLFolderViewModel : public LLFolderViewModelCommon +{ +public: + typedef SORT_TYPE SortType; + typedef ITEM_TYPE ItemType; + typedef FOLDER_TYPE FolderType; + typedef FILTER_TYPE FilterType; + + LLFolderViewModel(SortType* sorter, FilterType* filter) + : mSorter(sorter), + mFilter(filter) + {} + + virtual ~LLFolderViewModel() {} + + virtual SortType& getSorter() { return *mSorter; } + virtual const SortType& getSorter() const { return *mSorter; } + virtual void setSorter(const SortType& sorter) { mSorter.reset(new SortType(sorter)); requestSortAll(); } + + virtual FilterType& getFilter() { return *mFilter; } + virtual const FilterType& getFilter() const { return *mFilter; } + virtual void setFilter(const FilterType& filter) { mFilter.reset(new FilterType(filter)); } + + // By default, we assume the content is available. If a network fetch mechanism is implemented for the model, + // this method needs to be overloaded and return the relevant fetch status. + virtual bool contentsReady() { return true; } + virtual bool isFolderComplete(LLFolderViewFolder* folder) { return true; } + + struct ViewModelCompare + { + ViewModelCompare(const SortType& sorter) + : mSorter(sorter) + {} + + bool operator () (const LLFolderViewItem* a, const LLFolderViewItem* b) const + { + return mSorter(static_cast(a->getViewModelItem()), static_cast(b->getViewModelItem())); + } + + bool operator () (const LLFolderViewFolder* a, const LLFolderViewFolder* b) const + { + return mSorter(static_cast(a->getViewModelItem()), static_cast(b->getViewModelItem())); + } + + const SortType& mSorter; + }; + + void sort(LLFolderViewFolder* folder) + { + if (needsSort(folder->getViewModelItem())) + { + folder->sortFolders(ViewModelCompare(getSorter())); + folder->sortItems(ViewModelCompare(getSorter())); + folder->getViewModelItem()->setSortVersion(mTargetSortVersion); + folder->requestArrange(); + } + } + +protected: + std::unique_ptr mSorter; + std::unique_ptr mFilter; +}; + +#endif // LLFOLDERVIEWMODEL_H diff --git a/indra/llui/lliconctrl.cpp b/indra/llui/lliconctrl.cpp index 7492c44aea..83379dd0a9 100644 --- a/indra/llui/lliconctrl.cpp +++ b/indra/llui/lliconctrl.cpp @@ -1,173 +1,173 @@ -/** - * @file lliconctrl.cpp - * @brief LLIconCtrl base 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 "lliconctrl.h" - -// Linden library includes - -// Project includes -#include "llcontrol.h" -#include "llui.h" -#include "lluictrlfactory.h" -#include "lluiimage.h" -#include "llwindow.h" - -#include "llgltexture.h" - -static LLDefaultChildRegistry::Register r("icon"); - -LLIconCtrl::Params::Params() -: image("image_name"), - color("color"), - use_draw_context_alpha("use_draw_context_alpha", true), - interactable("interactable", false), - scale_image("scale_image"), - min_width("min_width", 0), - min_height("min_height", 0) -{} - -LLIconCtrl::LLIconCtrl(const LLIconCtrl::Params& p) -: LLUICtrl(p), - mColor(p.color()), - mImagep(p.image), - mUseDrawContextAlpha(p.use_draw_context_alpha), - mInteractable(p.interactable), - mPriority(0), - mMinWidth(p.min_width), - mMinHeight(p.min_height), - mMaxWidth(0), - mMaxHeight(0) -{ - if (mImagep.notNull()) - { - LLUICtrl::setValue(mImagep->getName()); - } -} - -LLIconCtrl::~LLIconCtrl() -{ - mImagep = NULL; -} - - -void LLIconCtrl::draw() -{ - if( mImagep.notNull() ) - { - const F32 alpha = mUseDrawContextAlpha ? getDrawContext().mAlpha : getCurrentTransparency(); - mImagep->draw(getLocalRect(), mColor.get() % alpha ); - } - - LLUICtrl::draw(); -} - -bool LLIconCtrl::handleHover(S32 x, S32 y, MASK mask) -{ - if (mInteractable && getEnabled()) - { - getWindow()->setCursor(UI_CURSOR_HAND); - return true; - } - return LLUICtrl::handleHover(x, y, mask); -} - -void LLIconCtrl::onVisibilityChange(bool new_visibility) -{ - LLUICtrl::onVisibilityChange(new_visibility); - if (mPriority == LLGLTexture::BOOST_ICON) - { - if (new_visibility) - { - loadImage(getValue(), mPriority); - } - else - { - mImagep = nullptr; - } - } -} - -// virtual -// value might be a string or a UUID -void LLIconCtrl::setValue(const LLSD& value) -{ - setValue(value, mPriority); -} - -void LLIconCtrl::setValue(const LLSD& value, S32 priority) -{ - LLSD tvalue(value); - if (value.isString() && LLUUID::validate(value.asString())) - { - //RN: support UUIDs masquerading as strings - tvalue = LLSD(LLUUID(value.asString())); - } - LLUICtrl::setValue(tvalue); - - loadImage(tvalue, priority); -} - -void LLIconCtrl::loadImage(const LLSD& tvalue, S32 priority) -{ - if(mPriority == LLGLTexture::BOOST_ICON && !getVisible()) return; - - if (tvalue.isUUID()) - { - mImagep = LLUI::getUIImageByID(tvalue.asUUID(), priority); - } - else - { - mImagep = LLUI::getUIImage(tvalue.asString(), priority); - } - - if(mImagep.notNull() - && mImagep->getImage().notNull() - && mMinWidth - && mMinHeight) - { - S32 desired_draw_width = llmax(mMinWidth, mImagep->getWidth()); - S32 desired_draw_height = llmax(mMinHeight, mImagep->getHeight()); - if (mMaxWidth && mMaxHeight) - { - desired_draw_width = llmin(desired_draw_width, mMaxWidth); - desired_draw_height = llmin(desired_draw_height, mMaxHeight); - } - - mImagep->getImage()->setKnownDrawSize(desired_draw_width, desired_draw_height); - } -} - -std::string LLIconCtrl::getImageName() const -{ - if (getValue().isString()) - return getValue().asString(); - else - return std::string(); -} - - +/** + * @file lliconctrl.cpp + * @brief LLIconCtrl base 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 "lliconctrl.h" + +// Linden library includes + +// Project includes +#include "llcontrol.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "lluiimage.h" +#include "llwindow.h" + +#include "llgltexture.h" + +static LLDefaultChildRegistry::Register r("icon"); + +LLIconCtrl::Params::Params() +: image("image_name"), + color("color"), + use_draw_context_alpha("use_draw_context_alpha", true), + interactable("interactable", false), + scale_image("scale_image"), + min_width("min_width", 0), + min_height("min_height", 0) +{} + +LLIconCtrl::LLIconCtrl(const LLIconCtrl::Params& p) +: LLUICtrl(p), + mColor(p.color()), + mImagep(p.image), + mUseDrawContextAlpha(p.use_draw_context_alpha), + mInteractable(p.interactable), + mPriority(0), + mMinWidth(p.min_width), + mMinHeight(p.min_height), + mMaxWidth(0), + mMaxHeight(0) +{ + if (mImagep.notNull()) + { + LLUICtrl::setValue(mImagep->getName()); + } +} + +LLIconCtrl::~LLIconCtrl() +{ + mImagep = NULL; +} + + +void LLIconCtrl::draw() +{ + if( mImagep.notNull() ) + { + const F32 alpha = mUseDrawContextAlpha ? getDrawContext().mAlpha : getCurrentTransparency(); + mImagep->draw(getLocalRect(), mColor.get() % alpha ); + } + + LLUICtrl::draw(); +} + +bool LLIconCtrl::handleHover(S32 x, S32 y, MASK mask) +{ + if (mInteractable && getEnabled()) + { + getWindow()->setCursor(UI_CURSOR_HAND); + return true; + } + return LLUICtrl::handleHover(x, y, mask); +} + +void LLIconCtrl::onVisibilityChange(bool new_visibility) +{ + LLUICtrl::onVisibilityChange(new_visibility); + if (mPriority == LLGLTexture::BOOST_ICON) + { + if (new_visibility) + { + loadImage(getValue(), mPriority); + } + else + { + mImagep = nullptr; + } + } +} + +// virtual +// value might be a string or a UUID +void LLIconCtrl::setValue(const LLSD& value) +{ + setValue(value, mPriority); +} + +void LLIconCtrl::setValue(const LLSD& value, S32 priority) +{ + LLSD tvalue(value); + if (value.isString() && LLUUID::validate(value.asString())) + { + //RN: support UUIDs masquerading as strings + tvalue = LLSD(LLUUID(value.asString())); + } + LLUICtrl::setValue(tvalue); + + loadImage(tvalue, priority); +} + +void LLIconCtrl::loadImage(const LLSD& tvalue, S32 priority) +{ + if(mPriority == LLGLTexture::BOOST_ICON && !getVisible()) return; + + if (tvalue.isUUID()) + { + mImagep = LLUI::getUIImageByID(tvalue.asUUID(), priority); + } + else + { + mImagep = LLUI::getUIImage(tvalue.asString(), priority); + } + + if(mImagep.notNull() + && mImagep->getImage().notNull() + && mMinWidth + && mMinHeight) + { + S32 desired_draw_width = llmax(mMinWidth, mImagep->getWidth()); + S32 desired_draw_height = llmax(mMinHeight, mImagep->getHeight()); + if (mMaxWidth && mMaxHeight) + { + desired_draw_width = llmin(desired_draw_width, mMaxWidth); + desired_draw_height = llmin(desired_draw_height, mMaxHeight); + } + + mImagep->getImage()->setKnownDrawSize(desired_draw_width, desired_draw_height); + } +} + +std::string LLIconCtrl::getImageName() const +{ + if (getValue().isString()) + return getValue().asString(); + else + return std::string(); +} + + diff --git a/indra/llui/lliconctrl.h b/indra/llui/lliconctrl.h index 04910c123c..aae1d1d572 100644 --- a/indra/llui/lliconctrl.h +++ b/indra/llui/lliconctrl.h @@ -1,107 +1,107 @@ -/** - * @file lliconctrl.h - * @brief LLIconCtrl base 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$ - */ - -#ifndef LL_LLICONCTRL_H -#define LL_LLICONCTRL_H - -#include "lluuid.h" -#include "v4color.h" -#include "lluictrl.h" -#include "lluiimage.h" - -class LLTextBox; -class LLUICtrlFactory; - -// -// Classes -// - -// Class for diplaying named UI textures -// Do not use for displaying textures from network, -// UI textures are stored permanently! -class LLIconCtrl -: public LLUICtrl -{ -public: - struct Params : public LLInitParam::Block - { - Optional image; - Optional color; - Optional use_draw_context_alpha, - interactable; - Optional min_width, - min_height; - Ignored scale_image; - - Params(); - }; -protected: - LLIconCtrl(const Params&); - friend class LLUICtrlFactory; - - void setValue(const LLSD& value, S32 priority); - -public: - virtual ~LLIconCtrl(); - - // llview overrides - virtual void draw(); - - // llview overrides - virtual bool handleHover(S32 x, S32 y, MASK mask); - - // lluictrl overrides - void onVisibilityChange(bool new_visibility); - virtual void setValue(const LLSD& value ); - - std::string getImageName() const; - - void setColor(const LLColor4& color) { mColor = color; } - void setImage(LLPointer image) { mImagep = image; } - const LLPointer getImage() { return mImagep; } - -protected: - S32 mPriority; - - //the output size of the icon image if set. - S32 mMinWidth, - mMinHeight, - mMaxWidth, - mMaxHeight; - - // If set to true (default), use the draw context transparency. - // If false, will use transparency returned by getCurrentTransparency(). See STORM-698. - bool mUseDrawContextAlpha; - bool mInteractable; - -private: - void loadImage(const LLSD& value, S32 priority); - - LLUIColor mColor; - LLPointer mImagep; -}; - -#endif +/** + * @file lliconctrl.h + * @brief LLIconCtrl base 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$ + */ + +#ifndef LL_LLICONCTRL_H +#define LL_LLICONCTRL_H + +#include "lluuid.h" +#include "v4color.h" +#include "lluictrl.h" +#include "lluiimage.h" + +class LLTextBox; +class LLUICtrlFactory; + +// +// Classes +// + +// Class for diplaying named UI textures +// Do not use for displaying textures from network, +// UI textures are stored permanently! +class LLIconCtrl +: public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block + { + Optional image; + Optional color; + Optional use_draw_context_alpha, + interactable; + Optional min_width, + min_height; + Ignored scale_image; + + Params(); + }; +protected: + LLIconCtrl(const Params&); + friend class LLUICtrlFactory; + + void setValue(const LLSD& value, S32 priority); + +public: + virtual ~LLIconCtrl(); + + // llview overrides + virtual void draw(); + + // llview overrides + virtual bool handleHover(S32 x, S32 y, MASK mask); + + // lluictrl overrides + void onVisibilityChange(bool new_visibility); + virtual void setValue(const LLSD& value ); + + std::string getImageName() const; + + void setColor(const LLColor4& color) { mColor = color; } + void setImage(LLPointer image) { mImagep = image; } + const LLPointer getImage() { return mImagep; } + +protected: + S32 mPriority; + + //the output size of the icon image if set. + S32 mMinWidth, + mMinHeight, + mMaxWidth, + mMaxHeight; + + // If set to true (default), use the draw context transparency. + // If false, will use transparency returned by getCurrentTransparency(). See STORM-698. + bool mUseDrawContextAlpha; + bool mInteractable; + +private: + void loadImage(const LLSD& value, S32 priority); + + LLUIColor mColor; + LLPointer mImagep; +}; + +#endif diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp index 6eeb98fc53..ce644b094c 100644 --- a/indra/llui/llkeywords.cpp +++ b/indra/llui/llkeywords.cpp @@ -1,813 +1,813 @@ -/** - * @file llkeywords.cpp - * @brief Keyword list for LSL - * - * $LicenseInfo:firstyear=2000&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 -#include - -#include "llkeywords.h" -#include "llsdserialize.h" -#include "lltexteditor.h" -#include "llstl.h" - -inline bool LLKeywordToken::isHead(const llwchar* s) const -{ - // strncmp is much faster than string compare - bool res = true; - const llwchar* t = mToken.c_str(); - S32 len = mToken.size(); - for (S32 i=0; isecond.get("type").asString() + " " + argsIt->first; - if (argsCount-- > 1) - { - argString += ", "; - } - } - } - else - { - LL_WARNS("SyntaxLSL") << "Argument array comtains a non-map element!" << LL_ENDL; - } - } - } - else if (!arguments.isUndefined()) - { - LL_WARNS("SyntaxLSL") << "Not an array! Invalid arguments LLSD passed to function." << arguments << LL_ENDL; - } - return argString; -} - -std::string LLKeywords::getAttribute(const std::string& key) -{ - attribute_iterator_t it = mAttributes.find(key); - return (it != mAttributes.end()) ? it->second : ""; -} - -LLColor4 LLKeywords::getColorGroup(const std::string& key_in) -{ - std::string color_group = "ScriptText"; - if (key_in == "functions") - { - color_group = "SyntaxLslFunction"; - } - else if (key_in == "controls") - { - color_group = "SyntaxLslControlFlow"; - } - else if (key_in == "events") - { - color_group = "SyntaxLslEvent"; - } - else if (key_in == "types") - { - color_group = "SyntaxLslDataType"; - } - else if (key_in == "misc-flow-label") - { - color_group = "SyntaxLslControlFlow"; - } - else if (key_in =="deprecated") - { - color_group = "SyntaxLslDeprecated"; - } - else if (key_in =="god-mode") - { - color_group = "SyntaxLslGodMode"; - } - else if (key_in == "constants" - || key_in == "constants-integer" - || key_in == "constants-float" - || key_in == "constants-string" - || key_in == "constants-key" - || key_in == "constants-rotation" - || key_in == "constants-vector") - { - color_group = "SyntaxLslConstant"; - } - else - { - LL_WARNS("SyntaxLSL") << "Color key '" << key_in << "' not recognized." << LL_ENDL; - } - - return LLUIColorTable::instance().getColor(color_group); -} - -void LLKeywords::initialize(LLSD SyntaxXML) -{ - mSyntax = SyntaxXML; - mLoaded = true; -} - -void LLKeywords::processTokens() -{ - if (!mLoaded) - { - return; - } - - // Add 'standard' stuff: Quotes, Comments, Strings, Labels, etc. before processing the LLSD - std::string delimiter; - addToken(LLKeywordToken::TT_LABEL, "@", getColorGroup("misc-flow-label"), "Label\nTarget for jump statement", delimiter ); - addToken(LLKeywordToken::TT_ONE_SIDED_DELIMITER, "//", LLUIColorTable::instance().getColor("SyntaxLslComment"), "Comment (single-line)\nNon-functional commentary or disabled code", delimiter ); - addToken(LLKeywordToken::TT_TWO_SIDED_DELIMITER, "/*", LLUIColorTable::instance().getColor("SyntaxLslComment"), "Comment (multi-line)\nNon-functional commentary or disabled code", "*/" ); - addToken(LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS, "\"", LLUIColorTable::instance().getColor("SyntaxLslStringLiteral"), "String literal", "\"" ); - - LLSD::map_iterator itr = mSyntax.beginMap(); - for ( ; itr != mSyntax.endMap(); ++itr) - { - if (itr->first == "llsd-lsl-syntax-version") - { - // Skip over version key. - } - else - { - if (itr->second.isMap()) - { - processTokensGroup(itr->second, itr->first); - } - else - { - LL_WARNS("LSL-Tokens-Processing") << "Map for " + itr->first + " entries is missing! Ignoring." << LL_ENDL; - } - } - } - LL_INFOS("SyntaxLSL") << "Finished processing tokens." << LL_ENDL; -} - -void LLKeywords::processTokensGroup(const LLSD& tokens, const std::string& group) -{ - LLColor4 color; - LLColor4 color_group; - LLColor4 color_deprecated = getColorGroup("deprecated"); - LLColor4 color_god_mode = getColorGroup("god-mode"); - - LLKeywordToken::ETokenType token_type = LLKeywordToken::TT_UNKNOWN; - // If a new token type is added here, it must also be added to the 'addToken' method - if (group == "constants") - { - token_type = LLKeywordToken::TT_CONSTANT; - } - else if (group == "controls") - { - token_type = LLKeywordToken::TT_CONTROL; - } - else if (group == "events") - { - token_type = LLKeywordToken::TT_EVENT; - } - else if (group == "functions") - { - token_type = LLKeywordToken::TT_FUNCTION; - } - else if (group == "label") - { - token_type = LLKeywordToken::TT_LABEL; - } - else if (group == "types") - { - token_type = LLKeywordToken::TT_TYPE; - } - - color_group = getColorGroup(group); - LL_DEBUGS("SyntaxLSL") << "Group: '" << group << "', using color: '" << color_group << "'" << LL_ENDL; - - if (tokens.isMap()) - { - LLSD::map_const_iterator outer_itr = tokens.beginMap(); - for ( ; outer_itr != tokens.endMap(); ++outer_itr ) - { - if (outer_itr->second.isMap()) - { - mAttributes.clear(); - LLSD arguments = LLSD(); - LLSD::map_const_iterator inner_itr = outer_itr->second.beginMap(); - for ( ; inner_itr != outer_itr->second.endMap(); ++inner_itr ) - { - if (inner_itr->first == "arguments") - { - if (inner_itr->second.isArray()) - { - arguments = inner_itr->second; - } - } - else if (!inner_itr->second.isMap() && !inner_itr->second.isArray()) - { - mAttributes[inner_itr->first] = inner_itr->second.asString(); - } - else - { - LL_WARNS("SyntaxLSL") << "Not a valid attribute: " << inner_itr->first << LL_ENDL; - } - } - - std::string tooltip = ""; - switch (token_type) - { - case LLKeywordToken::TT_CONSTANT: - if (getAttribute("type").length() > 0) - { - color_group = getColorGroup(group + "-" + getAttribute("type")); - } - else - { - color_group = getColorGroup(group); - } - tooltip = "Type: " + getAttribute("type") + ", Value: " + getAttribute("value"); - break; - case LLKeywordToken::TT_EVENT: - tooltip = outer_itr->first + "(" + getArguments(arguments) + ")"; - break; - case LLKeywordToken::TT_FUNCTION: - tooltip = getAttribute("return") + " " + outer_itr->first + "(" + getArguments(arguments) + ");"; - tooltip.append("\nEnergy: "); - tooltip.append(getAttribute("energy").empty() ? "0.0" : getAttribute("energy")); - if (!getAttribute("sleep").empty()) - { - tooltip += ", Sleep: " + getAttribute("sleep"); - } - default: - break; - } - - if (!getAttribute("tooltip").empty()) - { - if (!tooltip.empty()) - { - tooltip.append("\n"); - } - tooltip.append(getAttribute("tooltip")); - } - - color = getAttribute("deprecated") == "true" ? color_deprecated : color_group; - - if (getAttribute("god-mode") == "true") - { - color = color_god_mode; - } - - addToken(token_type, outer_itr->first, color, tooltip); - } - } - } - else if (tokens.isArray()) // Currently nothing should need this, but it's here for completeness - { - LL_INFOS("SyntaxLSL") << "Curious, shouldn't be an array here; adding all using color " << color << LL_ENDL; - for (S32 count = 0; count < tokens.size(); ++count) - { - addToken(token_type, tokens[count], color, ""); - } - } - else - { - LL_WARNS("SyntaxLSL") << "Invalid map/array passed: '" << tokens << "'" << LL_ENDL; - } -} - -LLKeywords::WStringMapIndex::WStringMapIndex(const WStringMapIndex& other) -{ - if(other.mOwner) - { - copyData(other.mData, other.mLength); - } - else - { - mOwner = false; - mLength = other.mLength; - mData = other.mData; - } -} - -LLKeywords::WStringMapIndex::WStringMapIndex(const LLWString& str) -{ - copyData(str.data(), str.size()); -} - -LLKeywords::WStringMapIndex::WStringMapIndex(const llwchar *start, size_t length) -: mData(start) -, mLength(length) -, mOwner(false) -{ -} - -LLKeywords::WStringMapIndex::~WStringMapIndex() -{ - if (mOwner) - { - delete[] mData; - } -} - -void LLKeywords::WStringMapIndex::copyData(const llwchar *start, size_t length) -{ - llwchar *data = new llwchar[length]; - memcpy((void*)data, (const void*)start, length * sizeof(llwchar)); - - mOwner = true; - mLength = length; - mData = data; -} - -bool LLKeywords::WStringMapIndex::operator<(const LLKeywords::WStringMapIndex &other) const -{ - // NOTE: Since this is only used to organize a std::map, it doesn't matter if it uses correct collate order or not. - // The comparison only needs to strictly order all possible strings, and be stable. - - bool result = false; - const llwchar* self_iter = mData; - const llwchar* self_end = mData + mLength; - const llwchar* other_iter = other.mData; - const llwchar* other_end = other.mData + other.mLength; - - while(true) - { - if(other_iter >= other_end) - { - // We've hit the end of other. - // This covers two cases: other being shorter than self, or the strings being equal. - // In either case, we want to return false. - result = false; - break; - } - else if(self_iter >= self_end) - { - // self is shorter than other. - result = true; - break; - } - else if(*self_iter != *other_iter) - { - // The current character differs. The strings are not equal. - result = *self_iter < *other_iter; - break; - } - - self_iter++; - other_iter++; - } - - return result; -} - -LLTrace::BlockTimerStatHandle FTM_SYNTAX_COLORING("Syntax Coloring"); - -// Walk through a string, applying the rules specified by the keyword token list and -// create a list of color segments. -void LLKeywords::findSegments(std::vector* seg_list, const LLWString& wtext, LLTextEditor& editor, LLStyleConstSP style) -{ - LL_RECORD_BLOCK_TIME(FTM_SYNTAX_COLORING); - seg_list->clear(); - - if( wtext.empty() ) - { - return; - } - - S32 text_len = wtext.size() + 1; - - seg_list->push_back( new LLNormalTextSegment( style, 0, text_len, editor ) ); - - const llwchar* base = wtext.c_str(); - const llwchar* cur = base; - while( *cur ) - { - if( *cur == '\n' || cur == base ) - { - if( *cur == '\n' ) - { - LLTextSegmentPtr text_segment = new LLLineBreakTextSegment(style, cur-base); - text_segment->setToken( 0 ); - insertSegment( *seg_list, text_segment, text_len, style, editor); - cur++; - if( !*cur || *cur == '\n' ) - { - continue; - } - } - - // Skip white space - while( *cur && iswspace(*cur) && (*cur != '\n') ) - { - cur++; - } - if( !*cur || *cur == '\n' ) - { - continue; - } - - // cur is now at the first non-whitespace character of a new line - - // Line start tokens - { - bool line_done = false; - for (token_list_t::iterator iter = mLineTokenList.begin(); - iter != mLineTokenList.end(); ++iter) - { - LLKeywordToken* cur_token = *iter; - if( cur_token->isHead( cur ) ) - { - S32 seg_start = cur - base; - while( *cur && *cur != '\n' ) - { - // skip the rest of the line - cur++; - } - S32 seg_end = cur - base; - - //create segments from seg_start to seg_end - insertSegments(wtext, *seg_list,cur_token, text_len, seg_start, seg_end, style, editor); - line_done = true; // to break out of second loop. - break; - } - } - - if( line_done ) - { - continue; - } - } - } - - // Skip white space - while( *cur && iswspace(*cur) && (*cur != '\n') ) - { - cur++; - } - - while( *cur && *cur != '\n' ) - { - // Check against delimiters - { - S32 seg_start = 0; - LLKeywordToken* cur_delimiter = NULL; - for (token_list_t::iterator iter = mDelimiterTokenList.begin(); - iter != mDelimiterTokenList.end(); ++iter) - { - LLKeywordToken* delimiter = *iter; - if( delimiter->isHead( cur ) ) - { - cur_delimiter = delimiter; - break; - } - } - - if( cur_delimiter ) - { - S32 between_delimiters = 0; - S32 seg_end = 0; - - seg_start = cur - base; - cur += cur_delimiter->getLengthHead(); - - LLKeywordToken::ETokenType type = cur_delimiter->getType(); - if( type == LLKeywordToken::TT_TWO_SIDED_DELIMITER || type == LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS ) - { - while( *cur && !cur_delimiter->isTail(cur)) - { - // Check for an escape sequence. - if (type == LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS && *cur == '\\') - { - // Count the number of backslashes. - S32 num_backslashes = 0; - while (*cur == '\\') - { - num_backslashes++; - between_delimiters++; - cur++; - } - // If the next character is the end delimiter? - if (cur_delimiter->isTail(cur)) - { - // If there was an odd number of backslashes, then this delimiter - // does not end the sequence. - if (num_backslashes % 2 == 1) - { - between_delimiters++; - cur++; - } - else - { - // This is an end delimiter. - break; - } - } - } - else - { - between_delimiters++; - cur++; - } - } - - if( *cur ) - { - cur += cur_delimiter->getLengthHead(); - seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead() + cur_delimiter->getLengthTail(); - } - else - { - // eof - seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead(); - } - } - else - { - llassert( cur_delimiter->getType() == LLKeywordToken::TT_ONE_SIDED_DELIMITER ); - // Left side is the delimiter. Right side is eol or eof. - while( *cur && ('\n' != *cur) ) - { - between_delimiters++; - cur++; - } - seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead(); - } - - insertSegments(wtext, *seg_list,cur_delimiter, text_len, seg_start, seg_end, style, editor); - /* - LLTextSegmentPtr text_segment = new LLNormalTextSegment( cur_delimiter->getColor(), seg_start, seg_end, editor ); - text_segment->setToken( cur_delimiter ); - insertSegment( seg_list, text_segment, text_len, defaultColor, editor); - */ - // Note: we don't increment cur, since the end of one delimited seg may be immediately - // followed by the start of another one. - continue; - } - } - - // check against words - llwchar prev = cur > base ? *(cur-1) : 0; - if( !iswalnum( prev ) && (prev != '_') ) - { - const llwchar* p = cur; - while( iswalnum( *p ) || (*p == '_') ) - { - p++; - } - S32 seg_len = p - cur; - if( seg_len > 0 ) - { - WStringMapIndex word( cur, seg_len ); - word_token_map_t::iterator map_iter = mWordTokenMap.find(word); - if( map_iter != mWordTokenMap.end() ) - { - LLKeywordToken* cur_token = map_iter->second; - S32 seg_start = cur - base; - S32 seg_end = seg_start + seg_len; - - // LL_INFOS("SyntaxLSL") << "Seg: [" << word.c_str() << "]" << LL_ENDL; - - insertSegments(wtext, *seg_list,cur_token, text_len, seg_start, seg_end, style, editor); - } - cur += seg_len; - continue; - } - } - - if( *cur && *cur != '\n' ) - { - cur++; - } - } - } -} - -void LLKeywords::insertSegments(const LLWString& wtext, std::vector& seg_list, LLKeywordToken* cur_token, S32 text_len, S32 seg_start, S32 seg_end, LLStyleConstSP style, LLTextEditor& editor ) -{ - std::string::size_type pos = wtext.find('\n',seg_start); - - LLStyleConstSP cur_token_style = new LLStyle(LLStyle::Params().font(style->getFont()).color(cur_token->getColor())); - - while (pos!=-1 && pos < (std::string::size_type)seg_end) - { - if (pos!=seg_start) - { - LLTextSegmentPtr text_segment = new LLNormalTextSegment(cur_token_style, seg_start, pos, editor); - text_segment->setToken( cur_token ); - insertSegment( seg_list, text_segment, text_len, style, editor); - } - - LLTextSegmentPtr text_segment = new LLLineBreakTextSegment(style, pos); - text_segment->setToken( cur_token ); - insertSegment( seg_list, text_segment, text_len, style, editor); - - seg_start = pos+1; - pos = wtext.find('\n',seg_start); - } - - LLTextSegmentPtr text_segment = new LLNormalTextSegment(cur_token_style, seg_start, seg_end, editor); - text_segment->setToken( cur_token ); - insertSegment( seg_list, text_segment, text_len, style, editor); -} - -void LLKeywords::insertSegment(std::vector& seg_list, LLTextSegmentPtr new_segment, S32 text_len, const LLColor4 &defaultColor, LLTextEditor& editor ) -{ - LLTextSegmentPtr last = seg_list.back(); - S32 new_seg_end = new_segment->getEnd(); - - if( new_segment->getStart() == last->getStart() ) - { - seg_list.pop_back(); - } - else - { - last->setEnd( new_segment->getStart() ); - } - seg_list.push_back( new_segment ); - - if( new_seg_end < text_len ) - { - seg_list.push_back( new LLNormalTextSegment( defaultColor, new_seg_end, text_len, editor ) ); - } -} - -void LLKeywords::insertSegment(std::vector& seg_list, LLTextSegmentPtr new_segment, S32 text_len, LLStyleConstSP style, LLTextEditor& editor ) -{ - LLTextSegmentPtr last = seg_list.back(); - S32 new_seg_end = new_segment->getEnd(); - - if( new_segment->getStart() == last->getStart() ) - { - seg_list.pop_back(); - } - else - { - last->setEnd( new_segment->getStart() ); - } - seg_list.push_back( new_segment ); - - if( new_seg_end < text_len ) - { - seg_list.push_back( new LLNormalTextSegment( style, new_seg_end, text_len, editor ) ); - } -} - -#ifdef _DEBUG -void LLKeywords::dump() -{ - LL_INFOS() << "LLKeywords" << LL_ENDL; - - - LL_INFOS() << "LLKeywords::sWordTokenMap" << LL_ENDL; - word_token_map_t::iterator word_token_iter = mWordTokenMap.begin(); - while( word_token_iter != mWordTokenMap.end() ) - { - LLKeywordToken* word_token = word_token_iter->second; - word_token->dump(); - ++word_token_iter; - } - - LL_INFOS() << "LLKeywords::sLineTokenList" << LL_ENDL; - for (token_list_t::iterator iter = mLineTokenList.begin(); - iter != mLineTokenList.end(); ++iter) - { - LLKeywordToken* line_token = *iter; - line_token->dump(); - } - - - LL_INFOS() << "LLKeywords::sDelimiterTokenList" << LL_ENDL; - for (token_list_t::iterator iter = mDelimiterTokenList.begin(); - iter != mDelimiterTokenList.end(); ++iter) - { - LLKeywordToken* delimiter_token = *iter; - delimiter_token->dump(); - } -} - -void LLKeywordToken::dump() -{ - LL_INFOS() << "[" << - mColor.mV[VX] << ", " << - mColor.mV[VY] << ", " << - mColor.mV[VZ] << "] [" << - wstring_to_utf8str(mToken) << "]" << - LL_ENDL; -} - -#endif // DEBUG +/** + * @file llkeywords.cpp + * @brief Keyword list for LSL + * + * $LicenseInfo:firstyear=2000&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 +#include + +#include "llkeywords.h" +#include "llsdserialize.h" +#include "lltexteditor.h" +#include "llstl.h" + +inline bool LLKeywordToken::isHead(const llwchar* s) const +{ + // strncmp is much faster than string compare + bool res = true; + const llwchar* t = mToken.c_str(); + S32 len = mToken.size(); + for (S32 i=0; isecond.get("type").asString() + " " + argsIt->first; + if (argsCount-- > 1) + { + argString += ", "; + } + } + } + else + { + LL_WARNS("SyntaxLSL") << "Argument array comtains a non-map element!" << LL_ENDL; + } + } + } + else if (!arguments.isUndefined()) + { + LL_WARNS("SyntaxLSL") << "Not an array! Invalid arguments LLSD passed to function." << arguments << LL_ENDL; + } + return argString; +} + +std::string LLKeywords::getAttribute(const std::string& key) +{ + attribute_iterator_t it = mAttributes.find(key); + return (it != mAttributes.end()) ? it->second : ""; +} + +LLColor4 LLKeywords::getColorGroup(const std::string& key_in) +{ + std::string color_group = "ScriptText"; + if (key_in == "functions") + { + color_group = "SyntaxLslFunction"; + } + else if (key_in == "controls") + { + color_group = "SyntaxLslControlFlow"; + } + else if (key_in == "events") + { + color_group = "SyntaxLslEvent"; + } + else if (key_in == "types") + { + color_group = "SyntaxLslDataType"; + } + else if (key_in == "misc-flow-label") + { + color_group = "SyntaxLslControlFlow"; + } + else if (key_in =="deprecated") + { + color_group = "SyntaxLslDeprecated"; + } + else if (key_in =="god-mode") + { + color_group = "SyntaxLslGodMode"; + } + else if (key_in == "constants" + || key_in == "constants-integer" + || key_in == "constants-float" + || key_in == "constants-string" + || key_in == "constants-key" + || key_in == "constants-rotation" + || key_in == "constants-vector") + { + color_group = "SyntaxLslConstant"; + } + else + { + LL_WARNS("SyntaxLSL") << "Color key '" << key_in << "' not recognized." << LL_ENDL; + } + + return LLUIColorTable::instance().getColor(color_group); +} + +void LLKeywords::initialize(LLSD SyntaxXML) +{ + mSyntax = SyntaxXML; + mLoaded = true; +} + +void LLKeywords::processTokens() +{ + if (!mLoaded) + { + return; + } + + // Add 'standard' stuff: Quotes, Comments, Strings, Labels, etc. before processing the LLSD + std::string delimiter; + addToken(LLKeywordToken::TT_LABEL, "@", getColorGroup("misc-flow-label"), "Label\nTarget for jump statement", delimiter ); + addToken(LLKeywordToken::TT_ONE_SIDED_DELIMITER, "//", LLUIColorTable::instance().getColor("SyntaxLslComment"), "Comment (single-line)\nNon-functional commentary or disabled code", delimiter ); + addToken(LLKeywordToken::TT_TWO_SIDED_DELIMITER, "/*", LLUIColorTable::instance().getColor("SyntaxLslComment"), "Comment (multi-line)\nNon-functional commentary or disabled code", "*/" ); + addToken(LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS, "\"", LLUIColorTable::instance().getColor("SyntaxLslStringLiteral"), "String literal", "\"" ); + + LLSD::map_iterator itr = mSyntax.beginMap(); + for ( ; itr != mSyntax.endMap(); ++itr) + { + if (itr->first == "llsd-lsl-syntax-version") + { + // Skip over version key. + } + else + { + if (itr->second.isMap()) + { + processTokensGroup(itr->second, itr->first); + } + else + { + LL_WARNS("LSL-Tokens-Processing") << "Map for " + itr->first + " entries is missing! Ignoring." << LL_ENDL; + } + } + } + LL_INFOS("SyntaxLSL") << "Finished processing tokens." << LL_ENDL; +} + +void LLKeywords::processTokensGroup(const LLSD& tokens, const std::string& group) +{ + LLColor4 color; + LLColor4 color_group; + LLColor4 color_deprecated = getColorGroup("deprecated"); + LLColor4 color_god_mode = getColorGroup("god-mode"); + + LLKeywordToken::ETokenType token_type = LLKeywordToken::TT_UNKNOWN; + // If a new token type is added here, it must also be added to the 'addToken' method + if (group == "constants") + { + token_type = LLKeywordToken::TT_CONSTANT; + } + else if (group == "controls") + { + token_type = LLKeywordToken::TT_CONTROL; + } + else if (group == "events") + { + token_type = LLKeywordToken::TT_EVENT; + } + else if (group == "functions") + { + token_type = LLKeywordToken::TT_FUNCTION; + } + else if (group == "label") + { + token_type = LLKeywordToken::TT_LABEL; + } + else if (group == "types") + { + token_type = LLKeywordToken::TT_TYPE; + } + + color_group = getColorGroup(group); + LL_DEBUGS("SyntaxLSL") << "Group: '" << group << "', using color: '" << color_group << "'" << LL_ENDL; + + if (tokens.isMap()) + { + LLSD::map_const_iterator outer_itr = tokens.beginMap(); + for ( ; outer_itr != tokens.endMap(); ++outer_itr ) + { + if (outer_itr->second.isMap()) + { + mAttributes.clear(); + LLSD arguments = LLSD(); + LLSD::map_const_iterator inner_itr = outer_itr->second.beginMap(); + for ( ; inner_itr != outer_itr->second.endMap(); ++inner_itr ) + { + if (inner_itr->first == "arguments") + { + if (inner_itr->second.isArray()) + { + arguments = inner_itr->second; + } + } + else if (!inner_itr->second.isMap() && !inner_itr->second.isArray()) + { + mAttributes[inner_itr->first] = inner_itr->second.asString(); + } + else + { + LL_WARNS("SyntaxLSL") << "Not a valid attribute: " << inner_itr->first << LL_ENDL; + } + } + + std::string tooltip = ""; + switch (token_type) + { + case LLKeywordToken::TT_CONSTANT: + if (getAttribute("type").length() > 0) + { + color_group = getColorGroup(group + "-" + getAttribute("type")); + } + else + { + color_group = getColorGroup(group); + } + tooltip = "Type: " + getAttribute("type") + ", Value: " + getAttribute("value"); + break; + case LLKeywordToken::TT_EVENT: + tooltip = outer_itr->first + "(" + getArguments(arguments) + ")"; + break; + case LLKeywordToken::TT_FUNCTION: + tooltip = getAttribute("return") + " " + outer_itr->first + "(" + getArguments(arguments) + ");"; + tooltip.append("\nEnergy: "); + tooltip.append(getAttribute("energy").empty() ? "0.0" : getAttribute("energy")); + if (!getAttribute("sleep").empty()) + { + tooltip += ", Sleep: " + getAttribute("sleep"); + } + default: + break; + } + + if (!getAttribute("tooltip").empty()) + { + if (!tooltip.empty()) + { + tooltip.append("\n"); + } + tooltip.append(getAttribute("tooltip")); + } + + color = getAttribute("deprecated") == "true" ? color_deprecated : color_group; + + if (getAttribute("god-mode") == "true") + { + color = color_god_mode; + } + + addToken(token_type, outer_itr->first, color, tooltip); + } + } + } + else if (tokens.isArray()) // Currently nothing should need this, but it's here for completeness + { + LL_INFOS("SyntaxLSL") << "Curious, shouldn't be an array here; adding all using color " << color << LL_ENDL; + for (S32 count = 0; count < tokens.size(); ++count) + { + addToken(token_type, tokens[count], color, ""); + } + } + else + { + LL_WARNS("SyntaxLSL") << "Invalid map/array passed: '" << tokens << "'" << LL_ENDL; + } +} + +LLKeywords::WStringMapIndex::WStringMapIndex(const WStringMapIndex& other) +{ + if(other.mOwner) + { + copyData(other.mData, other.mLength); + } + else + { + mOwner = false; + mLength = other.mLength; + mData = other.mData; + } +} + +LLKeywords::WStringMapIndex::WStringMapIndex(const LLWString& str) +{ + copyData(str.data(), str.size()); +} + +LLKeywords::WStringMapIndex::WStringMapIndex(const llwchar *start, size_t length) +: mData(start) +, mLength(length) +, mOwner(false) +{ +} + +LLKeywords::WStringMapIndex::~WStringMapIndex() +{ + if (mOwner) + { + delete[] mData; + } +} + +void LLKeywords::WStringMapIndex::copyData(const llwchar *start, size_t length) +{ + llwchar *data = new llwchar[length]; + memcpy((void*)data, (const void*)start, length * sizeof(llwchar)); + + mOwner = true; + mLength = length; + mData = data; +} + +bool LLKeywords::WStringMapIndex::operator<(const LLKeywords::WStringMapIndex &other) const +{ + // NOTE: Since this is only used to organize a std::map, it doesn't matter if it uses correct collate order or not. + // The comparison only needs to strictly order all possible strings, and be stable. + + bool result = false; + const llwchar* self_iter = mData; + const llwchar* self_end = mData + mLength; + const llwchar* other_iter = other.mData; + const llwchar* other_end = other.mData + other.mLength; + + while(true) + { + if(other_iter >= other_end) + { + // We've hit the end of other. + // This covers two cases: other being shorter than self, or the strings being equal. + // In either case, we want to return false. + result = false; + break; + } + else if(self_iter >= self_end) + { + // self is shorter than other. + result = true; + break; + } + else if(*self_iter != *other_iter) + { + // The current character differs. The strings are not equal. + result = *self_iter < *other_iter; + break; + } + + self_iter++; + other_iter++; + } + + return result; +} + +LLTrace::BlockTimerStatHandle FTM_SYNTAX_COLORING("Syntax Coloring"); + +// Walk through a string, applying the rules specified by the keyword token list and +// create a list of color segments. +void LLKeywords::findSegments(std::vector* seg_list, const LLWString& wtext, LLTextEditor& editor, LLStyleConstSP style) +{ + LL_RECORD_BLOCK_TIME(FTM_SYNTAX_COLORING); + seg_list->clear(); + + if( wtext.empty() ) + { + return; + } + + S32 text_len = wtext.size() + 1; + + seg_list->push_back( new LLNormalTextSegment( style, 0, text_len, editor ) ); + + const llwchar* base = wtext.c_str(); + const llwchar* cur = base; + while( *cur ) + { + if( *cur == '\n' || cur == base ) + { + if( *cur == '\n' ) + { + LLTextSegmentPtr text_segment = new LLLineBreakTextSegment(style, cur-base); + text_segment->setToken( 0 ); + insertSegment( *seg_list, text_segment, text_len, style, editor); + cur++; + if( !*cur || *cur == '\n' ) + { + continue; + } + } + + // Skip white space + while( *cur && iswspace(*cur) && (*cur != '\n') ) + { + cur++; + } + if( !*cur || *cur == '\n' ) + { + continue; + } + + // cur is now at the first non-whitespace character of a new line + + // Line start tokens + { + bool line_done = false; + for (token_list_t::iterator iter = mLineTokenList.begin(); + iter != mLineTokenList.end(); ++iter) + { + LLKeywordToken* cur_token = *iter; + if( cur_token->isHead( cur ) ) + { + S32 seg_start = cur - base; + while( *cur && *cur != '\n' ) + { + // skip the rest of the line + cur++; + } + S32 seg_end = cur - base; + + //create segments from seg_start to seg_end + insertSegments(wtext, *seg_list,cur_token, text_len, seg_start, seg_end, style, editor); + line_done = true; // to break out of second loop. + break; + } + } + + if( line_done ) + { + continue; + } + } + } + + // Skip white space + while( *cur && iswspace(*cur) && (*cur != '\n') ) + { + cur++; + } + + while( *cur && *cur != '\n' ) + { + // Check against delimiters + { + S32 seg_start = 0; + LLKeywordToken* cur_delimiter = NULL; + for (token_list_t::iterator iter = mDelimiterTokenList.begin(); + iter != mDelimiterTokenList.end(); ++iter) + { + LLKeywordToken* delimiter = *iter; + if( delimiter->isHead( cur ) ) + { + cur_delimiter = delimiter; + break; + } + } + + if( cur_delimiter ) + { + S32 between_delimiters = 0; + S32 seg_end = 0; + + seg_start = cur - base; + cur += cur_delimiter->getLengthHead(); + + LLKeywordToken::ETokenType type = cur_delimiter->getType(); + if( type == LLKeywordToken::TT_TWO_SIDED_DELIMITER || type == LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS ) + { + while( *cur && !cur_delimiter->isTail(cur)) + { + // Check for an escape sequence. + if (type == LLKeywordToken::TT_DOUBLE_QUOTATION_MARKS && *cur == '\\') + { + // Count the number of backslashes. + S32 num_backslashes = 0; + while (*cur == '\\') + { + num_backslashes++; + between_delimiters++; + cur++; + } + // If the next character is the end delimiter? + if (cur_delimiter->isTail(cur)) + { + // If there was an odd number of backslashes, then this delimiter + // does not end the sequence. + if (num_backslashes % 2 == 1) + { + between_delimiters++; + cur++; + } + else + { + // This is an end delimiter. + break; + } + } + } + else + { + between_delimiters++; + cur++; + } + } + + if( *cur ) + { + cur += cur_delimiter->getLengthHead(); + seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead() + cur_delimiter->getLengthTail(); + } + else + { + // eof + seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead(); + } + } + else + { + llassert( cur_delimiter->getType() == LLKeywordToken::TT_ONE_SIDED_DELIMITER ); + // Left side is the delimiter. Right side is eol or eof. + while( *cur && ('\n' != *cur) ) + { + between_delimiters++; + cur++; + } + seg_end = seg_start + between_delimiters + cur_delimiter->getLengthHead(); + } + + insertSegments(wtext, *seg_list,cur_delimiter, text_len, seg_start, seg_end, style, editor); + /* + LLTextSegmentPtr text_segment = new LLNormalTextSegment( cur_delimiter->getColor(), seg_start, seg_end, editor ); + text_segment->setToken( cur_delimiter ); + insertSegment( seg_list, text_segment, text_len, defaultColor, editor); + */ + // Note: we don't increment cur, since the end of one delimited seg may be immediately + // followed by the start of another one. + continue; + } + } + + // check against words + llwchar prev = cur > base ? *(cur-1) : 0; + if( !iswalnum( prev ) && (prev != '_') ) + { + const llwchar* p = cur; + while( iswalnum( *p ) || (*p == '_') ) + { + p++; + } + S32 seg_len = p - cur; + if( seg_len > 0 ) + { + WStringMapIndex word( cur, seg_len ); + word_token_map_t::iterator map_iter = mWordTokenMap.find(word); + if( map_iter != mWordTokenMap.end() ) + { + LLKeywordToken* cur_token = map_iter->second; + S32 seg_start = cur - base; + S32 seg_end = seg_start + seg_len; + + // LL_INFOS("SyntaxLSL") << "Seg: [" << word.c_str() << "]" << LL_ENDL; + + insertSegments(wtext, *seg_list,cur_token, text_len, seg_start, seg_end, style, editor); + } + cur += seg_len; + continue; + } + } + + if( *cur && *cur != '\n' ) + { + cur++; + } + } + } +} + +void LLKeywords::insertSegments(const LLWString& wtext, std::vector& seg_list, LLKeywordToken* cur_token, S32 text_len, S32 seg_start, S32 seg_end, LLStyleConstSP style, LLTextEditor& editor ) +{ + std::string::size_type pos = wtext.find('\n',seg_start); + + LLStyleConstSP cur_token_style = new LLStyle(LLStyle::Params().font(style->getFont()).color(cur_token->getColor())); + + while (pos!=-1 && pos < (std::string::size_type)seg_end) + { + if (pos!=seg_start) + { + LLTextSegmentPtr text_segment = new LLNormalTextSegment(cur_token_style, seg_start, pos, editor); + text_segment->setToken( cur_token ); + insertSegment( seg_list, text_segment, text_len, style, editor); + } + + LLTextSegmentPtr text_segment = new LLLineBreakTextSegment(style, pos); + text_segment->setToken( cur_token ); + insertSegment( seg_list, text_segment, text_len, style, editor); + + seg_start = pos+1; + pos = wtext.find('\n',seg_start); + } + + LLTextSegmentPtr text_segment = new LLNormalTextSegment(cur_token_style, seg_start, seg_end, editor); + text_segment->setToken( cur_token ); + insertSegment( seg_list, text_segment, text_len, style, editor); +} + +void LLKeywords::insertSegment(std::vector& seg_list, LLTextSegmentPtr new_segment, S32 text_len, const LLColor4 &defaultColor, LLTextEditor& editor ) +{ + LLTextSegmentPtr last = seg_list.back(); + S32 new_seg_end = new_segment->getEnd(); + + if( new_segment->getStart() == last->getStart() ) + { + seg_list.pop_back(); + } + else + { + last->setEnd( new_segment->getStart() ); + } + seg_list.push_back( new_segment ); + + if( new_seg_end < text_len ) + { + seg_list.push_back( new LLNormalTextSegment( defaultColor, new_seg_end, text_len, editor ) ); + } +} + +void LLKeywords::insertSegment(std::vector& seg_list, LLTextSegmentPtr new_segment, S32 text_len, LLStyleConstSP style, LLTextEditor& editor ) +{ + LLTextSegmentPtr last = seg_list.back(); + S32 new_seg_end = new_segment->getEnd(); + + if( new_segment->getStart() == last->getStart() ) + { + seg_list.pop_back(); + } + else + { + last->setEnd( new_segment->getStart() ); + } + seg_list.push_back( new_segment ); + + if( new_seg_end < text_len ) + { + seg_list.push_back( new LLNormalTextSegment( style, new_seg_end, text_len, editor ) ); + } +} + +#ifdef _DEBUG +void LLKeywords::dump() +{ + LL_INFOS() << "LLKeywords" << LL_ENDL; + + + LL_INFOS() << "LLKeywords::sWordTokenMap" << LL_ENDL; + word_token_map_t::iterator word_token_iter = mWordTokenMap.begin(); + while( word_token_iter != mWordTokenMap.end() ) + { + LLKeywordToken* word_token = word_token_iter->second; + word_token->dump(); + ++word_token_iter; + } + + LL_INFOS() << "LLKeywords::sLineTokenList" << LL_ENDL; + for (token_list_t::iterator iter = mLineTokenList.begin(); + iter != mLineTokenList.end(); ++iter) + { + LLKeywordToken* line_token = *iter; + line_token->dump(); + } + + + LL_INFOS() << "LLKeywords::sDelimiterTokenList" << LL_ENDL; + for (token_list_t::iterator iter = mDelimiterTokenList.begin(); + iter != mDelimiterTokenList.end(); ++iter) + { + LLKeywordToken* delimiter_token = *iter; + delimiter_token->dump(); + } +} + +void LLKeywordToken::dump() +{ + LL_INFOS() << "[" << + mColor.mV[VX] << ", " << + mColor.mV[VY] << ", " << + mColor.mV[VZ] << "] [" << + wstring_to_utf8str(mToken) << "]" << + LL_ENDL; +} + +#endif // DEBUG diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp index 1b6a785f27..6db9f64a97 100644 --- a/indra/llui/lllayoutstack.cpp +++ b/indra/llui/lllayoutstack.cpp @@ -1,1022 +1,1022 @@ -/** - * @file lllayoutstack.cpp - * @brief LLLayout class - dynamic stacking of UI elements - * - * $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$ - */ - -// Opaque view with a background and a border. Can contain LLUICtrls. - -#include "linden_common.h" - -#include "lllayoutstack.h" - -#include "lllocalcliprect.h" -#include "llpanel.h" -#include "llcriticaldamp.h" -#include "lliconctrl.h" -#include "boost/foreach.hpp" - -static const F32 MIN_FRACTIONAL_SIZE = 0.00001f; -static const F32 MAX_FRACTIONAL_SIZE = 1.f; - -static LLDefaultChildRegistry::Register register_layout_stack("layout_stack"); -static LLLayoutStack::LayoutStackRegistry::Register register_layout_panel("layout_panel"); - -// -// LLLayoutPanel -// -LLLayoutPanel::Params::Params() -: expanded_min_dim("expanded_min_dim", 0), - min_dim("min_dim", -1), - user_resize("user_resize", false), - auto_resize("auto_resize", true) -{ - addSynonym(min_dim, "min_width"); - addSynonym(min_dim, "min_height"); -} - -LLLayoutPanel::LLLayoutPanel(const Params& p) -: LLPanel(p), - mExpandedMinDim(p.expanded_min_dim.isProvided() ? p.expanded_min_dim : p.min_dim), - mMinDim(p.min_dim), - mAutoResize(p.auto_resize), - mUserResize(p.user_resize), - mCollapsed(false), - mCollapseAmt(0.f), - mVisibleAmt(1.f), // default to fully visible - mResizeBar(NULL), - mFractionalSize(0.f), - mTargetDim(0), - mIgnoreReshape(false), - mOrientation(LLLayoutStack::HORIZONTAL) -{ - // panels initialized as hidden should not start out partially visible - if (!getVisible()) - { - mVisibleAmt = 0.f; - } -} - -void LLLayoutPanel::initFromParams(const Params& p) -{ - LLPanel::initFromParams(p); - setFollowsNone(); -} - - -LLLayoutPanel::~LLLayoutPanel() -{ - // probably not necessary, but... - delete mResizeBar; - mResizeBar = NULL; - - gFocusMgr.removeKeyboardFocusWithoutCallback(this); -} - -F32 LLLayoutPanel::getAutoResizeFactor() const -{ - return mVisibleAmt * (1.f - mCollapseAmt); -} - -F32 LLLayoutPanel::getVisibleAmount() const -{ - return mVisibleAmt; -} - -S32 LLLayoutPanel::getLayoutDim() const -{ - return ll_round((F32)((mOrientation == LLLayoutStack::HORIZONTAL) - ? getRect().getWidth() - : getRect().getHeight())); -} - -S32 LLLayoutPanel::getTargetDim() const -{ - return mTargetDim; -} - -void LLLayoutPanel::setTargetDim(S32 value) -{ - LLRect new_rect(getRect()); - if (mOrientation == LLLayoutStack::HORIZONTAL) - { - new_rect.mRight = new_rect.mLeft + value; - } - else - { - new_rect.mTop = new_rect.mBottom + value; - } - setShape(new_rect, true); -} - -S32 LLLayoutPanel::getVisibleDim() const -{ - F32 min_dim = getRelevantMinDim(); - return ll_round(mVisibleAmt - * (min_dim - + (((F32)mTargetDim - min_dim) * (1.f - mCollapseAmt)))); -} - -void LLLayoutPanel::setOrientation( LLView::EOrientation orientation ) -{ - mOrientation = orientation; - S32 layout_dim = ll_round((F32)((mOrientation == LLLayoutStack::HORIZONTAL) - ? getRect().getWidth() - : getRect().getHeight())); - - if (!mAutoResize && mUserResize && mMinDim == -1) - { - setMinDim(layout_dim); - } - mTargetDim = llmax(layout_dim, getMinDim()); -} - -void LLLayoutPanel::setVisible( bool visible ) -{ - if (visible != getVisible()) - { - LLLayoutStack* stackp = dynamic_cast(getParent()); - if (stackp) - { - stackp->mNeedsLayout = true; - } - } - LLPanel::setVisible(visible); -} - -void LLLayoutPanel::reshape( S32 width, S32 height, bool called_from_parent /*= true*/ ) -{ - if (width == getRect().getWidth() && height == getRect().getHeight() && !LLView::sForceReshape) return; - - if (!mIgnoreReshape && !mAutoResize) - { - mTargetDim = (mOrientation == LLLayoutStack::HORIZONTAL) ? width : height; - LLLayoutStack* stackp = dynamic_cast(getParent()); - if (stackp) - { - stackp->mNeedsLayout = true; - } - } - LLPanel::reshape(width, height, called_from_parent); -} - -void LLLayoutPanel::handleReshape(const LLRect& new_rect, bool by_user) -{ - LLLayoutStack* stackp = dynamic_cast(getParent()); - if (stackp) - { - if (by_user) - { // tell layout stack to account for new shape - - // make sure that panels have already been auto resized - stackp->updateLayout(); - // now apply requested size to panel - stackp->updatePanelRect(this, new_rect); - } - stackp->mNeedsLayout = true; - } - LLPanel::handleReshape(new_rect, by_user); -} - -// -// LLLayoutStack -// - -LLLayoutStack::Params::Params() -: orientation("orientation"), - animate("animate", true), - clip("clip", true), - open_time_constant("open_time_constant", 0.02f), - close_time_constant("close_time_constant", 0.03f), - resize_bar_overlap("resize_bar_overlap", 1), - border_size("border_size", LLCachedControl(*LLUI::getInstance()->mSettingGroups["config"], "UIResizeBarHeight", 0)), - show_drag_handle("show_drag_handle", false), - drag_handle_first_indent("drag_handle_first_indent", 0), - drag_handle_second_indent("drag_handle_second_indent", 0), - drag_handle_thickness("drag_handle_thickness", 5), - drag_handle_shift("drag_handle_shift", 2), - drag_handle_color("drag_handle_color", LLUIColorTable::instance().getColor("ResizebarBody")) -{ - addSynonym(border_size, "drag_handle_gap"); -} - -LLLayoutStack::LLLayoutStack(const LLLayoutStack::Params& p) -: LLView(p), - mPanelSpacing(p.border_size), - mOrientation(p.orientation), - mAnimate(p.animate), - mAnimatedThisFrame(false), - mNeedsLayout(true), - mClip(p.clip), - mOpenTimeConstant(p.open_time_constant), - mCloseTimeConstant(p.close_time_constant), - mResizeBarOverlap(p.resize_bar_overlap), - mShowDragHandle(p.show_drag_handle), - mDragHandleFirstIndent(p.drag_handle_first_indent), - mDragHandleSecondIndent(p.drag_handle_second_indent), - mDragHandleThickness(p.drag_handle_thickness), - mDragHandleShift(p.drag_handle_shift), - mDragHandleColor(p.drag_handle_color()) -{ -} - -LLLayoutStack::~LLLayoutStack() -{ - e_panel_list_t panels = mPanels; // copy list of panel pointers - mPanels.clear(); // clear so that removeChild() calls don't cause trouble - std::for_each(panels.begin(), panels.end(), DeletePointer()); -} - -void LLLayoutStack::draw() -{ - updateLayout(); - - // always clip to stack itself - LLLocalClipRect clip(getLocalRect()); - for (LLLayoutPanel* panelp : mPanels) - { - if ((!panelp->getVisible() || panelp->mCollapsed) - && (panelp->mVisibleAmt < 0.001f || !mAnimate)) - { - // essentially invisible - continue; - } - // clip to layout rectangle, not bounding rectangle - LLRect clip_rect = panelp->getRect(); - // scale clipping rectangle by visible amount - if (mOrientation == HORIZONTAL) - { - clip_rect.mRight = clip_rect.mLeft + panelp->getVisibleDim(); - } - else - { - clip_rect.mBottom = clip_rect.mTop - panelp->getVisibleDim(); - } - - {LLLocalClipRect clip(clip_rect, mClip); - // only force drawing invisible children if visible amount is non-zero - drawChild(panelp, 0, 0, !clip_rect.isEmpty()); - } - if (panelp->getResizeBar()->getVisible()) - { - drawChild(panelp->getResizeBar()); - } - } -} - -void LLLayoutStack::deleteAllChildren() -{ - mPanels.clear(); - LLView::deleteAllChildren(); - - // Not really needed since nothing is left to - // display, but for the sake of consistency - updateFractionalSizes(); - mNeedsLayout = true; -} - -void LLLayoutStack::removeChild(LLView* view) -{ - LLLayoutPanel* embedded_panelp = findEmbeddedPanel(dynamic_cast(view)); - - if (embedded_panelp) - { - mPanels.erase(std::find(mPanels.begin(), mPanels.end(), embedded_panelp)); - LLView::removeChild(view); - updateFractionalSizes(); - mNeedsLayout = true; - } - else - { - LLView::removeChild(view); - } -} - -bool LLLayoutStack::postBuild() -{ - updateLayout(); - return true; -} - -bool LLLayoutStack::addChild(LLView* child, S32 tab_group) -{ - LLLayoutPanel* panelp = dynamic_cast(child); - if (panelp) - { - panelp->setOrientation(mOrientation); - mPanels.push_back(panelp); - createResizeBar(panelp); - mNeedsLayout = true; - } - bool result = LLView::addChild(child, tab_group); - - updateFractionalSizes(); - return result; -} - -void LLLayoutStack::addPanel(LLLayoutPanel* panel, EAnimate animate) -{ - addChild(panel); - - // panel starts off invisible (collapsed) - if (animate == ANIMATE) - { - panel->mVisibleAmt = 0.f; - panel->setVisible(true); - } -} - -void LLLayoutStack::collapsePanel(LLPanel* panel, bool collapsed) -{ - LLLayoutPanel* panel_container = findEmbeddedPanel(panel); - if (!panel_container) return; - - panel_container->mCollapsed = collapsed; - mNeedsLayout = true; -} - -class LLImagePanel : public LLPanel -{ -public: - struct Params : public LLInitParam::Block - { - Optional horizontal; - Params() : horizontal("horizontal", false) {} - }; - LLImagePanel(const Params& p) : LLPanel(p), mHorizontal(p.horizontal) {} - virtual ~LLImagePanel() {} - - void draw() - { - const LLRect& parent_rect = getParent()->getRect(); - const LLRect& rect = getRect(); - LLRect clip_rect( -rect.mLeft, parent_rect.getHeight() - rect.mBottom - 2 - , parent_rect.getWidth() - rect.mLeft - (mHorizontal ? 2 : 0), -rect.mBottom); - LLLocalClipRect clip(clip_rect); - LLPanel::draw(); - } - -private: - bool mHorizontal; -}; - -void LLLayoutStack::updateLayout() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - - if (!mNeedsLayout) return; - - bool continue_animating = animatePanels(); - F32 total_visible_fraction = 0.f; - S32 space_to_distribute = (mOrientation == HORIZONTAL) - ? getRect().getWidth() - : getRect().getHeight(); - - // first, assign minimum dimensions - for (LLLayoutPanel* panelp : mPanels) - { - if (panelp->mAutoResize) - { - panelp->mTargetDim = panelp->getRelevantMinDim(); - } - space_to_distribute -= panelp->getVisibleDim() + ll_round((F32)mPanelSpacing * panelp->getVisibleAmount()); - total_visible_fraction += panelp->mFractionalSize * panelp->getAutoResizeFactor(); - } - - llassert(total_visible_fraction < 1.05f); - - // don't need spacing after last panel - if (!mPanels.empty()) - { - space_to_distribute += ll_round(F32(mPanelSpacing) * mPanels.back()->getVisibleAmount()); - } - - S32 remaining_space = space_to_distribute; - if (space_to_distribute > 0 && total_visible_fraction > 0.f) - { // give space proportionally to visible auto resize panels - for (LLLayoutPanel* panelp : mPanels) - { - if (panelp->mAutoResize) - { - F32 fraction_to_distribute = (panelp->mFractionalSize * panelp->getAutoResizeFactor()) / (total_visible_fraction); - S32 delta = ll_round((F32)space_to_distribute * fraction_to_distribute); - panelp->mTargetDim += delta; - remaining_space -= delta; - } - } - } - - // distribute any left over pixels to non-collapsed, visible panels - for (LLLayoutPanel* panelp : mPanels) - { - if (remaining_space == 0) break; - - if (panelp->mAutoResize - && !panelp->mCollapsed - && panelp->getVisible()) - { - S32 space_for_panel = remaining_space > 0 ? 1 : -1; - panelp->mTargetDim += space_for_panel; - remaining_space -= space_for_panel; - } - } - - F32 cur_pos = (mOrientation == HORIZONTAL) ? 0.f : (F32)getRect().getHeight(); - - for (LLLayoutPanel* panelp : mPanels) - { - F32 panel_dim = llmax(panelp->getExpandedMinDim(), panelp->mTargetDim); - - LLRect panel_rect; - if (mOrientation == HORIZONTAL) - { - panel_rect.setLeftTopAndSize(ll_round(cur_pos), - getRect().getHeight(), - ll_round(panel_dim), - getRect().getHeight()); - } - else - { - panel_rect.setLeftTopAndSize(0, - ll_round(cur_pos), - getRect().getWidth(), - ll_round(panel_dim)); - } - - LLRect resize_bar_rect(panel_rect); - F32 panel_spacing = (F32)mPanelSpacing * panelp->getVisibleAmount(); - F32 panel_visible_dim = panelp->getVisibleDim(); - S32 panel_spacing_round = (S32)(ll_round(panel_spacing)); - - if (mOrientation == HORIZONTAL) - { - cur_pos += panel_visible_dim + panel_spacing; - - if (mShowDragHandle && panel_spacing_round > mDragHandleThickness) - { - resize_bar_rect.mLeft = panel_rect.mRight + mDragHandleShift; - resize_bar_rect.mRight = resize_bar_rect.mLeft + mDragHandleThickness; - } - else - { - resize_bar_rect.mLeft = panel_rect.mRight - mResizeBarOverlap; - resize_bar_rect.mRight = panel_rect.mRight + panel_spacing_round + mResizeBarOverlap; - } - - if (mShowDragHandle) - { - resize_bar_rect.mBottom += mDragHandleSecondIndent; - resize_bar_rect.mTop -= mDragHandleFirstIndent; - } - - } - else //VERTICAL - { - cur_pos -= panel_visible_dim + panel_spacing; - - if (mShowDragHandle && panel_spacing_round > mDragHandleThickness) - { - resize_bar_rect.mTop = panel_rect.mBottom - mDragHandleShift; - resize_bar_rect.mBottom = resize_bar_rect.mTop - mDragHandleThickness; - } - else - { - resize_bar_rect.mTop = panel_rect.mBottom + mResizeBarOverlap; - resize_bar_rect.mBottom = panel_rect.mBottom - panel_spacing_round - mResizeBarOverlap; - } - - if (mShowDragHandle) - { - resize_bar_rect.mLeft += mDragHandleFirstIndent; - resize_bar_rect.mRight -= mDragHandleSecondIndent; - } - } - - panelp->setIgnoreReshape(true); - panelp->setShape(panel_rect); - panelp->setIgnoreReshape(false); - panelp->mResizeBar->setShape(resize_bar_rect); - } - - updateResizeBarLimits(); - - // clear animation flag at end, since panel resizes will set it - // and leave it set if there is any animation in progress - mNeedsLayout = continue_animating; -} // end LLLayoutStack::updateLayout - -void LLLayoutStack::setPanelSpacing(S32 val) -{ - if (mPanelSpacing != val) - { - mPanelSpacing = val; - mNeedsLayout = true; - } -} - -LLLayoutPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) const -{ - if (!panelp) return NULL; - - for (LLLayoutPanel* p : mPanels) - { - if (p == panelp) - { - return p; - } - } - return NULL; -} - -LLLayoutPanel* LLLayoutStack::findEmbeddedPanelByName(const std::string& name) const -{ - LLLayoutPanel* result = NULL; - - for (LLLayoutPanel* p : mPanels) - { - if (p->getName() == name) - { - result = p; - break; - } - } - - return result; -} - -void LLLayoutStack::createResizeBar(LLLayoutPanel* panelp) -{ - for (LLLayoutPanel* lp : mPanels) - { - if (lp->mResizeBar == NULL) - { - LLResizeBar::Params resize_params; - resize_params.name("resize"); - resize_params.resizing_view(lp); - resize_params.min_size(lp->getRelevantMinDim()); - resize_params.side((mOrientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM); - resize_params.snapping_enabled(false); - LLResizeBar* resize_bar = LLUICtrlFactory::create(resize_params); - lp->mResizeBar = resize_bar; - - if (mShowDragHandle) - { - LLPanel::Params resize_bar_bg_panel_p; - resize_bar_bg_panel_p.name = "resize_handle_bg_panel"; - resize_bar_bg_panel_p.rect = lp->mResizeBar->getLocalRect(); - resize_bar_bg_panel_p.follows.flags = FOLLOWS_ALL; - resize_bar_bg_panel_p.tab_stop = false; - resize_bar_bg_panel_p.background_visible = true; - resize_bar_bg_panel_p.bg_alpha_color = mDragHandleColor; - resize_bar_bg_panel_p.has_border = true; - resize_bar_bg_panel_p.border.border_thickness = 1; - resize_bar_bg_panel_p.border.highlight_light_color = LLUIColorTable::instance().getColor("ResizebarBorderLight"); - resize_bar_bg_panel_p.border.shadow_dark_color = LLUIColorTable::instance().getColor("ResizebarBorderDark"); - - LLPanel* resize_bar_bg_panel = LLUICtrlFactory::create(resize_bar_bg_panel_p); - - LLIconCtrl::Params icon_p; - icon_p.name = "resize_handle_image"; - icon_p.rect = lp->mResizeBar->getLocalRect(); - icon_p.follows.flags = FOLLOWS_ALL; - icon_p.image = LLUI::getUIImage(mOrientation == HORIZONTAL ? "Vertical Drag Handle" : "Horizontal Drag Handle"); - resize_bar_bg_panel->addChild(LLUICtrlFactory::create(icon_p)); - - lp->mResizeBar->addChild(resize_bar_bg_panel); - } - - /*if (mShowDragHandle) - { - LLViewBorder::Params border_params; - border_params.border_thickness = 1; - border_params.highlight_light_color = LLUIColorTable::instance().getColor("ResizebarBorderLight"); - border_params.shadow_dark_color = LLUIColorTable::instance().getColor("ResizebarBorderDark"); - - addBorder(border_params); - setBorderVisible(true); - - LLImagePanel::Params image_panel; - mDragHandleImage = LLUI::getUIImage(LLResizeBar::RIGHT == mSide ? "Vertical Drag Handle" : "Horizontal Drag Handle"); - image_panel.bg_alpha_image = mDragHandleImage; - image_panel.background_visible = true; - image_panel.horizontal = (LLResizeBar::BOTTOM == mSide); - mImagePanel = LLUICtrlFactory::create(image_panel); - setImagePanel(mImagePanel); - }*/ - - //if (mShowDragHandle) - //{ - // setBackgroundVisible(true); - // setTransparentColor(LLUIColorTable::instance().getColor("ResizebarBody")); - //} - - /*if (mShowDragHandle) - { - S32 image_width = mDragHandleImage->getTextureWidth(); - S32 image_height = mDragHandleImage->getTextureHeight(); - const LLRect& panel_rect = getRect(); - S32 image_left = (panel_rect.getWidth() - image_width) / 2 - 1; - S32 image_bottom = (panel_rect.getHeight() - image_height) / 2; - mImagePanel->setRect(LLRect(image_left, image_bottom + image_height, image_left + image_width, image_bottom)); - }*/ - LLView::addChild(resize_bar, 0); - } - } - // bring all resize bars to the front so that they are clickable even over the panels - // with a bit of overlap - for (e_panel_list_t::iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) - { - LLResizeBar* resize_barp = (*panel_it)->mResizeBar; - sendChildToFront(resize_barp); - } -} - -// update layout stack animations, etc. once per frame -// NOTE: we use this to size world view based on animating UI, *before* we draw the UI -// we might still need to call updateLayout during UI draw phase, in case UI elements -// are resizing themselves dynamically -//static -void LLLayoutStack::updateClass() -{ - for (auto& layout : instance_snapshot()) - { - layout.updateLayout(); - layout.mAnimatedThisFrame = false; - } -} - -void LLLayoutStack::updateFractionalSizes() -{ - F32 total_resizable_dim = 0.f; - - for (LLLayoutPanel* panelp : mPanels) - { - if (panelp->mAutoResize) - { - total_resizable_dim += llmax(MIN_FRACTIONAL_SIZE, (F32)(panelp->getLayoutDim() - panelp->getRelevantMinDim())); - } - } - - for (LLLayoutPanel* panelp : mPanels) - { - if (panelp->mAutoResize) - { - F32 panel_resizable_dim = llmax(MIN_FRACTIONAL_SIZE, (F32)(panelp->getLayoutDim() - panelp->getRelevantMinDim())); - panelp->mFractionalSize = panel_resizable_dim > 0.f - ? llclamp(panel_resizable_dim / total_resizable_dim, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE) - : MIN_FRACTIONAL_SIZE; - llassert(!llisnan(panelp->mFractionalSize)); - } - } - - normalizeFractionalSizes(); -} - - -void LLLayoutStack::normalizeFractionalSizes() -{ - S32 num_auto_resize_panels = 0; - F32 total_fractional_size = 0.f; - - for (LLLayoutPanel* panelp : mPanels) - { - if (panelp->mAutoResize) - { - total_fractional_size += panelp->mFractionalSize; - num_auto_resize_panels++; - } - } - - if (total_fractional_size == 0.f) - { // equal distribution - for (LLLayoutPanel* panelp : mPanels) - { - if (panelp->mAutoResize) - { - panelp->mFractionalSize = MAX_FRACTIONAL_SIZE / (F32)num_auto_resize_panels; - } - } - } - else - { // renormalize - for (LLLayoutPanel* panelp : mPanels) - { - if (panelp->mAutoResize) - { - panelp->mFractionalSize /= total_fractional_size; - } - } - } -} - -bool LLLayoutStack::animatePanels() -{ - bool continue_animating = false; - - // - // animate visibility - // - for (LLLayoutPanel* panelp : mPanels) - { - if (panelp->getVisible()) - { - if (mAnimate && panelp->mVisibleAmt < 1.f) - { - if (!mAnimatedThisFrame) - { - panelp->mVisibleAmt = lerp(panelp->mVisibleAmt, 1.f, LLSmoothInterpolation::getInterpolant(mOpenTimeConstant)); - if (panelp->mVisibleAmt > 0.99f) - { - panelp->mVisibleAmt = 1.f; - } - } - - mAnimatedThisFrame = true; - continue_animating = true; - } - else - { - if (panelp->mVisibleAmt != 1.f) - { - panelp->mVisibleAmt = 1.f; - mAnimatedThisFrame = true; - } - } - } - else // not visible - { - if (mAnimate && panelp->mVisibleAmt > 0.f) - { - if (!mAnimatedThisFrame) - { - panelp->mVisibleAmt = lerp(panelp->mVisibleAmt, 0.f, LLSmoothInterpolation::getInterpolant(mCloseTimeConstant)); - if (panelp->mVisibleAmt < 0.001f) - { - panelp->mVisibleAmt = 0.f; - } - } - - continue_animating = true; - mAnimatedThisFrame = true; - } - else - { - if (panelp->mVisibleAmt != 0.f) - { - panelp->mVisibleAmt = 0.f; - mAnimatedThisFrame = true; - } - } - } - - F32 collapse_state = panelp->mCollapsed ? 1.f : 0.f; - if (panelp->mCollapseAmt != collapse_state) - { - if (mAnimate) - { - if (!mAnimatedThisFrame) - { - panelp->mCollapseAmt = lerp(panelp->mCollapseAmt, collapse_state, LLSmoothInterpolation::getInterpolant(mCloseTimeConstant)); - } - - if (llabs(panelp->mCollapseAmt - collapse_state) < 0.001f) - { - panelp->mCollapseAmt = collapse_state; - } - - mAnimatedThisFrame = true; - continue_animating = true; - } - else - { - panelp->mCollapseAmt = collapse_state; - mAnimatedThisFrame = true; - } - } - } - - if (mAnimatedThisFrame) mNeedsLayout = true; - return continue_animating; -} - -void LLLayoutStack::updatePanelRect( LLLayoutPanel* resized_panel, const LLRect& new_rect ) -{ - S32 new_dim = (mOrientation == HORIZONTAL) - ? new_rect.getWidth() - : new_rect.getHeight(); - S32 delta_panel_dim = new_dim - resized_panel->getVisibleDim(); - if (delta_panel_dim == 0) return; - - F32 total_visible_fraction = 0.f; - F32 delta_auto_resize_headroom = 0.f; - F32 old_auto_resize_headroom = 0.f; - - LLLayoutPanel* other_resize_panel = NULL; - LLLayoutPanel* following_panel = NULL; - - BOOST_REVERSE_FOREACH(LLLayoutPanel* panelp, mPanels) // Should replace this when C++20 reverse view adaptor becomes available... - { - if (panelp->mAutoResize) - { - old_auto_resize_headroom += (F32)(panelp->mTargetDim - panelp->getRelevantMinDim()); - if (panelp->getVisible() && !panelp->mCollapsed) - { - total_visible_fraction += panelp->mFractionalSize; - } - } - - if (panelp == resized_panel) - { - other_resize_panel = following_panel; - } - - if (panelp->getVisible() && !panelp->mCollapsed) - { - following_panel = panelp; - } - } - - if (resized_panel->mAutoResize) - { - if (!other_resize_panel || !other_resize_panel->mAutoResize) - { - delta_auto_resize_headroom += delta_panel_dim; - } - } - else - { - if (!other_resize_panel || other_resize_panel->mAutoResize) - { - delta_auto_resize_headroom -= delta_panel_dim; - } - } - - F32 fraction_given_up = 0.f; - F32 fraction_remaining = 1.f; - F32 new_auto_resize_headroom = old_auto_resize_headroom + delta_auto_resize_headroom; - - enum - { - BEFORE_RESIZED_PANEL, - RESIZED_PANEL, - NEXT_PANEL, - AFTER_RESIZED_PANEL - } which_panel = BEFORE_RESIZED_PANEL; - - for (LLLayoutPanel* panelp : mPanels) - { - if (!panelp->getVisible() || panelp->mCollapsed) - { - if (panelp->mAutoResize) - { - fraction_remaining -= panelp->mFractionalSize; - } - continue; - } - - if (panelp == resized_panel) - { - which_panel = RESIZED_PANEL; - } - - switch(which_panel) - { - case BEFORE_RESIZED_PANEL: - if (panelp->mAutoResize) - { // freeze current size as fraction of overall auto_resize space - F32 fractional_adjustment_factor = new_auto_resize_headroom == 0.f - ? 1.f - : old_auto_resize_headroom / new_auto_resize_headroom; - F32 new_fractional_size = llclamp(panelp->mFractionalSize * fractional_adjustment_factor, - MIN_FRACTIONAL_SIZE, - MAX_FRACTIONAL_SIZE); - fraction_given_up -= new_fractional_size - panelp->mFractionalSize; - fraction_remaining -= panelp->mFractionalSize; - panelp->mFractionalSize = new_fractional_size; - llassert(!llisnan(panelp->mFractionalSize)); - } - else - { - // leave non auto-resize panels alone - } - break; - case RESIZED_PANEL: - if (panelp->mAutoResize) - { // freeze new size as fraction - F32 new_fractional_size = (new_auto_resize_headroom == 0.f) - ? MAX_FRACTIONAL_SIZE - : llclamp(total_visible_fraction * (F32)(new_dim - panelp->getRelevantMinDim()) / new_auto_resize_headroom, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE); - fraction_given_up -= new_fractional_size - panelp->mFractionalSize; - fraction_remaining -= panelp->mFractionalSize; - panelp->mFractionalSize = new_fractional_size; - llassert(!llisnan(panelp->mFractionalSize)); - } - else - { // freeze new size as original size - panelp->mTargetDim = new_dim; - } - which_panel = NEXT_PANEL; - break; - case NEXT_PANEL: - if (panelp->mAutoResize) - { - fraction_remaining -= panelp->mFractionalSize; - if (resized_panel->mAutoResize) - { - panelp->mFractionalSize = llclamp(panelp->mFractionalSize + fraction_given_up, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE); - fraction_given_up = 0.f; - } - else - { - if (new_auto_resize_headroom < 1.f) - { - new_auto_resize_headroom = 1.f; - } - - F32 new_fractional_size = llclamp(total_visible_fraction * (F32)(panelp->mTargetDim - panelp->getRelevantMinDim() + delta_auto_resize_headroom) - / new_auto_resize_headroom, - MIN_FRACTIONAL_SIZE, - MAX_FRACTIONAL_SIZE); - fraction_given_up -= new_fractional_size - panelp->mFractionalSize; - panelp->mFractionalSize = new_fractional_size; - } - } - else - { - panelp->mTargetDim -= delta_panel_dim; - } - which_panel = AFTER_RESIZED_PANEL; - break; - case AFTER_RESIZED_PANEL: - if (panelp->mAutoResize && fraction_given_up != 0.f) - { - panelp->mFractionalSize = llclamp(panelp->mFractionalSize + (panelp->mFractionalSize / fraction_remaining) * fraction_given_up, - MIN_FRACTIONAL_SIZE, - MAX_FRACTIONAL_SIZE); - } - break; - default: - break; - } - } - updateLayout(); - //normalizeFractionalSizes(); -} - -void LLLayoutStack::reshape(S32 width, S32 height, bool called_from_parent) -{ - mNeedsLayout = true; - LLView::reshape(width, height, called_from_parent); -} - -void LLLayoutStack::updateResizeBarLimits() -{ - LLLayoutPanel* previous_visible_panelp{ nullptr }; - BOOST_REVERSE_FOREACH(LLLayoutPanel* visible_panelp, mPanels) // Should replace this when C++20 reverse view adaptor becomes available... - { - if (!visible_panelp->getVisible() || visible_panelp->mCollapsed) - { - visible_panelp->mResizeBar->setVisible(false); - continue; - } - - // toggle resize bars based on panel visibility, resizability, etc - if (previous_visible_panelp - && (visible_panelp->mUserResize || previous_visible_panelp->mUserResize) // one of the pair is user resizable - && (visible_panelp->mAutoResize || visible_panelp->mUserResize) // current panel is resizable - && (previous_visible_panelp->mAutoResize || previous_visible_panelp->mUserResize)) // previous panel is resizable - { - visible_panelp->mResizeBar->setVisible(true); - S32 previous_panel_headroom = previous_visible_panelp->getVisibleDim() - previous_visible_panelp->getRelevantMinDim(); - visible_panelp->mResizeBar->setResizeLimits(visible_panelp->getRelevantMinDim(), - visible_panelp->getVisibleDim() + previous_panel_headroom); - } - else - { - visible_panelp->mResizeBar->setVisible(false); - } - - previous_visible_panelp = visible_panelp; - } -} - +/** + * @file lllayoutstack.cpp + * @brief LLLayout class - dynamic stacking of UI elements + * + * $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$ + */ + +// Opaque view with a background and a border. Can contain LLUICtrls. + +#include "linden_common.h" + +#include "lllayoutstack.h" + +#include "lllocalcliprect.h" +#include "llpanel.h" +#include "llcriticaldamp.h" +#include "lliconctrl.h" +#include "boost/foreach.hpp" + +static const F32 MIN_FRACTIONAL_SIZE = 0.00001f; +static const F32 MAX_FRACTIONAL_SIZE = 1.f; + +static LLDefaultChildRegistry::Register register_layout_stack("layout_stack"); +static LLLayoutStack::LayoutStackRegistry::Register register_layout_panel("layout_panel"); + +// +// LLLayoutPanel +// +LLLayoutPanel::Params::Params() +: expanded_min_dim("expanded_min_dim", 0), + min_dim("min_dim", -1), + user_resize("user_resize", false), + auto_resize("auto_resize", true) +{ + addSynonym(min_dim, "min_width"); + addSynonym(min_dim, "min_height"); +} + +LLLayoutPanel::LLLayoutPanel(const Params& p) +: LLPanel(p), + mExpandedMinDim(p.expanded_min_dim.isProvided() ? p.expanded_min_dim : p.min_dim), + mMinDim(p.min_dim), + mAutoResize(p.auto_resize), + mUserResize(p.user_resize), + mCollapsed(false), + mCollapseAmt(0.f), + mVisibleAmt(1.f), // default to fully visible + mResizeBar(NULL), + mFractionalSize(0.f), + mTargetDim(0), + mIgnoreReshape(false), + mOrientation(LLLayoutStack::HORIZONTAL) +{ + // panels initialized as hidden should not start out partially visible + if (!getVisible()) + { + mVisibleAmt = 0.f; + } +} + +void LLLayoutPanel::initFromParams(const Params& p) +{ + LLPanel::initFromParams(p); + setFollowsNone(); +} + + +LLLayoutPanel::~LLLayoutPanel() +{ + // probably not necessary, but... + delete mResizeBar; + mResizeBar = NULL; + + gFocusMgr.removeKeyboardFocusWithoutCallback(this); +} + +F32 LLLayoutPanel::getAutoResizeFactor() const +{ + return mVisibleAmt * (1.f - mCollapseAmt); +} + +F32 LLLayoutPanel::getVisibleAmount() const +{ + return mVisibleAmt; +} + +S32 LLLayoutPanel::getLayoutDim() const +{ + return ll_round((F32)((mOrientation == LLLayoutStack::HORIZONTAL) + ? getRect().getWidth() + : getRect().getHeight())); +} + +S32 LLLayoutPanel::getTargetDim() const +{ + return mTargetDim; +} + +void LLLayoutPanel::setTargetDim(S32 value) +{ + LLRect new_rect(getRect()); + if (mOrientation == LLLayoutStack::HORIZONTAL) + { + new_rect.mRight = new_rect.mLeft + value; + } + else + { + new_rect.mTop = new_rect.mBottom + value; + } + setShape(new_rect, true); +} + +S32 LLLayoutPanel::getVisibleDim() const +{ + F32 min_dim = getRelevantMinDim(); + return ll_round(mVisibleAmt + * (min_dim + + (((F32)mTargetDim - min_dim) * (1.f - mCollapseAmt)))); +} + +void LLLayoutPanel::setOrientation( LLView::EOrientation orientation ) +{ + mOrientation = orientation; + S32 layout_dim = ll_round((F32)((mOrientation == LLLayoutStack::HORIZONTAL) + ? getRect().getWidth() + : getRect().getHeight())); + + if (!mAutoResize && mUserResize && mMinDim == -1) + { + setMinDim(layout_dim); + } + mTargetDim = llmax(layout_dim, getMinDim()); +} + +void LLLayoutPanel::setVisible( bool visible ) +{ + if (visible != getVisible()) + { + LLLayoutStack* stackp = dynamic_cast(getParent()); + if (stackp) + { + stackp->mNeedsLayout = true; + } + } + LLPanel::setVisible(visible); +} + +void LLLayoutPanel::reshape( S32 width, S32 height, bool called_from_parent /*= true*/ ) +{ + if (width == getRect().getWidth() && height == getRect().getHeight() && !LLView::sForceReshape) return; + + if (!mIgnoreReshape && !mAutoResize) + { + mTargetDim = (mOrientation == LLLayoutStack::HORIZONTAL) ? width : height; + LLLayoutStack* stackp = dynamic_cast(getParent()); + if (stackp) + { + stackp->mNeedsLayout = true; + } + } + LLPanel::reshape(width, height, called_from_parent); +} + +void LLLayoutPanel::handleReshape(const LLRect& new_rect, bool by_user) +{ + LLLayoutStack* stackp = dynamic_cast(getParent()); + if (stackp) + { + if (by_user) + { // tell layout stack to account for new shape + + // make sure that panels have already been auto resized + stackp->updateLayout(); + // now apply requested size to panel + stackp->updatePanelRect(this, new_rect); + } + stackp->mNeedsLayout = true; + } + LLPanel::handleReshape(new_rect, by_user); +} + +// +// LLLayoutStack +// + +LLLayoutStack::Params::Params() +: orientation("orientation"), + animate("animate", true), + clip("clip", true), + open_time_constant("open_time_constant", 0.02f), + close_time_constant("close_time_constant", 0.03f), + resize_bar_overlap("resize_bar_overlap", 1), + border_size("border_size", LLCachedControl(*LLUI::getInstance()->mSettingGroups["config"], "UIResizeBarHeight", 0)), + show_drag_handle("show_drag_handle", false), + drag_handle_first_indent("drag_handle_first_indent", 0), + drag_handle_second_indent("drag_handle_second_indent", 0), + drag_handle_thickness("drag_handle_thickness", 5), + drag_handle_shift("drag_handle_shift", 2), + drag_handle_color("drag_handle_color", LLUIColorTable::instance().getColor("ResizebarBody")) +{ + addSynonym(border_size, "drag_handle_gap"); +} + +LLLayoutStack::LLLayoutStack(const LLLayoutStack::Params& p) +: LLView(p), + mPanelSpacing(p.border_size), + mOrientation(p.orientation), + mAnimate(p.animate), + mAnimatedThisFrame(false), + mNeedsLayout(true), + mClip(p.clip), + mOpenTimeConstant(p.open_time_constant), + mCloseTimeConstant(p.close_time_constant), + mResizeBarOverlap(p.resize_bar_overlap), + mShowDragHandle(p.show_drag_handle), + mDragHandleFirstIndent(p.drag_handle_first_indent), + mDragHandleSecondIndent(p.drag_handle_second_indent), + mDragHandleThickness(p.drag_handle_thickness), + mDragHandleShift(p.drag_handle_shift), + mDragHandleColor(p.drag_handle_color()) +{ +} + +LLLayoutStack::~LLLayoutStack() +{ + e_panel_list_t panels = mPanels; // copy list of panel pointers + mPanels.clear(); // clear so that removeChild() calls don't cause trouble + std::for_each(panels.begin(), panels.end(), DeletePointer()); +} + +void LLLayoutStack::draw() +{ + updateLayout(); + + // always clip to stack itself + LLLocalClipRect clip(getLocalRect()); + for (LLLayoutPanel* panelp : mPanels) + { + if ((!panelp->getVisible() || panelp->mCollapsed) + && (panelp->mVisibleAmt < 0.001f || !mAnimate)) + { + // essentially invisible + continue; + } + // clip to layout rectangle, not bounding rectangle + LLRect clip_rect = panelp->getRect(); + // scale clipping rectangle by visible amount + if (mOrientation == HORIZONTAL) + { + clip_rect.mRight = clip_rect.mLeft + panelp->getVisibleDim(); + } + else + { + clip_rect.mBottom = clip_rect.mTop - panelp->getVisibleDim(); + } + + {LLLocalClipRect clip(clip_rect, mClip); + // only force drawing invisible children if visible amount is non-zero + drawChild(panelp, 0, 0, !clip_rect.isEmpty()); + } + if (panelp->getResizeBar()->getVisible()) + { + drawChild(panelp->getResizeBar()); + } + } +} + +void LLLayoutStack::deleteAllChildren() +{ + mPanels.clear(); + LLView::deleteAllChildren(); + + // Not really needed since nothing is left to + // display, but for the sake of consistency + updateFractionalSizes(); + mNeedsLayout = true; +} + +void LLLayoutStack::removeChild(LLView* view) +{ + LLLayoutPanel* embedded_panelp = findEmbeddedPanel(dynamic_cast(view)); + + if (embedded_panelp) + { + mPanels.erase(std::find(mPanels.begin(), mPanels.end(), embedded_panelp)); + LLView::removeChild(view); + updateFractionalSizes(); + mNeedsLayout = true; + } + else + { + LLView::removeChild(view); + } +} + +bool LLLayoutStack::postBuild() +{ + updateLayout(); + return true; +} + +bool LLLayoutStack::addChild(LLView* child, S32 tab_group) +{ + LLLayoutPanel* panelp = dynamic_cast(child); + if (panelp) + { + panelp->setOrientation(mOrientation); + mPanels.push_back(panelp); + createResizeBar(panelp); + mNeedsLayout = true; + } + bool result = LLView::addChild(child, tab_group); + + updateFractionalSizes(); + return result; +} + +void LLLayoutStack::addPanel(LLLayoutPanel* panel, EAnimate animate) +{ + addChild(panel); + + // panel starts off invisible (collapsed) + if (animate == ANIMATE) + { + panel->mVisibleAmt = 0.f; + panel->setVisible(true); + } +} + +void LLLayoutStack::collapsePanel(LLPanel* panel, bool collapsed) +{ + LLLayoutPanel* panel_container = findEmbeddedPanel(panel); + if (!panel_container) return; + + panel_container->mCollapsed = collapsed; + mNeedsLayout = true; +} + +class LLImagePanel : public LLPanel +{ +public: + struct Params : public LLInitParam::Block + { + Optional horizontal; + Params() : horizontal("horizontal", false) {} + }; + LLImagePanel(const Params& p) : LLPanel(p), mHorizontal(p.horizontal) {} + virtual ~LLImagePanel() {} + + void draw() + { + const LLRect& parent_rect = getParent()->getRect(); + const LLRect& rect = getRect(); + LLRect clip_rect( -rect.mLeft, parent_rect.getHeight() - rect.mBottom - 2 + , parent_rect.getWidth() - rect.mLeft - (mHorizontal ? 2 : 0), -rect.mBottom); + LLLocalClipRect clip(clip_rect); + LLPanel::draw(); + } + +private: + bool mHorizontal; +}; + +void LLLayoutStack::updateLayout() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + + if (!mNeedsLayout) return; + + bool continue_animating = animatePanels(); + F32 total_visible_fraction = 0.f; + S32 space_to_distribute = (mOrientation == HORIZONTAL) + ? getRect().getWidth() + : getRect().getHeight(); + + // first, assign minimum dimensions + for (LLLayoutPanel* panelp : mPanels) + { + if (panelp->mAutoResize) + { + panelp->mTargetDim = panelp->getRelevantMinDim(); + } + space_to_distribute -= panelp->getVisibleDim() + ll_round((F32)mPanelSpacing * panelp->getVisibleAmount()); + total_visible_fraction += panelp->mFractionalSize * panelp->getAutoResizeFactor(); + } + + llassert(total_visible_fraction < 1.05f); + + // don't need spacing after last panel + if (!mPanels.empty()) + { + space_to_distribute += ll_round(F32(mPanelSpacing) * mPanels.back()->getVisibleAmount()); + } + + S32 remaining_space = space_to_distribute; + if (space_to_distribute > 0 && total_visible_fraction > 0.f) + { // give space proportionally to visible auto resize panels + for (LLLayoutPanel* panelp : mPanels) + { + if (panelp->mAutoResize) + { + F32 fraction_to_distribute = (panelp->mFractionalSize * panelp->getAutoResizeFactor()) / (total_visible_fraction); + S32 delta = ll_round((F32)space_to_distribute * fraction_to_distribute); + panelp->mTargetDim += delta; + remaining_space -= delta; + } + } + } + + // distribute any left over pixels to non-collapsed, visible panels + for (LLLayoutPanel* panelp : mPanels) + { + if (remaining_space == 0) break; + + if (panelp->mAutoResize + && !panelp->mCollapsed + && panelp->getVisible()) + { + S32 space_for_panel = remaining_space > 0 ? 1 : -1; + panelp->mTargetDim += space_for_panel; + remaining_space -= space_for_panel; + } + } + + F32 cur_pos = (mOrientation == HORIZONTAL) ? 0.f : (F32)getRect().getHeight(); + + for (LLLayoutPanel* panelp : mPanels) + { + F32 panel_dim = llmax(panelp->getExpandedMinDim(), panelp->mTargetDim); + + LLRect panel_rect; + if (mOrientation == HORIZONTAL) + { + panel_rect.setLeftTopAndSize(ll_round(cur_pos), + getRect().getHeight(), + ll_round(panel_dim), + getRect().getHeight()); + } + else + { + panel_rect.setLeftTopAndSize(0, + ll_round(cur_pos), + getRect().getWidth(), + ll_round(panel_dim)); + } + + LLRect resize_bar_rect(panel_rect); + F32 panel_spacing = (F32)mPanelSpacing * panelp->getVisibleAmount(); + F32 panel_visible_dim = panelp->getVisibleDim(); + S32 panel_spacing_round = (S32)(ll_round(panel_spacing)); + + if (mOrientation == HORIZONTAL) + { + cur_pos += panel_visible_dim + panel_spacing; + + if (mShowDragHandle && panel_spacing_round > mDragHandleThickness) + { + resize_bar_rect.mLeft = panel_rect.mRight + mDragHandleShift; + resize_bar_rect.mRight = resize_bar_rect.mLeft + mDragHandleThickness; + } + else + { + resize_bar_rect.mLeft = panel_rect.mRight - mResizeBarOverlap; + resize_bar_rect.mRight = panel_rect.mRight + panel_spacing_round + mResizeBarOverlap; + } + + if (mShowDragHandle) + { + resize_bar_rect.mBottom += mDragHandleSecondIndent; + resize_bar_rect.mTop -= mDragHandleFirstIndent; + } + + } + else //VERTICAL + { + cur_pos -= panel_visible_dim + panel_spacing; + + if (mShowDragHandle && panel_spacing_round > mDragHandleThickness) + { + resize_bar_rect.mTop = panel_rect.mBottom - mDragHandleShift; + resize_bar_rect.mBottom = resize_bar_rect.mTop - mDragHandleThickness; + } + else + { + resize_bar_rect.mTop = panel_rect.mBottom + mResizeBarOverlap; + resize_bar_rect.mBottom = panel_rect.mBottom - panel_spacing_round - mResizeBarOverlap; + } + + if (mShowDragHandle) + { + resize_bar_rect.mLeft += mDragHandleFirstIndent; + resize_bar_rect.mRight -= mDragHandleSecondIndent; + } + } + + panelp->setIgnoreReshape(true); + panelp->setShape(panel_rect); + panelp->setIgnoreReshape(false); + panelp->mResizeBar->setShape(resize_bar_rect); + } + + updateResizeBarLimits(); + + // clear animation flag at end, since panel resizes will set it + // and leave it set if there is any animation in progress + mNeedsLayout = continue_animating; +} // end LLLayoutStack::updateLayout + +void LLLayoutStack::setPanelSpacing(S32 val) +{ + if (mPanelSpacing != val) + { + mPanelSpacing = val; + mNeedsLayout = true; + } +} + +LLLayoutPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) const +{ + if (!panelp) return NULL; + + for (LLLayoutPanel* p : mPanels) + { + if (p == panelp) + { + return p; + } + } + return NULL; +} + +LLLayoutPanel* LLLayoutStack::findEmbeddedPanelByName(const std::string& name) const +{ + LLLayoutPanel* result = NULL; + + for (LLLayoutPanel* p : mPanels) + { + if (p->getName() == name) + { + result = p; + break; + } + } + + return result; +} + +void LLLayoutStack::createResizeBar(LLLayoutPanel* panelp) +{ + for (LLLayoutPanel* lp : mPanels) + { + if (lp->mResizeBar == NULL) + { + LLResizeBar::Params resize_params; + resize_params.name("resize"); + resize_params.resizing_view(lp); + resize_params.min_size(lp->getRelevantMinDim()); + resize_params.side((mOrientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM); + resize_params.snapping_enabled(false); + LLResizeBar* resize_bar = LLUICtrlFactory::create(resize_params); + lp->mResizeBar = resize_bar; + + if (mShowDragHandle) + { + LLPanel::Params resize_bar_bg_panel_p; + resize_bar_bg_panel_p.name = "resize_handle_bg_panel"; + resize_bar_bg_panel_p.rect = lp->mResizeBar->getLocalRect(); + resize_bar_bg_panel_p.follows.flags = FOLLOWS_ALL; + resize_bar_bg_panel_p.tab_stop = false; + resize_bar_bg_panel_p.background_visible = true; + resize_bar_bg_panel_p.bg_alpha_color = mDragHandleColor; + resize_bar_bg_panel_p.has_border = true; + resize_bar_bg_panel_p.border.border_thickness = 1; + resize_bar_bg_panel_p.border.highlight_light_color = LLUIColorTable::instance().getColor("ResizebarBorderLight"); + resize_bar_bg_panel_p.border.shadow_dark_color = LLUIColorTable::instance().getColor("ResizebarBorderDark"); + + LLPanel* resize_bar_bg_panel = LLUICtrlFactory::create(resize_bar_bg_panel_p); + + LLIconCtrl::Params icon_p; + icon_p.name = "resize_handle_image"; + icon_p.rect = lp->mResizeBar->getLocalRect(); + icon_p.follows.flags = FOLLOWS_ALL; + icon_p.image = LLUI::getUIImage(mOrientation == HORIZONTAL ? "Vertical Drag Handle" : "Horizontal Drag Handle"); + resize_bar_bg_panel->addChild(LLUICtrlFactory::create(icon_p)); + + lp->mResizeBar->addChild(resize_bar_bg_panel); + } + + /*if (mShowDragHandle) + { + LLViewBorder::Params border_params; + border_params.border_thickness = 1; + border_params.highlight_light_color = LLUIColorTable::instance().getColor("ResizebarBorderLight"); + border_params.shadow_dark_color = LLUIColorTable::instance().getColor("ResizebarBorderDark"); + + addBorder(border_params); + setBorderVisible(true); + + LLImagePanel::Params image_panel; + mDragHandleImage = LLUI::getUIImage(LLResizeBar::RIGHT == mSide ? "Vertical Drag Handle" : "Horizontal Drag Handle"); + image_panel.bg_alpha_image = mDragHandleImage; + image_panel.background_visible = true; + image_panel.horizontal = (LLResizeBar::BOTTOM == mSide); + mImagePanel = LLUICtrlFactory::create(image_panel); + setImagePanel(mImagePanel); + }*/ + + //if (mShowDragHandle) + //{ + // setBackgroundVisible(true); + // setTransparentColor(LLUIColorTable::instance().getColor("ResizebarBody")); + //} + + /*if (mShowDragHandle) + { + S32 image_width = mDragHandleImage->getTextureWidth(); + S32 image_height = mDragHandleImage->getTextureHeight(); + const LLRect& panel_rect = getRect(); + S32 image_left = (panel_rect.getWidth() - image_width) / 2 - 1; + S32 image_bottom = (panel_rect.getHeight() - image_height) / 2; + mImagePanel->setRect(LLRect(image_left, image_bottom + image_height, image_left + image_width, image_bottom)); + }*/ + LLView::addChild(resize_bar, 0); + } + } + // bring all resize bars to the front so that they are clickable even over the panels + // with a bit of overlap + for (e_panel_list_t::iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + LLResizeBar* resize_barp = (*panel_it)->mResizeBar; + sendChildToFront(resize_barp); + } +} + +// update layout stack animations, etc. once per frame +// NOTE: we use this to size world view based on animating UI, *before* we draw the UI +// we might still need to call updateLayout during UI draw phase, in case UI elements +// are resizing themselves dynamically +//static +void LLLayoutStack::updateClass() +{ + for (auto& layout : instance_snapshot()) + { + layout.updateLayout(); + layout.mAnimatedThisFrame = false; + } +} + +void LLLayoutStack::updateFractionalSizes() +{ + F32 total_resizable_dim = 0.f; + + for (LLLayoutPanel* panelp : mPanels) + { + if (panelp->mAutoResize) + { + total_resizable_dim += llmax(MIN_FRACTIONAL_SIZE, (F32)(panelp->getLayoutDim() - panelp->getRelevantMinDim())); + } + } + + for (LLLayoutPanel* panelp : mPanels) + { + if (panelp->mAutoResize) + { + F32 panel_resizable_dim = llmax(MIN_FRACTIONAL_SIZE, (F32)(panelp->getLayoutDim() - panelp->getRelevantMinDim())); + panelp->mFractionalSize = panel_resizable_dim > 0.f + ? llclamp(panel_resizable_dim / total_resizable_dim, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE) + : MIN_FRACTIONAL_SIZE; + llassert(!llisnan(panelp->mFractionalSize)); + } + } + + normalizeFractionalSizes(); +} + + +void LLLayoutStack::normalizeFractionalSizes() +{ + S32 num_auto_resize_panels = 0; + F32 total_fractional_size = 0.f; + + for (LLLayoutPanel* panelp : mPanels) + { + if (panelp->mAutoResize) + { + total_fractional_size += panelp->mFractionalSize; + num_auto_resize_panels++; + } + } + + if (total_fractional_size == 0.f) + { // equal distribution + for (LLLayoutPanel* panelp : mPanels) + { + if (panelp->mAutoResize) + { + panelp->mFractionalSize = MAX_FRACTIONAL_SIZE / (F32)num_auto_resize_panels; + } + } + } + else + { // renormalize + for (LLLayoutPanel* panelp : mPanels) + { + if (panelp->mAutoResize) + { + panelp->mFractionalSize /= total_fractional_size; + } + } + } +} + +bool LLLayoutStack::animatePanels() +{ + bool continue_animating = false; + + // + // animate visibility + // + for (LLLayoutPanel* panelp : mPanels) + { + if (panelp->getVisible()) + { + if (mAnimate && panelp->mVisibleAmt < 1.f) + { + if (!mAnimatedThisFrame) + { + panelp->mVisibleAmt = lerp(panelp->mVisibleAmt, 1.f, LLSmoothInterpolation::getInterpolant(mOpenTimeConstant)); + if (panelp->mVisibleAmt > 0.99f) + { + panelp->mVisibleAmt = 1.f; + } + } + + mAnimatedThisFrame = true; + continue_animating = true; + } + else + { + if (panelp->mVisibleAmt != 1.f) + { + panelp->mVisibleAmt = 1.f; + mAnimatedThisFrame = true; + } + } + } + else // not visible + { + if (mAnimate && panelp->mVisibleAmt > 0.f) + { + if (!mAnimatedThisFrame) + { + panelp->mVisibleAmt = lerp(panelp->mVisibleAmt, 0.f, LLSmoothInterpolation::getInterpolant(mCloseTimeConstant)); + if (panelp->mVisibleAmt < 0.001f) + { + panelp->mVisibleAmt = 0.f; + } + } + + continue_animating = true; + mAnimatedThisFrame = true; + } + else + { + if (panelp->mVisibleAmt != 0.f) + { + panelp->mVisibleAmt = 0.f; + mAnimatedThisFrame = true; + } + } + } + + F32 collapse_state = panelp->mCollapsed ? 1.f : 0.f; + if (panelp->mCollapseAmt != collapse_state) + { + if (mAnimate) + { + if (!mAnimatedThisFrame) + { + panelp->mCollapseAmt = lerp(panelp->mCollapseAmt, collapse_state, LLSmoothInterpolation::getInterpolant(mCloseTimeConstant)); + } + + if (llabs(panelp->mCollapseAmt - collapse_state) < 0.001f) + { + panelp->mCollapseAmt = collapse_state; + } + + mAnimatedThisFrame = true; + continue_animating = true; + } + else + { + panelp->mCollapseAmt = collapse_state; + mAnimatedThisFrame = true; + } + } + } + + if (mAnimatedThisFrame) mNeedsLayout = true; + return continue_animating; +} + +void LLLayoutStack::updatePanelRect( LLLayoutPanel* resized_panel, const LLRect& new_rect ) +{ + S32 new_dim = (mOrientation == HORIZONTAL) + ? new_rect.getWidth() + : new_rect.getHeight(); + S32 delta_panel_dim = new_dim - resized_panel->getVisibleDim(); + if (delta_panel_dim == 0) return; + + F32 total_visible_fraction = 0.f; + F32 delta_auto_resize_headroom = 0.f; + F32 old_auto_resize_headroom = 0.f; + + LLLayoutPanel* other_resize_panel = NULL; + LLLayoutPanel* following_panel = NULL; + + BOOST_REVERSE_FOREACH(LLLayoutPanel* panelp, mPanels) // Should replace this when C++20 reverse view adaptor becomes available... + { + if (panelp->mAutoResize) + { + old_auto_resize_headroom += (F32)(panelp->mTargetDim - panelp->getRelevantMinDim()); + if (panelp->getVisible() && !panelp->mCollapsed) + { + total_visible_fraction += panelp->mFractionalSize; + } + } + + if (panelp == resized_panel) + { + other_resize_panel = following_panel; + } + + if (panelp->getVisible() && !panelp->mCollapsed) + { + following_panel = panelp; + } + } + + if (resized_panel->mAutoResize) + { + if (!other_resize_panel || !other_resize_panel->mAutoResize) + { + delta_auto_resize_headroom += delta_panel_dim; + } + } + else + { + if (!other_resize_panel || other_resize_panel->mAutoResize) + { + delta_auto_resize_headroom -= delta_panel_dim; + } + } + + F32 fraction_given_up = 0.f; + F32 fraction_remaining = 1.f; + F32 new_auto_resize_headroom = old_auto_resize_headroom + delta_auto_resize_headroom; + + enum + { + BEFORE_RESIZED_PANEL, + RESIZED_PANEL, + NEXT_PANEL, + AFTER_RESIZED_PANEL + } which_panel = BEFORE_RESIZED_PANEL; + + for (LLLayoutPanel* panelp : mPanels) + { + if (!panelp->getVisible() || panelp->mCollapsed) + { + if (panelp->mAutoResize) + { + fraction_remaining -= panelp->mFractionalSize; + } + continue; + } + + if (panelp == resized_panel) + { + which_panel = RESIZED_PANEL; + } + + switch(which_panel) + { + case BEFORE_RESIZED_PANEL: + if (panelp->mAutoResize) + { // freeze current size as fraction of overall auto_resize space + F32 fractional_adjustment_factor = new_auto_resize_headroom == 0.f + ? 1.f + : old_auto_resize_headroom / new_auto_resize_headroom; + F32 new_fractional_size = llclamp(panelp->mFractionalSize * fractional_adjustment_factor, + MIN_FRACTIONAL_SIZE, + MAX_FRACTIONAL_SIZE); + fraction_given_up -= new_fractional_size - panelp->mFractionalSize; + fraction_remaining -= panelp->mFractionalSize; + panelp->mFractionalSize = new_fractional_size; + llassert(!llisnan(panelp->mFractionalSize)); + } + else + { + // leave non auto-resize panels alone + } + break; + case RESIZED_PANEL: + if (panelp->mAutoResize) + { // freeze new size as fraction + F32 new_fractional_size = (new_auto_resize_headroom == 0.f) + ? MAX_FRACTIONAL_SIZE + : llclamp(total_visible_fraction * (F32)(new_dim - panelp->getRelevantMinDim()) / new_auto_resize_headroom, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE); + fraction_given_up -= new_fractional_size - panelp->mFractionalSize; + fraction_remaining -= panelp->mFractionalSize; + panelp->mFractionalSize = new_fractional_size; + llassert(!llisnan(panelp->mFractionalSize)); + } + else + { // freeze new size as original size + panelp->mTargetDim = new_dim; + } + which_panel = NEXT_PANEL; + break; + case NEXT_PANEL: + if (panelp->mAutoResize) + { + fraction_remaining -= panelp->mFractionalSize; + if (resized_panel->mAutoResize) + { + panelp->mFractionalSize = llclamp(panelp->mFractionalSize + fraction_given_up, MIN_FRACTIONAL_SIZE, MAX_FRACTIONAL_SIZE); + fraction_given_up = 0.f; + } + else + { + if (new_auto_resize_headroom < 1.f) + { + new_auto_resize_headroom = 1.f; + } + + F32 new_fractional_size = llclamp(total_visible_fraction * (F32)(panelp->mTargetDim - panelp->getRelevantMinDim() + delta_auto_resize_headroom) + / new_auto_resize_headroom, + MIN_FRACTIONAL_SIZE, + MAX_FRACTIONAL_SIZE); + fraction_given_up -= new_fractional_size - panelp->mFractionalSize; + panelp->mFractionalSize = new_fractional_size; + } + } + else + { + panelp->mTargetDim -= delta_panel_dim; + } + which_panel = AFTER_RESIZED_PANEL; + break; + case AFTER_RESIZED_PANEL: + if (panelp->mAutoResize && fraction_given_up != 0.f) + { + panelp->mFractionalSize = llclamp(panelp->mFractionalSize + (panelp->mFractionalSize / fraction_remaining) * fraction_given_up, + MIN_FRACTIONAL_SIZE, + MAX_FRACTIONAL_SIZE); + } + break; + default: + break; + } + } + updateLayout(); + //normalizeFractionalSizes(); +} + +void LLLayoutStack::reshape(S32 width, S32 height, bool called_from_parent) +{ + mNeedsLayout = true; + LLView::reshape(width, height, called_from_parent); +} + +void LLLayoutStack::updateResizeBarLimits() +{ + LLLayoutPanel* previous_visible_panelp{ nullptr }; + BOOST_REVERSE_FOREACH(LLLayoutPanel* visible_panelp, mPanels) // Should replace this when C++20 reverse view adaptor becomes available... + { + if (!visible_panelp->getVisible() || visible_panelp->mCollapsed) + { + visible_panelp->mResizeBar->setVisible(false); + continue; + } + + // toggle resize bars based on panel visibility, resizability, etc + if (previous_visible_panelp + && (visible_panelp->mUserResize || previous_visible_panelp->mUserResize) // one of the pair is user resizable + && (visible_panelp->mAutoResize || visible_panelp->mUserResize) // current panel is resizable + && (previous_visible_panelp->mAutoResize || previous_visible_panelp->mUserResize)) // previous panel is resizable + { + visible_panelp->mResizeBar->setVisible(true); + S32 previous_panel_headroom = previous_visible_panelp->getVisibleDim() - previous_visible_panelp->getRelevantMinDim(); + visible_panelp->mResizeBar->setResizeLimits(visible_panelp->getRelevantMinDim(), + visible_panelp->getVisibleDim() + previous_panel_headroom); + } + else + { + visible_panelp->mResizeBar->setVisible(false); + } + + previous_visible_panelp = visible_panelp; + } +} + diff --git a/indra/llui/lllayoutstack.h b/indra/llui/lllayoutstack.h index 080559477b..d362d4362c 100644 --- a/indra/llui/lllayoutstack.h +++ b/indra/llui/lllayoutstack.h @@ -1,216 +1,216 @@ -/** - * @file lllayoutstack.h - * @author Richard Nelson - * @brief LLLayout class - dynamic stacking of UI elements - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Reshasearch, 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$ - */ - -#ifndef LL_LLLAYOUTSTACK_H -#define LL_LLLAYOUTSTACK_H - -#include "llpanel.h" -#include "llresizebar.h" - - -class LLLayoutPanel; - - -class LLLayoutStack : public LLView, public LLInstanceTracker -{ -public: - - struct LayoutStackRegistry : public LLChildRegistry - { - LLSINGLETON_EMPTY_CTOR(LayoutStackRegistry); - }; - - struct Params : public LLInitParam::Block - { - Mandatory orientation; - Optional border_size; - Optional animate, - clip; - Optional open_time_constant, - close_time_constant; - Optional resize_bar_overlap; - Optional show_drag_handle; - Optional drag_handle_first_indent; - Optional drag_handle_second_indent; - Optional drag_handle_thickness; - Optional drag_handle_shift; - - Optional drag_handle_color; - - Params(); - }; - - typedef LayoutStackRegistry child_registry_t; - - virtual ~LLLayoutStack(); - - /*virtual*/ void draw(); - /*virtual*/ void deleteAllChildren(); - /*virtual*/ void removeChild(LLView*); - /*virtual*/ bool postBuild(); - /*virtual*/ bool addChild(LLView* child, S32 tab_group = 0); - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - - - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); - - typedef enum e_animate - { - NO_ANIMATE, - ANIMATE - } EAnimate; - - void addPanel(LLLayoutPanel* panel, EAnimate animate = NO_ANIMATE); - void collapsePanel(LLPanel* panel, bool collapsed = true); - S32 getNumPanels() { return mPanels.size(); } - - void updateLayout(); - - S32 getPanelSpacing() const { return mPanelSpacing; } - void setPanelSpacing(S32 val); - - static void updateClass(); - -protected: - LLLayoutStack(const Params&); - friend class LLUICtrlFactory; - friend class LLLayoutPanel; - -private: - void updateResizeBarLimits(); - bool animatePanels(); - void createResizeBar(LLLayoutPanel* panel); - - const EOrientation mOrientation; - - typedef std::vector e_panel_list_t; - e_panel_list_t mPanels; - - LLLayoutPanel* findEmbeddedPanel(LLPanel* panelp) const; - LLLayoutPanel* findEmbeddedPanelByName(const std::string& name) const; - void updateFractionalSizes(); - void normalizeFractionalSizes(); - void updatePanelRect( LLLayoutPanel* param1, const LLRect& new_rect ); - - S32 mPanelSpacing; - - // true if we already applied animation this frame - bool mAnimatedThisFrame; - bool mAnimate; - bool mClip; - F32 mOpenTimeConstant; - F32 mCloseTimeConstant; - bool mNeedsLayout; - S32 mResizeBarOverlap; - bool mShowDragHandle; - S32 mDragHandleFirstIndent; - S32 mDragHandleSecondIndent; - S32 mDragHandleThickness; - S32 mDragHandleShift; - LLUIColor mDragHandleColor; -}; // end class LLLayoutStack - - -class LLLayoutPanel : public LLPanel -{ -friend class LLLayoutStack; -friend class LLUICtrlFactory; -public: - struct Params : public LLInitParam::Block - { - Optional expanded_min_dim, - min_dim; - Optional user_resize, - auto_resize; - - Params(); - }; - - ~LLLayoutPanel(); - - void initFromParams(const Params& p); - - void handleReshape(const LLRect& new_rect, bool by_user); - - void reshape(S32 width, S32 height, bool called_from_parent = true); - - - void setVisible(bool visible); - - S32 getLayoutDim() const; - S32 getTargetDim() const; - void setTargetDim(S32 value); - S32 getMinDim() const { return llmax(0, mMinDim); } - void setMinDim(S32 value) { mMinDim = value; } - - S32 getExpandedMinDim() const { return mExpandedMinDim >= 0 ? mExpandedMinDim : getMinDim(); } - void setExpandedMinDim(S32 value) { mExpandedMinDim = value; } - - S32 getRelevantMinDim() const - { - S32 min_dim = mMinDim; - - if (!mCollapsed) - { - min_dim = getExpandedMinDim(); - } - - return min_dim; - } - - F32 getAutoResizeFactor() const; - F32 getVisibleAmount() const; - S32 getVisibleDim() const; - LLResizeBar* getResizeBar() { return mResizeBar; } - - bool isCollapsed() const { return mCollapsed;} - - void setOrientation(LLView::EOrientation orientation); - void storeOriginalDim(); - - void setIgnoreReshape(bool ignore) { mIgnoreReshape = ignore; } - -protected: - LLLayoutPanel(const Params& p); - - const bool mAutoResize; - const bool mUserResize; - - S32 mExpandedMinDim; - S32 mMinDim; - bool mCollapsed; - F32 mVisibleAmt; - F32 mCollapseAmt; - F32 mFractionalSize; - S32 mTargetDim; - bool mIgnoreReshape; - LLView::EOrientation mOrientation; - class LLResizeBar* mResizeBar; -}; - - -#endif +/** + * @file lllayoutstack.h + * @author Richard Nelson + * @brief LLLayout class - dynamic stacking of UI elements + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Reshasearch, 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$ + */ + +#ifndef LL_LLLAYOUTSTACK_H +#define LL_LLLAYOUTSTACK_H + +#include "llpanel.h" +#include "llresizebar.h" + + +class LLLayoutPanel; + + +class LLLayoutStack : public LLView, public LLInstanceTracker +{ +public: + + struct LayoutStackRegistry : public LLChildRegistry + { + LLSINGLETON_EMPTY_CTOR(LayoutStackRegistry); + }; + + struct Params : public LLInitParam::Block + { + Mandatory orientation; + Optional border_size; + Optional animate, + clip; + Optional open_time_constant, + close_time_constant; + Optional resize_bar_overlap; + Optional show_drag_handle; + Optional drag_handle_first_indent; + Optional drag_handle_second_indent; + Optional drag_handle_thickness; + Optional drag_handle_shift; + + Optional drag_handle_color; + + Params(); + }; + + typedef LayoutStackRegistry child_registry_t; + + virtual ~LLLayoutStack(); + + /*virtual*/ void draw(); + /*virtual*/ void deleteAllChildren(); + /*virtual*/ void removeChild(LLView*); + /*virtual*/ bool postBuild(); + /*virtual*/ bool addChild(LLView* child, S32 tab_group = 0); + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + + + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); + + typedef enum e_animate + { + NO_ANIMATE, + ANIMATE + } EAnimate; + + void addPanel(LLLayoutPanel* panel, EAnimate animate = NO_ANIMATE); + void collapsePanel(LLPanel* panel, bool collapsed = true); + S32 getNumPanels() { return mPanels.size(); } + + void updateLayout(); + + S32 getPanelSpacing() const { return mPanelSpacing; } + void setPanelSpacing(S32 val); + + static void updateClass(); + +protected: + LLLayoutStack(const Params&); + friend class LLUICtrlFactory; + friend class LLLayoutPanel; + +private: + void updateResizeBarLimits(); + bool animatePanels(); + void createResizeBar(LLLayoutPanel* panel); + + const EOrientation mOrientation; + + typedef std::vector e_panel_list_t; + e_panel_list_t mPanels; + + LLLayoutPanel* findEmbeddedPanel(LLPanel* panelp) const; + LLLayoutPanel* findEmbeddedPanelByName(const std::string& name) const; + void updateFractionalSizes(); + void normalizeFractionalSizes(); + void updatePanelRect( LLLayoutPanel* param1, const LLRect& new_rect ); + + S32 mPanelSpacing; + + // true if we already applied animation this frame + bool mAnimatedThisFrame; + bool mAnimate; + bool mClip; + F32 mOpenTimeConstant; + F32 mCloseTimeConstant; + bool mNeedsLayout; + S32 mResizeBarOverlap; + bool mShowDragHandle; + S32 mDragHandleFirstIndent; + S32 mDragHandleSecondIndent; + S32 mDragHandleThickness; + S32 mDragHandleShift; + LLUIColor mDragHandleColor; +}; // end class LLLayoutStack + + +class LLLayoutPanel : public LLPanel +{ +friend class LLLayoutStack; +friend class LLUICtrlFactory; +public: + struct Params : public LLInitParam::Block + { + Optional expanded_min_dim, + min_dim; + Optional user_resize, + auto_resize; + + Params(); + }; + + ~LLLayoutPanel(); + + void initFromParams(const Params& p); + + void handleReshape(const LLRect& new_rect, bool by_user); + + void reshape(S32 width, S32 height, bool called_from_parent = true); + + + void setVisible(bool visible); + + S32 getLayoutDim() const; + S32 getTargetDim() const; + void setTargetDim(S32 value); + S32 getMinDim() const { return llmax(0, mMinDim); } + void setMinDim(S32 value) { mMinDim = value; } + + S32 getExpandedMinDim() const { return mExpandedMinDim >= 0 ? mExpandedMinDim : getMinDim(); } + void setExpandedMinDim(S32 value) { mExpandedMinDim = value; } + + S32 getRelevantMinDim() const + { + S32 min_dim = mMinDim; + + if (!mCollapsed) + { + min_dim = getExpandedMinDim(); + } + + return min_dim; + } + + F32 getAutoResizeFactor() const; + F32 getVisibleAmount() const; + S32 getVisibleDim() const; + LLResizeBar* getResizeBar() { return mResizeBar; } + + bool isCollapsed() const { return mCollapsed;} + + void setOrientation(LLView::EOrientation orientation); + void storeOriginalDim(); + + void setIgnoreReshape(bool ignore) { mIgnoreReshape = ignore; } + +protected: + LLLayoutPanel(const Params& p); + + const bool mAutoResize; + const bool mUserResize; + + S32 mExpandedMinDim; + S32 mMinDim; + bool mCollapsed; + F32 mVisibleAmt; + F32 mCollapseAmt; + F32 mFractionalSize; + S32 mTargetDim; + bool mIgnoreReshape; + LLView::EOrientation mOrientation; + class LLResizeBar* mResizeBar; +}; + + +#endif diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 8c5e69fbb2..5ec4c34f57 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -1,2735 +1,2735 @@ -/** - * @file lllineeditor.cpp - * @brief LLLineEditor base 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$ - */ - -// Text editor widget to let users enter a single line. - -#include "linden_common.h" - -#define LLLINEEDITOR_CPP -#include "lllineeditor.h" - -#include "lltexteditor.h" -#include "llmath.h" -#include "llfontgl.h" -#include "llgl.h" -#include "lltimer.h" - -#include "llcalc.h" -//#include "llclipboard.h" -#include "llcontrol.h" -#include "llbutton.h" -#include "llfocusmgr.h" -#include "llkeyboard.h" -#include "llrect.h" -#include "llresmgr.h" -#include "llspellcheck.h" -#include "llstring.h" -#include "llwindow.h" -#include "llui.h" -#include "lluictrlfactory.h" -#include "llclipboard.h" -#include "llmenugl.h" - -// -// Imported globals -// - -// -// Constants -// - -const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds -const S32 SCROLL_INCREMENT_ADD = 0; // make space for typing -const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing -const F32 AUTO_SCROLL_TIME = 0.05f; -const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click. *TODO: make this equal to the double click interval? -const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and spell checking the word the cursor is on - -const std::string PASSWORD_ASTERISK( "\xE2\x80\xA2" ); // U+2022 BULLET - -static LLDefaultChildRegistry::Register r1("line_editor"); - -// Compiler optimization, generate extern template -template class LLLineEditor* LLView::getChild( - const std::string& name, bool recurse) const; - -// -// Member functions -// - -LLLineEditor::Params::Params() -: max_length(""), - keystroke_callback("keystroke_callback"), - prevalidator("prevalidator"), - input_prevalidator("input_prevalidator"), - background_image("background_image"), - background_image_disabled("background_image_disabled"), - background_image_focused("background_image_focused"), - bg_image_always_focused("bg_image_always_focused", false), - show_label_focused("show_label_focused", false), - select_on_focus("select_on_focus", false), - revert_on_esc("revert_on_esc", true), - spellcheck("spellcheck", false), - commit_on_focus_lost("commit_on_focus_lost", true), - ignore_tab("ignore_tab", true), - is_password("is_password", false), - allow_emoji("allow_emoji", true), - cursor_color("cursor_color"), - use_bg_color("use_bg_color", false), - bg_color("bg_color"), - text_color("text_color"), - text_readonly_color("text_readonly_color"), - text_tentative_color("text_tentative_color"), - highlight_color("highlight_color"), - preedit_bg_color("preedit_bg_color"), - border(""), - bg_visible("bg_visible"), - text_pad_left("text_pad_left"), - text_pad_right("text_pad_right"), - default_text("default_text") -{ - changeDefault(mouse_opaque, true); - addSynonym(prevalidator, "prevalidate_callback"); - addSynonym(input_prevalidator, "prevalidate_input_callback"); - addSynonym(select_on_focus, "select_all_on_focus_received"); - addSynonym(border, "border"); - addSynonym(label, "watermark_text"); - addSynonym(max_length.chars, "max_length"); -} - -LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) -: LLUICtrl(p), - mMaxLengthBytes(p.max_length.bytes), - mMaxLengthChars(p.max_length.chars), - mCursorPos( 0 ), - mScrollHPos( 0 ), - mTextPadLeft(p.text_pad_left), - mTextPadRight(p.text_pad_right), - mTextLeftEdge(0), // computed in updateTextPadding() below - mTextRightEdge(0), // computed in updateTextPadding() below - mCommitOnFocusLost( p.commit_on_focus_lost ), - mKeystrokeOnEsc(false), - mRevertOnEsc( p.revert_on_esc ), - mKeystrokeCallback( p.keystroke_callback() ), - mIsSelecting( false ), - mSelectionStart( 0 ), - mSelectionEnd( 0 ), - mLastSelectionX(-1), - mLastSelectionY(-1), - mLastSelectionStart(-1), - mLastSelectionEnd(-1), - mBorderThickness( 0 ), - mIgnoreArrowKeys( false ), - mIgnoreTab( p.ignore_tab ), - mDrawAsterixes( p.is_password ), - mAllowEmoji( p.allow_emoji ), - mSpellCheck( p.spellcheck ), - mSpellCheckStart(-1), - mSpellCheckEnd(-1), - mSelectAllonFocusReceived( p.select_on_focus ), - mSelectAllonCommit( true ), - mPassDelete(false), - mReadOnly(false), - mBgImage( p.background_image ), - mBgImageDisabled( p.background_image_disabled ), - mBgImageFocused( p.background_image_focused ), - mShowImageFocused( p.bg_image_always_focused ), - mShowLabelFocused( p.show_label_focused ), - mUseBgColor(p.use_bg_color), - mHaveHistory(false), - mReplaceNewlinesWithSpaces( true ), - mPrevalidator(p.prevalidator()), - mInputPrevalidator(p.input_prevalidator()), - mLabel(p.label), - mCursorColor(p.cursor_color()), - mBgColor(p.bg_color()), - mFgColor(p.text_color()), - mReadOnlyFgColor(p.text_readonly_color()), - mTentativeFgColor(p.text_tentative_color()), - mHighlightColor(p.highlight_color()), - mPreeditBgColor(p.preedit_bg_color()), - mGLFont(p.font), - mContextMenuHandle(), - mShowContextMenu(true) -{ - llassert( mMaxLengthBytes > 0 ); - - LLUICtrl::setEnabled(true); - setEnabled(p.enabled); - - mScrollTimer.reset(); - mTripleClickTimer.reset(); - setText(p.default_text()); - - if (p.initial_value.isProvided() - && !p.control_name.isProvided()) - { - // Initial value often is descriptive, like "Type some ID here" - // and can be longer than size limitation, ignore size - setText(p.initial_value.getValue().asString(), false); - } - - // Initialize current history line iterator - mCurrentHistoryLine = mLineHistory.begin(); - - LLRect border_rect(getLocalRect()); - // adjust for gl line drawing glitch - border_rect.mTop -= 1; - border_rect.mRight -=1; - LLViewBorder::Params border_p(p.border); - border_p.rect = border_rect; - border_p.follows.flags = FOLLOWS_ALL; - border_p.bevel_style = LLViewBorder::BEVEL_IN; - mBorder = LLUICtrlFactory::create(border_p); - addChild( mBorder ); - - // clamp text padding to current editor size - updateTextPadding(); - setCursor(mText.length()); - - if (mSpellCheck) - { - LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLLineEditor::onSpellCheckSettingsChange, this)); - } - mSpellCheckTimer.reset(); - - updateAllowingLanguageInput(); -} - -LLLineEditor::~LLLineEditor() -{ - mCommitOnFocusLost = false; - - // Make sure no context menu linger around once the widget is deleted - LLContextMenu* menu = static_cast(mContextMenuHandle.get()); - if (menu) - { - menu->hide(); - } - setContextMenu(NULL); - - // calls onCommit() while LLLineEditor still valid - gFocusMgr.releaseFocusIfNeeded( this ); -} - -void LLLineEditor::initFromParams(const LLLineEditor::Params& params) -{ - LLUICtrl::initFromParams(params); - LLUICtrl::setEnabled(true); - setEnabled(params.enabled); -} - -void LLLineEditor::onFocusReceived() -{ - gEditMenuHandler = this; - LLUICtrl::onFocusReceived(); - updateAllowingLanguageInput(); -} - -void LLLineEditor::onFocusLost() -{ - // The call to updateAllowLanguageInput() - // when loosing the keyboard focus *may* - // indirectly invoke handleUnicodeCharHere(), - // so it must be called before onCommit. - updateAllowingLanguageInput(); - - if( mCommitOnFocusLost && mText.getString() != mPrevText) - { - onCommit(); - } - - if( gEditMenuHandler == this ) - { - gEditMenuHandler = NULL; - } - - getWindow()->showCursorFromMouseMove(); - - LLUICtrl::onFocusLost(); -} - -// virtual -void LLLineEditor::onCommit() -{ - // put current line into the line history - updateHistory(); - - setControlValue(getValue()); - LLUICtrl::onCommit(); - resetDirty(); - - // Selection on commit needs to be turned off when evaluating maths - // expressions, to allow indication of the error position - if (mSelectAllonCommit) selectAll(); -} - -// Returns true if user changed value at all -// virtual -bool LLLineEditor::isDirty() const -{ - return mText.getString() != mPrevText; -} - -// Clear dirty state -// virtual -void LLLineEditor::resetDirty() -{ - mPrevText = mText.getString(); -} - -// assumes UTF8 text -// virtual -void LLLineEditor::setValue(const LLSD& value ) -{ - setText(value.asString()); -} - -//virtual -LLSD LLLineEditor::getValue() const -{ - return LLSD(getText()); -} - - -// line history support -void LLLineEditor::updateHistory() -{ - // On history enabled line editors, remember committed line and - // reset current history line number. - // Be sure only to remember lines that are not empty and that are - // different from the last on the list. - if( mHaveHistory && getLength() ) - { - if( !mLineHistory.empty() ) - { - // When not empty, last line of history should always be blank. - if( mLineHistory.back().empty() ) - { - // discard the empty line - mLineHistory.pop_back(); - } - else - { - LL_WARNS("") << "Last line of history was not blank." << LL_ENDL; - } - } - - // Add text to history, ignoring duplicates - if( mLineHistory.empty() || getText() != mLineHistory.back() ) - { - mLineHistory.push_back( getText() ); - } - - // Restore the blank line and set mCurrentHistoryLine to point at it - mLineHistory.push_back( "" ); - mCurrentHistoryLine = mLineHistory.end() - 1; - } -} - -void LLLineEditor::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLUICtrl::reshape(width, height, called_from_parent); - updateTextPadding(); // For clamping side-effect. - setCursor(mCursorPos); // For clamping side-effect. -} - -void LLLineEditor::setEnabled(bool enabled) -{ - mReadOnly = !enabled; - setTabStop(!mReadOnly); - updateAllowingLanguageInput(); -} - - -void LLLineEditor::setMaxTextLength(S32 max_text_length) -{ - S32 max_len = llmax(0, max_text_length); - mMaxLengthBytes = max_len; -} - -void LLLineEditor::setMaxTextChars(S32 max_text_chars) -{ - S32 max_chars = llmax(0, max_text_chars); - mMaxLengthChars = max_chars; -} - -void LLLineEditor::getTextPadding(S32 *left, S32 *right) -{ - *left = mTextPadLeft; - *right = mTextPadRight; -} - -void LLLineEditor::setTextPadding(S32 left, S32 right) -{ - mTextPadLeft = left; - mTextPadRight = right; - updateTextPadding(); -} - -void LLLineEditor::updateTextPadding() -{ - mTextLeftEdge = llclamp(mTextPadLeft, 0, getRect().getWidth()); - mTextRightEdge = getRect().getWidth() - llclamp(mTextPadRight, 0, getRect().getWidth()); -} - - -void LLLineEditor::setText(const LLStringExplicit &new_text) -{ - setText(new_text, true); -} - -void LLLineEditor::setText(const LLStringExplicit &new_text, bool use_size_limit) -{ - // If new text is identical, don't copy and don't move insertion point - if (mText.getString() == new_text) - { - return; - } - - // Check to see if entire field is selected. - S32 len = mText.length(); - bool all_selected = (len > 0) - && (( mSelectionStart == 0 && mSelectionEnd == len ) - || ( mSelectionStart == len && mSelectionEnd == 0 )); - - // Do safe truncation so we don't split multi-byte characters - // also consider entire string selected when mSelectAllonFocusReceived is set on an empty, focused line editor - all_selected = all_selected || (len == 0 && hasFocus() && mSelectAllonFocusReceived); - - std::string truncated_utf8 = new_text; - if (!mAllowEmoji) - { - // Cut emoji symbols if exist - utf8str_remove_emojis(truncated_utf8); - } - if (use_size_limit && truncated_utf8.size() > (U32)mMaxLengthBytes) - { - truncated_utf8 = utf8str_truncate(new_text, mMaxLengthBytes); - } - mText.assign(truncated_utf8); - - if (use_size_limit && mMaxLengthChars) - { - mText.assign(utf8str_symbol_truncate(truncated_utf8, mMaxLengthChars)); - } - - if (all_selected) - { - // ...keep whole thing selected - selectAll(); - } - else - { - // try to preserve insertion point, but deselect text - deselect(); - } - setCursor(llmin((S32)mText.length(), getCursor())); - - // Set current history line to end of history. - if (mLineHistory.empty()) - { - mCurrentHistoryLine = mLineHistory.end(); - } - else - { - mCurrentHistoryLine = mLineHistory.end() - 1; - } - - mPrevText = mText; -} - - -// Picks a new cursor position based on the actual screen size of text being drawn. -void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) -{ - S32 cursor_pos = calcCursorPos(local_mouse_x); - - S32 left_pos = llmin( mSelectionStart, cursor_pos ); - S32 length = llabs( mSelectionStart - cursor_pos ); - const LLWString& substr = mText.getWString().substr(left_pos, length); - - if (mIsSelecting && !prevalidateInput(substr)) - return; - - setCursor(cursor_pos); -} - -void LLLineEditor::setCursor( S32 pos ) -{ - S32 old_cursor_pos = getCursor(); - mCursorPos = llclamp( pos, 0, mText.length()); - - // position of end of next character after cursor - S32 pixels_after_scroll = findPixelNearestPos(); - if( pixels_after_scroll > mTextRightEdge ) - { - S32 width_chars_to_left = mGLFont->getWidth(mText.getWString().c_str(), 0, mScrollHPos); - S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mTextRightEdge - mTextLeftEdge + width_chars_to_left))); - // character immediately to left of cursor should be last one visible (SCROLL_INCREMENT_ADD will scroll in more characters) - // or first character if cursor is at beginning - S32 new_last_visible_char = llmax(0, getCursor() - 1); - S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), new_last_visible_char); - if (old_cursor_pos == last_visible_char) - { - mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD)); - } - else - { - mScrollHPos = min_scroll; - } - } - else if (getCursor() < mScrollHPos) - { - if (old_cursor_pos == mScrollHPos) - { - mScrollHPos = llmax(0, llmin(getCursor(), mScrollHPos - SCROLL_INCREMENT_DEL)); - } - else - { - mScrollHPos = getCursor(); - } - } -} - - -void LLLineEditor::setCursorToEnd() -{ - setCursor(mText.length()); - deselect(); -} - -void LLLineEditor::resetScrollPosition() -{ - mScrollHPos = 0; - // make sure cursor says in visible range - setCursor(getCursor()); -} - -bool LLLineEditor::canDeselect() const -{ - return hasSelection(); -} - -void LLLineEditor::deselect() -{ - mSelectionStart = 0; - mSelectionEnd = 0; - mIsSelecting = false; -} - - -void LLLineEditor::startSelection() -{ - mIsSelecting = true; - mSelectionStart = getCursor(); - mSelectionEnd = getCursor(); -} - -void LLLineEditor::endSelection() -{ - if( mIsSelecting ) - { - mIsSelecting = false; - mSelectionEnd = getCursor(); - } -} - -bool LLLineEditor::canSelectAll() const -{ - return true; -} - -void LLLineEditor::selectAll() -{ - if (!prevalidateInput(mText.getWString())) - { - return; - } - - mSelectionStart = mText.length(); - mSelectionEnd = 0; - setCursor(mSelectionEnd); - //mScrollHPos = 0; - mIsSelecting = true; - updatePrimary(); -} - -bool LLLineEditor::getSpellCheck() const -{ - return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); -} - -const std::string& LLLineEditor::getSuggestion(U32 index) const -{ - return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null; -} - -U32 LLLineEditor::getSuggestionCount() const -{ - return mSuggestionList.size(); -} - -void LLLineEditor::replaceWithSuggestion(U32 index) -{ - for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) - { - if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) ) - { - LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]); - if (!mAllowEmoji) - { - // Cut emoji symbols if exist - wstring_remove_emojis(suggestion); - } - if (suggestion.empty()) - return; - - deselect(); - - // Delete the misspelled word - mText.erase(it->first, it->second - it->first); - - // Insert the suggestion in its place - mText.insert(it->first, suggestion); - setCursor(it->first + (S32)suggestion.length()); - - break; - } - } - mSpellCheckStart = mSpellCheckEnd = -1; -} - -void LLLineEditor::addToDictionary() -{ - if (canAddToDictionary()) - { - LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos)); - } -} - -bool LLLineEditor::canAddToDictionary() const -{ - return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); -} - -void LLLineEditor::addToIgnore() -{ - if (canAddToIgnore()) - { - LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos)); - } -} - -bool LLLineEditor::canAddToIgnore() const -{ - return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); -} - -std::string LLLineEditor::getMisspelledWord(U32 pos) const -{ - for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) - { - if ( (it->first <= pos) && (it->second >= pos) ) - { - return wstring_to_utf8str(mText.getWString().substr(it->first, it->second - it->first)); - } - } - return LLStringUtil::null; -} - -bool LLLineEditor::isMisspelledWord(U32 pos) const -{ - for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) - { - if ( (it->first <= pos) && (it->second >= pos) ) - { - return true; - } - } - return false; -} - -void LLLineEditor::onSpellCheckSettingsChange() -{ - // Recheck the spelling on every change - mMisspellRanges.clear(); - mSpellCheckStart = mSpellCheckEnd = -1; -} - -bool LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - setFocus( true ); - mTripleClickTimer.setTimerExpirySec(TRIPLE_CLICK_INTERVAL); - - if (mSelectionEnd == 0 && mSelectionStart == mText.length()) - { - // if everything is selected, handle this as a normal click to change insertion point - handleMouseDown(x, y, mask); - } - else - { - const LLWString& wtext = mText.getWString(); - - bool doSelectAll = true; - - // Select the word we're on - if( LLWStringUtil::isPartOfWord( wtext[mCursorPos] ) ) - { - S32 old_selection_start = mLastSelectionStart; - S32 old_selection_end = mLastSelectionEnd; - - // Select word the cursor is over - while ((mCursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[mCursorPos-1] )) - { // Find the start of the word - mCursorPos--; - } - startSelection(); - - while ((mCursorPos < (S32)wtext.length()) && LLWStringUtil::isPartOfWord( wtext[mCursorPos] ) ) - { // Find the end of the word - mCursorPos++; - } - mSelectionEnd = mCursorPos; - - // If nothing changed, then the word was already selected. Select the whole line. - doSelectAll = (old_selection_start == mSelectionStart) && - (old_selection_end == mSelectionEnd); - } - - if ( doSelectAll ) - { // Select everything - selectAll(); - } - } - - // We don't want handleMouseUp() to "finish" the selection (and thereby - // set mSelectionEnd to where the mouse is), so we finish the selection - // here. - mIsSelecting = false; - - // delay cursor flashing - mKeystrokeTimer.reset(); - - // take selection to 'primary' clipboard - updatePrimary(); - - return true; -} - -bool LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // Check first whether the "clear search" button wants to deal with this. - if(childrenHandleMouseDown(x, y, mask) != NULL) - { - return true; - } - - if (!mSelectAllonFocusReceived - || gFocusMgr.getKeyboardFocus() == this) - { - mLastSelectionStart = -1; - mLastSelectionStart = -1; - - if (mask & MASK_SHIFT) - { - // assume we're starting a drag select - mIsSelecting = true; - - // Handle selection extension - S32 old_cursor_pos = getCursor(); - setCursorAtLocalPos(x); - - if (hasSelection()) - { - /* Mac-like behavior - extend selection towards the cursor - if (getCursor() < mSelectionStart - && getCursor() < mSelectionEnd) - { - // ...left of selection - mSelectionStart = llmax(mSelectionStart, mSelectionEnd); - mSelectionEnd = getCursor(); - } - else if (getCursor() > mSelectionStart - && getCursor() > mSelectionEnd) - { - // ...right of selection - mSelectionStart = llmin(mSelectionStart, mSelectionEnd); - mSelectionEnd = getCursor(); - } - else - { - mSelectionEnd = getCursor(); - } - */ - // Windows behavior - mSelectionEnd = getCursor(); - } - else - { - mSelectionStart = old_cursor_pos; - mSelectionEnd = getCursor(); - } - } - else - { - if (mTripleClickTimer.hasExpired()) - { - // Save selection for word/line selecting on double-click - mLastSelectionStart = mSelectionStart; - mLastSelectionEnd = mSelectionEnd; - - // Move cursor and deselect for regular click - setCursorAtLocalPos( x ); - deselect(); - startSelection(); - } - else // handle triple click - { - selectAll(); - // We don't want handleMouseUp() to "finish" the selection (and thereby - // set mSelectionEnd to where the mouse is), so we finish the selection - // here. - mIsSelecting = false; - } - } - - gFocusMgr.setMouseCapture( this ); - } - - setFocus(true); - - // delay cursor flashing - mKeystrokeTimer.reset(); - - if (mMouseDownSignal) - (*mMouseDownSignal)(this,x,y,mask); - - return true; -} - -bool LLLineEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask) -{ - // LL_INFOS() << "MiddleMouseDown" << LL_ENDL; - setFocus( true ); - if( canPastePrimary() ) - { - setCursorAtLocalPos(x); - pastePrimary(); - } - return true; -} - -bool LLLineEditor::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - setFocus(true); - if (!LLUICtrl::handleRightMouseDown(x, y, mask) && getShowContextMenu()) - { - showContextMenu(x, y); - } - return true; -} - -bool LLLineEditor::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = false; - // Check first whether the "clear search" button wants to deal with this. - if(!hasMouseCapture()) - { - if(childrenHandleHover(x, y, mask) != NULL) - { - return true; - } - } - - if( (hasMouseCapture()) && mIsSelecting ) - { - if (x != mLastSelectionX || y != mLastSelectionY) - { - mLastSelectionX = x; - mLastSelectionY = y; - } - // Scroll if mouse cursor outside of bounds - if (mScrollTimer.hasExpired()) - { - S32 increment = ll_round(mScrollTimer.getElapsedTimeF32() / AUTO_SCROLL_TIME); - mScrollTimer.reset(); - mScrollTimer.setTimerExpirySec(AUTO_SCROLL_TIME); - if( (x < mTextLeftEdge) && (mScrollHPos > 0 ) ) - { - // Scroll to the left - mScrollHPos = llclamp(mScrollHPos - increment, 0, mText.length()); - } - else - if( (x > mTextRightEdge) && (mCursorPos < (S32)mText.length()) ) - { - // If scrolling one pixel would make a difference... - S32 pixels_after_scrolling_one_char = findPixelNearestPos(1); - if( pixels_after_scrolling_one_char >= mTextRightEdge ) - { - // ...scroll to the right - mScrollHPos = llclamp(mScrollHPos + increment, 0, mText.length()); - } - } - } - - setCursorAtLocalPos( x ); - mSelectionEnd = getCursor(); - - // delay cursor flashing - mKeystrokeTimer.reset(); - - getWindow()->setCursor(UI_CURSOR_IBEAM); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; - handled = true; - } - - if( !handled ) - { - getWindow()->setCursor(UI_CURSOR_IBEAM); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; - handled = true; - } - - return handled; -} - - -bool LLLineEditor::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - if( hasMouseCapture() ) - { - gFocusMgr.setMouseCapture( NULL ); - handled = true; - } - - // Check first whether the "clear search" button wants to deal with this. - if(!handled && childrenHandleMouseUp(x, y, mask) != NULL) - { - return true; - } - - if( mIsSelecting ) - { - setCursorAtLocalPos( x ); - mSelectionEnd = getCursor(); - - handled = true; - } - - if( handled ) - { - // delay cursor flashing - mKeystrokeTimer.reset(); - - // take selection to 'primary' clipboard - updatePrimary(); - } - - // We won't call LLUICtrl::handleMouseUp to avoid double calls of childrenHandleMouseUp().Just invoke the signal manually. - if (mMouseUpSignal) - (*mMouseUpSignal)(this,x,y, mask); - return handled; -} - - -// Remove a single character from the text -void LLLineEditor::removeChar() -{ - if( getCursor() > 0 ) - { - if (!prevalidateInput(mText.getWString().substr(getCursor()-1, 1))) - return; - - mText.erase(getCursor() - 1, 1); - - setCursor(getCursor() - 1); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } -} - -void LLLineEditor::addChar(const llwchar uni_char) -{ - if (!mAllowEmoji && LLStringOps::isEmoji(uni_char)) - return; - - llwchar new_c = uni_char; - if (hasSelection()) - { - deleteSelection(); - } - else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) - { - if (!prevalidateInput(mText.getWString().substr(getCursor(), 1))) - return; - - mText.erase(getCursor(), 1); - } - - S32 cur_bytes = mText.getString().size(); - - S32 new_bytes = wchar_utf8_length(new_c); - - bool allow_char = true; - - // Check byte length limit - if ((new_bytes + cur_bytes) > mMaxLengthBytes) - { - allow_char = false; - } - else if (mMaxLengthChars) - { - S32 wide_chars = mText.getWString().size(); - if ((wide_chars + 1) > mMaxLengthChars) - { - allow_char = false; - } - } - - if (allow_char) - { - // Will we need to scroll? - LLWString w_buf; - w_buf.assign(1, new_c); - - mText.insert(getCursor(), w_buf); - setCursor(getCursor() + 1); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - - getWindow()->hideCursorUntilMouseMove(); -} - -// Extends the selection box to the new cursor position -void LLLineEditor::extendSelection( S32 new_cursor_pos ) -{ - if( !mIsSelecting ) - { - startSelection(); - } - - S32 left_pos = llmin( mSelectionStart, new_cursor_pos ); - S32 selection_length = llabs( mSelectionStart - new_cursor_pos ); - const LLWString& selection = mText.getWString().substr(left_pos, selection_length); - - if (!prevalidateInput(selection)) - return; - - setCursor(new_cursor_pos); - mSelectionEnd = getCursor(); -} - - -void LLLineEditor::setSelection(S32 start, S32 end) -{ - S32 len = mText.length(); - - mIsSelecting = true; - - // JC, yes, this seems odd, but I think you have to presume a - // selection dragged from the end towards the start. - mSelectionStart = llclamp(end, 0, len); - mSelectionEnd = llclamp(start, 0, len); - setCursor(start); -} - -void LLLineEditor::setDrawAsterixes(bool b) -{ - mDrawAsterixes = b; - updateAllowingLanguageInput(); -} - -S32 LLLineEditor::prevWordPos(S32 cursorPos) const -{ - const LLWString& wtext = mText.getWString(); - while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') ) - { - cursorPos--; - } - while( (cursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[cursorPos-1] ) ) - { - cursorPos--; - } - return cursorPos; -} - -S32 LLLineEditor::nextWordPos(S32 cursorPos) const -{ - const LLWString& wtext = mText.getWString(); - while( (cursorPos < getLength()) && LLWStringUtil::isPartOfWord( wtext[cursorPos] ) ) - { - cursorPos++; - } - while( (cursorPos < getLength()) && (wtext[cursorPos] == ' ') ) - { - cursorPos++; - } - return cursorPos; -} - - -bool LLLineEditor::handleSelectionKey(KEY key, MASK mask) -{ - bool handled = false; - - if( mask & MASK_SHIFT ) - { - handled = true; - - switch( key ) - { - case KEY_LEFT: - if( 0 < getCursor() ) - { - S32 cursorPos = getCursor() - 1; - if( mask & MASK_CONTROL ) - { - cursorPos = prevWordPos(cursorPos); - } - extendSelection( cursorPos ); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - break; - - case KEY_RIGHT: - if( getCursor() < mText.length()) - { - S32 cursorPos = getCursor() + 1; - if( mask & MASK_CONTROL ) - { - cursorPos = nextWordPos(cursorPos); - } - extendSelection( cursorPos ); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - break; - - case KEY_PAGE_UP: - case KEY_HOME: - extendSelection( 0 ); - break; - - case KEY_PAGE_DOWN: - case KEY_END: - { - S32 len = mText.length(); - if( len ) - { - extendSelection( len ); - } - break; - } - - default: - handled = false; - break; - } - } - - if(handled) - { - // take selection to 'primary' clipboard - updatePrimary(); - } - - return handled; -} - -void LLLineEditor::deleteSelection() -{ - if( !mReadOnly && hasSelection() ) - { - S32 left_pos, selection_length; - getSelectionRange(&left_pos, &selection_length); - const LLWString& selection = mText.getWString().substr(left_pos, selection_length); - - if (!prevalidateInput(selection)) - return; - - mText.erase(left_pos, selection_length); - deselect(); - setCursor(left_pos); - } -} - -bool LLLineEditor::canCut() const -{ - return !mReadOnly && !mDrawAsterixes && hasSelection(); -} - -// cut selection to clipboard -void LLLineEditor::cut() -{ - if( canCut() ) - { - S32 left_pos, length; - getSelectionRange(&left_pos, &length); - const LLWString& selection = mText.getWString().substr(left_pos, length); - - if (!prevalidateInput(selection)) - return; - - // Prepare for possible rollback - LLLineEditorRollback rollback( this ); - - LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length ); - deleteSelection(); - - // Validate new string and rollback the if needed. - bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(mText.getWString()); - if (need_to_rollback) - { - rollback.doRollback( this ); - LLUI::getInstance()->reportBadKeystroke(); - mPrevalidator.showLastErrorUsingTimeout(); - } - else - { - onKeystroke(); - } - } -} - -bool LLLineEditor::canCopy() const -{ - return !mDrawAsterixes && hasSelection(); -} - - -// copy selection to clipboard -void LLLineEditor::copy() -{ - if( canCopy() ) - { - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = llabs( mSelectionStart - mSelectionEnd ); - LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length ); - } -} - -bool LLLineEditor::canPaste() const -{ - return !mReadOnly && LLClipboard::instance().isTextAvailable(); -} - -void LLLineEditor::paste() -{ - bool is_primary = false; - pasteHelper(is_primary); -} - -void LLLineEditor::pastePrimary() -{ - bool is_primary = true; - pasteHelper(is_primary); -} - -// paste from primary (is_primary==true) or clipboard (is_primary==false) -void LLLineEditor::pasteHelper(bool is_primary) -{ - bool can_paste_it; - if (is_primary) - { - can_paste_it = canPastePrimary(); - } - else - { - can_paste_it = canPaste(); - } - - if (can_paste_it) - { - LLWString paste; - LLClipboard::instance().pasteFromClipboard(paste, is_primary); - - if (!paste.empty()) - { - if (!mAllowEmoji) - { - wstring_remove_emojis(paste); - } - - if (!prevalidateInput(paste)) - return; - - // Prepare for possible rollback - LLLineEditorRollback rollback(this); - - // Delete any selected characters - if ((!is_primary) && hasSelection()) - { - deleteSelection(); - } - - // Clean up string (replace tabs and returns and remove characters that our fonts don't support.) - LLWString clean_string(paste); - LLWStringUtil::replaceTabsWithSpaces(clean_string, 1); - //clean_string = wstring_detabify(paste, 1); - LLWStringUtil::replaceChar(clean_string, '\n', mReplaceNewlinesWithSpaces ? ' ' : 182); // 182 == paragraph character - - // Insert the string - - // Check to see that the size isn't going to be larger than the max number of bytes - U32 available_bytes = mMaxLengthBytes - wstring_utf8_length(mText); - - if ( available_bytes < (U32) wstring_utf8_length(clean_string) ) - { // Doesn't all fit - llwchar current_symbol = clean_string[0]; - U32 wchars_that_fit = 0; - U32 total_bytes = wchar_utf8_length(current_symbol); - - //loop over the "wide" characters (symbols) - //and check to see how large (in bytes) each symbol is. - while ( total_bytes <= available_bytes ) - { - //while we still have available bytes - //"accept" the current symbol and check the size - //of the next one - current_symbol = clean_string[++wchars_that_fit]; - total_bytes += wchar_utf8_length(current_symbol); - } - // Truncate the clean string at the limit of what will fit - clean_string = clean_string.substr(0, wchars_that_fit); - LLUI::getInstance()->reportBadKeystroke(); - } - - if (mMaxLengthChars) - { - U32 available_chars = mMaxLengthChars - mText.getWString().size(); - - if (available_chars < clean_string.size()) - { - clean_string = clean_string.substr(0, available_chars); - } - - LLUI::getInstance()->reportBadKeystroke(); - } - - mText.insert(getCursor(), clean_string); - setCursor( getCursor() + (S32)clean_string.length() ); - deselect(); - - // Validate new string and rollback the if needed. - bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(mText.getWString()); - if (need_to_rollback) - { - rollback.doRollback( this ); - LLUI::getInstance()->reportBadKeystroke(); - mPrevalidator.showLastErrorUsingTimeout(); - } - else - { - onKeystroke(); - } - } - } -} - -// copy selection to primary -void LLLineEditor::copyPrimary() -{ - if( canCopy() ) - { - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = llabs( mSelectionStart - mSelectionEnd ); - LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length, true); - } -} - -bool LLLineEditor::canPastePrimary() const -{ - return !mReadOnly && LLClipboard::instance().isTextAvailable(true); -} - -void LLLineEditor::updatePrimary() -{ - if(canCopy() ) - { - copyPrimary(); - } -} - -bool LLLineEditor::handleSpecialKey(KEY key, MASK mask) -{ - bool handled = false; - - switch( key ) - { - case KEY_INSERT: - if (mask == MASK_NONE) - { - gKeyboard->toggleInsertMode(); - } - - handled = true; - break; - - case KEY_BACKSPACE: - if (!mReadOnly) - { - //LL_INFOS() << "Handling backspace" << LL_ENDL; - if( hasSelection() ) - { - deleteSelection(); - } - else - if( 0 < getCursor() ) - { - removeChar(); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - } - handled = true; - break; - - case KEY_PAGE_UP: - case KEY_HOME: - if (!mIgnoreArrowKeys) - { - setCursor(0); - handled = true; - } - break; - - case KEY_PAGE_DOWN: - case KEY_END: - if (!mIgnoreArrowKeys) - { - S32 len = mText.length(); - if( len ) - { - setCursor(len); - } - handled = true; - } - break; - - case KEY_LEFT: - if (mIgnoreArrowKeys && mask == MASK_NONE) - break; - if ((mask & MASK_ALT) == 0) - { - if( hasSelection() ) - { - setCursor(llmin( getCursor() - 1, mSelectionStart, mSelectionEnd )); - } - else - if( 0 < getCursor() ) - { - S32 cursorPos = getCursor() - 1; - if( mask & MASK_CONTROL ) - { - cursorPos = prevWordPos(cursorPos); - } - setCursor(cursorPos); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - handled = true; - } - break; - - case KEY_RIGHT: - if (mIgnoreArrowKeys && mask == MASK_NONE) - break; - if ((mask & MASK_ALT) == 0) - { - if (hasSelection()) - { - setCursor(llmax(getCursor() + 1, mSelectionStart, mSelectionEnd)); - } - else - if (getCursor() < mText.length()) - { - S32 cursorPos = getCursor() + 1; - if( mask & MASK_CONTROL ) - { - cursorPos = nextWordPos(cursorPos); - } - setCursor(cursorPos); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - handled = true; - } - break; - - // handle ctrl-uparrow if we have a history enabled line editor. - case KEY_UP: - if (mHaveHistory && (!mIgnoreArrowKeys || (MASK_CONTROL == mask))) - { - if (mCurrentHistoryLine > mLineHistory.begin()) - { - mText.assign(*(--mCurrentHistoryLine)); - setCursorToEnd(); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - handled = true; - } - break; - - // handle [ctrl]-downarrow if we have a history enabled line editor - case KEY_DOWN: - if (mHaveHistory && (!mIgnoreArrowKeys || (MASK_CONTROL == mask))) - { - if (!mLineHistory.empty() && mCurrentHistoryLine < mLineHistory.end() - 1) - { - mText.assign( *(++mCurrentHistoryLine) ); - setCursorToEnd(); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - handled = true; - } - break; - - case KEY_RETURN: - // store sent line in history - updateHistory(); - break; - - case KEY_ESCAPE: - if (mRevertOnEsc && mText.getString() != mPrevText) - { - setText(mPrevText); - // Note, don't set handled, still want to loose focus (won't commit becase text is now unchanged) - if (mKeystrokeOnEsc) - { - onKeystroke(); - } - } - break; - - default: - break; - } - - return handled; -} - - -bool LLLineEditor::handleKeyHere(KEY key, MASK mask ) -{ - bool handled = false; - bool selection_modified = false; - - if ( gFocusMgr.getKeyboardFocus() == this ) - { - LLLineEditorRollback rollback( this ); - - if( !handled ) - { - handled = handleSelectionKey( key, mask ); - selection_modified = handled; - } - - // Handle most keys only if the text editor is writeable. - if ( !mReadOnly ) - { - if( !handled ) - { - handled = handleSpecialKey( key, mask ); - } - } - - if( handled ) - { - mKeystrokeTimer.reset(); - - // Most keystrokes will make the selection box go away, but not all will. - if( !selection_modified && - KEY_SHIFT != key && - KEY_CONTROL != key && - KEY_ALT != key && - KEY_CAPSLOCK != key) - { - deselect(); - } - - bool prevalidator_failed = false; - - // If read-only, don't allow changes - bool need_to_rollback = mReadOnly && (mText.getString() == rollback.getText()); - - // Validate new string and rollback the keystroke if needed. - if (!need_to_rollback && mPrevalidator) - { - prevalidator_failed = !mPrevalidator.validate(mText.getWString()); - need_to_rollback |= prevalidator_failed; - } - - if (need_to_rollback) - { - rollback.doRollback(this); - - LLUI::getInstance()->reportBadKeystroke(); - if (prevalidator_failed) - { - mPrevalidator.showLastErrorUsingTimeout(); - } - } - - // Notify owner if requested - if (!need_to_rollback && handled) - { - onKeystroke(); - if ( (!selection_modified) && (KEY_BACKSPACE == key) ) - { - mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); - } - } - } - } - - return handled; -} - - -bool LLLineEditor::handleUnicodeCharHere(llwchar uni_char) -{ - if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL - { - return false; - } - - bool handled = false; - - if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible() && !mReadOnly) - { - handled = true; - - LLLineEditorRollback rollback( this ); - - { - LLWString u_char; - u_char.assign(1, uni_char); - if (!prevalidateInput(u_char)) - return handled; - } - - addChar(uni_char); - - mKeystrokeTimer.reset(); - - deselect(); - - // Validate new string and rollback the keystroke if needed. - bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(mText.getWString()); - if (need_to_rollback) - { - rollback.doRollback( this ); - - LLUI::getInstance()->reportBadKeystroke(); - mPrevalidator.showLastErrorUsingTimeout(); - } - - // Notify owner if requested - if (!need_to_rollback && handled) - { - // HACK! The only usage of this callback doesn't do anything with the character. - // We'll have to do something about this if something ever changes! - Doug - onKeystroke(); - - mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); - } - } - return handled; -} - - -bool LLLineEditor::canDoDelete() const -{ - return ( !mReadOnly && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) ); -} - -void LLLineEditor::doDelete() -{ - if (canDoDelete() && mText.length() > 0) - { - // Prepare for possible rollback - LLLineEditorRollback rollback( this ); - - if (hasSelection()) - { - deleteSelection(); - } - else if ( getCursor() < mText.length()) - { - const LLWString& text_to_delete = mText.getWString().substr(getCursor(), 1); - - if (!prevalidateInput(text_to_delete)) - { - onKeystroke(); - return; - } - setCursor(getCursor() + 1); - removeChar(); - } - - // Validate new string and rollback the if needed. - bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(mText.getWString()); - if (need_to_rollback) - { - rollback.doRollback(this); - LLUI::getInstance()->reportBadKeystroke(); - mPrevalidator.showLastErrorUsingTimeout(); - } - else - { - onKeystroke(); - - mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); - } - } -} - - -void LLLineEditor::drawBackground() -{ - F32 alpha = getCurrentTransparency(); - if (mUseBgColor) - { - gl_rect_2d(getLocalRect(), mBgColor % alpha, true); - } - else - { - bool has_focus = hasFocus(); - LLUIImage* image; - if (mReadOnly) - { - image = mBgImageDisabled; - } - else if (has_focus || mShowImageFocused) - { - image = mBgImageFocused; - } - else - { - image = mBgImage; - } - - if (!image) return; - // optionally draw programmatic border - if (has_focus) - { - LLColor4 tmp_color = gFocusMgr.getFocusColor(); - tmp_color.setAlpha(alpha); - image->drawBorder(0, 0, getRect().getWidth(), getRect().getHeight(), - tmp_color, - gFocusMgr.getFocusFlashWidth()); - } - LLColor4 tmp_color = UI_VERTEX_COLOR; - tmp_color.setAlpha(alpha); - image->draw(getLocalRect(), tmp_color); - } -} - -//virtual -void LLLineEditor::draw() -{ - F32 alpha = getDrawContext().mAlpha; - S32 text_len = mText.length(); - static LLUICachedControl lineeditor_cursor_thickness ("UILineEditorCursorThickness", 0); - static LLUICachedControl preedit_marker_brightness ("UIPreeditMarkerBrightness", 0); - static LLUICachedControl preedit_marker_gap ("UIPreeditMarkerGap", 0); - static LLUICachedControl preedit_marker_position ("UIPreeditMarkerPosition", 0); - static LLUICachedControl preedit_marker_thickness ("UIPreeditMarkerThickness", 0); - static LLUICachedControl preedit_standout_brightness ("UIPreeditStandoutBrightness", 0); - static LLUICachedControl preedit_standout_gap ("UIPreeditStandoutGap", 0); - static LLUICachedControl preedit_standout_position ("UIPreeditStandoutPosition", 0); - static LLUICachedControl preedit_standout_thickness ("UIPreeditStandoutThickness", 0); - - std::string saved_text; - if (mDrawAsterixes) - { - saved_text = mText.getString(); - std::string text; - for (S32 i = 0; i < mText.length(); i++) - { - text += PASSWORD_ASTERISK; - } - mText = text; - } - - // draw rectangle for the background - LLRect background( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - background.stretch( -mBorderThickness ); - - S32 lineeditor_v_pad = (background.getHeight() - mGLFont->getLineHeight()) / 2; - if (mSpellCheck) - { - lineeditor_v_pad += 1; - } - - drawBackground(); - - // draw text - - // With viewer-2 art files, input region is 2 pixels up - S32 cursor_bottom = background.mBottom + 2; - S32 cursor_top = background.mTop - 1; - - LLColor4 text_color; - if (!mReadOnly) - { - if (!getTentative()) - { - text_color = mFgColor.get(); - } - else - { - text_color = mTentativeFgColor.get(); - } - } - else - { - text_color = mReadOnlyFgColor.get(); - } - text_color.setAlpha(alpha); - LLColor4 label_color = mTentativeFgColor.get(); - label_color.setAlpha(alpha); - - if (hasPreeditString()) - { - // Draw preedit markers. This needs to be before drawing letters. - for (U32 i = 0; i < mPreeditStandouts.size(); i++) - { - const S32 preedit_left = mPreeditPositions[i]; - const S32 preedit_right = mPreeditPositions[i + 1]; - if (preedit_right > mScrollHPos) - { - S32 preedit_pixels_left = findPixelNearestPos(llmax(preedit_left, mScrollHPos) - getCursor()); - S32 preedit_pixels_right = llmin(findPixelNearestPos(preedit_right - getCursor()), background.mRight); - if (preedit_pixels_left >= background.mRight) - { - break; - } - if (mPreeditStandouts[i]) - { - gl_rect_2d(preedit_pixels_left + preedit_standout_gap, - background.mBottom + preedit_standout_position, - preedit_pixels_right - preedit_standout_gap - 1, - background.mBottom + preedit_standout_position - preedit_standout_thickness, - (text_color * preedit_standout_brightness - + mPreeditBgColor * (1 - preedit_standout_brightness)).setAlpha(alpha/*1.0f*/)); - } - else - { - gl_rect_2d(preedit_pixels_left + preedit_marker_gap, - background.mBottom + preedit_marker_position, - preedit_pixels_right - preedit_marker_gap - 1, - background.mBottom + preedit_marker_position - preedit_marker_thickness, - (text_color * preedit_marker_brightness - + mPreeditBgColor * (1 - preedit_marker_brightness)).setAlpha(alpha/*1.0f*/)); - } - } - } - } - - S32 rendered_text = 0; - F32 rendered_pixels_right = (F32)mTextLeftEdge; - F32 text_bottom = (F32)background.mBottom + (F32)lineeditor_v_pad; - - if( (gFocusMgr.getKeyboardFocus() == this) && hasSelection() ) - { - S32 select_left; - S32 select_right; - if (mSelectionStart < mSelectionEnd) - { - select_left = mSelectionStart; - select_right = mSelectionEnd; - } - else - { - select_left = mSelectionEnd; - select_right = mSelectionStart; - } - - if( select_left > mScrollHPos ) - { - // unselected, left side - rendered_text = mGLFont->render( - mText, mScrollHPos, - rendered_pixels_right, text_bottom, - text_color, - LLFontGL::LEFT, LLFontGL::BOTTOM, - 0, - LLFontGL::NO_SHADOW, - select_left - mScrollHPos, - mTextRightEdge - ll_round(rendered_pixels_right), - &rendered_pixels_right); - } - - if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) ) - { - LLColor4 color = mHighlightColor; - color.setAlpha(alpha); - // selected middle - S32 width = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos + rendered_text, select_right - mScrollHPos - rendered_text); - width = llmin(width, mTextRightEdge - ll_round(rendered_pixels_right)); - gl_rect_2d(ll_round(rendered_pixels_right), cursor_top, ll_round(rendered_pixels_right)+width, cursor_bottom, color); - - LLColor4 tmp_color( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], alpha ); - rendered_text += mGLFont->render( - mText, mScrollHPos + rendered_text, - rendered_pixels_right, text_bottom, - tmp_color, - LLFontGL::LEFT, LLFontGL::BOTTOM, - 0, - LLFontGL::NO_SHADOW, - select_right - mScrollHPos - rendered_text, - mTextRightEdge - ll_round(rendered_pixels_right), - &rendered_pixels_right); - } - - if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) ) - { - // unselected, right side - rendered_text += mGLFont->render( - mText, mScrollHPos + rendered_text, - rendered_pixels_right, text_bottom, - text_color, - LLFontGL::LEFT, LLFontGL::BOTTOM, - 0, - LLFontGL::NO_SHADOW, - S32_MAX, - mTextRightEdge - ll_round(rendered_pixels_right), - &rendered_pixels_right); - } - } - else - { - rendered_text = mGLFont->render( - mText, mScrollHPos, - rendered_pixels_right, text_bottom, - text_color, - LLFontGL::LEFT, LLFontGL::BOTTOM, - 0, - LLFontGL::NO_SHADOW, - S32_MAX, - mTextRightEdge - ll_round(rendered_pixels_right), - &rendered_pixels_right); - } -#if 1 // for when we're ready for image art. - mBorder->setVisible(false); // no more programmatic art. -#endif - - if ( (getSpellCheck()) && (mText.length() > 2) ) - { - // Calculate start and end indices for the first and last visible word - U32 start = prevWordPos(mScrollHPos), end = nextWordPos(mScrollHPos + rendered_text); - - if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) ) - { - const LLWString& text = mText.getWString().substr(start, end); - - // Find the start of the first word - U32 word_start = 0, word_end = 0; - while ( (word_start < text.length()) && (!LLStringOps::isAlpha(text[word_start])) ) - { - word_start++; - } - - // Iterate over all words in the text block and check them one by one - mMisspellRanges.clear(); - while (word_start < text.length()) - { - // Find the end of the current word (special case handling for "'" when it's used as a contraction) - word_end = word_start + 1; - while ( (word_end < text.length()) && - ((LLWStringUtil::isPartOfWord(text[word_end])) || - ((L'\'' == text[word_end]) && (word_end + 1 < text.length()) && - (LLStringOps::isAlnum(text[word_end - 1])) && (LLStringOps::isAlnum(text[word_end + 1])))) ) - { - word_end++; - } - if (word_end > text.length()) - { - break; - } - - // Don't process words shorter than 3 characters - std::string word = wstring_to_utf8str(text.substr(word_start, word_end - word_start)); - if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) ) - { - mMisspellRanges.push_back(std::pair(start + word_start, start + word_end)); - } - - // Find the start of the next word - word_start = word_end + 1; - while ( (word_start < text.length()) && (!LLWStringUtil::isPartOfWord(text[word_start])) ) - { - word_start++; - } - } - - mSpellCheckStart = start; - mSpellCheckEnd = end; - } - - // Draw squiggly lines under any (visible) misspelled words - for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) - { - // Skip over words that aren't (partially) visible - if ( ((it->first < start) && (it->second < start)) || (it->first > end) ) - { - continue; - } - - // Skip the current word if the user is still busy editing it - if ( (!mSpellCheckTimer.hasExpired()) && (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) ) - { - continue; - } - - S32 pxWidth = getRect().getWidth(); - S32 pxStart = findPixelNearestPos(it->first - getCursor()); - if (pxStart > pxWidth) - { - continue; - } - S32 pxEnd = findPixelNearestPos(it->second - getCursor()); - if (pxEnd > pxWidth) - { - pxEnd = pxWidth; - } - - S32 pxBottom = (S32)(text_bottom + mGLFont->getDescenderHeight()); - - gGL.color4ub(255, 0, 0, 200); - while (pxStart + 1 < pxEnd) - { - gl_line_2d(pxStart, pxBottom, pxStart + 2, pxBottom - 2); - if (pxStart + 3 < pxEnd) - { - gl_line_2d(pxStart + 2, pxBottom - 3, pxStart + 4, pxBottom - 1); - } - pxStart += 4; - } - } - } - - // If we're editing... - if( hasFocus()) - { - //mBorder->setVisible(true); // ok, programmer art just this once. - // (Flash the cursor every half second) - if (!mReadOnly && gFocusMgr.getAppHasFocus()) - { - F32 elapsed = mKeystrokeTimer.getElapsedTimeF32(); - if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) - { - S32 cursor_left = findPixelNearestPos(); - cursor_left -= lineeditor_cursor_thickness / 2; - S32 cursor_right = cursor_left + lineeditor_cursor_thickness; - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) - { - const LLWString space(utf8str_to_wstring(std::string(" "))); - S32 wswidth = mGLFont->getWidth(space.c_str()); - S32 width = mGLFont->getWidth(mText.getWString().c_str(), getCursor(), 1) + 1; - cursor_right = cursor_left + llmax(wswidth, width); - } - // Use same color as text for the Cursor - gl_rect_2d(cursor_left, cursor_top, - cursor_right, cursor_bottom, text_color); - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) - { - LLColor4 tmp_color( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], alpha ); - mGLFont->render(mText, getCursor(), (F32)(cursor_left + lineeditor_cursor_thickness / 2), text_bottom, - tmp_color, - LLFontGL::LEFT, LLFontGL::BOTTOM, - 0, - LLFontGL::NO_SHADOW, - 1); - } - - // Make sure the IME is in the right place - S32 pixels_after_scroll = findPixelNearestPos(); // RCalculcate for IME position - LLRect screen_pos = calcScreenRect(); - LLCoordGL ime_pos( screen_pos.mLeft + pixels_after_scroll, screen_pos.mTop - lineeditor_v_pad ); - - ime_pos.mX = (S32) (ime_pos.mX * LLUI::getScaleFactor().mV[VX]); - ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]); - getWindow()->setLanguageTextInput( ime_pos ); - } - } - - //draw label if no text is provided - //but we should draw it in a different color - //to give indication that it is not text you typed in - if (0 == mText.length() && (mReadOnly || mShowLabelFocused)) - { - mGLFont->render(mLabel.getWString(), 0, - mTextLeftEdge, (F32)text_bottom, - label_color, - LLFontGL::LEFT, - LLFontGL::BOTTOM, - 0, - LLFontGL::NO_SHADOW, - S32_MAX, - mTextRightEdge - ll_round(rendered_pixels_right), - &rendered_pixels_right, false); - } - - - // Draw children (border) - //mBorder->setVisible(true); - mBorder->setKeyboardFocusHighlight( true ); - LLView::draw(); - mBorder->setKeyboardFocusHighlight( false ); - //mBorder->setVisible(false); - } - else // does not have keyboard input - { - // draw label if no text provided - if (0 == mText.length()) - { - mGLFont->render(mLabel.getWString(), 0, - mTextLeftEdge, (F32)text_bottom, - label_color, - LLFontGL::LEFT, - LLFontGL::BOTTOM, - 0, - LLFontGL::NO_SHADOW, - S32_MAX, - mTextRightEdge - ll_round(rendered_pixels_right), - &rendered_pixels_right); - } - // Draw children (border) - LLView::draw(); - } - - if (mDrawAsterixes) - { - mText = saved_text; - } -} - - -// Returns the local screen space X coordinate associated with the text cursor position. -S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) const -{ - S32 dpos = getCursor() - mScrollHPos + cursor_offset; - S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mTextLeftEdge; - return result; -} - -S32 LLLineEditor::calcCursorPos(S32 mouse_x) -{ - const llwchar* wtext = mText.getWString().c_str(); - LLWString asterix_text; - if (mDrawAsterixes) - { - for (S32 i = 0; i < mText.length(); i++) - { - asterix_text += utf8str_to_wstring(PASSWORD_ASTERISK); - } - wtext = asterix_text.c_str(); - } - - S32 cur_pos = mScrollHPos + - mGLFont->charFromPixelOffset( - wtext, mScrollHPos, - (F32)(mouse_x - mTextLeftEdge), - (F32)(mTextRightEdge - mTextLeftEdge + 1)); // min-max range is inclusive - - return cur_pos; -} -//virtual -void LLLineEditor::clear() -{ - mText.clear(); - setCursor(0); -} - -//virtual -void LLLineEditor::onTabInto() -{ - selectAll(); - LLUICtrl::onTabInto(); -} - -//virtual -bool LLLineEditor::acceptsTextInput() const -{ - return true; -} - -// Start or stop the editor from accepting text-editing keystrokes -void LLLineEditor::setFocus( bool new_state ) -{ - bool old_state = hasFocus(); - - if (!new_state) - { - getWindow()->allowLanguageTextInput(this, false); - } - - - // getting focus when we didn't have it before, and we want to select all - if (!old_state && new_state && mSelectAllonFocusReceived) - { - selectAll(); - // We don't want handleMouseUp() to "finish" the selection (and thereby - // set mSelectionEnd to where the mouse is), so we finish the selection - // here. - mIsSelecting = false; - } - - if( new_state ) - { - gEditMenuHandler = this; - - // Don't start the cursor flashing right away - mKeystrokeTimer.reset(); - } - else - { - // Not really needed, since loss of keyboard focus should take care of this, - // but limited paranoia is ok. - if( gEditMenuHandler == this ) - { - gEditMenuHandler = NULL; - } - - endSelection(); - } - - LLUICtrl::setFocus( new_state ); - - if (new_state) - { - // Allow Language Text Input only when this LineEditor has - // no prevalidate function attached. This criterion works - // fine on 1.15.0.2, since all prevalidate func reject any - // non-ASCII characters. I'm not sure on future versions, - // however. - getWindow()->allowLanguageTextInput(this, !mPrevalidator); - } -} - -//virtual -void LLLineEditor::setRect(const LLRect& rect) -{ - LLUICtrl::setRect(rect); - if (mBorder) - { - LLRect border_rect = mBorder->getRect(); - // Scalable UI somehow made these rectangles off-by-one. - // I don't know why. JC - border_rect.setOriginAndSize(border_rect.mLeft, border_rect.mBottom, - rect.getWidth()-1, rect.getHeight()-1); - mBorder->setRect(border_rect); - } -} - -void LLLineEditor::setPrevalidate(LLTextValidate::Validator validator) -{ - mPrevalidator = validator; - updateAllowingLanguageInput(); -} - -void LLLineEditor::setPrevalidateInput(LLTextValidate::Validator validator) -{ - mInputPrevalidator = validator; - updateAllowingLanguageInput(); -} - -bool LLLineEditor::prevalidateInput(const LLWString& wstr) -{ - return mInputPrevalidator.validate(wstr); -} - -// static -bool LLLineEditor::postvalidateFloat(const std::string &str) -{ - LLLocale locale(LLLocale::USER_LOCALE); - - bool success = true; - bool has_decimal = false; - bool has_digit = false; - - LLWString trimmed = utf8str_to_wstring(str); - LLWStringUtil::trim(trimmed); - S32 len = trimmed.length(); - if( 0 < len ) - { - S32 i = 0; - - // First character can be a negative sign - if( '-' == trimmed[0] ) - { - i++; - } - - // May be a comma or period, depending on the locale - llwchar decimal_point = (llwchar)LLResMgr::getInstance()->getDecimalPoint(); - - for( ; i < len; i++ ) - { - if( decimal_point == trimmed[i] ) - { - if( has_decimal ) - { - // can't have two - success = false; - break; - } - else - { - has_decimal = true; - } - } - else - if( LLStringOps::isDigit( trimmed[i] ) ) - { - has_digit = true; - } - else - { - success = false; - break; - } - } - } - - // Gotta have at least one - success = has_digit; - - return success; -} - -bool LLLineEditor::evaluateFloat() -{ - bool success; - F32 result = 0.f; - std::string expr = getText(); - LLStringUtil::toUpper(expr); - - success = LLCalc::getInstance()->evalString(expr, result); - - if (!success) - { - // Move the cursor to near the error on failure - setCursor(LLCalc::getInstance()->getLastErrorPos()); - // *TODO: Translated error message indicating the type of error? Select error text? - } - else - { - // Replace the expression with the result - std::string result_str = llformat("%f",result); - setText(result_str); - selectAll(); - } - - return success; -} - -void LLLineEditor::onMouseCaptureLost() -{ - endSelection(); -} - - -void LLLineEditor::setSelectAllonFocusReceived(bool b) -{ - mSelectAllonFocusReceived = b; -} - -void LLLineEditor::onKeystroke() -{ - if (mKeystrokeCallback) - { - mKeystrokeCallback(this); - } - - mSpellCheckStart = mSpellCheckEnd = -1; -} - -void LLLineEditor::setKeystrokeCallback(callback_t callback, void* user_data) -{ - mKeystrokeCallback = boost::bind(callback, _1, user_data); -} - - -bool LLLineEditor::setTextArg( const std::string& key, const LLStringExplicit& text ) -{ - mText.setArg(key, text); - return true; -} - -bool LLLineEditor::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - mLabel.setArg(key, text); - return true; -} - - -void LLLineEditor::updateAllowingLanguageInput() -{ - // Allow Language Text Input only when this LineEditor has - // no prevalidate function attached (as long as other criteria - // common to LLTextEditor). This criterion works - // fine on 1.15.0.2, since all prevalidate func reject any - // non-ASCII characters. I'm not sure on future versions, - // however... - LLWindow* window = getWindow(); - if (!window) - { - // test app, no window available - return; - } - if (hasFocus() && !mReadOnly && !mDrawAsterixes && !mPrevalidator) - { - window->allowLanguageTextInput(this, true); - } - else - { - window->allowLanguageTextInput(this, false); - } -} - -bool LLLineEditor::hasPreeditString() const -{ - return (mPreeditPositions.size() > 1); -} - -void LLLineEditor::resetPreedit() -{ - if (hasSelection()) - { - if (hasPreeditString()) - { - LL_WARNS() << "Preedit and selection!" << LL_ENDL; - deselect(); - } - else - { - deleteSelection(); - } - } - if (hasPreeditString()) - { - const S32 preedit_pos = mPreeditPositions.front(); - mText.erase(preedit_pos, mPreeditPositions.back() - preedit_pos); - mText.insert(preedit_pos, mPreeditOverwrittenWString); - setCursor(preedit_pos); - - mPreeditWString.clear(); - mPreeditOverwrittenWString.clear(); - mPreeditPositions.clear(); - - // Don't reset key stroke timer nor invoke keystroke callback, - // because a call to updatePreedit should be follow soon in - // normal course of operation, and timer and callback will be - // maintained there. Doing so here made an odd sound. (VWR-3410) - } -} - -void LLLineEditor::updatePreedit(const LLWString &preedit_string, - const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) -{ - // Just in case. - if (mReadOnly) - { - return; - } - - // Note that call to updatePreedit is always preceeded by resetPreedit, - // so we have no existing selection/preedit. - - S32 insert_preedit_at = getCursor(); - - mPreeditWString = preedit_string; - mPreeditPositions.resize(preedit_segment_lengths.size() + 1); - S32 position = insert_preedit_at; - for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++) - { - mPreeditPositions[i] = position; - position += preedit_segment_lengths[i]; - } - mPreeditPositions.back() = position; - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) - { - mPreeditOverwrittenWString.assign( LLWString( mText, insert_preedit_at, mPreeditWString.length() ) ); - mText.erase(insert_preedit_at, mPreeditWString.length()); - } - else - { - mPreeditOverwrittenWString.clear(); - } - mText.insert(insert_preedit_at, mPreeditWString); - - mPreeditStandouts = preedit_standouts; - - setCursor(position); - setCursor(mPreeditPositions.front() + caret_position); - - // Update of the preedit should be caused by some key strokes. - mKeystrokeTimer.reset(); - onKeystroke(); - - mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); -} - -bool LLLineEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const -{ - if (control) - { - LLRect control_rect_screen; - localRectToScreen(getRect(), &control_rect_screen); - LLUI::getInstance()->screenRectToGL(control_rect_screen, control); - } - - S32 preedit_left_column, preedit_right_column; - if (hasPreeditString()) - { - preedit_left_column = mPreeditPositions.front(); - preedit_right_column = mPreeditPositions.back(); - } - else - { - preedit_left_column = preedit_right_column = getCursor(); - } - if (preedit_right_column < mScrollHPos) - { - // This should not occure... - return false; - } - - const S32 query = (query_offset >= 0 ? preedit_left_column + query_offset : getCursor()); - if (query < mScrollHPos || query < preedit_left_column || query > preedit_right_column) - { - return false; - } - - if (coord) - { - S32 query_local = findPixelNearestPos(query - getCursor()); - S32 query_screen_x, query_screen_y; - localPointToScreen(query_local, getRect().getHeight() / 2, &query_screen_x, &query_screen_y); - LLUI::getInstance()->screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); - } - - if (bounds) - { - S32 preedit_left_local = findPixelNearestPos(llmax(preedit_left_column, mScrollHPos) - getCursor()); - S32 preedit_right_local = llmin(findPixelNearestPos(preedit_right_column - getCursor()), getRect().getWidth() - mBorderThickness); - if (preedit_left_local > preedit_right_local) - { - // Is this condition possible? - preedit_right_local = preedit_left_local; - } - - LLRect preedit_rect_local(preedit_left_local, getRect().getHeight(), preedit_right_local, 0); - LLRect preedit_rect_screen; - localRectToScreen(preedit_rect_local, &preedit_rect_screen); - LLUI::getInstance()->screenRectToGL(preedit_rect_screen, bounds); - } - - return true; -} - -void LLLineEditor::getPreeditRange(S32 *position, S32 *length) const -{ - if (hasPreeditString()) - { - *position = mPreeditPositions.front(); - *length = mPreeditPositions.back() - mPreeditPositions.front(); - } - else - { - *position = mCursorPos; - *length = 0; - } -} - -void LLLineEditor::getSelectionRange(S32 *position, S32 *length) const -{ - if (hasSelection()) - { - *position = llmin(mSelectionStart, mSelectionEnd); - *length = llabs(mSelectionStart - mSelectionEnd); - } - else - { - *position = mCursorPos; - *length = 0; - } -} - -void LLLineEditor::markAsPreedit(S32 position, S32 length) -{ - deselect(); - setCursor(position); - if (hasPreeditString()) - { - LL_WARNS() << "markAsPreedit invoked when hasPreeditString is true." << LL_ENDL; - } - mPreeditWString.assign( LLWString( mText.getWString(), position, length ) ); - if (length > 0) - { - mPreeditPositions.resize(2); - mPreeditPositions[0] = position; - mPreeditPositions[1] = position + length; - mPreeditStandouts.resize(1); - mPreeditStandouts[0] = false; - } - else - { - mPreeditPositions.clear(); - mPreeditStandouts.clear(); - } - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) - { - mPreeditOverwrittenWString = mPreeditWString; - } - else - { - mPreeditOverwrittenWString.clear(); - } -} - -S32 LLLineEditor::getPreeditFontSize() const -{ - return ll_round(mGLFont->getLineHeight() * LLUI::getScaleFactor().mV[VY]); -} - -void LLLineEditor::setReplaceNewlinesWithSpaces(bool replace) -{ - mReplaceNewlinesWithSpaces = replace; -} - -LLWString LLLineEditor::getConvertedText() const -{ - LLWString text = getWText(); - LLWStringUtil::trim(text); - if (!mReplaceNewlinesWithSpaces) - { - LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. - } - return text; -} - -void LLLineEditor::showContextMenu(S32 x, S32 y) -{ - LLContextMenu* menu = static_cast(mContextMenuHandle.get()); - if (!menu) - { - llassert(LLMenuGL::sMenuContainer != NULL); - menu = LLUICtrlFactory::createFromFile - ("menu_text_editor.xml", - LLMenuGL::sMenuContainer, - LLMenuHolderGL::child_registry_t::instance()); - setContextMenu(menu); - } - - if (menu) - { - gEditMenuHandler = this; - - S32 screen_x, screen_y; - localPointToScreen(x, y, &screen_x, &screen_y); - - setCursorAtLocalPos(x); - if (hasSelection()) - { - if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) ) - { - deselect(); - } - else - { - setCursor(llmax(mSelectionStart, mSelectionEnd)); - } - } - - bool use_spellcheck = getSpellCheck(), is_misspelled = false; - if (use_spellcheck) - { - mSuggestionList.clear(); - - // If the cursor is on a misspelled word, retrieve suggestions for it - std::string misspelled_word = getMisspelledWord(mCursorPos); - if ((is_misspelled = !misspelled_word.empty())) - { - LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList); - } - } - - menu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty())); - menu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled)); - menu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled)); - menu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled)); - menu->show(screen_x, screen_y, this); - } -} - -void LLLineEditor::setContextMenu(LLContextMenu* new_context_menu) -{ - LLContextMenu* menu = static_cast(mContextMenuHandle.get()); - if (menu) - { - menu->die(); - mContextMenuHandle.markDead(); - } - - if (new_context_menu) - { - mContextMenuHandle = new_context_menu->getHandle(); - } -} - -void LLLineEditor::setFont(const LLFontGL* font) -{ - mGLFont = font; -} +/** + * @file lllineeditor.cpp + * @brief LLLineEditor base 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$ + */ + +// Text editor widget to let users enter a single line. + +#include "linden_common.h" + +#define LLLINEEDITOR_CPP +#include "lllineeditor.h" + +#include "lltexteditor.h" +#include "llmath.h" +#include "llfontgl.h" +#include "llgl.h" +#include "lltimer.h" + +#include "llcalc.h" +//#include "llclipboard.h" +#include "llcontrol.h" +#include "llbutton.h" +#include "llfocusmgr.h" +#include "llkeyboard.h" +#include "llrect.h" +#include "llresmgr.h" +#include "llspellcheck.h" +#include "llstring.h" +#include "llwindow.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llclipboard.h" +#include "llmenugl.h" + +// +// Imported globals +// + +// +// Constants +// + +const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds +const S32 SCROLL_INCREMENT_ADD = 0; // make space for typing +const S32 SCROLL_INCREMENT_DEL = 4; // make space for baskspacing +const F32 AUTO_SCROLL_TIME = 0.05f; +const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click. *TODO: make this equal to the double click interval? +const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and spell checking the word the cursor is on + +const std::string PASSWORD_ASTERISK( "\xE2\x80\xA2" ); // U+2022 BULLET + +static LLDefaultChildRegistry::Register r1("line_editor"); + +// Compiler optimization, generate extern template +template class LLLineEditor* LLView::getChild( + const std::string& name, bool recurse) const; + +// +// Member functions +// + +LLLineEditor::Params::Params() +: max_length(""), + keystroke_callback("keystroke_callback"), + prevalidator("prevalidator"), + input_prevalidator("input_prevalidator"), + background_image("background_image"), + background_image_disabled("background_image_disabled"), + background_image_focused("background_image_focused"), + bg_image_always_focused("bg_image_always_focused", false), + show_label_focused("show_label_focused", false), + select_on_focus("select_on_focus", false), + revert_on_esc("revert_on_esc", true), + spellcheck("spellcheck", false), + commit_on_focus_lost("commit_on_focus_lost", true), + ignore_tab("ignore_tab", true), + is_password("is_password", false), + allow_emoji("allow_emoji", true), + cursor_color("cursor_color"), + use_bg_color("use_bg_color", false), + bg_color("bg_color"), + text_color("text_color"), + text_readonly_color("text_readonly_color"), + text_tentative_color("text_tentative_color"), + highlight_color("highlight_color"), + preedit_bg_color("preedit_bg_color"), + border(""), + bg_visible("bg_visible"), + text_pad_left("text_pad_left"), + text_pad_right("text_pad_right"), + default_text("default_text") +{ + changeDefault(mouse_opaque, true); + addSynonym(prevalidator, "prevalidate_callback"); + addSynonym(input_prevalidator, "prevalidate_input_callback"); + addSynonym(select_on_focus, "select_all_on_focus_received"); + addSynonym(border, "border"); + addSynonym(label, "watermark_text"); + addSynonym(max_length.chars, "max_length"); +} + +LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) +: LLUICtrl(p), + mMaxLengthBytes(p.max_length.bytes), + mMaxLengthChars(p.max_length.chars), + mCursorPos( 0 ), + mScrollHPos( 0 ), + mTextPadLeft(p.text_pad_left), + mTextPadRight(p.text_pad_right), + mTextLeftEdge(0), // computed in updateTextPadding() below + mTextRightEdge(0), // computed in updateTextPadding() below + mCommitOnFocusLost( p.commit_on_focus_lost ), + mKeystrokeOnEsc(false), + mRevertOnEsc( p.revert_on_esc ), + mKeystrokeCallback( p.keystroke_callback() ), + mIsSelecting( false ), + mSelectionStart( 0 ), + mSelectionEnd( 0 ), + mLastSelectionX(-1), + mLastSelectionY(-1), + mLastSelectionStart(-1), + mLastSelectionEnd(-1), + mBorderThickness( 0 ), + mIgnoreArrowKeys( false ), + mIgnoreTab( p.ignore_tab ), + mDrawAsterixes( p.is_password ), + mAllowEmoji( p.allow_emoji ), + mSpellCheck( p.spellcheck ), + mSpellCheckStart(-1), + mSpellCheckEnd(-1), + mSelectAllonFocusReceived( p.select_on_focus ), + mSelectAllonCommit( true ), + mPassDelete(false), + mReadOnly(false), + mBgImage( p.background_image ), + mBgImageDisabled( p.background_image_disabled ), + mBgImageFocused( p.background_image_focused ), + mShowImageFocused( p.bg_image_always_focused ), + mShowLabelFocused( p.show_label_focused ), + mUseBgColor(p.use_bg_color), + mHaveHistory(false), + mReplaceNewlinesWithSpaces( true ), + mPrevalidator(p.prevalidator()), + mInputPrevalidator(p.input_prevalidator()), + mLabel(p.label), + mCursorColor(p.cursor_color()), + mBgColor(p.bg_color()), + mFgColor(p.text_color()), + mReadOnlyFgColor(p.text_readonly_color()), + mTentativeFgColor(p.text_tentative_color()), + mHighlightColor(p.highlight_color()), + mPreeditBgColor(p.preedit_bg_color()), + mGLFont(p.font), + mContextMenuHandle(), + mShowContextMenu(true) +{ + llassert( mMaxLengthBytes > 0 ); + + LLUICtrl::setEnabled(true); + setEnabled(p.enabled); + + mScrollTimer.reset(); + mTripleClickTimer.reset(); + setText(p.default_text()); + + if (p.initial_value.isProvided() + && !p.control_name.isProvided()) + { + // Initial value often is descriptive, like "Type some ID here" + // and can be longer than size limitation, ignore size + setText(p.initial_value.getValue().asString(), false); + } + + // Initialize current history line iterator + mCurrentHistoryLine = mLineHistory.begin(); + + LLRect border_rect(getLocalRect()); + // adjust for gl line drawing glitch + border_rect.mTop -= 1; + border_rect.mRight -=1; + LLViewBorder::Params border_p(p.border); + border_p.rect = border_rect; + border_p.follows.flags = FOLLOWS_ALL; + border_p.bevel_style = LLViewBorder::BEVEL_IN; + mBorder = LLUICtrlFactory::create(border_p); + addChild( mBorder ); + + // clamp text padding to current editor size + updateTextPadding(); + setCursor(mText.length()); + + if (mSpellCheck) + { + LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLLineEditor::onSpellCheckSettingsChange, this)); + } + mSpellCheckTimer.reset(); + + updateAllowingLanguageInput(); +} + +LLLineEditor::~LLLineEditor() +{ + mCommitOnFocusLost = false; + + // Make sure no context menu linger around once the widget is deleted + LLContextMenu* menu = static_cast(mContextMenuHandle.get()); + if (menu) + { + menu->hide(); + } + setContextMenu(NULL); + + // calls onCommit() while LLLineEditor still valid + gFocusMgr.releaseFocusIfNeeded( this ); +} + +void LLLineEditor::initFromParams(const LLLineEditor::Params& params) +{ + LLUICtrl::initFromParams(params); + LLUICtrl::setEnabled(true); + setEnabled(params.enabled); +} + +void LLLineEditor::onFocusReceived() +{ + gEditMenuHandler = this; + LLUICtrl::onFocusReceived(); + updateAllowingLanguageInput(); +} + +void LLLineEditor::onFocusLost() +{ + // The call to updateAllowLanguageInput() + // when loosing the keyboard focus *may* + // indirectly invoke handleUnicodeCharHere(), + // so it must be called before onCommit. + updateAllowingLanguageInput(); + + if( mCommitOnFocusLost && mText.getString() != mPrevText) + { + onCommit(); + } + + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + getWindow()->showCursorFromMouseMove(); + + LLUICtrl::onFocusLost(); +} + +// virtual +void LLLineEditor::onCommit() +{ + // put current line into the line history + updateHistory(); + + setControlValue(getValue()); + LLUICtrl::onCommit(); + resetDirty(); + + // Selection on commit needs to be turned off when evaluating maths + // expressions, to allow indication of the error position + if (mSelectAllonCommit) selectAll(); +} + +// Returns true if user changed value at all +// virtual +bool LLLineEditor::isDirty() const +{ + return mText.getString() != mPrevText; +} + +// Clear dirty state +// virtual +void LLLineEditor::resetDirty() +{ + mPrevText = mText.getString(); +} + +// assumes UTF8 text +// virtual +void LLLineEditor::setValue(const LLSD& value ) +{ + setText(value.asString()); +} + +//virtual +LLSD LLLineEditor::getValue() const +{ + return LLSD(getText()); +} + + +// line history support +void LLLineEditor::updateHistory() +{ + // On history enabled line editors, remember committed line and + // reset current history line number. + // Be sure only to remember lines that are not empty and that are + // different from the last on the list. + if( mHaveHistory && getLength() ) + { + if( !mLineHistory.empty() ) + { + // When not empty, last line of history should always be blank. + if( mLineHistory.back().empty() ) + { + // discard the empty line + mLineHistory.pop_back(); + } + else + { + LL_WARNS("") << "Last line of history was not blank." << LL_ENDL; + } + } + + // Add text to history, ignoring duplicates + if( mLineHistory.empty() || getText() != mLineHistory.back() ) + { + mLineHistory.push_back( getText() ); + } + + // Restore the blank line and set mCurrentHistoryLine to point at it + mLineHistory.push_back( "" ); + mCurrentHistoryLine = mLineHistory.end() - 1; + } +} + +void LLLineEditor::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLUICtrl::reshape(width, height, called_from_parent); + updateTextPadding(); // For clamping side-effect. + setCursor(mCursorPos); // For clamping side-effect. +} + +void LLLineEditor::setEnabled(bool enabled) +{ + mReadOnly = !enabled; + setTabStop(!mReadOnly); + updateAllowingLanguageInput(); +} + + +void LLLineEditor::setMaxTextLength(S32 max_text_length) +{ + S32 max_len = llmax(0, max_text_length); + mMaxLengthBytes = max_len; +} + +void LLLineEditor::setMaxTextChars(S32 max_text_chars) +{ + S32 max_chars = llmax(0, max_text_chars); + mMaxLengthChars = max_chars; +} + +void LLLineEditor::getTextPadding(S32 *left, S32 *right) +{ + *left = mTextPadLeft; + *right = mTextPadRight; +} + +void LLLineEditor::setTextPadding(S32 left, S32 right) +{ + mTextPadLeft = left; + mTextPadRight = right; + updateTextPadding(); +} + +void LLLineEditor::updateTextPadding() +{ + mTextLeftEdge = llclamp(mTextPadLeft, 0, getRect().getWidth()); + mTextRightEdge = getRect().getWidth() - llclamp(mTextPadRight, 0, getRect().getWidth()); +} + + +void LLLineEditor::setText(const LLStringExplicit &new_text) +{ + setText(new_text, true); +} + +void LLLineEditor::setText(const LLStringExplicit &new_text, bool use_size_limit) +{ + // If new text is identical, don't copy and don't move insertion point + if (mText.getString() == new_text) + { + return; + } + + // Check to see if entire field is selected. + S32 len = mText.length(); + bool all_selected = (len > 0) + && (( mSelectionStart == 0 && mSelectionEnd == len ) + || ( mSelectionStart == len && mSelectionEnd == 0 )); + + // Do safe truncation so we don't split multi-byte characters + // also consider entire string selected when mSelectAllonFocusReceived is set on an empty, focused line editor + all_selected = all_selected || (len == 0 && hasFocus() && mSelectAllonFocusReceived); + + std::string truncated_utf8 = new_text; + if (!mAllowEmoji) + { + // Cut emoji symbols if exist + utf8str_remove_emojis(truncated_utf8); + } + if (use_size_limit && truncated_utf8.size() > (U32)mMaxLengthBytes) + { + truncated_utf8 = utf8str_truncate(new_text, mMaxLengthBytes); + } + mText.assign(truncated_utf8); + + if (use_size_limit && mMaxLengthChars) + { + mText.assign(utf8str_symbol_truncate(truncated_utf8, mMaxLengthChars)); + } + + if (all_selected) + { + // ...keep whole thing selected + selectAll(); + } + else + { + // try to preserve insertion point, but deselect text + deselect(); + } + setCursor(llmin((S32)mText.length(), getCursor())); + + // Set current history line to end of history. + if (mLineHistory.empty()) + { + mCurrentHistoryLine = mLineHistory.end(); + } + else + { + mCurrentHistoryLine = mLineHistory.end() - 1; + } + + mPrevText = mText; +} + + +// Picks a new cursor position based on the actual screen size of text being drawn. +void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) +{ + S32 cursor_pos = calcCursorPos(local_mouse_x); + + S32 left_pos = llmin( mSelectionStart, cursor_pos ); + S32 length = llabs( mSelectionStart - cursor_pos ); + const LLWString& substr = mText.getWString().substr(left_pos, length); + + if (mIsSelecting && !prevalidateInput(substr)) + return; + + setCursor(cursor_pos); +} + +void LLLineEditor::setCursor( S32 pos ) +{ + S32 old_cursor_pos = getCursor(); + mCursorPos = llclamp( pos, 0, mText.length()); + + // position of end of next character after cursor + S32 pixels_after_scroll = findPixelNearestPos(); + if( pixels_after_scroll > mTextRightEdge ) + { + S32 width_chars_to_left = mGLFont->getWidth(mText.getWString().c_str(), 0, mScrollHPos); + S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mTextRightEdge - mTextLeftEdge + width_chars_to_left))); + // character immediately to left of cursor should be last one visible (SCROLL_INCREMENT_ADD will scroll in more characters) + // or first character if cursor is at beginning + S32 new_last_visible_char = llmax(0, getCursor() - 1); + S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), new_last_visible_char); + if (old_cursor_pos == last_visible_char) + { + mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD)); + } + else + { + mScrollHPos = min_scroll; + } + } + else if (getCursor() < mScrollHPos) + { + if (old_cursor_pos == mScrollHPos) + { + mScrollHPos = llmax(0, llmin(getCursor(), mScrollHPos - SCROLL_INCREMENT_DEL)); + } + else + { + mScrollHPos = getCursor(); + } + } +} + + +void LLLineEditor::setCursorToEnd() +{ + setCursor(mText.length()); + deselect(); +} + +void LLLineEditor::resetScrollPosition() +{ + mScrollHPos = 0; + // make sure cursor says in visible range + setCursor(getCursor()); +} + +bool LLLineEditor::canDeselect() const +{ + return hasSelection(); +} + +void LLLineEditor::deselect() +{ + mSelectionStart = 0; + mSelectionEnd = 0; + mIsSelecting = false; +} + + +void LLLineEditor::startSelection() +{ + mIsSelecting = true; + mSelectionStart = getCursor(); + mSelectionEnd = getCursor(); +} + +void LLLineEditor::endSelection() +{ + if( mIsSelecting ) + { + mIsSelecting = false; + mSelectionEnd = getCursor(); + } +} + +bool LLLineEditor::canSelectAll() const +{ + return true; +} + +void LLLineEditor::selectAll() +{ + if (!prevalidateInput(mText.getWString())) + { + return; + } + + mSelectionStart = mText.length(); + mSelectionEnd = 0; + setCursor(mSelectionEnd); + //mScrollHPos = 0; + mIsSelecting = true; + updatePrimary(); +} + +bool LLLineEditor::getSpellCheck() const +{ + return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); +} + +const std::string& LLLineEditor::getSuggestion(U32 index) const +{ + return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null; +} + +U32 LLLineEditor::getSuggestionCount() const +{ + return mSuggestionList.size(); +} + +void LLLineEditor::replaceWithSuggestion(U32 index) +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) ) + { + LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]); + if (!mAllowEmoji) + { + // Cut emoji symbols if exist + wstring_remove_emojis(suggestion); + } + if (suggestion.empty()) + return; + + deselect(); + + // Delete the misspelled word + mText.erase(it->first, it->second - it->first); + + // Insert the suggestion in its place + mText.insert(it->first, suggestion); + setCursor(it->first + (S32)suggestion.length()); + + break; + } + } + mSpellCheckStart = mSpellCheckEnd = -1; +} + +void LLLineEditor::addToDictionary() +{ + if (canAddToDictionary()) + { + LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos)); + } +} + +bool LLLineEditor::canAddToDictionary() const +{ + return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +void LLLineEditor::addToIgnore() +{ + if (canAddToIgnore()) + { + LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos)); + } +} + +bool LLLineEditor::canAddToIgnore() const +{ + return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +std::string LLLineEditor::getMisspelledWord(U32 pos) const +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= pos) && (it->second >= pos) ) + { + return wstring_to_utf8str(mText.getWString().substr(it->first, it->second - it->first)); + } + } + return LLStringUtil::null; +} + +bool LLLineEditor::isMisspelledWord(U32 pos) const +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= pos) && (it->second >= pos) ) + { + return true; + } + } + return false; +} + +void LLLineEditor::onSpellCheckSettingsChange() +{ + // Recheck the spelling on every change + mMisspellRanges.clear(); + mSpellCheckStart = mSpellCheckEnd = -1; +} + +bool LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + setFocus( true ); + mTripleClickTimer.setTimerExpirySec(TRIPLE_CLICK_INTERVAL); + + if (mSelectionEnd == 0 && mSelectionStart == mText.length()) + { + // if everything is selected, handle this as a normal click to change insertion point + handleMouseDown(x, y, mask); + } + else + { + const LLWString& wtext = mText.getWString(); + + bool doSelectAll = true; + + // Select the word we're on + if( LLWStringUtil::isPartOfWord( wtext[mCursorPos] ) ) + { + S32 old_selection_start = mLastSelectionStart; + S32 old_selection_end = mLastSelectionEnd; + + // Select word the cursor is over + while ((mCursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[mCursorPos-1] )) + { // Find the start of the word + mCursorPos--; + } + startSelection(); + + while ((mCursorPos < (S32)wtext.length()) && LLWStringUtil::isPartOfWord( wtext[mCursorPos] ) ) + { // Find the end of the word + mCursorPos++; + } + mSelectionEnd = mCursorPos; + + // If nothing changed, then the word was already selected. Select the whole line. + doSelectAll = (old_selection_start == mSelectionStart) && + (old_selection_end == mSelectionEnd); + } + + if ( doSelectAll ) + { // Select everything + selectAll(); + } + } + + // We don't want handleMouseUp() to "finish" the selection (and thereby + // set mSelectionEnd to where the mouse is), so we finish the selection + // here. + mIsSelecting = false; + + // delay cursor flashing + mKeystrokeTimer.reset(); + + // take selection to 'primary' clipboard + updatePrimary(); + + return true; +} + +bool LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Check first whether the "clear search" button wants to deal with this. + if(childrenHandleMouseDown(x, y, mask) != NULL) + { + return true; + } + + if (!mSelectAllonFocusReceived + || gFocusMgr.getKeyboardFocus() == this) + { + mLastSelectionStart = -1; + mLastSelectionStart = -1; + + if (mask & MASK_SHIFT) + { + // assume we're starting a drag select + mIsSelecting = true; + + // Handle selection extension + S32 old_cursor_pos = getCursor(); + setCursorAtLocalPos(x); + + if (hasSelection()) + { + /* Mac-like behavior - extend selection towards the cursor + if (getCursor() < mSelectionStart + && getCursor() < mSelectionEnd) + { + // ...left of selection + mSelectionStart = llmax(mSelectionStart, mSelectionEnd); + mSelectionEnd = getCursor(); + } + else if (getCursor() > mSelectionStart + && getCursor() > mSelectionEnd) + { + // ...right of selection + mSelectionStart = llmin(mSelectionStart, mSelectionEnd); + mSelectionEnd = getCursor(); + } + else + { + mSelectionEnd = getCursor(); + } + */ + // Windows behavior + mSelectionEnd = getCursor(); + } + else + { + mSelectionStart = old_cursor_pos; + mSelectionEnd = getCursor(); + } + } + else + { + if (mTripleClickTimer.hasExpired()) + { + // Save selection for word/line selecting on double-click + mLastSelectionStart = mSelectionStart; + mLastSelectionEnd = mSelectionEnd; + + // Move cursor and deselect for regular click + setCursorAtLocalPos( x ); + deselect(); + startSelection(); + } + else // handle triple click + { + selectAll(); + // We don't want handleMouseUp() to "finish" the selection (and thereby + // set mSelectionEnd to where the mouse is), so we finish the selection + // here. + mIsSelecting = false; + } + } + + gFocusMgr.setMouseCapture( this ); + } + + setFocus(true); + + // delay cursor flashing + mKeystrokeTimer.reset(); + + if (mMouseDownSignal) + (*mMouseDownSignal)(this,x,y,mask); + + return true; +} + +bool LLLineEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask) +{ + // LL_INFOS() << "MiddleMouseDown" << LL_ENDL; + setFocus( true ); + if( canPastePrimary() ) + { + setCursorAtLocalPos(x); + pastePrimary(); + } + return true; +} + +bool LLLineEditor::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + setFocus(true); + if (!LLUICtrl::handleRightMouseDown(x, y, mask) && getShowContextMenu()) + { + showContextMenu(x, y); + } + return true; +} + +bool LLLineEditor::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = false; + // Check first whether the "clear search" button wants to deal with this. + if(!hasMouseCapture()) + { + if(childrenHandleHover(x, y, mask) != NULL) + { + return true; + } + } + + if( (hasMouseCapture()) && mIsSelecting ) + { + if (x != mLastSelectionX || y != mLastSelectionY) + { + mLastSelectionX = x; + mLastSelectionY = y; + } + // Scroll if mouse cursor outside of bounds + if (mScrollTimer.hasExpired()) + { + S32 increment = ll_round(mScrollTimer.getElapsedTimeF32() / AUTO_SCROLL_TIME); + mScrollTimer.reset(); + mScrollTimer.setTimerExpirySec(AUTO_SCROLL_TIME); + if( (x < mTextLeftEdge) && (mScrollHPos > 0 ) ) + { + // Scroll to the left + mScrollHPos = llclamp(mScrollHPos - increment, 0, mText.length()); + } + else + if( (x > mTextRightEdge) && (mCursorPos < (S32)mText.length()) ) + { + // If scrolling one pixel would make a difference... + S32 pixels_after_scrolling_one_char = findPixelNearestPos(1); + if( pixels_after_scrolling_one_char >= mTextRightEdge ) + { + // ...scroll to the right + mScrollHPos = llclamp(mScrollHPos + increment, 0, mText.length()); + } + } + } + + setCursorAtLocalPos( x ); + mSelectionEnd = getCursor(); + + // delay cursor flashing + mKeystrokeTimer.reset(); + + getWindow()->setCursor(UI_CURSOR_IBEAM); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; + handled = true; + } + + if( !handled ) + { + getWindow()->setCursor(UI_CURSOR_IBEAM); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; + handled = true; + } + + return handled; +} + + +bool LLLineEditor::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + if( hasMouseCapture() ) + { + gFocusMgr.setMouseCapture( NULL ); + handled = true; + } + + // Check first whether the "clear search" button wants to deal with this. + if(!handled && childrenHandleMouseUp(x, y, mask) != NULL) + { + return true; + } + + if( mIsSelecting ) + { + setCursorAtLocalPos( x ); + mSelectionEnd = getCursor(); + + handled = true; + } + + if( handled ) + { + // delay cursor flashing + mKeystrokeTimer.reset(); + + // take selection to 'primary' clipboard + updatePrimary(); + } + + // We won't call LLUICtrl::handleMouseUp to avoid double calls of childrenHandleMouseUp().Just invoke the signal manually. + if (mMouseUpSignal) + (*mMouseUpSignal)(this,x,y, mask); + return handled; +} + + +// Remove a single character from the text +void LLLineEditor::removeChar() +{ + if( getCursor() > 0 ) + { + if (!prevalidateInput(mText.getWString().substr(getCursor()-1, 1))) + return; + + mText.erase(getCursor() - 1, 1); + + setCursor(getCursor() - 1); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } +} + +void LLLineEditor::addChar(const llwchar uni_char) +{ + if (!mAllowEmoji && LLStringOps::isEmoji(uni_char)) + return; + + llwchar new_c = uni_char; + if (hasSelection()) + { + deleteSelection(); + } + else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + if (!prevalidateInput(mText.getWString().substr(getCursor(), 1))) + return; + + mText.erase(getCursor(), 1); + } + + S32 cur_bytes = mText.getString().size(); + + S32 new_bytes = wchar_utf8_length(new_c); + + bool allow_char = true; + + // Check byte length limit + if ((new_bytes + cur_bytes) > mMaxLengthBytes) + { + allow_char = false; + } + else if (mMaxLengthChars) + { + S32 wide_chars = mText.getWString().size(); + if ((wide_chars + 1) > mMaxLengthChars) + { + allow_char = false; + } + } + + if (allow_char) + { + // Will we need to scroll? + LLWString w_buf; + w_buf.assign(1, new_c); + + mText.insert(getCursor(), w_buf); + setCursor(getCursor() + 1); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + + getWindow()->hideCursorUntilMouseMove(); +} + +// Extends the selection box to the new cursor position +void LLLineEditor::extendSelection( S32 new_cursor_pos ) +{ + if( !mIsSelecting ) + { + startSelection(); + } + + S32 left_pos = llmin( mSelectionStart, new_cursor_pos ); + S32 selection_length = llabs( mSelectionStart - new_cursor_pos ); + const LLWString& selection = mText.getWString().substr(left_pos, selection_length); + + if (!prevalidateInput(selection)) + return; + + setCursor(new_cursor_pos); + mSelectionEnd = getCursor(); +} + + +void LLLineEditor::setSelection(S32 start, S32 end) +{ + S32 len = mText.length(); + + mIsSelecting = true; + + // JC, yes, this seems odd, but I think you have to presume a + // selection dragged from the end towards the start. + mSelectionStart = llclamp(end, 0, len); + mSelectionEnd = llclamp(start, 0, len); + setCursor(start); +} + +void LLLineEditor::setDrawAsterixes(bool b) +{ + mDrawAsterixes = b; + updateAllowingLanguageInput(); +} + +S32 LLLineEditor::prevWordPos(S32 cursorPos) const +{ + const LLWString& wtext = mText.getWString(); + while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') ) + { + cursorPos--; + } + while( (cursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[cursorPos-1] ) ) + { + cursorPos--; + } + return cursorPos; +} + +S32 LLLineEditor::nextWordPos(S32 cursorPos) const +{ + const LLWString& wtext = mText.getWString(); + while( (cursorPos < getLength()) && LLWStringUtil::isPartOfWord( wtext[cursorPos] ) ) + { + cursorPos++; + } + while( (cursorPos < getLength()) && (wtext[cursorPos] == ' ') ) + { + cursorPos++; + } + return cursorPos; +} + + +bool LLLineEditor::handleSelectionKey(KEY key, MASK mask) +{ + bool handled = false; + + if( mask & MASK_SHIFT ) + { + handled = true; + + switch( key ) + { + case KEY_LEFT: + if( 0 < getCursor() ) + { + S32 cursorPos = getCursor() - 1; + if( mask & MASK_CONTROL ) + { + cursorPos = prevWordPos(cursorPos); + } + extendSelection( cursorPos ); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + break; + + case KEY_RIGHT: + if( getCursor() < mText.length()) + { + S32 cursorPos = getCursor() + 1; + if( mask & MASK_CONTROL ) + { + cursorPos = nextWordPos(cursorPos); + } + extendSelection( cursorPos ); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + break; + + case KEY_PAGE_UP: + case KEY_HOME: + extendSelection( 0 ); + break; + + case KEY_PAGE_DOWN: + case KEY_END: + { + S32 len = mText.length(); + if( len ) + { + extendSelection( len ); + } + break; + } + + default: + handled = false; + break; + } + } + + if(handled) + { + // take selection to 'primary' clipboard + updatePrimary(); + } + + return handled; +} + +void LLLineEditor::deleteSelection() +{ + if( !mReadOnly && hasSelection() ) + { + S32 left_pos, selection_length; + getSelectionRange(&left_pos, &selection_length); + const LLWString& selection = mText.getWString().substr(left_pos, selection_length); + + if (!prevalidateInput(selection)) + return; + + mText.erase(left_pos, selection_length); + deselect(); + setCursor(left_pos); + } +} + +bool LLLineEditor::canCut() const +{ + return !mReadOnly && !mDrawAsterixes && hasSelection(); +} + +// cut selection to clipboard +void LLLineEditor::cut() +{ + if( canCut() ) + { + S32 left_pos, length; + getSelectionRange(&left_pos, &length); + const LLWString& selection = mText.getWString().substr(left_pos, length); + + if (!prevalidateInput(selection)) + return; + + // Prepare for possible rollback + LLLineEditorRollback rollback( this ); + + LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length ); + deleteSelection(); + + // Validate new string and rollback the if needed. + bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(mText.getWString()); + if (need_to_rollback) + { + rollback.doRollback( this ); + LLUI::getInstance()->reportBadKeystroke(); + mPrevalidator.showLastErrorUsingTimeout(); + } + else + { + onKeystroke(); + } + } +} + +bool LLLineEditor::canCopy() const +{ + return !mDrawAsterixes && hasSelection(); +} + + +// copy selection to clipboard +void LLLineEditor::copy() +{ + if( canCopy() ) + { + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); + LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length ); + } +} + +bool LLLineEditor::canPaste() const +{ + return !mReadOnly && LLClipboard::instance().isTextAvailable(); +} + +void LLLineEditor::paste() +{ + bool is_primary = false; + pasteHelper(is_primary); +} + +void LLLineEditor::pastePrimary() +{ + bool is_primary = true; + pasteHelper(is_primary); +} + +// paste from primary (is_primary==true) or clipboard (is_primary==false) +void LLLineEditor::pasteHelper(bool is_primary) +{ + bool can_paste_it; + if (is_primary) + { + can_paste_it = canPastePrimary(); + } + else + { + can_paste_it = canPaste(); + } + + if (can_paste_it) + { + LLWString paste; + LLClipboard::instance().pasteFromClipboard(paste, is_primary); + + if (!paste.empty()) + { + if (!mAllowEmoji) + { + wstring_remove_emojis(paste); + } + + if (!prevalidateInput(paste)) + return; + + // Prepare for possible rollback + LLLineEditorRollback rollback(this); + + // Delete any selected characters + if ((!is_primary) && hasSelection()) + { + deleteSelection(); + } + + // Clean up string (replace tabs and returns and remove characters that our fonts don't support.) + LLWString clean_string(paste); + LLWStringUtil::replaceTabsWithSpaces(clean_string, 1); + //clean_string = wstring_detabify(paste, 1); + LLWStringUtil::replaceChar(clean_string, '\n', mReplaceNewlinesWithSpaces ? ' ' : 182); // 182 == paragraph character + + // Insert the string + + // Check to see that the size isn't going to be larger than the max number of bytes + U32 available_bytes = mMaxLengthBytes - wstring_utf8_length(mText); + + if ( available_bytes < (U32) wstring_utf8_length(clean_string) ) + { // Doesn't all fit + llwchar current_symbol = clean_string[0]; + U32 wchars_that_fit = 0; + U32 total_bytes = wchar_utf8_length(current_symbol); + + //loop over the "wide" characters (symbols) + //and check to see how large (in bytes) each symbol is. + while ( total_bytes <= available_bytes ) + { + //while we still have available bytes + //"accept" the current symbol and check the size + //of the next one + current_symbol = clean_string[++wchars_that_fit]; + total_bytes += wchar_utf8_length(current_symbol); + } + // Truncate the clean string at the limit of what will fit + clean_string = clean_string.substr(0, wchars_that_fit); + LLUI::getInstance()->reportBadKeystroke(); + } + + if (mMaxLengthChars) + { + U32 available_chars = mMaxLengthChars - mText.getWString().size(); + + if (available_chars < clean_string.size()) + { + clean_string = clean_string.substr(0, available_chars); + } + + LLUI::getInstance()->reportBadKeystroke(); + } + + mText.insert(getCursor(), clean_string); + setCursor( getCursor() + (S32)clean_string.length() ); + deselect(); + + // Validate new string and rollback the if needed. + bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(mText.getWString()); + if (need_to_rollback) + { + rollback.doRollback( this ); + LLUI::getInstance()->reportBadKeystroke(); + mPrevalidator.showLastErrorUsingTimeout(); + } + else + { + onKeystroke(); + } + } + } +} + +// copy selection to primary +void LLLineEditor::copyPrimary() +{ + if( canCopy() ) + { + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); + LLClipboard::instance().copyToClipboard( mText.getWString(), left_pos, length, true); + } +} + +bool LLLineEditor::canPastePrimary() const +{ + return !mReadOnly && LLClipboard::instance().isTextAvailable(true); +} + +void LLLineEditor::updatePrimary() +{ + if(canCopy() ) + { + copyPrimary(); + } +} + +bool LLLineEditor::handleSpecialKey(KEY key, MASK mask) +{ + bool handled = false; + + switch( key ) + { + case KEY_INSERT: + if (mask == MASK_NONE) + { + gKeyboard->toggleInsertMode(); + } + + handled = true; + break; + + case KEY_BACKSPACE: + if (!mReadOnly) + { + //LL_INFOS() << "Handling backspace" << LL_ENDL; + if( hasSelection() ) + { + deleteSelection(); + } + else + if( 0 < getCursor() ) + { + removeChar(); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + } + handled = true; + break; + + case KEY_PAGE_UP: + case KEY_HOME: + if (!mIgnoreArrowKeys) + { + setCursor(0); + handled = true; + } + break; + + case KEY_PAGE_DOWN: + case KEY_END: + if (!mIgnoreArrowKeys) + { + S32 len = mText.length(); + if( len ) + { + setCursor(len); + } + handled = true; + } + break; + + case KEY_LEFT: + if (mIgnoreArrowKeys && mask == MASK_NONE) + break; + if ((mask & MASK_ALT) == 0) + { + if( hasSelection() ) + { + setCursor(llmin( getCursor() - 1, mSelectionStart, mSelectionEnd )); + } + else + if( 0 < getCursor() ) + { + S32 cursorPos = getCursor() - 1; + if( mask & MASK_CONTROL ) + { + cursorPos = prevWordPos(cursorPos); + } + setCursor(cursorPos); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + handled = true; + } + break; + + case KEY_RIGHT: + if (mIgnoreArrowKeys && mask == MASK_NONE) + break; + if ((mask & MASK_ALT) == 0) + { + if (hasSelection()) + { + setCursor(llmax(getCursor() + 1, mSelectionStart, mSelectionEnd)); + } + else + if (getCursor() < mText.length()) + { + S32 cursorPos = getCursor() + 1; + if( mask & MASK_CONTROL ) + { + cursorPos = nextWordPos(cursorPos); + } + setCursor(cursorPos); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + handled = true; + } + break; + + // handle ctrl-uparrow if we have a history enabled line editor. + case KEY_UP: + if (mHaveHistory && (!mIgnoreArrowKeys || (MASK_CONTROL == mask))) + { + if (mCurrentHistoryLine > mLineHistory.begin()) + { + mText.assign(*(--mCurrentHistoryLine)); + setCursorToEnd(); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + handled = true; + } + break; + + // handle [ctrl]-downarrow if we have a history enabled line editor + case KEY_DOWN: + if (mHaveHistory && (!mIgnoreArrowKeys || (MASK_CONTROL == mask))) + { + if (!mLineHistory.empty() && mCurrentHistoryLine < mLineHistory.end() - 1) + { + mText.assign( *(++mCurrentHistoryLine) ); + setCursorToEnd(); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + handled = true; + } + break; + + case KEY_RETURN: + // store sent line in history + updateHistory(); + break; + + case KEY_ESCAPE: + if (mRevertOnEsc && mText.getString() != mPrevText) + { + setText(mPrevText); + // Note, don't set handled, still want to loose focus (won't commit becase text is now unchanged) + if (mKeystrokeOnEsc) + { + onKeystroke(); + } + } + break; + + default: + break; + } + + return handled; +} + + +bool LLLineEditor::handleKeyHere(KEY key, MASK mask ) +{ + bool handled = false; + bool selection_modified = false; + + if ( gFocusMgr.getKeyboardFocus() == this ) + { + LLLineEditorRollback rollback( this ); + + if( !handled ) + { + handled = handleSelectionKey( key, mask ); + selection_modified = handled; + } + + // Handle most keys only if the text editor is writeable. + if ( !mReadOnly ) + { + if( !handled ) + { + handled = handleSpecialKey( key, mask ); + } + } + + if( handled ) + { + mKeystrokeTimer.reset(); + + // Most keystrokes will make the selection box go away, but not all will. + if( !selection_modified && + KEY_SHIFT != key && + KEY_CONTROL != key && + KEY_ALT != key && + KEY_CAPSLOCK != key) + { + deselect(); + } + + bool prevalidator_failed = false; + + // If read-only, don't allow changes + bool need_to_rollback = mReadOnly && (mText.getString() == rollback.getText()); + + // Validate new string and rollback the keystroke if needed. + if (!need_to_rollback && mPrevalidator) + { + prevalidator_failed = !mPrevalidator.validate(mText.getWString()); + need_to_rollback |= prevalidator_failed; + } + + if (need_to_rollback) + { + rollback.doRollback(this); + + LLUI::getInstance()->reportBadKeystroke(); + if (prevalidator_failed) + { + mPrevalidator.showLastErrorUsingTimeout(); + } + } + + // Notify owner if requested + if (!need_to_rollback && handled) + { + onKeystroke(); + if ( (!selection_modified) && (KEY_BACKSPACE == key) ) + { + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); + } + } + } + } + + return handled; +} + + +bool LLLineEditor::handleUnicodeCharHere(llwchar uni_char) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return false; + } + + bool handled = false; + + if ( (gFocusMgr.getKeyboardFocus() == this) && getVisible() && !mReadOnly) + { + handled = true; + + LLLineEditorRollback rollback( this ); + + { + LLWString u_char; + u_char.assign(1, uni_char); + if (!prevalidateInput(u_char)) + return handled; + } + + addChar(uni_char); + + mKeystrokeTimer.reset(); + + deselect(); + + // Validate new string and rollback the keystroke if needed. + bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(mText.getWString()); + if (need_to_rollback) + { + rollback.doRollback( this ); + + LLUI::getInstance()->reportBadKeystroke(); + mPrevalidator.showLastErrorUsingTimeout(); + } + + // Notify owner if requested + if (!need_to_rollback && handled) + { + // HACK! The only usage of this callback doesn't do anything with the character. + // We'll have to do something about this if something ever changes! - Doug + onKeystroke(); + + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); + } + } + return handled; +} + + +bool LLLineEditor::canDoDelete() const +{ + return ( !mReadOnly && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) ); +} + +void LLLineEditor::doDelete() +{ + if (canDoDelete() && mText.length() > 0) + { + // Prepare for possible rollback + LLLineEditorRollback rollback( this ); + + if (hasSelection()) + { + deleteSelection(); + } + else if ( getCursor() < mText.length()) + { + const LLWString& text_to_delete = mText.getWString().substr(getCursor(), 1); + + if (!prevalidateInput(text_to_delete)) + { + onKeystroke(); + return; + } + setCursor(getCursor() + 1); + removeChar(); + } + + // Validate new string and rollback the if needed. + bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(mText.getWString()); + if (need_to_rollback) + { + rollback.doRollback(this); + LLUI::getInstance()->reportBadKeystroke(); + mPrevalidator.showLastErrorUsingTimeout(); + } + else + { + onKeystroke(); + + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); + } + } +} + + +void LLLineEditor::drawBackground() +{ + F32 alpha = getCurrentTransparency(); + if (mUseBgColor) + { + gl_rect_2d(getLocalRect(), mBgColor % alpha, true); + } + else + { + bool has_focus = hasFocus(); + LLUIImage* image; + if (mReadOnly) + { + image = mBgImageDisabled; + } + else if (has_focus || mShowImageFocused) + { + image = mBgImageFocused; + } + else + { + image = mBgImage; + } + + if (!image) return; + // optionally draw programmatic border + if (has_focus) + { + LLColor4 tmp_color = gFocusMgr.getFocusColor(); + tmp_color.setAlpha(alpha); + image->drawBorder(0, 0, getRect().getWidth(), getRect().getHeight(), + tmp_color, + gFocusMgr.getFocusFlashWidth()); + } + LLColor4 tmp_color = UI_VERTEX_COLOR; + tmp_color.setAlpha(alpha); + image->draw(getLocalRect(), tmp_color); + } +} + +//virtual +void LLLineEditor::draw() +{ + F32 alpha = getDrawContext().mAlpha; + S32 text_len = mText.length(); + static LLUICachedControl lineeditor_cursor_thickness ("UILineEditorCursorThickness", 0); + static LLUICachedControl preedit_marker_brightness ("UIPreeditMarkerBrightness", 0); + static LLUICachedControl preedit_marker_gap ("UIPreeditMarkerGap", 0); + static LLUICachedControl preedit_marker_position ("UIPreeditMarkerPosition", 0); + static LLUICachedControl preedit_marker_thickness ("UIPreeditMarkerThickness", 0); + static LLUICachedControl preedit_standout_brightness ("UIPreeditStandoutBrightness", 0); + static LLUICachedControl preedit_standout_gap ("UIPreeditStandoutGap", 0); + static LLUICachedControl preedit_standout_position ("UIPreeditStandoutPosition", 0); + static LLUICachedControl preedit_standout_thickness ("UIPreeditStandoutThickness", 0); + + std::string saved_text; + if (mDrawAsterixes) + { + saved_text = mText.getString(); + std::string text; + for (S32 i = 0; i < mText.length(); i++) + { + text += PASSWORD_ASTERISK; + } + mText = text; + } + + // draw rectangle for the background + LLRect background( 0, getRect().getHeight(), getRect().getWidth(), 0 ); + background.stretch( -mBorderThickness ); + + S32 lineeditor_v_pad = (background.getHeight() - mGLFont->getLineHeight()) / 2; + if (mSpellCheck) + { + lineeditor_v_pad += 1; + } + + drawBackground(); + + // draw text + + // With viewer-2 art files, input region is 2 pixels up + S32 cursor_bottom = background.mBottom + 2; + S32 cursor_top = background.mTop - 1; + + LLColor4 text_color; + if (!mReadOnly) + { + if (!getTentative()) + { + text_color = mFgColor.get(); + } + else + { + text_color = mTentativeFgColor.get(); + } + } + else + { + text_color = mReadOnlyFgColor.get(); + } + text_color.setAlpha(alpha); + LLColor4 label_color = mTentativeFgColor.get(); + label_color.setAlpha(alpha); + + if (hasPreeditString()) + { + // Draw preedit markers. This needs to be before drawing letters. + for (U32 i = 0; i < mPreeditStandouts.size(); i++) + { + const S32 preedit_left = mPreeditPositions[i]; + const S32 preedit_right = mPreeditPositions[i + 1]; + if (preedit_right > mScrollHPos) + { + S32 preedit_pixels_left = findPixelNearestPos(llmax(preedit_left, mScrollHPos) - getCursor()); + S32 preedit_pixels_right = llmin(findPixelNearestPos(preedit_right - getCursor()), background.mRight); + if (preedit_pixels_left >= background.mRight) + { + break; + } + if (mPreeditStandouts[i]) + { + gl_rect_2d(preedit_pixels_left + preedit_standout_gap, + background.mBottom + preedit_standout_position, + preedit_pixels_right - preedit_standout_gap - 1, + background.mBottom + preedit_standout_position - preedit_standout_thickness, + (text_color * preedit_standout_brightness + + mPreeditBgColor * (1 - preedit_standout_brightness)).setAlpha(alpha/*1.0f*/)); + } + else + { + gl_rect_2d(preedit_pixels_left + preedit_marker_gap, + background.mBottom + preedit_marker_position, + preedit_pixels_right - preedit_marker_gap - 1, + background.mBottom + preedit_marker_position - preedit_marker_thickness, + (text_color * preedit_marker_brightness + + mPreeditBgColor * (1 - preedit_marker_brightness)).setAlpha(alpha/*1.0f*/)); + } + } + } + } + + S32 rendered_text = 0; + F32 rendered_pixels_right = (F32)mTextLeftEdge; + F32 text_bottom = (F32)background.mBottom + (F32)lineeditor_v_pad; + + if( (gFocusMgr.getKeyboardFocus() == this) && hasSelection() ) + { + S32 select_left; + S32 select_right; + if (mSelectionStart < mSelectionEnd) + { + select_left = mSelectionStart; + select_right = mSelectionEnd; + } + else + { + select_left = mSelectionEnd; + select_right = mSelectionStart; + } + + if( select_left > mScrollHPos ) + { + // unselected, left side + rendered_text = mGLFont->render( + mText, mScrollHPos, + rendered_pixels_right, text_bottom, + text_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + 0, + LLFontGL::NO_SHADOW, + select_left - mScrollHPos, + mTextRightEdge - ll_round(rendered_pixels_right), + &rendered_pixels_right); + } + + if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) ) + { + LLColor4 color = mHighlightColor; + color.setAlpha(alpha); + // selected middle + S32 width = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos + rendered_text, select_right - mScrollHPos - rendered_text); + width = llmin(width, mTextRightEdge - ll_round(rendered_pixels_right)); + gl_rect_2d(ll_round(rendered_pixels_right), cursor_top, ll_round(rendered_pixels_right)+width, cursor_bottom, color); + + LLColor4 tmp_color( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], alpha ); + rendered_text += mGLFont->render( + mText, mScrollHPos + rendered_text, + rendered_pixels_right, text_bottom, + tmp_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + 0, + LLFontGL::NO_SHADOW, + select_right - mScrollHPos - rendered_text, + mTextRightEdge - ll_round(rendered_pixels_right), + &rendered_pixels_right); + } + + if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) ) + { + // unselected, right side + rendered_text += mGLFont->render( + mText, mScrollHPos + rendered_text, + rendered_pixels_right, text_bottom, + text_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + 0, + LLFontGL::NO_SHADOW, + S32_MAX, + mTextRightEdge - ll_round(rendered_pixels_right), + &rendered_pixels_right); + } + } + else + { + rendered_text = mGLFont->render( + mText, mScrollHPos, + rendered_pixels_right, text_bottom, + text_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + 0, + LLFontGL::NO_SHADOW, + S32_MAX, + mTextRightEdge - ll_round(rendered_pixels_right), + &rendered_pixels_right); + } +#if 1 // for when we're ready for image art. + mBorder->setVisible(false); // no more programmatic art. +#endif + + if ( (getSpellCheck()) && (mText.length() > 2) ) + { + // Calculate start and end indices for the first and last visible word + U32 start = prevWordPos(mScrollHPos), end = nextWordPos(mScrollHPos + rendered_text); + + if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) ) + { + const LLWString& text = mText.getWString().substr(start, end); + + // Find the start of the first word + U32 word_start = 0, word_end = 0; + while ( (word_start < text.length()) && (!LLStringOps::isAlpha(text[word_start])) ) + { + word_start++; + } + + // Iterate over all words in the text block and check them one by one + mMisspellRanges.clear(); + while (word_start < text.length()) + { + // Find the end of the current word (special case handling for "'" when it's used as a contraction) + word_end = word_start + 1; + while ( (word_end < text.length()) && + ((LLWStringUtil::isPartOfWord(text[word_end])) || + ((L'\'' == text[word_end]) && (word_end + 1 < text.length()) && + (LLStringOps::isAlnum(text[word_end - 1])) && (LLStringOps::isAlnum(text[word_end + 1])))) ) + { + word_end++; + } + if (word_end > text.length()) + { + break; + } + + // Don't process words shorter than 3 characters + std::string word = wstring_to_utf8str(text.substr(word_start, word_end - word_start)); + if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) ) + { + mMisspellRanges.push_back(std::pair(start + word_start, start + word_end)); + } + + // Find the start of the next word + word_start = word_end + 1; + while ( (word_start < text.length()) && (!LLWStringUtil::isPartOfWord(text[word_start])) ) + { + word_start++; + } + } + + mSpellCheckStart = start; + mSpellCheckEnd = end; + } + + // Draw squiggly lines under any (visible) misspelled words + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + // Skip over words that aren't (partially) visible + if ( ((it->first < start) && (it->second < start)) || (it->first > end) ) + { + continue; + } + + // Skip the current word if the user is still busy editing it + if ( (!mSpellCheckTimer.hasExpired()) && (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) ) + { + continue; + } + + S32 pxWidth = getRect().getWidth(); + S32 pxStart = findPixelNearestPos(it->first - getCursor()); + if (pxStart > pxWidth) + { + continue; + } + S32 pxEnd = findPixelNearestPos(it->second - getCursor()); + if (pxEnd > pxWidth) + { + pxEnd = pxWidth; + } + + S32 pxBottom = (S32)(text_bottom + mGLFont->getDescenderHeight()); + + gGL.color4ub(255, 0, 0, 200); + while (pxStart + 1 < pxEnd) + { + gl_line_2d(pxStart, pxBottom, pxStart + 2, pxBottom - 2); + if (pxStart + 3 < pxEnd) + { + gl_line_2d(pxStart + 2, pxBottom - 3, pxStart + 4, pxBottom - 1); + } + pxStart += 4; + } + } + } + + // If we're editing... + if( hasFocus()) + { + //mBorder->setVisible(true); // ok, programmer art just this once. + // (Flash the cursor every half second) + if (!mReadOnly && gFocusMgr.getAppHasFocus()) + { + F32 elapsed = mKeystrokeTimer.getElapsedTimeF32(); + if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) + { + S32 cursor_left = findPixelNearestPos(); + cursor_left -= lineeditor_cursor_thickness / 2; + S32 cursor_right = cursor_left + lineeditor_cursor_thickness; + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) + { + const LLWString space(utf8str_to_wstring(std::string(" "))); + S32 wswidth = mGLFont->getWidth(space.c_str()); + S32 width = mGLFont->getWidth(mText.getWString().c_str(), getCursor(), 1) + 1; + cursor_right = cursor_left + llmax(wswidth, width); + } + // Use same color as text for the Cursor + gl_rect_2d(cursor_left, cursor_top, + cursor_right, cursor_bottom, text_color); + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) + { + LLColor4 tmp_color( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], alpha ); + mGLFont->render(mText, getCursor(), (F32)(cursor_left + lineeditor_cursor_thickness / 2), text_bottom, + tmp_color, + LLFontGL::LEFT, LLFontGL::BOTTOM, + 0, + LLFontGL::NO_SHADOW, + 1); + } + + // Make sure the IME is in the right place + S32 pixels_after_scroll = findPixelNearestPos(); // RCalculcate for IME position + LLRect screen_pos = calcScreenRect(); + LLCoordGL ime_pos( screen_pos.mLeft + pixels_after_scroll, screen_pos.mTop - lineeditor_v_pad ); + + ime_pos.mX = (S32) (ime_pos.mX * LLUI::getScaleFactor().mV[VX]); + ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]); + getWindow()->setLanguageTextInput( ime_pos ); + } + } + + //draw label if no text is provided + //but we should draw it in a different color + //to give indication that it is not text you typed in + if (0 == mText.length() && (mReadOnly || mShowLabelFocused)) + { + mGLFont->render(mLabel.getWString(), 0, + mTextLeftEdge, (F32)text_bottom, + label_color, + LLFontGL::LEFT, + LLFontGL::BOTTOM, + 0, + LLFontGL::NO_SHADOW, + S32_MAX, + mTextRightEdge - ll_round(rendered_pixels_right), + &rendered_pixels_right, false); + } + + + // Draw children (border) + //mBorder->setVisible(true); + mBorder->setKeyboardFocusHighlight( true ); + LLView::draw(); + mBorder->setKeyboardFocusHighlight( false ); + //mBorder->setVisible(false); + } + else // does not have keyboard input + { + // draw label if no text provided + if (0 == mText.length()) + { + mGLFont->render(mLabel.getWString(), 0, + mTextLeftEdge, (F32)text_bottom, + label_color, + LLFontGL::LEFT, + LLFontGL::BOTTOM, + 0, + LLFontGL::NO_SHADOW, + S32_MAX, + mTextRightEdge - ll_round(rendered_pixels_right), + &rendered_pixels_right); + } + // Draw children (border) + LLView::draw(); + } + + if (mDrawAsterixes) + { + mText = saved_text; + } +} + + +// Returns the local screen space X coordinate associated with the text cursor position. +S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) const +{ + S32 dpos = getCursor() - mScrollHPos + cursor_offset; + S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mTextLeftEdge; + return result; +} + +S32 LLLineEditor::calcCursorPos(S32 mouse_x) +{ + const llwchar* wtext = mText.getWString().c_str(); + LLWString asterix_text; + if (mDrawAsterixes) + { + for (S32 i = 0; i < mText.length(); i++) + { + asterix_text += utf8str_to_wstring(PASSWORD_ASTERISK); + } + wtext = asterix_text.c_str(); + } + + S32 cur_pos = mScrollHPos + + mGLFont->charFromPixelOffset( + wtext, mScrollHPos, + (F32)(mouse_x - mTextLeftEdge), + (F32)(mTextRightEdge - mTextLeftEdge + 1)); // min-max range is inclusive + + return cur_pos; +} +//virtual +void LLLineEditor::clear() +{ + mText.clear(); + setCursor(0); +} + +//virtual +void LLLineEditor::onTabInto() +{ + selectAll(); + LLUICtrl::onTabInto(); +} + +//virtual +bool LLLineEditor::acceptsTextInput() const +{ + return true; +} + +// Start or stop the editor from accepting text-editing keystrokes +void LLLineEditor::setFocus( bool new_state ) +{ + bool old_state = hasFocus(); + + if (!new_state) + { + getWindow()->allowLanguageTextInput(this, false); + } + + + // getting focus when we didn't have it before, and we want to select all + if (!old_state && new_state && mSelectAllonFocusReceived) + { + selectAll(); + // We don't want handleMouseUp() to "finish" the selection (and thereby + // set mSelectionEnd to where the mouse is), so we finish the selection + // here. + mIsSelecting = false; + } + + if( new_state ) + { + gEditMenuHandler = this; + + // Don't start the cursor flashing right away + mKeystrokeTimer.reset(); + } + else + { + // Not really needed, since loss of keyboard focus should take care of this, + // but limited paranoia is ok. + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + endSelection(); + } + + LLUICtrl::setFocus( new_state ); + + if (new_state) + { + // Allow Language Text Input only when this LineEditor has + // no prevalidate function attached. This criterion works + // fine on 1.15.0.2, since all prevalidate func reject any + // non-ASCII characters. I'm not sure on future versions, + // however. + getWindow()->allowLanguageTextInput(this, !mPrevalidator); + } +} + +//virtual +void LLLineEditor::setRect(const LLRect& rect) +{ + LLUICtrl::setRect(rect); + if (mBorder) + { + LLRect border_rect = mBorder->getRect(); + // Scalable UI somehow made these rectangles off-by-one. + // I don't know why. JC + border_rect.setOriginAndSize(border_rect.mLeft, border_rect.mBottom, + rect.getWidth()-1, rect.getHeight()-1); + mBorder->setRect(border_rect); + } +} + +void LLLineEditor::setPrevalidate(LLTextValidate::Validator validator) +{ + mPrevalidator = validator; + updateAllowingLanguageInput(); +} + +void LLLineEditor::setPrevalidateInput(LLTextValidate::Validator validator) +{ + mInputPrevalidator = validator; + updateAllowingLanguageInput(); +} + +bool LLLineEditor::prevalidateInput(const LLWString& wstr) +{ + return mInputPrevalidator.validate(wstr); +} + +// static +bool LLLineEditor::postvalidateFloat(const std::string &str) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + bool success = true; + bool has_decimal = false; + bool has_digit = false; + + LLWString trimmed = utf8str_to_wstring(str); + LLWStringUtil::trim(trimmed); + S32 len = trimmed.length(); + if( 0 < len ) + { + S32 i = 0; + + // First character can be a negative sign + if( '-' == trimmed[0] ) + { + i++; + } + + // May be a comma or period, depending on the locale + llwchar decimal_point = (llwchar)LLResMgr::getInstance()->getDecimalPoint(); + + for( ; i < len; i++ ) + { + if( decimal_point == trimmed[i] ) + { + if( has_decimal ) + { + // can't have two + success = false; + break; + } + else + { + has_decimal = true; + } + } + else + if( LLStringOps::isDigit( trimmed[i] ) ) + { + has_digit = true; + } + else + { + success = false; + break; + } + } + } + + // Gotta have at least one + success = has_digit; + + return success; +} + +bool LLLineEditor::evaluateFloat() +{ + bool success; + F32 result = 0.f; + std::string expr = getText(); + LLStringUtil::toUpper(expr); + + success = LLCalc::getInstance()->evalString(expr, result); + + if (!success) + { + // Move the cursor to near the error on failure + setCursor(LLCalc::getInstance()->getLastErrorPos()); + // *TODO: Translated error message indicating the type of error? Select error text? + } + else + { + // Replace the expression with the result + std::string result_str = llformat("%f",result); + setText(result_str); + selectAll(); + } + + return success; +} + +void LLLineEditor::onMouseCaptureLost() +{ + endSelection(); +} + + +void LLLineEditor::setSelectAllonFocusReceived(bool b) +{ + mSelectAllonFocusReceived = b; +} + +void LLLineEditor::onKeystroke() +{ + if (mKeystrokeCallback) + { + mKeystrokeCallback(this); + } + + mSpellCheckStart = mSpellCheckEnd = -1; +} + +void LLLineEditor::setKeystrokeCallback(callback_t callback, void* user_data) +{ + mKeystrokeCallback = boost::bind(callback, _1, user_data); +} + + +bool LLLineEditor::setTextArg( const std::string& key, const LLStringExplicit& text ) +{ + mText.setArg(key, text); + return true; +} + +bool LLLineEditor::setLabelArg( const std::string& key, const LLStringExplicit& text ) +{ + mLabel.setArg(key, text); + return true; +} + + +void LLLineEditor::updateAllowingLanguageInput() +{ + // Allow Language Text Input only when this LineEditor has + // no prevalidate function attached (as long as other criteria + // common to LLTextEditor). This criterion works + // fine on 1.15.0.2, since all prevalidate func reject any + // non-ASCII characters. I'm not sure on future versions, + // however... + LLWindow* window = getWindow(); + if (!window) + { + // test app, no window available + return; + } + if (hasFocus() && !mReadOnly && !mDrawAsterixes && !mPrevalidator) + { + window->allowLanguageTextInput(this, true); + } + else + { + window->allowLanguageTextInput(this, false); + } +} + +bool LLLineEditor::hasPreeditString() const +{ + return (mPreeditPositions.size() > 1); +} + +void LLLineEditor::resetPreedit() +{ + if (hasSelection()) + { + if (hasPreeditString()) + { + LL_WARNS() << "Preedit and selection!" << LL_ENDL; + deselect(); + } + else + { + deleteSelection(); + } + } + if (hasPreeditString()) + { + const S32 preedit_pos = mPreeditPositions.front(); + mText.erase(preedit_pos, mPreeditPositions.back() - preedit_pos); + mText.insert(preedit_pos, mPreeditOverwrittenWString); + setCursor(preedit_pos); + + mPreeditWString.clear(); + mPreeditOverwrittenWString.clear(); + mPreeditPositions.clear(); + + // Don't reset key stroke timer nor invoke keystroke callback, + // because a call to updatePreedit should be follow soon in + // normal course of operation, and timer and callback will be + // maintained there. Doing so here made an odd sound. (VWR-3410) + } +} + +void LLLineEditor::updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) +{ + // Just in case. + if (mReadOnly) + { + return; + } + + // Note that call to updatePreedit is always preceeded by resetPreedit, + // so we have no existing selection/preedit. + + S32 insert_preedit_at = getCursor(); + + mPreeditWString = preedit_string; + mPreeditPositions.resize(preedit_segment_lengths.size() + 1); + S32 position = insert_preedit_at; + for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++) + { + mPreeditPositions[i] = position; + position += preedit_segment_lengths[i]; + } + mPreeditPositions.back() = position; + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString.assign( LLWString( mText, insert_preedit_at, mPreeditWString.length() ) ); + mText.erase(insert_preedit_at, mPreeditWString.length()); + } + else + { + mPreeditOverwrittenWString.clear(); + } + mText.insert(insert_preedit_at, mPreeditWString); + + mPreeditStandouts = preedit_standouts; + + setCursor(position); + setCursor(mPreeditPositions.front() + caret_position); + + // Update of the preedit should be caused by some key strokes. + mKeystrokeTimer.reset(); + onKeystroke(); + + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); +} + +bool LLLineEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const +{ + if (control) + { + LLRect control_rect_screen; + localRectToScreen(getRect(), &control_rect_screen); + LLUI::getInstance()->screenRectToGL(control_rect_screen, control); + } + + S32 preedit_left_column, preedit_right_column; + if (hasPreeditString()) + { + preedit_left_column = mPreeditPositions.front(); + preedit_right_column = mPreeditPositions.back(); + } + else + { + preedit_left_column = preedit_right_column = getCursor(); + } + if (preedit_right_column < mScrollHPos) + { + // This should not occure... + return false; + } + + const S32 query = (query_offset >= 0 ? preedit_left_column + query_offset : getCursor()); + if (query < mScrollHPos || query < preedit_left_column || query > preedit_right_column) + { + return false; + } + + if (coord) + { + S32 query_local = findPixelNearestPos(query - getCursor()); + S32 query_screen_x, query_screen_y; + localPointToScreen(query_local, getRect().getHeight() / 2, &query_screen_x, &query_screen_y); + LLUI::getInstance()->screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); + } + + if (bounds) + { + S32 preedit_left_local = findPixelNearestPos(llmax(preedit_left_column, mScrollHPos) - getCursor()); + S32 preedit_right_local = llmin(findPixelNearestPos(preedit_right_column - getCursor()), getRect().getWidth() - mBorderThickness); + if (preedit_left_local > preedit_right_local) + { + // Is this condition possible? + preedit_right_local = preedit_left_local; + } + + LLRect preedit_rect_local(preedit_left_local, getRect().getHeight(), preedit_right_local, 0); + LLRect preedit_rect_screen; + localRectToScreen(preedit_rect_local, &preedit_rect_screen); + LLUI::getInstance()->screenRectToGL(preedit_rect_screen, bounds); + } + + return true; +} + +void LLLineEditor::getPreeditRange(S32 *position, S32 *length) const +{ + if (hasPreeditString()) + { + *position = mPreeditPositions.front(); + *length = mPreeditPositions.back() - mPreeditPositions.front(); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLLineEditor::getSelectionRange(S32 *position, S32 *length) const +{ + if (hasSelection()) + { + *position = llmin(mSelectionStart, mSelectionEnd); + *length = llabs(mSelectionStart - mSelectionEnd); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLLineEditor::markAsPreedit(S32 position, S32 length) +{ + deselect(); + setCursor(position); + if (hasPreeditString()) + { + LL_WARNS() << "markAsPreedit invoked when hasPreeditString is true." << LL_ENDL; + } + mPreeditWString.assign( LLWString( mText.getWString(), position, length ) ); + if (length > 0) + { + mPreeditPositions.resize(2); + mPreeditPositions[0] = position; + mPreeditPositions[1] = position + length; + mPreeditStandouts.resize(1); + mPreeditStandouts[0] = false; + } + else + { + mPreeditPositions.clear(); + mPreeditStandouts.clear(); + } + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString = mPreeditWString; + } + else + { + mPreeditOverwrittenWString.clear(); + } +} + +S32 LLLineEditor::getPreeditFontSize() const +{ + return ll_round(mGLFont->getLineHeight() * LLUI::getScaleFactor().mV[VY]); +} + +void LLLineEditor::setReplaceNewlinesWithSpaces(bool replace) +{ + mReplaceNewlinesWithSpaces = replace; +} + +LLWString LLLineEditor::getConvertedText() const +{ + LLWString text = getWText(); + LLWStringUtil::trim(text); + if (!mReplaceNewlinesWithSpaces) + { + LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. + } + return text; +} + +void LLLineEditor::showContextMenu(S32 x, S32 y) +{ + LLContextMenu* menu = static_cast(mContextMenuHandle.get()); + if (!menu) + { + llassert(LLMenuGL::sMenuContainer != NULL); + menu = LLUICtrlFactory::createFromFile + ("menu_text_editor.xml", + LLMenuGL::sMenuContainer, + LLMenuHolderGL::child_registry_t::instance()); + setContextMenu(menu); + } + + if (menu) + { + gEditMenuHandler = this; + + S32 screen_x, screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + + setCursorAtLocalPos(x); + if (hasSelection()) + { + if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) ) + { + deselect(); + } + else + { + setCursor(llmax(mSelectionStart, mSelectionEnd)); + } + } + + bool use_spellcheck = getSpellCheck(), is_misspelled = false; + if (use_spellcheck) + { + mSuggestionList.clear(); + + // If the cursor is on a misspelled word, retrieve suggestions for it + std::string misspelled_word = getMisspelledWord(mCursorPos); + if ((is_misspelled = !misspelled_word.empty())) + { + LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList); + } + } + + menu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty())); + menu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled)); + menu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled)); + menu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled)); + menu->show(screen_x, screen_y, this); + } +} + +void LLLineEditor::setContextMenu(LLContextMenu* new_context_menu) +{ + LLContextMenu* menu = static_cast(mContextMenuHandle.get()); + if (menu) + { + menu->die(); + mContextMenuHandle.markDead(); + } + + if (new_context_menu) + { + mContextMenuHandle = new_context_menu->getHandle(); + } +} + +void LLLineEditor::setFont(const LLFontGL* font) +{ + mGLFont = font; +} diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index debc19f7a6..e955cbb17d 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -1,472 +1,472 @@ -/** - * @file lllineeditor.h - * @brief Text editor widget to let users enter/edit a single line. - * - * Features: - * Text entry of a single line (text, delete, left and right arrow, insert, return). - * Callbacks either on every keystroke or just on the return key. - * Focus (allow multiple text entry widgets) - * Clipboard (cut, copy, and paste) - * Horizontal scrolling to allow strings longer than widget size allows - * Pre-validation (limit which keys can be used) - * Optional line history so previous entries can be recalled by CTRL UP/DOWN - * - * $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$ - */ - -#ifndef LL_LLLINEEDITOR_H -#define LL_LLLINEEDITOR_H - -#include "v4color.h" -#include "llframetimer.h" - -#include "lleditmenuhandler.h" -#include "llspellcheckmenuhandler.h" -#include "lluictrl.h" -#include "lluiimage.h" -#include "lluistring.h" -#include "llviewborder.h" - -#include "llpreeditor.h" -#include "lltextvalidate.h" - -class LLFontGL; -class LLLineEditorRollback; -class LLButton; -class LLContextMenu; - -class LLLineEditor -: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor, public LLSpellCheckMenuHandler -{ -public: - - typedef boost::function keystroke_callback_t; - - struct MaxLength : public LLInitParam::ChoiceBlock - { - Alternative bytes, chars; - - MaxLength() : bytes("max_length_bytes", 254), - chars("max_length_chars", 0) - {} - }; - - struct Params : public LLInitParam::Block - { - Optional default_text; - Optional max_length; - Optional keystroke_callback; - - Optional prevalidator; - Optional input_prevalidator; - - Optional border; - - Optional background_image, - background_image_disabled, - background_image_focused; - - Optional select_on_focus, - revert_on_esc, - spellcheck, - commit_on_focus_lost, - ignore_tab, - bg_image_always_focused, - show_label_focused, - is_password, - allow_emoji, - use_bg_color; - - // colors - Optional cursor_color, - bg_color, - text_color, - text_readonly_color, - text_tentative_color, - highlight_color, - preedit_bg_color; - - Optional text_pad_left, - text_pad_right; - - Ignored bg_visible; - - Params(); - }; - - void initFromParams(const LLLineEditor::Params& params); - -protected: - LLLineEditor(const Params&); - friend class LLUICtrlFactory; - friend class LLFloaterEditUI; - void showContextMenu(S32 x, S32 y); - -public: - virtual ~LLLineEditor(); - - // mousehandler overrides - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleDoubleClick(S32 x,S32 y,MASK mask) override; - /*virtual*/ bool handleMiddleMouseDown(S32 x,S32 y,MASK mask) override; - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleKeyHere(KEY key, MASK mask) override; - /*virtual*/ bool handleUnicodeCharHere(llwchar uni_char) override; - /*virtual*/ void onMouseCaptureLost() override; - - // LLEditMenuHandler overrides - /*virtual*/ void cut() override; - /*virtual*/ bool canCut() const override; - /*virtual*/ void copy() override; - /*virtual*/ bool canCopy() const override; - /*virtual*/ void paste() override; - /*virtual*/ bool canPaste() const override; - - virtual void updatePrimary(); - virtual void copyPrimary(); - virtual void pastePrimary(); - virtual bool canPastePrimary() const; - - /*virtual*/ void doDelete() override; - /*virtual*/ bool canDoDelete() const override; - - /*virtual*/ void selectAll() override; - /*virtual*/ bool canSelectAll() const override; - - /*virtual*/ void deselect() override; - /*virtual*/ bool canDeselect() const override; - - // LLSpellCheckMenuHandler overrides - /*virtual*/ bool getSpellCheck() const override; - - /*virtual*/ const std::string& getSuggestion(U32 index) const override; - /*virtual*/ U32 getSuggestionCount() const override; - /*virtual*/ void replaceWithSuggestion(U32 index) override; - - /*virtual*/ void addToDictionary() override; - /*virtual*/ bool canAddToDictionary() const override; - - /*virtual*/ void addToIgnore() override; - /*virtual*/ bool canAddToIgnore() const override; - - // Spell checking helper functions - std::string getMisspelledWord(U32 pos) const; - bool isMisspelledWord(U32 pos) const; - void onSpellCheckSettingsChange(); - - // view overrides - /*virtual*/ void draw() override; - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true) override; - /*virtual*/ void onFocusReceived() override; - /*virtual*/ void onFocusLost() override; - /*virtual*/ void setEnabled(bool enabled) override; - - // UI control overrides - /*virtual*/ void clear() override; - /*virtual*/ void onTabInto() override; - /*virtual*/ void setFocus(bool b) override; - /*virtual*/ void setRect(const LLRect& rect) override; - /*virtual*/ bool acceptsTextInput() const override; - /*virtual*/ void onCommit() override; - /*virtual*/ bool isDirty() const override; // Returns true if user changed value at all - /*virtual*/ void resetDirty() override; // Clear dirty state - - // assumes UTF8 text - /*virtual*/ void setValue(const LLSD& value) override; - /*virtual*/ LLSD getValue() const override; - /*virtual*/ bool setTextArg(const std::string& key, const LLStringExplicit& text) override; - /*virtual*/ bool setLabelArg(const std::string& key, const LLStringExplicit& text) override; - - void setLabel(const LLStringExplicit &new_label) { mLabel = new_label; } - const std::string& getLabel() { return mLabel.getString(); } - - void setText(const LLStringExplicit &new_text); - - const std::string& getText() const override { return mText.getString(); } - LLWString getWText() const { return mText.getWString(); } - LLWString getConvertedText() const; // trimmed text with paragraphs converted to newlines - - S32 getLength() const { return mText.length(); } - - S32 getCursor() const { return mCursorPos; } - void setCursor( S32 pos ); - void setCursorToEnd(); - - // set scroll to earliest position it can reasonable set - void resetScrollPosition(); - - // Selects characters 'start' to 'end'. - void setSelection(S32 start, S32 end); - /*virtual*/ void getSelectionRange(S32 *position, S32 *length) const override; - - void setCommitOnFocusLost( bool b ) { mCommitOnFocusLost = b; } - void setRevertOnEsc( bool b ) { mRevertOnEsc = b; } - void setKeystrokeOnEsc(bool b) { mKeystrokeOnEsc = b; } - - void setCursorColor(const LLColor4& c) { mCursorColor = c; } - const LLColor4& getCursorColor() const { return mCursorColor.get(); } - - void setFgColor( const LLColor4& c ) { mFgColor = c; } - void setReadOnlyFgColor( const LLColor4& c ) { mReadOnlyFgColor = c; } - void setTentativeFgColor(const LLColor4& c) { mTentativeFgColor = c; } - - const LLColor4& getFgColor() const { return mFgColor.get(); } - const LLColor4& getReadOnlyFgColor() const { return mReadOnlyFgColor.get(); } - const LLColor4& getTentativeFgColor() const { return mTentativeFgColor.get(); } - - const LLFontGL* getFont() const override { return mGLFont; } - void setFont(const LLFontGL* font); - - void setIgnoreArrowKeys(bool b) { mIgnoreArrowKeys = b; } - void setIgnoreTab(bool b) { mIgnoreTab = b; } - void setPassDelete(bool b) { mPassDelete = b; } - void setAllowEmoji(bool b) { mAllowEmoji = b; } - void setDrawAsterixes(bool b); - - // get the cursor position of the beginning/end of the prev/next word in the text - S32 prevWordPos(S32 cursorPos) const; - S32 nextWordPos(S32 cursorPos) const; - - bool hasSelection() const { return (mSelectionStart != mSelectionEnd); } - void startSelection(); - void endSelection(); - void extendSelection(S32 new_cursor_pos); - void deleteSelection(); - - void setSelectAllonFocusReceived(bool b); - void setSelectAllonCommit(bool b) { mSelectAllonCommit = b; } - - void onKeystroke(); - typedef boost::function callback_t; - void setKeystrokeCallback(callback_t callback, void* user_data); - - void setMaxTextLength(S32 max_text_length); - void setMaxTextChars(S32 max_text_chars); - // Manipulate left and right padding for text - void getTextPadding(S32 *left, S32 *right); - void setTextPadding(S32 left, S32 right); - - // Prevalidation controls which keystrokes can affect the editor - void setPrevalidate(LLTextValidate::Validator validator); - // This method sets callback that prevents from: - // - deleting, selecting, typing, cutting, pasting characters that are not valid. - // Also callback that this method sets differs from setPrevalidate in a way that it validates just inputed - // symbols, before existing text is modified, but setPrevalidate validates line after it was modified. - void setPrevalidateInput(LLTextValidate::Validator validator); - static bool postvalidateFloat(const std::string &str); - - bool prevalidateInput(const LLWString& wstr); - bool evaluateFloat(); - - // line history support: - void setEnableLineHistory( bool enabled ) { mHaveHistory = enabled; } // switches line history on or off - void updateHistory(); // stores current line in history - - void setReplaceNewlinesWithSpaces(bool replace); - - void resetContextMenu() { setContextMenu(NULL); }; - - void setBgImage(LLPointer image) { mBgImage = image; } - void setBgImageFocused(LLPointer image) { mBgImageFocused = image; } - - void setShowContextMenu(bool show) { mShowContextMenu = show; } - bool getShowContextMenu() const { return mShowContextMenu; } - - private: - // private helper methods - - void pasteHelper(bool is_primary); - - void removeChar(); - void addChar(const llwchar c); - void setCursorAtLocalPos(S32 local_mouse_x); - S32 findPixelNearestPos(S32 cursor_offset = 0) const; - S32 calcCursorPos(S32 mouse_x); - bool handleSpecialKey(KEY key, MASK mask); - bool handleSelectionKey(KEY key, MASK mask); - bool handleControlKey(KEY key, MASK mask); - S32 handleCommitKey(KEY key, MASK mask); - void updateTextPadding(); - - // Draw the background image depending on enabled/focused state. - void drawBackground(); - - // - // private data members - // - void updateAllowingLanguageInput(); - bool hasPreeditString() const; - // Implementation (overrides) of LLPreeditor - /*virtual*/ void resetPreedit() override; - /*virtual*/ void updatePreedit(const LLWString &preedit_string, - const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) override; - /*virtual*/ void markAsPreedit(S32 position, S32 length) override; - /*virtual*/ void getPreeditRange(S32 *position, S32 *length) const override; - /*virtual*/ bool getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const override; - /*virtual*/ S32 getPreeditFontSize() const override; - /*virtual*/ LLWString getPreeditString() const override { return getWText(); } - - void setText(const LLStringExplicit &new_text, bool use_size_limit); - - void setContextMenu(LLContextMenu* new_context_menu); - -protected: - LLUIString mText; // The string being edited. - std::string mPrevText; // Saved string for 'ESC' revert - LLUIString mLabel; // text label that is visible when no user text provided - - // line history support: - bool mHaveHistory; // flag for enabled line history - typedef std::vector line_history_t; - line_history_t mLineHistory; // line history storage - line_history_t::iterator mCurrentHistoryLine; // currently browsed history line - - LLViewBorder* mBorder; - const LLFontGL* mGLFont; - S32 mMaxLengthBytes; // Max length of the UTF8 string in bytes - S32 mMaxLengthChars; // Maximum number of characters in the string - S32 mCursorPos; // I-beam is just after the mCursorPos-th character. - S32 mScrollHPos; // Horizontal offset from the start of mText. Used for scrolling. - LLFrameTimer mScrollTimer; - S32 mTextPadLeft; // Used to reserve space before the beginning of the text for children. - S32 mTextPadRight; // Used to reserve space after the end of the text for children. - S32 mTextLeftEdge; // Pixels, cached left edge of text based on left padding and width - S32 mTextRightEdge; // Pixels, cached right edge of text based on right padding and width - - bool mCommitOnFocusLost; - bool mRevertOnEsc; - bool mKeystrokeOnEsc; - - keystroke_callback_t mKeystrokeCallback; - - bool mIsSelecting; // Selection for clipboard operations - S32 mSelectionStart; - S32 mSelectionEnd; - S32 mLastSelectionX; - S32 mLastSelectionY; - S32 mLastSelectionStart; - S32 mLastSelectionEnd; - - bool mSpellCheck; - S32 mSpellCheckStart; - S32 mSpellCheckEnd; - LLTimer mSpellCheckTimer; - std::list > mMisspellRanges; - std::vector mSuggestionList; - - LLTextValidate::Validator mPrevalidator; - LLTextValidate::Validator mInputPrevalidator; - - LLFrameTimer mKeystrokeTimer; - LLTimer mTripleClickTimer; - - LLUIColor mCursorColor; - LLUIColor mBgColor; - LLUIColor mFgColor; - LLUIColor mReadOnlyFgColor; - LLUIColor mTentativeFgColor; - LLUIColor mHighlightColor; // background for selected text - LLUIColor mPreeditBgColor; // preedit marker background color - - S32 mBorderThickness; - - bool mIgnoreArrowKeys; - bool mIgnoreTab; - bool mDrawAsterixes; - - bool mSelectAllonFocusReceived; - bool mSelectAllonCommit; - bool mPassDelete; - - bool mReadOnly; - - bool mShowImageFocused; - bool mShowLabelFocused; - - bool mAllowEmoji; - bool mUseBgColor; - - LLWString mPreeditWString; - LLWString mPreeditOverwrittenWString; - std::vector mPreeditPositions; - LLPreeditor::standouts_t mPreeditStandouts; - - LLHandle mContextMenuHandle; - - bool mShowContextMenu; - -private: - // Instances that by default point to the statics but can be overidden in XML. - LLPointer mBgImage; - LLPointer mBgImageDisabled; - LLPointer mBgImageFocused; - - bool mReplaceNewlinesWithSpaces; // if false, will replace pasted newlines with paragraph symbol. - - // private helper class - class LLLineEditorRollback - { - public: - LLLineEditorRollback( LLLineEditor* ed ) - : - mCursorPos( ed->mCursorPos ), - mScrollHPos( ed->mScrollHPos ), - mIsSelecting( ed->mIsSelecting ), - mSelectionStart( ed->mSelectionStart ), - mSelectionEnd( ed->mSelectionEnd ) - { - mText = ed->getText(); - } - - void doRollback( LLLineEditor* ed ) - { - ed->mCursorPos = mCursorPos; - ed->mScrollHPos = mScrollHPos; - ed->mIsSelecting = mIsSelecting; - ed->mSelectionStart = mSelectionStart; - ed->mSelectionEnd = mSelectionEnd; - ed->mText = mText; - ed->mPrevText = mText; - } - - std::string getText() { return mText; } - - private: - std::string mText; - S32 mCursorPos; - S32 mScrollHPos; - bool mIsSelecting; - S32 mSelectionStart; - S32 mSelectionEnd; - }; // end class LLLineEditorRollback - -}; // end class LLLineEditor - -// Build time optimization, generate once in .cpp file -#ifndef LLLINEEDITOR_CPP -extern template class LLLineEditor* LLView::getChild( - const std::string& name, bool recurse) const; -#endif - -#endif // LL_LINEEDITOR_ +/** + * @file lllineeditor.h + * @brief Text editor widget to let users enter/edit a single line. + * + * Features: + * Text entry of a single line (text, delete, left and right arrow, insert, return). + * Callbacks either on every keystroke or just on the return key. + * Focus (allow multiple text entry widgets) + * Clipboard (cut, copy, and paste) + * Horizontal scrolling to allow strings longer than widget size allows + * Pre-validation (limit which keys can be used) + * Optional line history so previous entries can be recalled by CTRL UP/DOWN + * + * $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$ + */ + +#ifndef LL_LLLINEEDITOR_H +#define LL_LLLINEEDITOR_H + +#include "v4color.h" +#include "llframetimer.h" + +#include "lleditmenuhandler.h" +#include "llspellcheckmenuhandler.h" +#include "lluictrl.h" +#include "lluiimage.h" +#include "lluistring.h" +#include "llviewborder.h" + +#include "llpreeditor.h" +#include "lltextvalidate.h" + +class LLFontGL; +class LLLineEditorRollback; +class LLButton; +class LLContextMenu; + +class LLLineEditor +: public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor, public LLSpellCheckMenuHandler +{ +public: + + typedef boost::function keystroke_callback_t; + + struct MaxLength : public LLInitParam::ChoiceBlock + { + Alternative bytes, chars; + + MaxLength() : bytes("max_length_bytes", 254), + chars("max_length_chars", 0) + {} + }; + + struct Params : public LLInitParam::Block + { + Optional default_text; + Optional max_length; + Optional keystroke_callback; + + Optional prevalidator; + Optional input_prevalidator; + + Optional border; + + Optional background_image, + background_image_disabled, + background_image_focused; + + Optional select_on_focus, + revert_on_esc, + spellcheck, + commit_on_focus_lost, + ignore_tab, + bg_image_always_focused, + show_label_focused, + is_password, + allow_emoji, + use_bg_color; + + // colors + Optional cursor_color, + bg_color, + text_color, + text_readonly_color, + text_tentative_color, + highlight_color, + preedit_bg_color; + + Optional text_pad_left, + text_pad_right; + + Ignored bg_visible; + + Params(); + }; + + void initFromParams(const LLLineEditor::Params& params); + +protected: + LLLineEditor(const Params&); + friend class LLUICtrlFactory; + friend class LLFloaterEditUI; + void showContextMenu(S32 x, S32 y); + +public: + virtual ~LLLineEditor(); + + // mousehandler overrides + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleDoubleClick(S32 x,S32 y,MASK mask) override; + /*virtual*/ bool handleMiddleMouseDown(S32 x,S32 y,MASK mask) override; + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleKeyHere(KEY key, MASK mask) override; + /*virtual*/ bool handleUnicodeCharHere(llwchar uni_char) override; + /*virtual*/ void onMouseCaptureLost() override; + + // LLEditMenuHandler overrides + /*virtual*/ void cut() override; + /*virtual*/ bool canCut() const override; + /*virtual*/ void copy() override; + /*virtual*/ bool canCopy() const override; + /*virtual*/ void paste() override; + /*virtual*/ bool canPaste() const override; + + virtual void updatePrimary(); + virtual void copyPrimary(); + virtual void pastePrimary(); + virtual bool canPastePrimary() const; + + /*virtual*/ void doDelete() override; + /*virtual*/ bool canDoDelete() const override; + + /*virtual*/ void selectAll() override; + /*virtual*/ bool canSelectAll() const override; + + /*virtual*/ void deselect() override; + /*virtual*/ bool canDeselect() const override; + + // LLSpellCheckMenuHandler overrides + /*virtual*/ bool getSpellCheck() const override; + + /*virtual*/ const std::string& getSuggestion(U32 index) const override; + /*virtual*/ U32 getSuggestionCount() const override; + /*virtual*/ void replaceWithSuggestion(U32 index) override; + + /*virtual*/ void addToDictionary() override; + /*virtual*/ bool canAddToDictionary() const override; + + /*virtual*/ void addToIgnore() override; + /*virtual*/ bool canAddToIgnore() const override; + + // Spell checking helper functions + std::string getMisspelledWord(U32 pos) const; + bool isMisspelledWord(U32 pos) const; + void onSpellCheckSettingsChange(); + + // view overrides + /*virtual*/ void draw() override; + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true) override; + /*virtual*/ void onFocusReceived() override; + /*virtual*/ void onFocusLost() override; + /*virtual*/ void setEnabled(bool enabled) override; + + // UI control overrides + /*virtual*/ void clear() override; + /*virtual*/ void onTabInto() override; + /*virtual*/ void setFocus(bool b) override; + /*virtual*/ void setRect(const LLRect& rect) override; + /*virtual*/ bool acceptsTextInput() const override; + /*virtual*/ void onCommit() override; + /*virtual*/ bool isDirty() const override; // Returns true if user changed value at all + /*virtual*/ void resetDirty() override; // Clear dirty state + + // assumes UTF8 text + /*virtual*/ void setValue(const LLSD& value) override; + /*virtual*/ LLSD getValue() const override; + /*virtual*/ bool setTextArg(const std::string& key, const LLStringExplicit& text) override; + /*virtual*/ bool setLabelArg(const std::string& key, const LLStringExplicit& text) override; + + void setLabel(const LLStringExplicit &new_label) { mLabel = new_label; } + const std::string& getLabel() { return mLabel.getString(); } + + void setText(const LLStringExplicit &new_text); + + const std::string& getText() const override { return mText.getString(); } + LLWString getWText() const { return mText.getWString(); } + LLWString getConvertedText() const; // trimmed text with paragraphs converted to newlines + + S32 getLength() const { return mText.length(); } + + S32 getCursor() const { return mCursorPos; } + void setCursor( S32 pos ); + void setCursorToEnd(); + + // set scroll to earliest position it can reasonable set + void resetScrollPosition(); + + // Selects characters 'start' to 'end'. + void setSelection(S32 start, S32 end); + /*virtual*/ void getSelectionRange(S32 *position, S32 *length) const override; + + void setCommitOnFocusLost( bool b ) { mCommitOnFocusLost = b; } + void setRevertOnEsc( bool b ) { mRevertOnEsc = b; } + void setKeystrokeOnEsc(bool b) { mKeystrokeOnEsc = b; } + + void setCursorColor(const LLColor4& c) { mCursorColor = c; } + const LLColor4& getCursorColor() const { return mCursorColor.get(); } + + void setFgColor( const LLColor4& c ) { mFgColor = c; } + void setReadOnlyFgColor( const LLColor4& c ) { mReadOnlyFgColor = c; } + void setTentativeFgColor(const LLColor4& c) { mTentativeFgColor = c; } + + const LLColor4& getFgColor() const { return mFgColor.get(); } + const LLColor4& getReadOnlyFgColor() const { return mReadOnlyFgColor.get(); } + const LLColor4& getTentativeFgColor() const { return mTentativeFgColor.get(); } + + const LLFontGL* getFont() const override { return mGLFont; } + void setFont(const LLFontGL* font); + + void setIgnoreArrowKeys(bool b) { mIgnoreArrowKeys = b; } + void setIgnoreTab(bool b) { mIgnoreTab = b; } + void setPassDelete(bool b) { mPassDelete = b; } + void setAllowEmoji(bool b) { mAllowEmoji = b; } + void setDrawAsterixes(bool b); + + // get the cursor position of the beginning/end of the prev/next word in the text + S32 prevWordPos(S32 cursorPos) const; + S32 nextWordPos(S32 cursorPos) const; + + bool hasSelection() const { return (mSelectionStart != mSelectionEnd); } + void startSelection(); + void endSelection(); + void extendSelection(S32 new_cursor_pos); + void deleteSelection(); + + void setSelectAllonFocusReceived(bool b); + void setSelectAllonCommit(bool b) { mSelectAllonCommit = b; } + + void onKeystroke(); + typedef boost::function callback_t; + void setKeystrokeCallback(callback_t callback, void* user_data); + + void setMaxTextLength(S32 max_text_length); + void setMaxTextChars(S32 max_text_chars); + // Manipulate left and right padding for text + void getTextPadding(S32 *left, S32 *right); + void setTextPadding(S32 left, S32 right); + + // Prevalidation controls which keystrokes can affect the editor + void setPrevalidate(LLTextValidate::Validator validator); + // This method sets callback that prevents from: + // - deleting, selecting, typing, cutting, pasting characters that are not valid. + // Also callback that this method sets differs from setPrevalidate in a way that it validates just inputed + // symbols, before existing text is modified, but setPrevalidate validates line after it was modified. + void setPrevalidateInput(LLTextValidate::Validator validator); + static bool postvalidateFloat(const std::string &str); + + bool prevalidateInput(const LLWString& wstr); + bool evaluateFloat(); + + // line history support: + void setEnableLineHistory( bool enabled ) { mHaveHistory = enabled; } // switches line history on or off + void updateHistory(); // stores current line in history + + void setReplaceNewlinesWithSpaces(bool replace); + + void resetContextMenu() { setContextMenu(NULL); }; + + void setBgImage(LLPointer image) { mBgImage = image; } + void setBgImageFocused(LLPointer image) { mBgImageFocused = image; } + + void setShowContextMenu(bool show) { mShowContextMenu = show; } + bool getShowContextMenu() const { return mShowContextMenu; } + + private: + // private helper methods + + void pasteHelper(bool is_primary); + + void removeChar(); + void addChar(const llwchar c); + void setCursorAtLocalPos(S32 local_mouse_x); + S32 findPixelNearestPos(S32 cursor_offset = 0) const; + S32 calcCursorPos(S32 mouse_x); + bool handleSpecialKey(KEY key, MASK mask); + bool handleSelectionKey(KEY key, MASK mask); + bool handleControlKey(KEY key, MASK mask); + S32 handleCommitKey(KEY key, MASK mask); + void updateTextPadding(); + + // Draw the background image depending on enabled/focused state. + void drawBackground(); + + // + // private data members + // + void updateAllowingLanguageInput(); + bool hasPreeditString() const; + // Implementation (overrides) of LLPreeditor + /*virtual*/ void resetPreedit() override; + /*virtual*/ void updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) override; + /*virtual*/ void markAsPreedit(S32 position, S32 length) override; + /*virtual*/ void getPreeditRange(S32 *position, S32 *length) const override; + /*virtual*/ bool getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const override; + /*virtual*/ S32 getPreeditFontSize() const override; + /*virtual*/ LLWString getPreeditString() const override { return getWText(); } + + void setText(const LLStringExplicit &new_text, bool use_size_limit); + + void setContextMenu(LLContextMenu* new_context_menu); + +protected: + LLUIString mText; // The string being edited. + std::string mPrevText; // Saved string for 'ESC' revert + LLUIString mLabel; // text label that is visible when no user text provided + + // line history support: + bool mHaveHistory; // flag for enabled line history + typedef std::vector line_history_t; + line_history_t mLineHistory; // line history storage + line_history_t::iterator mCurrentHistoryLine; // currently browsed history line + + LLViewBorder* mBorder; + const LLFontGL* mGLFont; + S32 mMaxLengthBytes; // Max length of the UTF8 string in bytes + S32 mMaxLengthChars; // Maximum number of characters in the string + S32 mCursorPos; // I-beam is just after the mCursorPos-th character. + S32 mScrollHPos; // Horizontal offset from the start of mText. Used for scrolling. + LLFrameTimer mScrollTimer; + S32 mTextPadLeft; // Used to reserve space before the beginning of the text for children. + S32 mTextPadRight; // Used to reserve space after the end of the text for children. + S32 mTextLeftEdge; // Pixels, cached left edge of text based on left padding and width + S32 mTextRightEdge; // Pixels, cached right edge of text based on right padding and width + + bool mCommitOnFocusLost; + bool mRevertOnEsc; + bool mKeystrokeOnEsc; + + keystroke_callback_t mKeystrokeCallback; + + bool mIsSelecting; // Selection for clipboard operations + S32 mSelectionStart; + S32 mSelectionEnd; + S32 mLastSelectionX; + S32 mLastSelectionY; + S32 mLastSelectionStart; + S32 mLastSelectionEnd; + + bool mSpellCheck; + S32 mSpellCheckStart; + S32 mSpellCheckEnd; + LLTimer mSpellCheckTimer; + std::list > mMisspellRanges; + std::vector mSuggestionList; + + LLTextValidate::Validator mPrevalidator; + LLTextValidate::Validator mInputPrevalidator; + + LLFrameTimer mKeystrokeTimer; + LLTimer mTripleClickTimer; + + LLUIColor mCursorColor; + LLUIColor mBgColor; + LLUIColor mFgColor; + LLUIColor mReadOnlyFgColor; + LLUIColor mTentativeFgColor; + LLUIColor mHighlightColor; // background for selected text + LLUIColor mPreeditBgColor; // preedit marker background color + + S32 mBorderThickness; + + bool mIgnoreArrowKeys; + bool mIgnoreTab; + bool mDrawAsterixes; + + bool mSelectAllonFocusReceived; + bool mSelectAllonCommit; + bool mPassDelete; + + bool mReadOnly; + + bool mShowImageFocused; + bool mShowLabelFocused; + + bool mAllowEmoji; + bool mUseBgColor; + + LLWString mPreeditWString; + LLWString mPreeditOverwrittenWString; + std::vector mPreeditPositions; + LLPreeditor::standouts_t mPreeditStandouts; + + LLHandle mContextMenuHandle; + + bool mShowContextMenu; + +private: + // Instances that by default point to the statics but can be overidden in XML. + LLPointer mBgImage; + LLPointer mBgImageDisabled; + LLPointer mBgImageFocused; + + bool mReplaceNewlinesWithSpaces; // if false, will replace pasted newlines with paragraph symbol. + + // private helper class + class LLLineEditorRollback + { + public: + LLLineEditorRollback( LLLineEditor* ed ) + : + mCursorPos( ed->mCursorPos ), + mScrollHPos( ed->mScrollHPos ), + mIsSelecting( ed->mIsSelecting ), + mSelectionStart( ed->mSelectionStart ), + mSelectionEnd( ed->mSelectionEnd ) + { + mText = ed->getText(); + } + + void doRollback( LLLineEditor* ed ) + { + ed->mCursorPos = mCursorPos; + ed->mScrollHPos = mScrollHPos; + ed->mIsSelecting = mIsSelecting; + ed->mSelectionStart = mSelectionStart; + ed->mSelectionEnd = mSelectionEnd; + ed->mText = mText; + ed->mPrevText = mText; + } + + std::string getText() { return mText; } + + private: + std::string mText; + S32 mCursorPos; + S32 mScrollHPos; + bool mIsSelecting; + S32 mSelectionStart; + S32 mSelectionEnd; + }; // end class LLLineEditorRollback + +}; // end class LLLineEditor + +// Build time optimization, generate once in .cpp file +#ifndef LLLINEEDITOR_CPP +extern template class LLLineEditor* LLView::getChild( + const std::string& name, bool recurse) const; +#endif + +#endif // LL_LINEEDITOR_ diff --git a/indra/llui/lllocalcliprect.cpp b/indra/llui/lllocalcliprect.cpp index 6508106a9c..0204946125 100644 --- a/indra/llui/lllocalcliprect.cpp +++ b/indra/llui/lllocalcliprect.cpp @@ -1,110 +1,110 @@ -/** -* @file lllocalcliprect.cpp -* -* $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 "lllocalcliprect.h" - -#include "llfontgl.h" -#include "llui.h" - -/*static*/ std::stack LLScreenClipRect::sClipRectStack; - - -LLScreenClipRect::LLScreenClipRect(const LLRect& rect, bool enabled) -: mScissorState(GL_SCISSOR_TEST), - mEnabled(enabled) -{ - if (mEnabled) - { - pushClipRect(rect); - mScissorState.setEnabled(!sClipRectStack.empty()); - updateScissorRegion(); - } -} - -LLScreenClipRect::~LLScreenClipRect() -{ - if (mEnabled) - { - popClipRect(); - updateScissorRegion(); - } -} - -//static -void LLScreenClipRect::pushClipRect(const LLRect& rect) -{ - LLRect combined_clip_rect = rect; - if (!sClipRectStack.empty()) - { - LLRect top = sClipRectStack.top(); - combined_clip_rect.intersectWith(top); - - if(combined_clip_rect.isEmpty()) - { - // avoid artifacts where zero area rects show up as lines - combined_clip_rect = LLRect::null; - } - } - sClipRectStack.push(combined_clip_rect); -} - -//static -void LLScreenClipRect::popClipRect() -{ - sClipRectStack.pop(); -} - -//static -void LLScreenClipRect::updateScissorRegion() -{ - if (sClipRectStack.empty()) return; - - // finish any deferred calls in the old clipping region - gGL.flush(); - - LLRect rect = sClipRectStack.top(); - stop_glerror(); - S32 x,y,w,h; - x = llfloor(rect.mLeft * LLUI::getScaleFactor().mV[VX]); - y = llfloor(rect.mBottom * LLUI::getScaleFactor().mV[VY]); - w = llmax(0, llceil(rect.getWidth() * LLUI::getScaleFactor().mV[VX])) + 1; - h = llmax(0, llceil(rect.getHeight() * LLUI::getScaleFactor().mV[VY])) + 1; - glScissor( x,y,w,h ); - stop_glerror(); -} - -//--------------------------------------------------------------------------- -// LLLocalClipRect -//--------------------------------------------------------------------------- -LLLocalClipRect::LLLocalClipRect(const LLRect& rect, bool enabled /* = true */) -: LLScreenClipRect(LLRect(rect.mLeft + LLFontGL::sCurOrigin.mX, - rect.mTop + LLFontGL::sCurOrigin.mY, - rect.mRight + LLFontGL::sCurOrigin.mX, - rect.mBottom + LLFontGL::sCurOrigin.mY), enabled) -{} - -LLLocalClipRect::~LLLocalClipRect() -{} +/** +* @file lllocalcliprect.cpp +* +* $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 "lllocalcliprect.h" + +#include "llfontgl.h" +#include "llui.h" + +/*static*/ std::stack LLScreenClipRect::sClipRectStack; + + +LLScreenClipRect::LLScreenClipRect(const LLRect& rect, bool enabled) +: mScissorState(GL_SCISSOR_TEST), + mEnabled(enabled) +{ + if (mEnabled) + { + pushClipRect(rect); + mScissorState.setEnabled(!sClipRectStack.empty()); + updateScissorRegion(); + } +} + +LLScreenClipRect::~LLScreenClipRect() +{ + if (mEnabled) + { + popClipRect(); + updateScissorRegion(); + } +} + +//static +void LLScreenClipRect::pushClipRect(const LLRect& rect) +{ + LLRect combined_clip_rect = rect; + if (!sClipRectStack.empty()) + { + LLRect top = sClipRectStack.top(); + combined_clip_rect.intersectWith(top); + + if(combined_clip_rect.isEmpty()) + { + // avoid artifacts where zero area rects show up as lines + combined_clip_rect = LLRect::null; + } + } + sClipRectStack.push(combined_clip_rect); +} + +//static +void LLScreenClipRect::popClipRect() +{ + sClipRectStack.pop(); +} + +//static +void LLScreenClipRect::updateScissorRegion() +{ + if (sClipRectStack.empty()) return; + + // finish any deferred calls in the old clipping region + gGL.flush(); + + LLRect rect = sClipRectStack.top(); + stop_glerror(); + S32 x,y,w,h; + x = llfloor(rect.mLeft * LLUI::getScaleFactor().mV[VX]); + y = llfloor(rect.mBottom * LLUI::getScaleFactor().mV[VY]); + w = llmax(0, llceil(rect.getWidth() * LLUI::getScaleFactor().mV[VX])) + 1; + h = llmax(0, llceil(rect.getHeight() * LLUI::getScaleFactor().mV[VY])) + 1; + glScissor( x,y,w,h ); + stop_glerror(); +} + +//--------------------------------------------------------------------------- +// LLLocalClipRect +//--------------------------------------------------------------------------- +LLLocalClipRect::LLLocalClipRect(const LLRect& rect, bool enabled /* = true */) +: LLScreenClipRect(LLRect(rect.mLeft + LLFontGL::sCurOrigin.mX, + rect.mTop + LLFontGL::sCurOrigin.mY, + rect.mRight + LLFontGL::sCurOrigin.mX, + rect.mBottom + LLFontGL::sCurOrigin.mY), enabled) +{} + +LLLocalClipRect::~LLLocalClipRect() +{} diff --git a/indra/llui/lllocalcliprect.h b/indra/llui/lllocalcliprect.h index c39087070e..2a858e6163 100644 --- a/indra/llui/lllocalcliprect.h +++ b/indra/llui/lllocalcliprect.h @@ -1,63 +1,63 @@ -/** -* @file lllocalcliprect.h -* -* $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$ -*/ -#ifndef LLLOCALCLIPRECT_H -#define LLLOCALCLIPRECT_H - -#include "llgl.h" -#include "llrect.h" // can't forward declare, it's templated -#include - -// Clip rendering to a specific rectangle using GL scissor -// Just create one of these on the stack: -// { -// LLLocalClipRect(rect); -// draw(); -// } -class LLScreenClipRect -{ -public: - LLScreenClipRect(const LLRect& rect, bool enabled = true); - virtual ~LLScreenClipRect(); - -private: - static void pushClipRect(const LLRect& rect); - static void popClipRect(); - static void updateScissorRegion(); - -private: - LLGLState mScissorState; - bool mEnabled; - - static std::stack sClipRectStack; -}; - -class LLLocalClipRect : public LLScreenClipRect -{ -public: - LLLocalClipRect(const LLRect& rect, bool enabled = true); - ~LLLocalClipRect(); -}; - -#endif +/** +* @file lllocalcliprect.h +* +* $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$ +*/ +#ifndef LLLOCALCLIPRECT_H +#define LLLOCALCLIPRECT_H + +#include "llgl.h" +#include "llrect.h" // can't forward declare, it's templated +#include + +// Clip rendering to a specific rectangle using GL scissor +// Just create one of these on the stack: +// { +// LLLocalClipRect(rect); +// draw(); +// } +class LLScreenClipRect +{ +public: + LLScreenClipRect(const LLRect& rect, bool enabled = true); + virtual ~LLScreenClipRect(); + +private: + static void pushClipRect(const LLRect& rect); + static void popClipRect(); + static void updateScissorRegion(); + +private: + LLGLState mScissorState; + bool mEnabled; + + static std::stack sClipRectStack; +}; + +class LLLocalClipRect : public LLScreenClipRect +{ +public: + LLLocalClipRect(const LLRect& rect, bool enabled = true); + ~LLLocalClipRect(); +}; + +#endif diff --git a/indra/llui/llmenubutton.cpp b/indra/llui/llmenubutton.cpp index e3cb35abc7..2f91dcb046 100644 --- a/indra/llui/llmenubutton.cpp +++ b/indra/llui/llmenubutton.cpp @@ -1,246 +1,246 @@ -/** - * @file llbutton.cpp - * @brief LLButton base 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 "llmenubutton.h" - -// Linden library includes -#include "lltoggleablemenu.h" -#include "llstring.h" -#include "v4color.h" - -static LLDefaultChildRegistry::Register r("menu_button"); - -void LLMenuButton::MenuPositions::declareValues() -{ - declare("topleft", MP_TOP_LEFT); - declare("topright", MP_TOP_RIGHT); - declare("bottomleft", MP_BOTTOM_LEFT); - declare("bottomright", MP_BOTTOM_RIGHT); -} - -LLMenuButton::Params::Params() -: menu_filename("menu_filename"), - position("menu_position", MP_BOTTOM_LEFT) -{ - addSynonym(position, "position"); -} - - -LLMenuButton::LLMenuButton(const LLMenuButton::Params& p) -: LLButton(p), - mIsMenuShown(false), - mMenuPosition(p.position), - mOwnMenu(false) -{ - std::string menu_filename = p.menu_filename; - - setMenu(menu_filename, mMenuPosition); - updateMenuOrigin(); -} - -LLMenuButton::~LLMenuButton() -{ - cleanup(); -} - -boost::signals2::connection LLMenuButton::setMouseDownCallback( const mouse_signal_t::slot_type& cb ) -{ - return LLUICtrl::setMouseDownCallback(cb); -} - -void LLMenuButton::hideMenu() -{ - LLToggleableMenu* menu = getMenu(); - if (menu) - { - menu->setVisible(false); - } -} - -LLToggleableMenu* LLMenuButton::getMenu() -{ - return dynamic_cast(mMenuHandle.get()); -} - -void LLMenuButton::setMenu(const std::string& menu_filename, EMenuPosition position /*MP_TOP_LEFT*/) -{ - if (menu_filename.empty()) - { - return; - } - - llassert(LLMenuGL::sMenuContainer != NULL); - LLToggleableMenu* menu = LLUICtrlFactory::getInstance()->createFromFile(menu_filename, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); - if (!menu) - { - LL_WARNS() << "Error loading menu_button menu" << LL_ENDL; - return; - } - - setMenu(menu, position, true); -} - -void LLMenuButton::setMenu(LLToggleableMenu* menu, EMenuPosition position /*MP_TOP_LEFT*/, bool take_ownership /*false*/) -{ - if (!menu) return; - - cleanup(); // destroy the previous memnu if we own it - - mMenuHandle = menu->getHandle(); - mMenuPosition = position; - mOwnMenu = take_ownership; - - menu->setVisibilityChangeCallback(boost::bind(&LLMenuButton::onMenuVisibilityChange, this, _2)); -} - -bool LLMenuButton::handleKeyHere(KEY key, MASK mask ) -{ - if (!getMenu()) return false; - - if( KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) - { - // *HACK: We emit the mouse down signal to fire the callback bound to the - // menu emerging event before actually displaying the menu. See STORM-263. - LLUICtrl::handleMouseDown(-1, -1, MASK_NONE); - - toggleMenu(); - return true; - } - - LLToggleableMenu* menu = getMenu(); - if (menu && menu->getVisible() && key == KEY_ESCAPE && mask == MASK_NONE) - { - menu->setVisible(false); - return true; - } - - return false; -} - -bool LLMenuButton::handleMouseDown(S32 x, S32 y, MASK mask) -{ - LLButton::handleMouseDown(x, y, mask); - - toggleMenu(); - - return true; -} - -void LLMenuButton::toggleMenu() -{ - if (mValidateSignal && !(*mValidateSignal)(this, LLSD())) - { - return; - } - - LLToggleableMenu* menu = getMenu(); - if (!menu) return; - - // Store the button rectangle to toggle menu visibility if a mouse event - // occurred inside or outside the button rect. - menu->setButtonRect(this); - - if (!menu->toggleVisibility() && mIsMenuShown) - { - setForcePressedState(false); - mIsMenuShown = false; - } - else - { - menu->buildDrawLabels(); - menu->arrangeAndClear(); - menu->updateParent(LLMenuGL::sMenuContainer); - - updateMenuOrigin(); - - LLMenuGL::showPopup(getParent(), menu, mX, mY); - - setForcePressedState(true); - mIsMenuShown = true; - } -} - -void LLMenuButton::updateMenuOrigin() -{ - LLToggleableMenu* menu = getMenu(); - if (!menu) return; - - LLRect rect = getRect(); - - switch (mMenuPosition) - { - case MP_TOP_LEFT: - { - mX = rect.mLeft; - mY = rect.mTop + menu->getRect().getHeight(); - break; - } - case MP_TOP_RIGHT: - { - const LLRect& menu_rect = menu->getRect(); - mX = rect.mRight - menu_rect.getWidth(); - mY = rect.mTop + menu_rect.getHeight(); - break; - } - case MP_BOTTOM_LEFT: - { - mX = rect.mLeft; - mY = rect.mBottom; - break; - } - case MP_BOTTOM_RIGHT: - { - const LLRect& menu_rect = menu->getRect(); - mX = rect.mRight - menu_rect.getWidth(); - mY = rect.mBottom; - break; - } - } -} - -void LLMenuButton::onMenuVisibilityChange(const LLSD& param) -{ - bool new_visibility = param["visibility"].asBoolean(); - bool is_closed_by_button_click = param["closed_by_button_click"].asBoolean(); - - // Reset the button "pressed" state only if the menu is shown by this particular - // menu button (not any other control) and is not being closed by a click on the button. - if (!new_visibility && !is_closed_by_button_click && mIsMenuShown) - { - setForcePressedState(false); - mIsMenuShown = false; - } -} - -void LLMenuButton::cleanup() -{ - if (mMenuHandle.get() && mOwnMenu) - { - mMenuHandle.get()->die(); - } -} +/** + * @file llbutton.cpp + * @brief LLButton base 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 "llmenubutton.h" + +// Linden library includes +#include "lltoggleablemenu.h" +#include "llstring.h" +#include "v4color.h" + +static LLDefaultChildRegistry::Register r("menu_button"); + +void LLMenuButton::MenuPositions::declareValues() +{ + declare("topleft", MP_TOP_LEFT); + declare("topright", MP_TOP_RIGHT); + declare("bottomleft", MP_BOTTOM_LEFT); + declare("bottomright", MP_BOTTOM_RIGHT); +} + +LLMenuButton::Params::Params() +: menu_filename("menu_filename"), + position("menu_position", MP_BOTTOM_LEFT) +{ + addSynonym(position, "position"); +} + + +LLMenuButton::LLMenuButton(const LLMenuButton::Params& p) +: LLButton(p), + mIsMenuShown(false), + mMenuPosition(p.position), + mOwnMenu(false) +{ + std::string menu_filename = p.menu_filename; + + setMenu(menu_filename, mMenuPosition); + updateMenuOrigin(); +} + +LLMenuButton::~LLMenuButton() +{ + cleanup(); +} + +boost::signals2::connection LLMenuButton::setMouseDownCallback( const mouse_signal_t::slot_type& cb ) +{ + return LLUICtrl::setMouseDownCallback(cb); +} + +void LLMenuButton::hideMenu() +{ + LLToggleableMenu* menu = getMenu(); + if (menu) + { + menu->setVisible(false); + } +} + +LLToggleableMenu* LLMenuButton::getMenu() +{ + return dynamic_cast(mMenuHandle.get()); +} + +void LLMenuButton::setMenu(const std::string& menu_filename, EMenuPosition position /*MP_TOP_LEFT*/) +{ + if (menu_filename.empty()) + { + return; + } + + llassert(LLMenuGL::sMenuContainer != NULL); + LLToggleableMenu* menu = LLUICtrlFactory::getInstance()->createFromFile(menu_filename, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); + if (!menu) + { + LL_WARNS() << "Error loading menu_button menu" << LL_ENDL; + return; + } + + setMenu(menu, position, true); +} + +void LLMenuButton::setMenu(LLToggleableMenu* menu, EMenuPosition position /*MP_TOP_LEFT*/, bool take_ownership /*false*/) +{ + if (!menu) return; + + cleanup(); // destroy the previous memnu if we own it + + mMenuHandle = menu->getHandle(); + mMenuPosition = position; + mOwnMenu = take_ownership; + + menu->setVisibilityChangeCallback(boost::bind(&LLMenuButton::onMenuVisibilityChange, this, _2)); +} + +bool LLMenuButton::handleKeyHere(KEY key, MASK mask ) +{ + if (!getMenu()) return false; + + if( KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) + { + // *HACK: We emit the mouse down signal to fire the callback bound to the + // menu emerging event before actually displaying the menu. See STORM-263. + LLUICtrl::handleMouseDown(-1, -1, MASK_NONE); + + toggleMenu(); + return true; + } + + LLToggleableMenu* menu = getMenu(); + if (menu && menu->getVisible() && key == KEY_ESCAPE && mask == MASK_NONE) + { + menu->setVisible(false); + return true; + } + + return false; +} + +bool LLMenuButton::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLButton::handleMouseDown(x, y, mask); + + toggleMenu(); + + return true; +} + +void LLMenuButton::toggleMenu() +{ + if (mValidateSignal && !(*mValidateSignal)(this, LLSD())) + { + return; + } + + LLToggleableMenu* menu = getMenu(); + if (!menu) return; + + // Store the button rectangle to toggle menu visibility if a mouse event + // occurred inside or outside the button rect. + menu->setButtonRect(this); + + if (!menu->toggleVisibility() && mIsMenuShown) + { + setForcePressedState(false); + mIsMenuShown = false; + } + else + { + menu->buildDrawLabels(); + menu->arrangeAndClear(); + menu->updateParent(LLMenuGL::sMenuContainer); + + updateMenuOrigin(); + + LLMenuGL::showPopup(getParent(), menu, mX, mY); + + setForcePressedState(true); + mIsMenuShown = true; + } +} + +void LLMenuButton::updateMenuOrigin() +{ + LLToggleableMenu* menu = getMenu(); + if (!menu) return; + + LLRect rect = getRect(); + + switch (mMenuPosition) + { + case MP_TOP_LEFT: + { + mX = rect.mLeft; + mY = rect.mTop + menu->getRect().getHeight(); + break; + } + case MP_TOP_RIGHT: + { + const LLRect& menu_rect = menu->getRect(); + mX = rect.mRight - menu_rect.getWidth(); + mY = rect.mTop + menu_rect.getHeight(); + break; + } + case MP_BOTTOM_LEFT: + { + mX = rect.mLeft; + mY = rect.mBottom; + break; + } + case MP_BOTTOM_RIGHT: + { + const LLRect& menu_rect = menu->getRect(); + mX = rect.mRight - menu_rect.getWidth(); + mY = rect.mBottom; + break; + } + } +} + +void LLMenuButton::onMenuVisibilityChange(const LLSD& param) +{ + bool new_visibility = param["visibility"].asBoolean(); + bool is_closed_by_button_click = param["closed_by_button_click"].asBoolean(); + + // Reset the button "pressed" state only if the menu is shown by this particular + // menu button (not any other control) and is not being closed by a click on the button. + if (!new_visibility && !is_closed_by_button_click && mIsMenuShown) + { + setForcePressedState(false); + mIsMenuShown = false; + } +} + +void LLMenuButton::cleanup() +{ + if (mMenuHandle.get() && mOwnMenu) + { + mMenuHandle.get()->die(); + } +} diff --git a/indra/llui/llmenubutton.h b/indra/llui/llmenubutton.h index 4b66d236b7..a77ae7dae7 100644 --- a/indra/llui/llmenubutton.h +++ b/indra/llui/llmenubutton.h @@ -1,101 +1,101 @@ -/** - * @file llbutton.h - * @brief Header for buttons - * - * $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$ - */ - -#ifndef LL_LLMENUBUTTON_H -#define LL_LLMENUBUTTON_H - -#include "llbutton.h" - -class LLToggleableMenu; - -class LLMenuButton -: public LLButton -{ - LOG_CLASS(LLMenuButton); - -public: - typedef enum e_menu_position - { - MP_TOP_LEFT, - MP_TOP_RIGHT, - MP_BOTTOM_LEFT, - MP_BOTTOM_RIGHT - } EMenuPosition; - - struct MenuPositions - : public LLInitParam::TypeValuesHelper - { - static void declareValues(); - }; - - struct Params - : public LLInitParam::Block - { - // filename for it's toggleable menu - Optional menu_filename; - Optional position; - - Params(); - }; - - - - boost::signals2::connection setMouseDownCallback( const mouse_signal_t::slot_type& cb ); - - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask ); - - void hideMenu(); - - LLToggleableMenu* getMenu(); - void setMenu(const std::string& menu_filename, EMenuPosition position = MP_TOP_LEFT); - void setMenu(LLToggleableMenu* menu, EMenuPosition position = MP_TOP_LEFT, bool take_ownership = false); - - void setMenuPosition(EMenuPosition position) { mMenuPosition = position; } - -protected: - friend class LLUICtrlFactory; - LLMenuButton(const Params&); - ~LLMenuButton(); - - void toggleMenu(); - void updateMenuOrigin(); - - void onMenuVisibilityChange(const LLSD& param); - -private: - void cleanup(); - - LLHandle mMenuHandle; - bool mIsMenuShown; - EMenuPosition mMenuPosition; - S32 mX; - S32 mY; - bool mOwnMenu; // true if we manage the menu lifetime -}; - - -#endif // LL_LLMENUBUTTON_H +/** + * @file llbutton.h + * @brief Header for buttons + * + * $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$ + */ + +#ifndef LL_LLMENUBUTTON_H +#define LL_LLMENUBUTTON_H + +#include "llbutton.h" + +class LLToggleableMenu; + +class LLMenuButton +: public LLButton +{ + LOG_CLASS(LLMenuButton); + +public: + typedef enum e_menu_position + { + MP_TOP_LEFT, + MP_TOP_RIGHT, + MP_BOTTOM_LEFT, + MP_BOTTOM_RIGHT + } EMenuPosition; + + struct MenuPositions + : public LLInitParam::TypeValuesHelper + { + static void declareValues(); + }; + + struct Params + : public LLInitParam::Block + { + // filename for it's toggleable menu + Optional menu_filename; + Optional position; + + Params(); + }; + + + + boost::signals2::connection setMouseDownCallback( const mouse_signal_t::slot_type& cb ); + + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask ); + + void hideMenu(); + + LLToggleableMenu* getMenu(); + void setMenu(const std::string& menu_filename, EMenuPosition position = MP_TOP_LEFT); + void setMenu(LLToggleableMenu* menu, EMenuPosition position = MP_TOP_LEFT, bool take_ownership = false); + + void setMenuPosition(EMenuPosition position) { mMenuPosition = position; } + +protected: + friend class LLUICtrlFactory; + LLMenuButton(const Params&); + ~LLMenuButton(); + + void toggleMenu(); + void updateMenuOrigin(); + + void onMenuVisibilityChange(const LLSD& param); + +private: + void cleanup(); + + LLHandle mMenuHandle; + bool mIsMenuShown; + EMenuPosition mMenuPosition; + S32 mX; + S32 mY; + bool mOwnMenu; // true if we manage the menu lifetime +}; + + +#endif // LL_LLMENUBUTTON_H diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 9a058f420a..279f5628e1 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -1,4404 +1,4404 @@ -/** - * @file llmenugl.cpp - * @brief LLMenuItemGL base 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$ - */ - -//***************************************************************************** -// -// This file contains the opengl based menu implementation. -// -// NOTES: A menu label is split into 4 columns. The left column, the -// label colum, the accelerator column, and the right column. The left -// column is used for displaying boolean values for toggle and check -// controls. The right column is used for submenus. -// -//***************************************************************************** - -//#include "llviewerprecompiledheaders.h" -#include "linden_common.h" - -#include "llmenugl.h" - -#include "llgl.h" -#include "llmath.h" -#include "llrender.h" -#include "llfocusmgr.h" -#include "llcoord.h" -#include "llwindow.h" -#include "llcriticaldamp.h" -#include "lluictrlfactory.h" - -#include "llbutton.h" -#include "llfontgl.h" -#include "llresmgr.h" -#include "lltrans.h" -#include "llui.h" - -#include "llstl.h" - -#include "v2math.h" -#include -#include - -// static -LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL; -view_listener_t::listener_map_t view_listener_t::sListeners; - -S32 MENU_BAR_HEIGHT = 18; -S32 MENU_BAR_WIDTH = 410; - -///============================================================================ -/// Local function declarations, constants, enums, and typedefs -///============================================================================ - -const S32 LABEL_BOTTOM_PAD_PIXELS = 2; - -const U32 LEFT_PAD_PIXELS = 3; -const U32 LEFT_WIDTH_PIXELS = 15; -const U32 LEFT_PLAIN_PIXELS = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS; - -const U32 RIGHT_PAD_PIXELS = 7; -const U32 RIGHT_WIDTH_PIXELS = 15; -const U32 RIGHT_PLAIN_PIXELS = RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS; - -const U32 PLAIN_PAD_PIXELS = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS + RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS; - -const U32 BRIEF_PAD_PIXELS = 2; - -const U32 SEPARATOR_HEIGHT_PIXELS = 8; -const S32 TEAROFF_SEPARATOR_HEIGHT_PIXELS = 10; -const S32 MENU_ITEM_PADDING = 4; - -const std::string SEPARATOR_NAME("separator"); -const std::string VERTICAL_SEPARATOR_LABEL( "|" ); - -const std::string LLMenuGL::BOOLEAN_TRUE_PREFIX( "\xE2\x9C\x94" ); // U+2714 HEAVY CHECK MARK -const std::string LLMenuGL::BRANCH_SUFFIX( "\xe2\x96\xb8" ); // U+25B6 BLACK RIGHT-POINTING TRIANGLE -const std::string LLMenuGL::ARROW_UP ("^^^^^^^"); -const std::string LLMenuGL::ARROW_DOWN("vvvvvvv"); - -const F32 MAX_MOUSE_SLOPE_SUB_MENU = 0.9f; - -bool LLMenuGL::sKeyboardMode = false; - -LLHandle LLMenuHolderGL::sItemLastSelectedHandle; -LLFrameTimer LLMenuHolderGL::sItemActivationTimer; - -const F32 ACTIVATE_HIGHLIGHT_TIME = 0.3f; - -static MenuRegistry::Register register_menu_item("menu_item"); -static MenuRegistry::Register register_separator("menu_item_separator"); -static MenuRegistry::Register register_menu_item_call("menu_item_call"); -static MenuRegistry::Register register_menu_item_check("menu_item_check"); -// Created programmatically but we need to specify custom colors in xml -static MenuRegistry::Register register_menu_item_tear_off("menu_item_tear_off"); -static MenuRegistry::Register register_menu("menu"); - -static LLDefaultChildRegistry::Register register_menu_default("menu"); - - - -///============================================================================ -/// Class LLMenuItemGL -///============================================================================ - -LLMenuItemGL::Params::Params() -: shortcut("shortcut"), - jump_key("jump_key", KEY_NONE), - use_mac_ctrl("use_mac_ctrl", false), - allow_key_repeat("allow_key_repeat", false), - rect("rect"), - left("left"), - top("top"), - right("right"), - bottom("bottom"), - width("width"), - height("height"), - bottom_delta("bottom_delta"), - left_delta("left_delta"), - enabled_color("enabled_color"), - disabled_color("disabled_color"), - highlight_bg_color("highlight_bg_color"), - highlight_fg_color("highlight_fg_color") -{ - changeDefault(mouse_opaque, true); -} - -// Default constructor -LLMenuItemGL::LLMenuItemGL(const LLMenuItemGL::Params& p) -: LLUICtrl(p), - mJumpKey(p.jump_key), - mAllowKeyRepeat(p.allow_key_repeat), - mHighlight( false ), - mGotHover( false ), - mBriefItem( false ), - mDrawTextDisabled( false ), - mFont(p.font), - mAcceleratorKey(KEY_NONE), - mAcceleratorMask(MASK_NONE), - mLabel(p.label.isProvided() ? p.label() : p.name()), - mEnabledColor(p.enabled_color()), - mDisabledColor(p.disabled_color()), - mHighlightBackground(p.highlight_bg_color()), - mHighlightForeground(p.highlight_fg_color()) -{ -#ifdef LL_DARWIN - // See if this Mac accelerator should really use the ctrl key and not get mapped to cmd - bool useMacCtrl = p.use_mac_ctrl; -#endif // LL_DARWIN - - std::string shortcut = p.shortcut; - if (shortcut.find("control") != shortcut.npos) - { -#ifdef LL_DARWIN - if ( useMacCtrl ) - { - mAcceleratorMask |= MASK_MAC_CONTROL; - } -#endif // LL_DARWIN - mAcceleratorMask |= MASK_CONTROL; - } - if (shortcut.find("alt") != shortcut.npos) - { - mAcceleratorMask |= MASK_ALT; - } - if (shortcut.find("shift") != shortcut.npos) - { - mAcceleratorMask |= MASK_SHIFT; - } - S32 pipe_pos = shortcut.rfind("|"); - std::string key_str = shortcut.substr(pipe_pos+1); - - LLKeyboard::keyFromString(key_str, &mAcceleratorKey); - - LL_DEBUGS("HotKeys") << "Process short cut key: shortcut: " << shortcut - << ", key str: " << key_str - << ", accelerator mask: " << mAcceleratorMask - << ", accelerator key: " << mAcceleratorKey - << LL_ENDL; -} - -//virtual -void LLMenuItemGL::setValue(const LLSD& value) -{ - setLabel(value.asString()); -} - -//virtual -LLSD LLMenuItemGL::getValue() const -{ - return getLabel(); -} - -//virtual -bool LLMenuItemGL::hasAccelerator(const KEY &key, const MASK &mask) const -{ - return (mAcceleratorKey == key) && (mAcceleratorMask == mask); -} - -//virtual -bool LLMenuItemGL::handleAcceleratorKey(KEY key, MASK mask) -{ - if( getEnabled() && (!gKeyboard->getKeyRepeated(key) || mAllowKeyRepeat) && (key == mAcceleratorKey) && (mask == (mAcceleratorMask & MASK_NORMALKEYS)) ) - { - onCommit(); - return true; - } - return false; -} - -bool LLMenuItemGL::handleHover(S32 x, S32 y, MASK mask) -{ - getWindow()->setCursor(UI_CURSOR_ARROW); - return true; -} - -//virtual -bool LLMenuItemGL::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - return LLUICtrl::handleRightMouseDown(x,y,mask); -} - -void LLMenuItemGL::onMouseEnter(S32 x, S32 y, MASK mask) -{ - setHover(true); - LLUICtrl::onMouseEnter(x,y,mask); -} - -void LLMenuItemGL::onMouseLeave(S32 x, S32 y, MASK mask) -{ - setHover(false); - LLUICtrl::onMouseLeave(x,y,mask); -} - -//virtual -bool LLMenuItemGL::handleRightMouseUp(S32 x, S32 y, MASK mask) -{ - // If this event came from a right-click context menu spawn, - // process as a left-click to allow menu items to be hit - if (LLMenuHolderGL::sContextMenuSpawnPos.mX != S32_MAX - || LLMenuHolderGL::sContextMenuSpawnPos.mY != S32_MAX) - { - bool handled = handleMouseUp(x, y, mask); - return handled; - } - return LLUICtrl::handleRightMouseUp(x,y,mask); -} - -// This function checks to see if the accelerator key is already in use; -// if not, it will be added to the list -bool LLMenuItemGL::addToAcceleratorList(std::list *listp) -{ - LLMenuKeyboardBinding *accelerator = NULL; - - if (mAcceleratorKey != KEY_NONE) - { - std::list::iterator list_it; - for (list_it = listp->begin(); list_it != listp->end(); ++list_it) - { - accelerator = *list_it; - if ((accelerator->mKey == mAcceleratorKey) && (accelerator->mMask == (mAcceleratorMask & MASK_NORMALKEYS))) - { - - // *NOTE: get calling code to throw up warning or route - // warning messages back to app-provided output - // std::string warning; - // warning.append("Duplicate key binding <"); - // appendAcceleratorString( warning ); - // warning.append("> for menu items:\n "); - // warning.append(accelerator->mName); - // warning.append("\n "); - // warning.append(mLabel); - - // LL_WARNS() << warning << LL_ENDL; - // LLAlertDialog::modalAlert(warning); - return false; - } - } - if (!accelerator) - { - accelerator = new LLMenuKeyboardBinding; - if (accelerator) - { - accelerator->mKey = mAcceleratorKey; - accelerator->mMask = (mAcceleratorMask & MASK_NORMALKEYS); -// accelerator->mName = mLabel; - } - listp->push_back(accelerator);//addData(accelerator); - } - } - return true; -} - -// This function appends the character string representation of -// the current accelerator key and mask to the provided string. -void LLMenuItemGL::appendAcceleratorString( std::string& st ) const -{ - st = LLKeyboard::stringFromAccelerator( mAcceleratorMask, mAcceleratorKey ); - LL_DEBUGS("HotKeys") << "appendAcceleratorString: " << st << LL_ENDL; -} - -void LLMenuItemGL::setJumpKey(KEY key) -{ - mJumpKey = LLStringOps::toUpper((char)key); -} - - -// virtual -U32 LLMenuItemGL::getNominalHeight( void ) const -{ - return mFont->getLineHeight() + MENU_ITEM_PADDING; -} - -//virtual -void LLMenuItemGL::setBriefItem(bool brief) -{ - mBriefItem = brief; -} - -//virtual -bool LLMenuItemGL::isBriefItem() const -{ - return mBriefItem; -} - -// Get the parent menu for this item -LLMenuGL* LLMenuItemGL::getMenu() const -{ - return (LLMenuGL*) getParent(); -} - - -// getNominalWidth() - returns the normal width of this control in -// pixels - this is used for calculating the widest item, as well as -// for horizontal arrangement. -U32 LLMenuItemGL::getNominalWidth( void ) const -{ - U32 width; - - if (mBriefItem) - { - width = BRIEF_PAD_PIXELS; - } - else - { - width = PLAIN_PAD_PIXELS; - } - - if( KEY_NONE != mAcceleratorKey ) - { - width += getMenu()->getShortcutPad(); - std::string temp; - appendAcceleratorString( temp ); - width += mFont->getWidth( temp ); - } - width += mFont->getWidth( mLabel.getWString().c_str() ); - return width; -} - -// called to rebuild the draw label -void LLMenuItemGL::buildDrawLabel( void ) -{ - mDrawAccelLabel.clear(); - std::string st = mDrawAccelLabel.getString(); - appendAcceleratorString( st ); - mDrawAccelLabel = st; -} - -void LLMenuItemGL::onCommit( void ) -{ - // Check torn-off status to allow left-arrow keyboard navigation back - // to parent menu. - // Also, don't hide if item triggered by keyboard shortcut (and hence - // parent not visible). - if (!getMenu()->getTornOff() - && getMenu()->getVisible()) - { - LLMenuGL::sMenuContainer->hideMenus(); - } - - LLUICtrl::onCommit(); -} - -// set the hover status (called by it's menu) - void LLMenuItemGL::setHighlight( bool highlight ) -{ - if (highlight) - { - getMenu()->clearHoverItem(); - } - - if (mHighlight != highlight) - { - dirtyRect(); - } - - mHighlight = highlight; -} - - -bool LLMenuItemGL::handleKeyHere( KEY key, MASK mask ) -{ - if (getHighlight() && - getMenu()->isOpen()) - { - if (key == KEY_UP) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - getMenu()->highlightPrevItem(this); - return true; - } - else if (key == KEY_DOWN) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - getMenu()->highlightNextItem(this); - return true; - } - else if (key == KEY_RETURN && mask == MASK_NONE) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - onCommit(); - return true; - } - } - - return false; -} - -bool LLMenuItemGL::handleMouseUp( S32 x, S32 y, MASK mask) -{ - // switch to mouse navigation mode - LLMenuGL::setKeyboardMode(false); - - onCommit(); - make_ui_sound("UISndClickRelease"); - return LLView::handleMouseUp(x, y, mask); -} - -bool LLMenuItemGL::handleMouseDown( S32 x, S32 y, MASK mask) -{ - // switch to mouse navigation mode - LLMenuGL::setKeyboardMode(false); - - setHighlight(true); - return LLView::handleMouseDown(x, y, mask); -} - -bool LLMenuItemGL::handleScrollWheel( S32 x, S32 y, S32 clicks ) -{ - // If the menu is scrollable let it handle the wheel event. - return !getMenu()->isScrollable(); -} - -void LLMenuItemGL::draw( void ) -{ - // *FIX: This can be optimized by using switches. Want to avoid - // that until the functionality is finalized. - - // HACK: Brief items don't highlight. Pie menu takes care of it. JC - // let disabled items be highlighted, just don't draw them as such - if( getEnabled() && getHighlight() && !mBriefItem) - { - gGL.color4fv( mHighlightBackground.get().mV ); - - gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - } - - LLColor4 color; - - if ( getEnabled() && getHighlight() ) - { - color = mHighlightForeground.get(); - } - else if( getEnabled() && !mDrawTextDisabled ) - { - color = mEnabledColor.get(); - } - else - { - color = mDisabledColor.get(); - } - - // Highlight if needed - if( ll::ui::SearchableControl::getHighlighted() ) - color = ll::ui::SearchableControl::getHighlightColor(); - - // Draw the text on top. - if (mBriefItem) - { - mFont->render( mLabel, 0, BRIEF_PAD_PIXELS / 2, 0, color, - LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL); - } - else - { - if( !mDrawBoolLabel.empty() ) - { - mFont->render( mDrawBoolLabel.getWString(), 0, (F32)LEFT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, - LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); - } - mFont->render( mLabel.getWString(), 0, (F32)LEFT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, - LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); - if( !mDrawAccelLabel.empty() ) - { - mFont->render( mDrawAccelLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, - LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); - } - if( !mDrawBranchLabel.empty() ) - { - mFont->render( mDrawBranchLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, - LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); - } - } - - // underline "jump" key only when keyboard navigation has been initiated - if (getMenu()->jumpKeysActive() && LLMenuGL::getKeyboardMode()) - { - std::string upper_case_label = mLabel.getString(); - LLStringUtil::toUpper(upper_case_label); - std::string::size_type offset = upper_case_label.find(mJumpKey); - if (offset != std::string::npos) - { - S32 x_begin = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel, 0, offset); - S32 x_end = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel, 0, offset + 1); - gl_line_2d(x_begin, (MENU_ITEM_PADDING / 2) + 1, x_end, (MENU_ITEM_PADDING / 2) + 1); - } - } -} - -bool LLMenuItemGL::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - mLabel.setArg(key, text); - return true; -} - -void LLMenuItemGL::onVisibilityChange(bool new_visibility) -{ - if (getMenu()) - { - getMenu()->needsArrange(); - } - LLView::onVisibilityChange(new_visibility); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemSeparatorGL -// -// This class represents a separator. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -LLMenuItemSeparatorGL::Params::Params() - : on_visible("on_visible") -{ -} - -LLMenuItemSeparatorGL::LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p) : - LLMenuItemGL( p ) -{ - if (p.on_visible.isProvided()) - { - mVisibleSignal.connect(initEnableCallback(p.on_visible)); - } -} - -//virtual -U32 LLMenuItemSeparatorGL::getNominalHeight( void ) const -{ - return SEPARATOR_HEIGHT_PIXELS; -} - -void LLMenuItemSeparatorGL::draw( void ) -{ - gGL.color4fv( mDisabledColor.get().mV ); - const S32 y = getRect().getHeight() / 2; - const S32 PAD = 6; - gl_line_2d( PAD, y, getRect().getWidth() - PAD, y ); -} - -void LLMenuItemSeparatorGL::buildDrawLabel( void ) -{ - if (mVisibleSignal.num_slots() > 0) - { - bool visible = mVisibleSignal(this, LLSD()); - setVisible(visible); - } -} - -bool LLMenuItemSeparatorGL::handleMouseDown(S32 x, S32 y, MASK mask) -{ - LLMenuGL* parent_menu = getMenu(); - if (y > getRect().getHeight() / 2) - { - // the menu items are in the child list in bottom up order - LLView* prev_menu_item = parent_menu->findNextSibling(this); - return (prev_menu_item && prev_menu_item->getVisible() && prev_menu_item->getEnabled()) ? prev_menu_item->handleMouseDown(x, prev_menu_item->getRect().getHeight(), mask) : false; - } - else - { - LLView* next_menu_item = parent_menu->findPrevSibling(this); - return (next_menu_item && next_menu_item->getVisible() && next_menu_item->getEnabled()) ? next_menu_item->handleMouseDown(x, 0, mask) : false; - } -} - -bool LLMenuItemSeparatorGL::handleMouseUp(S32 x, S32 y, MASK mask) -{ - LLMenuGL* parent_menu = getMenu(); - if (y > getRect().getHeight() / 2) - { - LLView* prev_menu_item = parent_menu->findNextSibling(this); - return (prev_menu_item && prev_menu_item->getVisible() && prev_menu_item->getEnabled()) ? prev_menu_item->handleMouseUp(x, prev_menu_item->getRect().getHeight(), mask) : false; - } - else - { - LLView* next_menu_item = parent_menu->findPrevSibling(this); - return (next_menu_item && next_menu_item->getVisible() && next_menu_item->getEnabled()) ? next_menu_item->handleMouseUp(x, 0, mask) : false; - } -} - -bool LLMenuItemSeparatorGL::handleHover(S32 x, S32 y, MASK mask) -{ - LLMenuGL* parent_menu = getMenu(); - if (y > getRect().getHeight() / 2) - { - parent_menu->highlightPrevItem(this, false); - return false; - } - else - { - parent_menu->highlightNextItem(this, false); - return false; - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemVerticalSeparatorGL -// -// This class represents a vertical separator. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuItemVerticalSeparatorGL -: public LLMenuItemSeparatorGL -{ -public: - LLMenuItemVerticalSeparatorGL( void ); - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) { return false; } -}; - -LLMenuItemVerticalSeparatorGL::LLMenuItemVerticalSeparatorGL( void ) -{ - setLabel( VERTICAL_SEPARATOR_LABEL ); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemTearOffGL -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -LLMenuItemTearOffGL::LLMenuItemTearOffGL(const LLMenuItemTearOffGL::Params& p) -: LLMenuItemGL(p) -{ -} - -// Returns the first floater ancestor if there is one -LLFloater* LLMenuItemTearOffGL::getParentFloater() -{ - LLView* parent_view = getMenu(); - - while (parent_view) - { - if (dynamic_cast(parent_view)) - { - return dynamic_cast(parent_view); - } - - bool parent_is_menu = dynamic_cast(parent_view) && !dynamic_cast(parent_view); - - if (parent_is_menu) - { - // use menu parent - parent_view = dynamic_cast(parent_view)->getParentMenuItem(); - } - else - { - // just use regular view parent - parent_view = parent_view->getParent(); - } - } - - return NULL; -} - -void LLMenuItemTearOffGL::onCommit() -{ - if (getMenu()->getTornOff()) - { - LLTearOffMenu * torn_off_menu = dynamic_cast(getMenu()->getParent()); - if (torn_off_menu) - { - torn_off_menu->closeFloater(); - } - } - else - { - // transfer keyboard focus and highlight to first real item in list - if (getHighlight()) - { - getMenu()->highlightNextItem(this); - } - - getMenu()->needsArrange(); - - LLFloater* parent_floater = getParentFloater(); - LLFloater* tear_off_menu = LLTearOffMenu::create(getMenu()); - - if (tear_off_menu) - { - if (parent_floater) - { - parent_floater->addDependentFloater(tear_off_menu, false); - } - - // give focus to torn off menu because it will have - // been taken away when parent menu closes - tear_off_menu->setFocus(true); - } - } - LLMenuItemGL::onCommit(); -} - -void LLMenuItemTearOffGL::draw() -{ - // disabled items can be highlighted, but shouldn't render as such - if( getEnabled() && getHighlight() && !isBriefItem()) - { - gGL.color4fv( mHighlightBackground.get().mV ); - gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - } - - if (getEnabled()) - { - gGL.color4fv( mEnabledColor.get().mV ); - } - else - { - gGL.color4fv( mDisabledColor.get().mV ); - } - const S32 y = getRect().getHeight() / 3; - const S32 PAD = 6; - gl_line_2d( PAD, y, getRect().getWidth() - PAD, y ); - gl_line_2d( PAD, y * 2, getRect().getWidth() - PAD, y * 2 ); -} - -U32 LLMenuItemTearOffGL::getNominalHeight( void ) const -{ - return TEAROFF_SEPARATOR_HEIGHT_PIXELS; -} - -///============================================================================ -/// Class LLMenuItemCallGL -///============================================================================ - -LLMenuItemCallGL::LLMenuItemCallGL(const LLMenuItemCallGL::Params& p) -: LLMenuItemGL(p) -{ -} - -void LLMenuItemCallGL::initFromParams(const Params& p) -{ - if (p.on_visible.isProvided()) - { - mVisibleSignal.connect(initEnableCallback(p.on_visible)); - } - if (p.on_enable.isProvided()) - { - setEnableCallback(initEnableCallback(p.on_enable)); - // Set the enabled control variable (for backwards compatability) - if (p.on_enable.control_name.isProvided() && !p.on_enable.control_name().empty()) - { - LLControlVariable* control = findControl(p.on_enable.control_name()); - if (control) - { - setEnabledControlVariable(control); - } - else - { - LL_WARNS() << "Failed to assign 'enabled' control variable to menu " << getName() - << ": control " << p.on_enable.control_name() - << " does not exist." << LL_ENDL; - } - } - } - if (p.on_click.isProvided()) - { - setCommitCallback(initCommitCallback(p.on_click)); - } - - LLUICtrl::initFromParams(p); -} - -void LLMenuItemCallGL::onCommit( void ) -{ - // RN: menu item can be deleted in callback, so beware - getMenu()->setItemLastSelected( this ); - - LLMenuItemGL::onCommit(); -} - -void LLMenuItemCallGL::updateEnabled( void ) -{ - if (mEnableSignal.num_slots() > 0) - { - bool enabled = mEnableSignal(this, LLSD()); - if (mEnabledControlVariable) - { - if (!enabled) - { - // callback overrides control variable; this will call setEnabled() - mEnabledControlVariable->set(false); - } - } - else - { - setEnabled(enabled); - } - } -} - -void LLMenuItemCallGL::updateVisible( void ) -{ - if (mVisibleSignal.num_slots() > 0) - { - bool visible = mVisibleSignal(this, LLSD()); - setVisible(visible); - } -} - -void LLMenuItemCallGL::buildDrawLabel( void ) -{ - updateEnabled(); - updateVisible(); - LLMenuItemGL::buildDrawLabel(); -} - -bool LLMenuItemCallGL::handleKeyHere( KEY key, MASK mask ) -{ - return LLMenuItemGL::handleKeyHere(key, mask); -} - -bool LLMenuItemCallGL::handleAcceleratorKey( KEY key, MASK mask ) -{ - if( (!gKeyboard->getKeyRepeated(key) || getAllowKeyRepeat()) && (key == mAcceleratorKey) && (mask == (mAcceleratorMask & MASK_NORMALKEYS)) ) - { - updateEnabled(); - if (getEnabled()) - { - onCommit(); - return true; - } - } - return false; -} - -// handleRightMouseUp moved into base class LLMenuItemGL so clicks are -// handled for all menu item types - -///============================================================================ -/// Class LLMenuItemCheckGL -///============================================================================ -LLMenuItemCheckGL::LLMenuItemCheckGL (const LLMenuItemCheckGL::Params& p) -: LLMenuItemCallGL(p) -{ -} - -void LLMenuItemCheckGL::initFromParams(const Params& p) -{ - if (p.on_check.isProvided()) - { - setCheckCallback(initEnableCallback(p.on_check)); - // Set the control name (for backwards compatability) - if (p.on_check.control_name.isProvided() && !p.on_check.control_name().empty()) - { - setControlName(p.on_check.control_name()); - } - } - - LLMenuItemCallGL::initFromParams(p); -} - -void LLMenuItemCheckGL::onCommit( void ) -{ - LLMenuItemCallGL::onCommit(); -} - -//virtual -void LLMenuItemCheckGL::setValue(const LLSD& value) -{ - LLUICtrl::setValue(value); - if(value.asBoolean()) - { - mDrawBoolLabel = LLMenuGL::BOOLEAN_TRUE_PREFIX; - } - else - { - mDrawBoolLabel.clear(); - } -} - -//virtual -LLSD LLMenuItemCheckGL::getValue() const -{ - // Get our boolean value from the view model. - // If we don't override this method then the implementation from - // LLMenuItemGL will return a string. (EXT-8501) - return LLUICtrl::getValue(); -} - -// called to rebuild the draw label -void LLMenuItemCheckGL::buildDrawLabel( void ) -{ - // Note: mCheckSignal() returns true if no callbacks are set - bool checked = mCheckSignal(this, LLSD()); - if (mControlVariable) - { - if (!checked) - setControlValue(false); // callback overrides control variable; this will call setValue() - } - else - { - setValue(checked); - } - if(getValue().asBoolean()) - { - mDrawBoolLabel = LLMenuGL::BOOLEAN_TRUE_PREFIX; - } - else - { - mDrawBoolLabel.clear(); - } - LLMenuItemCallGL::buildDrawLabel(); -} - -///============================================================================ -/// Class LLMenuItemBranchGL -///============================================================================ -LLMenuItemBranchGL::LLMenuItemBranchGL(const LLMenuItemBranchGL::Params& p) - : LLMenuItemGL(p) -{ - LLMenuGL* branch = p.branch; - if (branch) - { - mBranchHandle = branch->getHandle(); - branch->setVisible(false); - branch->setParentMenuItem(this); - } -} - -LLMenuItemBranchGL::~LLMenuItemBranchGL() -{ - if (mBranchHandle.get()) - { - mBranchHandle.get()->die(); - } -} - - - -// virtual -LLView* LLMenuItemBranchGL::getChildView(const std::string& name, bool recurse) const -{ - LLMenuGL* branch = getBranch(); - if (branch) - { - if (branch->getName() == name) - { - return branch; - } - - // Always recurse on branches - return branch->getChildView(name, recurse); - } - - return LLView::getChildView(name, recurse); -} - -LLView* LLMenuItemBranchGL::findChildView(const std::string& name, bool recurse) const -{ - LLMenuGL* branch = getBranch(); - if (branch) - { - if (branch->getName() == name) - { - return branch; - } - - // Always recurse on branches - return branch->findChildView(name, recurse); - } - - return LLView::findChildView(name, recurse); -} - -// virtual -bool LLMenuItemBranchGL::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // switch to mouse navigation mode - LLMenuGL::setKeyboardMode(false); - - onCommit(); - make_ui_sound("UISndClickRelease"); - return true; -} - -bool LLMenuItemBranchGL::hasAccelerator(const KEY &key, const MASK &mask) const -{ - return getBranch() && getBranch()->hasAccelerator(key, mask); -} - -bool LLMenuItemBranchGL::handleAcceleratorKey(KEY key, MASK mask) -{ - return getBranch() && getBranch()->handleAcceleratorKey(key, mask); -} - -// This function checks to see if the accelerator key is already in use; -// if not, it will be added to the list -bool LLMenuItemBranchGL::addToAcceleratorList(std::list *listp) -{ - LLMenuGL* branch = getBranch(); - if (!branch) - return false; - - U32 item_count = branch->getItemCount(); - LLMenuItemGL *item; - - while (item_count--) - { - if ((item = branch->getItem(item_count))) - { - return item->addToAcceleratorList(listp); - } - } - - return false; -} - - -// called to rebuild the draw label -void LLMenuItemBranchGL::buildDrawLabel( void ) -{ - mDrawAccelLabel.clear(); - std::string st = mDrawAccelLabel; - appendAcceleratorString( st ); - mDrawAccelLabel = st; - mDrawBranchLabel = LLMenuGL::BRANCH_SUFFIX; -} - -void LLMenuItemBranchGL::onCommit( void ) -{ - openMenu(); - - // keyboard navigation automatically propagates highlight to sub-menu - // to facilitate fast menu control via jump keys - if (LLMenuGL::getKeyboardMode() && getBranch() && !getBranch()->getHighlightedItem()) - { - getBranch()->highlightNextItem(NULL); - } - - LLUICtrl::onCommit(); -} - -bool LLMenuItemBranchGL::handleKey(KEY key, MASK mask, bool called_from_parent) -{ - bool handled = false; - if (getBranch() && called_from_parent) - { - handled = getBranch()->handleKey(key, mask, called_from_parent); - } - - if (!handled) - { - handled = LLMenuItemGL::handleKey(key, mask, called_from_parent); - } - - return handled; -} - -bool LLMenuItemBranchGL::handleUnicodeChar(llwchar uni_char, bool called_from_parent) -{ - bool handled = false; - if (getBranch() && called_from_parent) - { - handled = getBranch()->handleUnicodeChar(uni_char, true); - } - - if (!handled) - { - handled = LLMenuItemGL::handleUnicodeChar(uni_char, called_from_parent); - } - - return handled; -} - - -void LLMenuItemBranchGL::setHighlight( bool highlight ) -{ - if (highlight == getHighlight()) - return; - - LLMenuGL* branch = getBranch(); - if (!branch) - return; - - bool auto_open = getEnabled() && (!branch->getVisible() || branch->getTornOff()); - // torn off menus don't open sub menus on hover unless they have focus - LLFloater * menu_parent = dynamic_cast(getMenu()->getParent()); - if (getMenu()->getTornOff() && menu_parent && !menu_parent->hasFocus()) - { - auto_open = false; - } - // don't auto open torn off sub-menus (need to explicitly active menu item to give them focus) - if (branch->getTornOff()) - { - auto_open = false; - } - LLMenuItemGL::setHighlight(highlight); - if( highlight ) - { - if(auto_open) - { - openMenu(); - } - } - else - { - if (branch->getTornOff()) - { - LLFloater * branch_parent = dynamic_cast(branch->getParent()); - if (branch_parent) - { - branch_parent->setFocus(false); - } - branch->clearHoverItem(); - } - else - { - branch->setVisible( false ); - } - } -} - -void LLMenuItemBranchGL::draw() -{ - LLMenuItemGL::draw(); - if (getBranch() && getBranch()->getVisible() && !getBranch()->getTornOff()) - { - setHighlight(true); - } -} - -void LLMenuItemBranchGL::updateBranchParent(LLView* parentp) -{ - if (getBranch() && getBranch()->getParent() == NULL) - { - // make the branch menu a sibling of my parent menu - getBranch()->updateParent(parentp); - } -} - -void LLMenuItemBranchGL::onVisibilityChange(bool new_visibility) -{ - if (!new_visibility && getBranch() && !getBranch()->getTornOff()) - { - getBranch()->setVisible(false); - } - LLMenuItemGL::onVisibilityChange(new_visibility); -} - -bool LLMenuItemBranchGL::handleKeyHere(KEY key, MASK mask) -{ - LLMenuGL* branch = getBranch(); - if (!branch) - return LLMenuItemGL::handleKeyHere(key, mask); - - // an item is highlighted, my menu is open, and I have an active sub menu or we are in - // keyboard navigation mode - if (getHighlight() - && getMenu()->isOpen() - && (isActive() || LLMenuGL::getKeyboardMode())) - { - if (branch->getVisible() && key == KEY_LEFT) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - bool handled = branch->clearHoverItem(); - if (branch->getTornOff()) - { - LLFloater * branch_parent = dynamic_cast(branch->getParent()); - if (branch_parent) - { - branch_parent->setFocus(false); - } - } - if (handled && getMenu()->getTornOff()) - { - LLFloater * menu_parent = dynamic_cast(getMenu()->getParent()); - if (menu_parent) - { - menu_parent->setFocus(true); - } - } - return handled; - } - - if (key == KEY_RIGHT && !branch->getHighlightedItem()) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - LLMenuItemGL* itemp = branch->highlightNextItem(NULL); - if (itemp) - { - return true; - } - } - } - return LLMenuItemGL::handleKeyHere(key, mask); -} - -//virtual -bool LLMenuItemBranchGL::isActive() const -{ - return isOpen() && getBranch() && getBranch()->getHighlightedItem(); -} - -//virtual -bool LLMenuItemBranchGL::isOpen() const -{ - return getBranch() && getBranch()->isOpen(); -} - -void LLMenuItemBranchGL::openMenu() -{ - LLMenuGL* branch = getBranch(); - if (!branch) - return; - - if (branch->getTornOff()) - { - LLFloater * branch_parent = dynamic_cast(branch->getParent()); - if (branch_parent) - { - gFloaterView->bringToFront(branch_parent); - // this might not be necessary, as torn off branches don't get focus and hence no highligth - branch->highlightNextItem(NULL); - } - } - else if( !branch->getVisible() ) - { - // get valid rectangle for menus - const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect(); - - branch->arrange(); - - LLRect branch_rect = branch->getRect(); - // calculate root-view relative position for branch menu - S32 left = getRect().mRight; - S32 top = getRect().mTop - getRect().mBottom; - - localPointToOtherView(left, top, &left, &top, branch->getParent()); - - branch_rect.setLeftTopAndSize( left, top, - branch_rect.getWidth(), branch_rect.getHeight() ); - - if (branch->getCanTearOff()) - { - branch_rect.translate(0, TEAROFF_SEPARATOR_HEIGHT_PIXELS); - } - branch->setRect( branch_rect ); - - // if branch extends outside of menu region change the direction it opens in - S32 x, y; - S32 delta_x = 0; - S32 delta_y = 0; - branch->localPointToOtherView( 0, 0, &x, &y, branch->getParent() ); - if( y < menu_region_rect.mBottom ) - { - // open upwards if menu extends past bottom - // adjust by the height of the menu item branch since it is a submenu - if (y + 2 * branch_rect.getHeight() - getRect().getHeight() > menu_region_rect.mTop) - { - // overlaps with top border, align with top - delta_y = menu_region_rect.mTop - y - branch_rect.getHeight(); - } - else - { - delta_y = branch_rect.getHeight() - getRect().getHeight(); - } - } - - if( x + branch_rect.getWidth() > menu_region_rect.mRight ) - { - // move sub-menu over to left side - delta_x = llmax(-x, ( -(branch_rect.getWidth() + getRect().getWidth()))); - } - branch->translate( delta_x, delta_y ); - - branch->setVisible( true ); - branch->getParent()->sendChildToFront(branch); - - dirtyRect(); - } -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemBranchDownGL -// -// The LLMenuItemBranchDownGL represents a menu item that has a -// sub-menu. This is used to make menu bar menus. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuItemBranchDownGL : public LLMenuItemBranchGL -{ -protected: - -public: - LLMenuItemBranchDownGL( const Params& ); - - // returns the normal width of this control in pixels - this is - // used for calculating the widest item, as well as for horizontal - // arrangement. - virtual U32 getNominalWidth( void ) const; - - // called to rebuild the draw label - virtual void buildDrawLabel( void ); - - // handles opening, positioning, and arranging the menu branch associated with this item - virtual void openMenu( void ); - - // set the hover status (called by it's menu) and if the object is - // active. This is used for behavior transfer. - virtual void setHighlight( bool highlight ); - - virtual bool isActive( void ) const; - - // LLView functionality - virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); - virtual void draw( void ); - virtual bool handleKeyHere(KEY key, MASK mask); - - virtual bool handleAcceleratorKey(KEY key, MASK mask); - - virtual void onFocusLost(); - virtual void setFocus(bool b); -}; - -LLMenuItemBranchDownGL::LLMenuItemBranchDownGL( const Params& p) : - LLMenuItemBranchGL(p) -{ -} - -// returns the normal width of this control in pixels - this is used -// for calculating the widest item, as well as for horizontal -// arrangement. -U32 LLMenuItemBranchDownGL::getNominalWidth( void ) const -{ - U32 width = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS + RIGHT_PAD_PIXELS; - width += getFont()->getWidth( mLabel.getWString().c_str() ); - return width; -} - -// called to rebuild the draw label -void LLMenuItemBranchDownGL::buildDrawLabel( void ) -{ - mDrawAccelLabel.clear(); - std::string st = mDrawAccelLabel; - appendAcceleratorString( st ); - mDrawAccelLabel = st; -} - -void LLMenuItemBranchDownGL::openMenu( void ) -{ - LLMenuGL* branch = getBranch(); - if( branch->getVisible() && !branch->getTornOff() ) - { - branch->setVisible( false ); - } - else - { - if (branch->getTornOff()) - { - LLFloater * branch_parent = dynamic_cast(branch->getParent()); - if (branch_parent) - { - gFloaterView->bringToFront(branch_parent); - } - } - else - { - // We're showing the drop-down menu, so patch up its labels/rects - branch->arrange(); - - LLRect rect = branch->getRect(); - S32 left = 0; - S32 top = getRect().mBottom; - localPointToOtherView(left, top, &left, &top, branch->getParent()); - - rect.setLeftTopAndSize( left, top, - rect.getWidth(), rect.getHeight() ); - branch->setRect( rect ); - S32 x = 0; - S32 y = 0; - branch->localPointToScreen( 0, 0, &x, &y ); - S32 delta_x = 0; - - LLCoordScreen window_size; - LLWindow* windowp = getWindow(); - windowp->getSize(&window_size); - - S32 window_width = window_size.mX; - if( x > window_width - rect.getWidth() ) - { - delta_x = (window_width - rect.getWidth()) - x; - } - branch->translate( delta_x, 0 ); - - setHighlight(true); - branch->setVisible( true ); - branch->getParent()->sendChildToFront(branch); - } - } -} - -// set the hover status (called by it's menu) -void LLMenuItemBranchDownGL::setHighlight( bool highlight ) -{ - if (highlight == getHighlight()) - return; - - //NOTE: Purposely calling all the way to the base to bypass auto-open. - LLMenuItemGL::setHighlight(highlight); - - LLMenuGL* branch = getBranch(); - if (!branch) - return; - - if( !highlight) - { - if (branch->getTornOff()) - { - LLFloater * branch_parent = dynamic_cast(branch->getParent()); - if (branch_parent) - { - branch_parent->setFocus(false); - } - branch->clearHoverItem(); - } - else - { - branch->setVisible( false ); - } - } -} - -bool LLMenuItemBranchDownGL::isActive() const -{ - // for top level menus, being open is sufficient to be considered - // active, because clicking on them with the mouse will open - // them, without moving keyboard focus to them - return isOpen(); -} - -bool LLMenuItemBranchDownGL::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - // switch to mouse control mode - LLMenuGL::setKeyboardMode(false); - - if (getVisible() && isOpen()) - { - LLMenuGL::sMenuContainer->hideMenus(); - } - else - { - onCommit(); - } - - make_ui_sound("UISndClick"); - return true; -} - -bool LLMenuItemBranchDownGL::handleMouseUp( S32 x, S32 y, MASK mask ) -{ - return true; -} - - -bool LLMenuItemBranchDownGL::handleAcceleratorKey(KEY key, MASK mask) -{ - bool branch_visible = getBranch()->getVisible(); - bool handled = getBranch()->handleAcceleratorKey(key, mask); - if (handled && !branch_visible && isInVisibleChain()) - { - // flash this menu entry because we triggered an invisible menu item - LLMenuHolderGL::setActivatedItem(this); - } - - return handled; -} -void LLMenuItemBranchDownGL::onFocusLost() -{ - // needed for tab-based selection - LLMenuItemBranchGL::onFocusLost(); - LLMenuGL::setKeyboardMode(false); - setHighlight(false); -} - -void LLMenuItemBranchDownGL::setFocus(bool b) -{ - // needed for tab-based selection - LLMenuItemBranchGL::setFocus(b); - LLMenuGL::setKeyboardMode(b); - setHighlight(b); -} - -bool LLMenuItemBranchDownGL::handleKeyHere(KEY key, MASK mask) -{ - bool menu_open = getBranch()->getVisible(); - // don't do keyboard navigation of top-level menus unless in keyboard mode, or menu expanded - if (getHighlight() && getMenu()->isOpen() && (isActive() || LLMenuGL::getKeyboardMode())) - { - if (key == KEY_LEFT) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - LLMenuItemGL* itemp = getMenu()->highlightPrevItem(this); - // open new menu only if previous menu was open - if (itemp && itemp->getEnabled() && menu_open) - { - itemp->onCommit(); - } - - return true; - } - else if (key == KEY_RIGHT) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - LLMenuItemGL* itemp = getMenu()->highlightNextItem(this); - // open new menu only if previous menu was open - if (itemp && itemp->getEnabled() && menu_open) - { - itemp->onCommit(); - } - - return true; - } - else if (key == KEY_DOWN) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - if (!isActive()) - { - onCommit(); - } - getBranch()->highlightNextItem(NULL); - return true; - } - else if (key == KEY_UP) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - if (!isActive()) - { - onCommit(); - } - getBranch()->highlightPrevItem(NULL); - return true; - } - } - - return false; -} - -void LLMenuItemBranchDownGL::draw( void ) -{ - //FIXME: try removing this - if (getBranch()->getVisible() && !getBranch()->getTornOff()) - { - setHighlight(true); - } - - if( getHighlight() ) - { - gGL.color4fv( mHighlightBackground.get().mV ); - gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - } - - LLColor4 color; - if (getHighlight()) - { - color = mHighlightForeground.get(); - } - else if( getEnabled() ) - { - color = mEnabledColor.get(); - } - else - { - color = mDisabledColor.get(); - } - getFont()->render( mLabel.getWString(), 0, (F32)getRect().getWidth() / 2.f, (F32)LABEL_BOTTOM_PAD_PIXELS, color, - LLFontGL::HCENTER, LLFontGL::BOTTOM, LLFontGL::NORMAL); - - - // underline navigation key only when keyboard navigation has been initiated - if (getMenu()->jumpKeysActive() && LLMenuGL::getKeyboardMode()) - { - std::string upper_case_label = mLabel.getString(); - LLStringUtil::toUpper(upper_case_label); - std::string::size_type offset = upper_case_label.find(getJumpKey()); - if (offset != std::string::npos) - { - S32 x_offset = ll_round((F32)getRect().getWidth() / 2.f - getFont()->getWidthF32(mLabel.getString(), 0, S32_MAX) / 2.f); - S32 x_begin = x_offset + getFont()->getWidth(mLabel, 0, offset); - S32 x_end = x_offset + getFont()->getWidth(mLabel, 0, offset + 1); - gl_line_2d(x_begin, LABEL_BOTTOM_PAD_PIXELS, x_end, LABEL_BOTTOM_PAD_PIXELS); - } - } -} - - -class LLMenuScrollItem : public LLMenuItemCallGL -{ -public: - enum EArrowType - { - ARROW_DOWN, - ARROW_UP - }; - struct ArrowTypes : public LLInitParam::TypeValuesHelper - { - static void declareValues() - { - declare("up", ARROW_UP); - declare("down", ARROW_DOWN); - } - }; - - struct Params : public LLInitParam::Block - { - Optional arrow_type; - Optional scroll_callback; - }; - -protected: - LLMenuScrollItem(const Params&); - friend class LLUICtrlFactory; - -public: - /*virtual*/ void draw(); - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent); - /*virtual*/ void setEnabled(bool enabled); - virtual void onCommit( void ); - -private: - LLButton* mArrowBtn; -}; - -LLMenuScrollItem::LLMenuScrollItem(const Params& p) -: LLMenuItemCallGL(p) -{ - std::string icon; - if (p.arrow_type.isProvided() && p.arrow_type == ARROW_UP) - { - icon = "arrow_up.tga"; - } - else - { - icon = "arrow_down.tga"; - } - - LLButton::Params bparams; - - // Disabled the Return key handling by LLMenuScrollItem instead of - // passing the key press to the currently selected menu item. See STORM-385. - bparams.commit_on_return(false); - bparams.mouse_opaque(true); - bparams.scale_image(false); - bparams.click_callback(p.scroll_callback); - bparams.mouse_held_callback(p.scroll_callback); - bparams.follows.flags(FOLLOWS_ALL); - std::string background = "transparent.j2c"; - bparams.image_unselected.name(background); - bparams.image_disabled.name(background); - bparams.image_selected.name(background); - bparams.image_hover_selected.name(background); - bparams.image_disabled_selected.name(background); - bparams.image_hover_unselected.name(background); - bparams.image_overlay.name(icon); - - mArrowBtn = LLUICtrlFactory::create(bparams); - addChild(mArrowBtn); -} - -/*virtual*/ -void LLMenuScrollItem::draw() -{ - LLUICtrl::draw(); -} - -/*virtual*/ -void LLMenuScrollItem::reshape(S32 width, S32 height, bool called_from_parent) -{ - mArrowBtn->reshape(width, height, called_from_parent); - LLView::reshape(width, height, called_from_parent); -} - -/*virtual*/ -void LLMenuScrollItem::setEnabled(bool enabled) -{ - mArrowBtn->setEnabled(enabled); - LLView::setEnabled(enabled); -} - -void LLMenuScrollItem::onCommit( void ) -{ - LLUICtrl::onCommit(); -} - -///============================================================================ -/// Class LLMenuGL -///============================================================================ - -LLMenuGL::LLMenuGL(const LLMenuGL::Params& p) -: LLUICtrl(p), - mBackgroundColor( p.bg_color() ), - mBgVisible( p.bg_visible ), - mDropShadowed( p.drop_shadow ), - mHasSelection(false), - mHorizontalLayout( p.horizontal_layout ), - mScrollable(mHorizontalLayout ? false : p.scrollable), // Scrolling is supported only for vertical layout - mMaxScrollableItems(p.max_scrollable_items), - mPreferredWidth(p.preferred_width), - mKeepFixedSize( p.keep_fixed_size ), - mLabel (p.label), - mLastMouseX(0), - mLastMouseY(0), - mMouseVelX(0), - mMouseVelY(0), - mTornOff(false), - mTearOffItem(NULL), - mSpilloverBranch(NULL), - mFirstVisibleItem(NULL), - mArrowUpItem(NULL), - mArrowDownItem(NULL), - mSpilloverMenu(NULL), - mJumpKey(p.jump_key), - mCreateJumpKeys(p.create_jump_keys), - mNeedsArrange(false), - mAlwaysShowMenu(false), - mResetScrollPositionOnShow(true), - mShortcutPad(p.shortcut_pad), - mFont(p.font) -{ - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("_"); - tokenizer tokens(p.label(), sep); - tokenizer::iterator token_iter; - - S32 token_count = 0; - std::string new_menu_label; - for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) - { - new_menu_label += (*token_iter); - if (token_count > 0) - { - setJumpKey((*token_iter).c_str()[0]); - } - ++token_count; - } - setLabel(new_menu_label); - - mFadeTimer.stop(); -} - -void LLMenuGL::initFromParams(const LLMenuGL::Params& p) -{ - LLUICtrl::initFromParams(p); - setCanTearOff(p.can_tear_off); -} - -// Destroys the object -LLMenuGL::~LLMenuGL( void ) -{ - // delete the branch, as it might not be in view hierarchy - // leave the menu, because it is always in view hierarchy - delete mSpilloverBranch; - mJumpKeys.clear(); -} - -void LLMenuGL::setCanTearOff(bool tear_off) -{ - if (tear_off && mTearOffItem == NULL) - { - LLMenuItemTearOffGL::Params p; - mTearOffItem = LLUICtrlFactory::create(p); - addChild(mTearOffItem); - } - else if (!tear_off && mTearOffItem != NULL) - { - mItems.remove(mTearOffItem); - removeChild(mTearOffItem); - delete mTearOffItem; - mTearOffItem = NULL; - needsArrange(); - } -} - -bool LLMenuGL::addChild(LLView* view, S32 tab_group) -{ - LLMenuGL* menup = dynamic_cast(view); - if (menup) - { - return appendMenu(menup); - } - - LLMenuItemGL* itemp = dynamic_cast(view); - if (itemp) - { - return append(itemp); - } - - return false; -} - -// Used in LLContextMenu and in LLTogleableMenu - -// Add an item to the context menu branch -bool LLMenuGL::addContextChild(LLView* view, S32 tab_group) -{ - LLContextMenu* context = dynamic_cast(view); - if (context) - { - return appendContextSubMenu(context); - } - - LLMenuItemSeparatorGL* separator = dynamic_cast(view); - if (separator) - { - return append(separator); - } - - LLMenuItemGL* item = dynamic_cast(view); - if (item) - { - return append(item); - } - - LLMenuGL* menup = dynamic_cast(view); - if (menup) - { - return appendMenu(menup); - } - - return false; -} - - -void LLMenuGL::deleteAllChildren() -{ - mItems.clear(); - LLUICtrl::deleteAllChildren(); -} - -void LLMenuGL::removeChild( LLView* ctrl) -{ - // previously a dynamic_cast with if statement to check validity - // unfortunately removeChild is called by ~LLView, and at that point the - // object being deleted is no longer a LLMenuItemGL so a dynamic_cast will fail - LLMenuItemGL* itemp = static_cast(ctrl); - - item_list_t::iterator found_it = std::find(mItems.begin(), mItems.end(), (itemp)); - if (found_it != mItems.end()) - { - mItems.erase(found_it); - } - - return LLUICtrl::removeChild(ctrl); -} - -bool LLMenuGL::postBuild() -{ - createJumpKeys(); - return LLUICtrl::postBuild(); -} - -// are we the childmost active menu and hence our jump keys should be enabled? -// or are we a free-standing torn-off menu (which uses jump keys too) -bool LLMenuGL::jumpKeysActive() -{ - LLMenuItemGL* highlighted_item = getHighlightedItem(); - bool active = getVisible() && getEnabled(); - - if (active) - { - if (getTornOff()) - { - // activation of jump keys on torn off menus controlled by keyboard focus - LLFloater * parent = dynamic_cast(getParent()); - if (parent) - { - active = parent->hasFocus(); - } - } - else - { - // Are we the terminal active menu? - // Yes, if parent menu item deems us to be active (just being visible is sufficient for top-level menus) - // and we don't have a highlighted menu item pointing to an active sub-menu - active = (!getParentMenuItem() || getParentMenuItem()->isActive()) // I have a parent that is active... - && (!highlighted_item || !highlighted_item->isActive()); //... but no child that is active - } - } - - return active; -} - -bool LLMenuGL::isOpen() -{ - if (getTornOff()) - { - LLMenuItemGL* itemp = getHighlightedItem(); - // if we have an open sub-menu, then we are considered part of - // the open menu chain even if we don't have focus - if (itemp && itemp->isOpen()) - { - return true; - } - // otherwise we are only active if we have keyboard focus - LLFloater * parent = dynamic_cast(getParent()); - if (parent) - { - return parent->hasFocus(); - } - return false; - } - else - { - // normally, menus are hidden as soon as the user focuses - // on another menu, so just use the visibility criterion - return getVisible(); - } -} - - - -bool LLMenuGL::scrollItems(EScrollingDirection direction) -{ - // Slowing down items scrolling when arrow button is held - if (mScrollItemsTimer.hasExpired() && NULL != mFirstVisibleItem) - { - mScrollItemsTimer.setTimerExpirySec(.033f); - } - else - { - return false; - } - - switch (direction) - { - case SD_UP: - { - item_list_t::iterator cur_item_iter; - item_list_t::iterator prev_item_iter; - for (cur_item_iter = mItems.begin(), prev_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) - { - if( (*cur_item_iter) == mFirstVisibleItem) - { - break; - } - if ((*cur_item_iter)->getVisible()) - { - prev_item_iter = cur_item_iter; - } - } - - if ((*prev_item_iter)->getVisible()) - { - mFirstVisibleItem = *prev_item_iter; - } - break; - } - case SD_DOWN: - { - if (NULL == mFirstVisibleItem) - { - mFirstVisibleItem = *mItems.begin(); - } - - item_list_t::iterator cur_item_iter; - - for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) - { - if( (*cur_item_iter) == mFirstVisibleItem) - { - break; - } - } - - item_list_t::iterator next_item_iter; - - if (cur_item_iter != mItems.end()) - { - for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++) - { - if( (*next_item_iter)->getVisible()) - { - break; - } - } - - if (next_item_iter != mItems.end() && - (*next_item_iter)->getVisible()) - { - mFirstVisibleItem = *next_item_iter; - } - } - break; - } - case SD_BEGIN: - { - mFirstVisibleItem = *mItems.begin(); - break; - } - case SD_END: - { - item_list_t::reverse_iterator first_visible_item_iter = mItems.rend(); - - // Need to scroll through number of actual existing items in menu. - // Otherwise viewer will hang for a time needed to scroll U32_MAX - // times in std::advance(). STORM-659. - size_t nitems = mItems.size(); - U32 scrollable_items = nitems < mMaxScrollableItems ? nitems : mMaxScrollableItems; - - // Advance by mMaxScrollableItems back from the end of the list - // to make the last item visible. - std::advance(first_visible_item_iter, scrollable_items); - mFirstVisibleItem = *first_visible_item_iter; - break; - } - default: - LL_WARNS() << "Unknown scrolling direction: " << direction << LL_ENDL; - } - - mNeedsArrange = true; - arrangeAndClear(); - - return true; -} - -// rearrange the child rects so they fit the shape of the menu. -void LLMenuGL::arrange( void ) -{ - // calculate the height & width, and set our rect based on that - // information. - const LLRect& initial_rect = getRect(); - - U32 width = 0, height = MENU_ITEM_PADDING; - - cleanupSpilloverBranch(); - - if( mItems.size() ) - { - const LLRect menu_region_rect = LLMenuGL::sMenuContainer ? LLMenuGL::sMenuContainer->getMenuRect() : LLRect(0, S32_MAX, S32_MAX, 0); - - // torn off menus are not constrained to the size of the screen - U32 max_width = getTornOff() ? U32_MAX : menu_region_rect.getWidth(); - U32 max_height = getTornOff() ? U32_MAX: menu_region_rect.getHeight(); - - // *FIX: create the item first and then ask for its dimensions? - S32 spillover_item_width = PLAIN_PAD_PIXELS + LLFontGL::getFontSansSerif()->getWidth( std::string("More") ); // *TODO: Translate - S32 spillover_item_height = LLFontGL::getFontSansSerif()->getLineHeight() + MENU_ITEM_PADDING; - - // Scrolling support - item_list_t::iterator first_visible_item_iter; - item_list_t::iterator first_hidden_item_iter = mItems.end(); - S32 height_before_first_visible_item = -1; - S32 visible_items_height = 0; - U32 scrollable_items_cnt = 0; - - if (mHorizontalLayout) - { - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - // do first so LLMenuGLItemCall can call on_visible to determine if visible - (*item_iter)->buildDrawLabel(); - - if ((*item_iter)->getVisible()) - { - if (!getTornOff() - && *item_iter != mSpilloverBranch - && width + (*item_iter)->getNominalWidth() > max_width - spillover_item_width) - { - // no room for any more items - createSpilloverBranch(); - - std::vector items_to_remove; - std::copy(item_iter, mItems.end(), std::back_inserter(items_to_remove)); - std::vector::iterator spillover_iter; - for (spillover_iter= items_to_remove.begin(); spillover_iter != items_to_remove.end(); ++spillover_iter) - { - LLMenuItemGL* itemp = (*spillover_iter); - removeChild(itemp); - mSpilloverMenu->addChild(itemp); - } - - addChild(mSpilloverBranch); - - height = llmax(height, mSpilloverBranch->getNominalHeight()); - width += mSpilloverBranch->getNominalWidth(); - - break; - } - else - { - // track our rect - height = llmax(height, (*item_iter)->getNominalHeight()); - width += (*item_iter)->getNominalWidth(); - } - } - } - } - else - { - for (LLMenuItemGL* itemp : mItems) - { - // do first so LLMenuGLItemCall can call on_visible to determine if visible - itemp->buildDrawLabel(); - } - item_list_t::iterator item_iter; - - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - if ((*item_iter)->getVisible()) - { - if (!getTornOff() - && !mScrollable - && *item_iter != mSpilloverBranch - && height + (*item_iter)->getNominalHeight() > max_height - spillover_item_height) - { - // don't show only one item - int visible_items = 0; - item_list_t::iterator count_iter; - for (count_iter = item_iter; count_iter != mItems.end(); ++count_iter) - { - if((*count_iter)->getVisible()) - visible_items++; - } - if (visible_items>1) - { - // no room for any more items - createSpilloverBranch(); - - std::vector items_to_remove; - std::copy(item_iter, mItems.end(), std::back_inserter(items_to_remove)); - std::vector::iterator spillover_iter; - for (spillover_iter= items_to_remove.begin(); spillover_iter != items_to_remove.end(); ++spillover_iter) - { - LLMenuItemGL* itemp = (*spillover_iter); - removeChild(itemp); - mSpilloverMenu->addChild(itemp); - } - - - addChild(mSpilloverBranch); - - height += mSpilloverBranch->getNominalHeight(); - width = llmax( width, mSpilloverBranch->getNominalWidth() ); - - break; - } - } - - // track our rect - height += (*item_iter)->getNominalHeight(); - width = llmax( width, (*item_iter)->getNominalWidth() ); - - if (mScrollable) - { - // Determining visible items boundaries - if (NULL == mFirstVisibleItem) - { - mFirstVisibleItem = *item_iter; - } - - if (*item_iter == mFirstVisibleItem) - { - height_before_first_visible_item = height - (*item_iter)->getNominalHeight(); - first_visible_item_iter = item_iter; - scrollable_items_cnt = 0; - } - - if (-1 != height_before_first_visible_item && 0 == visible_items_height && - (++scrollable_items_cnt > mMaxScrollableItems || - height - height_before_first_visible_item > max_height - spillover_item_height * 2 )) - { - first_hidden_item_iter = item_iter; - visible_items_height = height - height_before_first_visible_item - (*item_iter)->getNominalHeight(); - scrollable_items_cnt--; - } - } - } - } - - if (mPreferredWidth < U32_MAX) - width = llmin(mPreferredWidth, max_width); - - if (mScrollable) - { - S32 max_items_height = max_height - spillover_item_height * 2; - - if (visible_items_height == 0) - visible_items_height = height - height_before_first_visible_item; - - // Fix mFirstVisibleItem value, if it doesn't allow to display all items, that can fit - if (visible_items_height < max_items_height && scrollable_items_cnt < mMaxScrollableItems) - { - item_list_t::iterator tmp_iter(first_visible_item_iter); - while (visible_items_height < max_items_height && - scrollable_items_cnt < mMaxScrollableItems && - first_visible_item_iter != mItems.begin()) - { - if ((*first_visible_item_iter)->getVisible()) - { - // It keeps visible item, after first_visible_item_iter - tmp_iter = first_visible_item_iter; - } - - first_visible_item_iter--; - - if ((*first_visible_item_iter)->getVisible()) - { - visible_items_height += (*first_visible_item_iter)->getNominalHeight(); - height_before_first_visible_item -= (*first_visible_item_iter)->getNominalHeight(); - scrollable_items_cnt++; - } - } - - // Roll back one item, that doesn't fit - if (visible_items_height > max_items_height) - { - visible_items_height -= (*first_visible_item_iter)->getNominalHeight(); - height_before_first_visible_item += (*first_visible_item_iter)->getNominalHeight(); - scrollable_items_cnt--; - first_visible_item_iter = tmp_iter; - } - if (!(*first_visible_item_iter)->getVisible()) - { - first_visible_item_iter = tmp_iter; - } - - mFirstVisibleItem = *first_visible_item_iter; - } - } - } - - S32 cur_height = (S32)llmin(max_height, height); - - if (mScrollable && - (height_before_first_visible_item > MENU_ITEM_PADDING || - height_before_first_visible_item + visible_items_height < (S32)height)) - { - // Reserving 2 extra slots for arrow items - cur_height = visible_items_height + spillover_item_height * 2; - } - - setRect(LLRect(getRect().mLeft, getRect().mTop, getRect().mLeft + width, getRect().mTop - cur_height)); - - S32 cur_width = 0; - S32 offset = 0; - if (mScrollable) - { - // No space for all items, creating arrow items - if (height_before_first_visible_item > MENU_ITEM_PADDING || - height_before_first_visible_item + visible_items_height < (S32)height) - { - if (NULL == mArrowUpItem) - { - LLMenuScrollItem::Params item_params; - item_params.name(ARROW_UP); - item_params.arrow_type(LLMenuScrollItem::ARROW_UP); - item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_UP)); - - mArrowUpItem = LLUICtrlFactory::create(item_params); - LLUICtrl::addChild(mArrowUpItem); - - } - if (NULL == mArrowDownItem) - { - LLMenuScrollItem::Params item_params; - item_params.name(ARROW_DOWN); - item_params.arrow_type(LLMenuScrollItem::ARROW_DOWN); - item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_DOWN)); - - mArrowDownItem = LLUICtrlFactory::create(item_params); - LLUICtrl::addChild(mArrowDownItem); - } - - LLRect rect; - mArrowUpItem->setRect(rect.setLeftTopAndSize( 0, cur_height, width, mArrowUpItem->getNominalHeight())); - mArrowUpItem->setVisible(true); - mArrowUpItem->setEnabled(height_before_first_visible_item > MENU_ITEM_PADDING); - mArrowUpItem->reshape(width, mArrowUpItem->getNominalHeight()); - mArrowDownItem->setRect(rect.setLeftTopAndSize( 0, mArrowDownItem->getNominalHeight(), width, mArrowDownItem->getNominalHeight())); - mArrowDownItem->setVisible(true); - mArrowDownItem->setEnabled(height_before_first_visible_item + visible_items_height < (S32)height); - mArrowDownItem->reshape(width, mArrowDownItem->getNominalHeight()); - - cur_height -= mArrowUpItem->getNominalHeight(); - - offset = menu_region_rect.mRight; // This moves items behind visible area - } - else - { - if (NULL != mArrowUpItem) - { - mArrowUpItem->setVisible(false); - } - if (NULL != mArrowDownItem) - { - mArrowDownItem->setVisible(false); - } - } - - } - - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - if ((*item_iter)->getVisible()) - { - if (mScrollable) - { - if (item_iter == first_visible_item_iter) - { - offset = 0; - } - else if (item_iter == first_hidden_item_iter) - { - offset = menu_region_rect.mRight; // This moves items behind visible area - } - } - - // setup item rect to hold label - LLRect rect; - if (mHorizontalLayout) - { - rect.setLeftTopAndSize( cur_width, height, (*item_iter)->getNominalWidth(), height); - cur_width += (*item_iter)->getNominalWidth(); - } - else - { - rect.setLeftTopAndSize( 0 + offset, cur_height, width, (*item_iter)->getNominalHeight()); - if (offset == 0) - { - cur_height -= (*item_iter)->getNominalHeight(); - } - } - (*item_iter)->setRect( rect ); - } - } - - - if (getTornOff()) - { - LLTearOffMenu * torn_off_menu = dynamic_cast(getParent()); - if (torn_off_menu) - { - torn_off_menu->updateSize(); - } - } - } - if (mKeepFixedSize) - { - reshape(initial_rect.getWidth(), initial_rect.getHeight()); - } -} - -void LLMenuGL::arrangeAndClear( void ) -{ - if (mNeedsArrange) - { - arrange(); - mNeedsArrange = false; - } -} - -void LLMenuGL::createSpilloverBranch() -{ - if (!mSpilloverBranch) - { - // should be NULL but delete anyway - delete mSpilloverMenu; - // technically, you can't tear off spillover menus, but we're passing the handle - // along just to be safe - LLMenuGL::Params p; - std::string label = LLTrans::getString("More"); - p.name("More"); - p.label(label); - p.bg_color(mBackgroundColor); - p.bg_visible(true); - p.can_tear_off(false); - mSpilloverMenu = new LLMenuGL(p); - mSpilloverMenu->updateParent(LLMenuGL::sMenuContainer); - - LLMenuItemBranchGL::Params branch_params; - branch_params.name = "More"; - branch_params.label = label; - branch_params.branch = mSpilloverMenu; - branch_params.font.style = "italic"; - branch_params.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); - branch_params.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); - branch_params.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); - - mSpilloverBranch = LLUICtrlFactory::create(branch_params); - } -} - -void LLMenuGL::cleanupSpilloverBranch() -{ - if (mSpilloverBranch && mSpilloverBranch->getParent() == this) - { - // head-recursion to propagate items back up to root menu - mSpilloverMenu->cleanupSpilloverBranch(); - - // pop off spillover items - while (mSpilloverMenu->getItemCount()) - { - LLMenuItemGL* itemp = mSpilloverMenu->getItem(0); - mSpilloverMenu->removeChild(itemp); - // put them at the end of our own list - addChild(itemp); - } - - // Delete the branch, and since the branch will delete the menu, - // set the menu* to null. - delete mSpilloverBranch; - mSpilloverBranch = NULL; - mSpilloverMenu = NULL; - } -} - -void LLMenuGL::createJumpKeys() -{ - if (!mCreateJumpKeys) return; - mCreateJumpKeys = false; - - mJumpKeys.clear(); - - std::set unique_words; - std::set shared_words; - - item_list_t::iterator item_it; - typedef boost::tokenizer > tokenizer; - boost::char_separator sep(" "); - - for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) - { - std::string uppercase_label = (*item_it)->getLabel(); - LLStringUtil::toUpper(uppercase_label); - - tokenizer tokens(uppercase_label, sep); - tokenizer::iterator token_iter; - for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) - { - if (unique_words.find(*token_iter) != unique_words.end()) - { - // this word exists in more than one menu instance - shared_words.insert(*token_iter); - } - else - { - // we have a new word, keep track of it - unique_words.insert(*token_iter); - } - } - } - - // pre-assign specified jump keys - for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) - { - KEY jump_key = (*item_it)->getJumpKey(); - if(jump_key != KEY_NONE) - { - if (mJumpKeys.find(jump_key) == mJumpKeys.end()) - { - mJumpKeys.insert(std::pair(jump_key, (*item_it))); - } - else - { - // this key is already spoken for, - // so we need to reassign it below - (*item_it)->setJumpKey(KEY_NONE); - } - } - } - - for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) - { - // skip over items that already have assigned jump keys - if ((*item_it)->getJumpKey() != KEY_NONE) - { - continue; - } - std::string uppercase_label = (*item_it)->getLabel(); - LLStringUtil::toUpper(uppercase_label); - - tokenizer tokens(uppercase_label, sep); - tokenizer::iterator token_iter; - - bool found_key = false; - for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) - { - std::string uppercase_word = *token_iter; - - // this word is not shared with other menu entries... - if (shared_words.find(*token_iter) == shared_words.end()) - { - S32 i; - for(i = 0; i < (S32)uppercase_word.size(); i++) - { - char jump_key = uppercase_word[i]; - - if (LLStringOps::isDigit(jump_key) || (LLStringOps::isUpper(jump_key) && - mJumpKeys.find(jump_key) == mJumpKeys.end())) - { - mJumpKeys.insert(std::pair(jump_key, (*item_it))); - (*item_it)->setJumpKey(jump_key); - found_key = true; - break; - } - } - } - if (found_key) - { - break; - } - } - } -} - -// remove all items on the menu -void LLMenuGL::empty( void ) -{ - cleanupSpilloverBranch(); - - mItems.clear(); - mFirstVisibleItem = NULL; - mArrowUpItem = NULL; - mArrowDownItem = NULL; - - deleteAllChildren(); -} - -// erase group of items from menu -void LLMenuGL::erase( S32 begin, S32 end, bool arrange/* = true*/) -{ - S32 items = mItems.size(); - - if ( items == 0 || begin >= end || begin < 0 || end > items ) - { - return; - } - - item_list_t::iterator start_position = mItems.begin(); - std::advance(start_position, begin); - - item_list_t::iterator end_position = mItems.begin(); - std::advance(end_position, end); - - for (item_list_t::iterator position_iter = start_position; position_iter != end_position; position_iter++) - { - LLUICtrl::removeChild(*position_iter); - } - - mItems.erase(start_position, end_position); - - if (arrange) - { - needsArrange(); - } -} - -// add new item at position -void LLMenuGL::insert( S32 position, LLView * ctrl, bool arrange /*= true*/ ) -{ - LLMenuItemGL * item = dynamic_cast(ctrl); - - if (NULL == item || position < 0 || position >= mItems.size()) - { - return; - } - - item_list_t::iterator position_iter = mItems.begin(); - std::advance(position_iter, position); - mItems.insert(position_iter, item); - LLUICtrl::addChild(item); - - if (arrange) - { - needsArrange(); - } -} - -// Adjust rectangle of the menu -void LLMenuGL::setLeftAndBottom(S32 left, S32 bottom) -{ - setRect(LLRect(left, getRect().mTop, getRect().mRight, bottom)); - needsArrange(); -} - -bool LLMenuGL::handleJumpKey(KEY key) -{ - // must perform case-insensitive comparison, so just switch to uppercase input key - key = toupper(key); - navigation_key_map_t::iterator found_it = mJumpKeys.find(key); - if(found_it != mJumpKeys.end() && found_it->second->getEnabled()) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - // force highlight to close old menus and open and sub-menus - found_it->second->setHighlight(true); - found_it->second->onCommit(); - - } - // if we are navigating the menus, we need to eat the keystroke - // so rest of UI doesn't handle it - return true; -} - - -// Add the menu item to this menu. -bool LLMenuGL::append( LLMenuItemGL* item ) -{ - if (!item) return false; - mItems.push_back( item ); - LLUICtrl::addChild(item); - needsArrange(); - return true; -} - -// add a separator to this menu -bool LLMenuGL::addSeparator() -{ - LLMenuItemSeparatorGL::Params p; - LLMenuItemGL* separator = LLUICtrlFactory::create(p); - return addChild(separator); -} - -// add a menu - this will create a cascading menu -bool LLMenuGL::appendMenu( LLMenuGL* menu ) -{ - if( menu == this ) - { - LL_ERRS() << "** Attempt to attach menu to itself. This is certainly " - << "a logic error." << LL_ENDL; - } - bool success = true; - - LLMenuItemBranchGL::Params p; - p.name = menu->getName(); - p.label = menu->getLabel(); - p.branch = menu; - p.jump_key = menu->getJumpKey(); - p.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); - p.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor"); - p.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); - p.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); - - LLMenuItemBranchGL* branch = LLUICtrlFactory::create(p); - success &= append( branch ); - - // Inherit colors - menu->setBackgroundColor( mBackgroundColor ); - menu->updateParent(LLMenuGL::sMenuContainer); - return success; -} - -// add a context menu branch -bool LLMenuGL::appendContextSubMenu(LLMenuGL *menu) -{ - if (menu == this) - { - LL_ERRS() << "Can't attach a context menu to itself" << LL_ENDL; - } - - LLContextMenuBranch *item; - LLContextMenuBranch::Params p; - p.name = menu->getName(); - p.label = menu->getLabel(); - p.branch = (LLContextMenu *)menu; - p.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); - p.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor"); - p.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); - p.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); - - item = LLUICtrlFactory::create(p); - LLMenuGL::sMenuContainer->addChild(item->getBranch()); - - return append( item ); -} - -void LLMenuGL::setEnabledSubMenus(bool enable) -{ - setEnabled(enable); - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - (*item_iter)->setEnabledSubMenus( enable ); - } -} - -// setItemEnabled() - pass the label and the enable flag for a menu -// item. true will make sure it's enabled, false will disable it. -void LLMenuGL::setItemEnabled( const std::string& name, bool enable ) -{ - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - if( (*item_iter)->getName() == name ) - { - (*item_iter)->setEnabled( enable ); - (*item_iter)->setEnabledSubMenus( enable ); - break; - } - } -} - -void LLMenuGL::setItemVisible( const std::string& name, bool visible ) -{ - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - if( (*item_iter)->getName() == name ) - { - (*item_iter)->setVisible( visible ); - needsArrange(); - break; - } - } -} - - -void LLMenuGL::setItemLabel(const std::string &name, const std::string &label) -{ - LLMenuItemGL *item = getItem(name); - - if (item) - item->setLabel(label); -} - -void LLMenuGL::setItemLastSelected(LLMenuItemGL* item) -{ - if (getVisible()) - { - LLMenuHolderGL::setActivatedItem(item); - } - - // update enabled and checkmark status - item->buildDrawLabel(); -} - -// Set whether drop shadowed -void LLMenuGL::setDropShadowed( const bool shadowed ) -{ - mDropShadowed = shadowed; -} - -void LLMenuGL::setTornOff(bool torn_off) -{ - mTornOff = torn_off; -} - -U32 LLMenuGL::getItemCount() -{ - return mItems.size(); -} - -LLMenuItemGL* LLMenuGL::getItem(S32 number) -{ - if (number >= 0 && number < (S32)mItems.size()) - { - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - if (number == 0) - { - return (*item_iter); - } - number--; - } - } - return NULL; -} - -LLMenuItemGL* LLMenuGL::getItem(std::string name) -{ - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - if ((*item_iter)->getName() == name) - { - return (*item_iter); - } - } - return NULL; -} - -LLMenuItemGL* LLMenuGL::getHighlightedItem() -{ - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - if ((*item_iter)->getHighlight()) - { - return (*item_iter); - } - } - return NULL; -} - -LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, bool skip_disabled) -{ - if (mItems.empty()) return NULL; - // highlighting first item on a torn off menu is the - // same as giving focus to it - if (!cur_item && getTornOff()) - { - LLFloater * parent = dynamic_cast(getParent()); - if (parent) - { - parent->setFocus(true); - } - } - - // Current item position in the items list - item_list_t::iterator cur_item_iter = std::find(mItems.begin(), mItems.end(), cur_item); - - item_list_t::iterator next_item_iter; - if (cur_item_iter == mItems.end()) - { - next_item_iter = mItems.begin(); - } - else - { - next_item_iter = cur_item_iter; - next_item_iter++; - - // First visible item position in the items list - item_list_t::iterator first_visible_item_iter = std::find(mItems.begin(), mItems.end(), mFirstVisibleItem); - - if (next_item_iter == mItems.end()) - { - next_item_iter = mItems.begin(); - - // If current item is the last in the list, the menu is scrolled to the beginning - // and the first item is highlighted. - if (mScrollable && !scrollItems(SD_BEGIN)) - { - return NULL; - } - } - // If current item is the last visible, the menu is scrolled one item down - // and the next item is highlighted. - else if (mScrollable && - (U32)std::abs(std::distance(first_visible_item_iter, next_item_iter)) >= mMaxScrollableItems) - { - // Call highlightNextItem() recursively only if the menu was successfully scrolled down. - // If scroll timer hasn't expired yet the menu won't be scrolled and calling - // highlightNextItem() will result in an endless recursion. - if (scrollItems(SD_DOWN)) - { - return highlightNextItem(cur_item, skip_disabled); - } - else - { - return NULL; - } - } - } - - // when first highlighting a menu, skip over tear off menu item - if (mTearOffItem && !cur_item) - { - // we know the first item is the tear off menu item - cur_item_iter = mItems.begin(); - next_item_iter++; - if (next_item_iter == mItems.end()) - { - next_item_iter = mItems.begin(); - } - } - - while(1) - { - // skip separators and disabled/invisible items - if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getVisible() && !dynamic_cast(*next_item_iter)) - { - if (cur_item) - { - cur_item->setHighlight(false); - } - (*next_item_iter)->setHighlight(true); - return (*next_item_iter); - } - - - if (!skip_disabled || next_item_iter == cur_item_iter) - { - break; - } - - next_item_iter++; - if (next_item_iter == mItems.end()) - { - if (cur_item_iter == mItems.end()) - { - break; - } - next_item_iter = mItems.begin(); - } - } - - return NULL; -} - -LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, bool skip_disabled) -{ - if (mItems.empty()) return NULL; - - // highlighting first item on a torn off menu is the - // same as giving focus to it - if (!cur_item && getTornOff()) - { - LLFloater * parent = dynamic_cast(getParent()); - if (parent) - { - parent->setFocus(true); - } - } - - // Current item reverse position from the end of the list - item_list_t::reverse_iterator cur_item_iter = std::find(mItems.rbegin(), mItems.rend(), cur_item); - - item_list_t::reverse_iterator prev_item_iter; - if (cur_item_iter == mItems.rend()) - { - prev_item_iter = mItems.rbegin(); - } - else - { - prev_item_iter = cur_item_iter; - prev_item_iter++; - - // First visible item reverse position in the items list - item_list_t::reverse_iterator first_visible_item_iter = std::find(mItems.rbegin(), mItems.rend(), mFirstVisibleItem); - - if (prev_item_iter == mItems.rend()) - { - prev_item_iter = mItems.rbegin(); - - // If current item is the first in the list, the menu is scrolled to the end - // and the last item is highlighted. - if (mScrollable && !scrollItems(SD_END)) - { - return NULL; - } - } - // If current item is the first visible, the menu is scrolled one item up - // and the previous item is highlighted. - else if (mScrollable && - std::distance(first_visible_item_iter, cur_item_iter) <= 0) - { - // Call highlightNextItem() only if the menu was successfully scrolled up. - // If scroll timer hasn't expired yet the menu won't be scrolled and calling - // highlightNextItem() will result in an endless recursion. - if (scrollItems(SD_UP)) - { - return highlightPrevItem(cur_item, skip_disabled); - } - else - { - return NULL; - } - } - } - - while(1) - { - // skip separators and disabled/invisible items - if ((*prev_item_iter)->getEnabled() && (*prev_item_iter)->getVisible() && (*prev_item_iter)->getName() != SEPARATOR_NAME) - { - (*prev_item_iter)->setHighlight(true); - return (*prev_item_iter); - } - - if (!skip_disabled || prev_item_iter == cur_item_iter) - { - break; - } - - prev_item_iter++; - if (prev_item_iter == mItems.rend()) - { - if (cur_item_iter == mItems.rend()) - { - break; - } - - prev_item_iter = mItems.rbegin(); - } - } - - return NULL; -} - -void LLMenuGL::buildDrawLabels() -{ - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - (*item_iter)->buildDrawLabel(); - } -} - -void LLMenuGL::updateParent(LLView* parentp) -{ - if (getParent()) - { - getParent()->removeChild(this); - } - if (parentp) - { - parentp->addChild(this); - } - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - (*item_iter)->updateBranchParent(parentp); - } -} - -bool LLMenuGL::hasAccelerator(const KEY &key, const MASK &mask) const -{ - if (key == KEY_NONE) - { - return false; - } - // Note: checking this way because mAccelerators seems to be broken - // mAccelerators probably needs to be cleaned up or fixed - // It was used for dupplicate accelerator avoidance. - item_list_t::const_iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - LLMenuItemGL* itemp = *item_iter; - if (itemp->hasAccelerator(key, mask)) - { - return true; - } - } - return false; -} - -bool LLMenuGL::handleAcceleratorKey(KEY key, MASK mask) -{ - // don't handle if not enabled - if(!getEnabled()) - { - return false; - } - - // Pass down even if not visible - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - LLMenuItemGL* itemp = *item_iter; - if (itemp->handleAcceleratorKey(key, mask)) - { - return true; - } - } - - return false; -} - -bool LLMenuGL::handleUnicodeCharHere( llwchar uni_char ) -{ - if (jumpKeysActive()) - { - return handleJumpKey((KEY)uni_char); - } - return false; -} - -bool LLMenuGL::handleHover( S32 x, S32 y, MASK mask ) -{ - // leave submenu in place if slope of mouse < MAX_MOUSE_SLOPE_SUB_MENU - bool no_mouse_data = mLastMouseX == 0 && mLastMouseY == 0; - S32 mouse_delta_x = no_mouse_data ? 0 : x - mLastMouseX; - S32 mouse_delta_y = no_mouse_data ? 0 : y - mLastMouseY; - LLVector2 mouse_dir((F32)mouse_delta_x, (F32)mouse_delta_y); - mouse_dir.normVec(); - LLVector2 mouse_avg_dir((F32)mMouseVelX, (F32)mMouseVelY); - mouse_avg_dir.normVec(); - F32 interp = 0.5f * (llclamp(mouse_dir * mouse_avg_dir, 0.f, 1.f)); - mMouseVelX = ll_round(lerp((F32)mouse_delta_x, (F32)mMouseVelX, interp)); - mMouseVelY = ll_round(lerp((F32)mouse_delta_y, (F32)mMouseVelY, interp)); - mLastMouseX = x; - mLastMouseY = y; - - // don't change menu focus unless mouse is moving or alt key is not held down - if ((llabs(mMouseVelX) > 0 || - llabs(mMouseVelY) > 0) && - (!mHasSelection || - //(mouse_delta_x == 0 && mouse_delta_y == 0) || - (mMouseVelX < 0) || - llabs((F32)mMouseVelY) / llabs((F32)mMouseVelX) > MAX_MOUSE_SLOPE_SUB_MENU)) - { - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if (!viewp->pointInView(local_x, local_y) && ((LLMenuItemGL*)viewp)->getHighlight()) - { - // moving mouse always highlights new item - if (mouse_delta_x != 0 || mouse_delta_y != 0) - { - ((LLMenuItemGL*)viewp)->setHighlight(false); - } - } - } - - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - //RN: always call handleHover to track mGotHover status - // but only set highlight when mouse is moving - if( viewp->getVisible() && - //RN: allow disabled items to be highlighted to preserve "active" menus when - // moving mouse through them - //viewp->getEnabled() && - viewp->pointInView(local_x, local_y) && - viewp->handleHover(local_x, local_y, mask)) - { - // moving mouse always highlights new item - if (mouse_delta_x != 0 || mouse_delta_y != 0) - { - ((LLMenuItemGL*)viewp)->setHighlight(true); - LLMenuGL::setKeyboardMode(false); - } - mHasSelection = true; - } - } - } - getWindow()->setCursor(UI_CURSOR_ARROW); - - // *HACK Release the mouse capture - // This is done to release the mouse after the Navigation Bar "Back" or "Forward" button - // drop-down menu is shown. Otherwise any other view won't be able to handle mouse events - // until the user chooses one of the drop-down menu items. - - return true; -} - -bool LLMenuGL::handleScrollWheel( S32 x, S32 y, S32 clicks ) -{ - if (!mScrollable) - return blockMouseEvent(x, y); - - if( clicks > 0 ) - { - while( clicks-- ) - scrollItems(SD_DOWN); - } - else - { - while( clicks++ ) - scrollItems(SD_UP); - } - - return true; -} - - -void LLMenuGL::draw( void ) -{ - if (mNeedsArrange) - { - arrange(); - mNeedsArrange = false; - } - if (mDropShadowed && !mTornOff) - { - static LLUIColor color_drop_shadow = LLUIColorTable::instance().getColor("ColorDropShadow"); - gl_drop_shadow(0, getRect().getHeight(), getRect().getWidth(), 0, - color_drop_shadow, DROP_SHADOW_FLOATER); - } - - if( mBgVisible ) - { - gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0, mBackgroundColor.get() ); - } - LLView::draw(); -} - -void LLMenuGL::drawBackground(LLMenuItemGL* itemp, F32 alpha) -{ - LLColor4 color = itemp->getHighlightBgColor() % alpha; - gGL.color4fv( color.mV ); - LLRect item_rect = itemp->getRect(); - gl_rect_2d( 0, item_rect.getHeight(), item_rect.getWidth(), 0); -} - -void LLMenuGL::setVisible(bool visible) -{ - if (visible != getVisible()) - { - if (!visible) - { - mFadeTimer.start(); - clearHoverItem(); - // reset last known mouse coordinates so - // we don't spoof a mouse move next time we're opened - mLastMouseX = 0; - mLastMouseY = 0; - } - else - { - mHasSelection = true; - mFadeTimer.stop(); - } - - LLView::setVisible(visible); - } -} - -LLMenuGL* LLMenuGL::findChildMenuByName(const std::string& name, bool recurse) const -{ - LLView* view = findChildView(name, recurse); - if (view) - { - LLMenuItemBranchGL* branch = dynamic_cast(view); - if (branch) - { - return branch->getBranch(); - } - - LLMenuGL* menup = dynamic_cast(view); - if (menup) - { - return menup; - } - } - LL_WARNS() << "Child Menu " << name << " not found in menu " << getName() << LL_ENDL; - return NULL; -} - -bool LLMenuGL::clearHoverItem() -{ - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLMenuItemGL* itemp = (LLMenuItemGL*)*child_it; - if (itemp->getHighlight()) - { - itemp->setHighlight(false); - return true; - } - } - return false; -} - -void hide_top_view( LLView* view ) -{ - if( view ) view->setVisible( false ); -} - - -// x and y are the desired location for the popup, in the spawning_view's -// coordinate frame, NOT necessarily the mouse location -// static -void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y, S32 mouse_x, S32 mouse_y) -{ - const S32 CURSOR_HEIGHT = 22; // Approximate "normal" cursor size - const S32 CURSOR_WIDTH = 12; - - if (menu->getChildList()->empty()) - { - return; - } - - menu->setVisible( true ); - - if(!menu->getAlwaysShowMenu()) - { - //Do not show menu if all menu items are disabled - bool item_enabled = false; - for (LLView::child_list_t::const_iterator itor = menu->getChildList()->begin(); - itor != menu->getChildList()->end(); - ++itor) - { - LLView *menu_item = (*itor); - item_enabled = item_enabled || menu_item->getEnabled(); - } - - if(!item_enabled) - { - menu->setVisible( false ); - return; - } - } - - // Resetting scrolling position - if (menu->isScrollable() && menu->isScrollPositionOnShowReset()) - { - menu->mFirstVisibleItem = NULL; - } - - // Fix menu rect if needed. - menu->needsArrange(); - menu->arrangeAndClear(); - - if ((mouse_x == 0) || (mouse_y == 0)) - - { - // Save click point for detecting cursor moves before mouse-up. - // Must be in local coords to compare with mouseUp events. - // If the mouse doesn't move, the menu will stay open ala the Mac. - // See also LLContextMenu::show() - - LLUI::getInstance()->getMousePositionLocal(menu->getParent(), &mouse_x, &mouse_y); - } - - - LLMenuHolderGL::sContextMenuSpawnPos.set(mouse_x,mouse_y); - - const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getRect(); - - const S32 HPAD = 2; - LLRect rect = menu->getRect(); - S32 left = x + HPAD; - S32 top = y; - spawning_view->localPointToOtherView(left, top, &left, &top, menu->getParent()); - rect.setLeftTopAndSize( left, top, - rect.getWidth(), rect.getHeight() ); - menu->setRect( rect ); - - - // Adjust context menu to fit onscreen - LLRect mouse_rect; - const S32 MOUSE_CURSOR_PADDING = 5; - mouse_rect.setLeftTopAndSize(mouse_x - MOUSE_CURSOR_PADDING, - mouse_y + MOUSE_CURSOR_PADDING, - CURSOR_WIDTH + MOUSE_CURSOR_PADDING * 2, - CURSOR_HEIGHT + MOUSE_CURSOR_PADDING * 2); - menu->translateIntoRectWithExclusion( menu_region_rect, mouse_rect ); - if (menu->getRect().mTop > menu_region_rect.mTop) - { - // not enough space: align with top, ignore exclusion - menu->translateIntoRect( menu_region_rect ); - } - menu->getParent()->sendChildToFront(menu); -} - -///============================================================================ -/// Class LLMenuBarGL -///============================================================================ - -static LLDefaultChildRegistry::Register r2("menu_bar"); - -LLMenuBarGL::LLMenuBarGL( const Params& p ) -: LLMenuGL(p), - mAltKeyTrigger(false) -{} - -// Default destructor -LLMenuBarGL::~LLMenuBarGL() -{ - std::for_each(mAccelerators.begin(), mAccelerators.end(), DeletePointer()); - mAccelerators.clear(); -} - -bool LLMenuBarGL::handleAcceleratorKey(KEY key, MASK mask) -{ - if (getHighlightedItem() && mask == MASK_NONE) - { - // unmodified key accelerators are ignored when navigating menu - // (but are used as jump keys so will still work when appropriate menu is up) - return false; - } - bool result = LLMenuGL::handleAcceleratorKey(key, mask); - if (result && mask & MASK_ALT) - { - // ALT key used to trigger hotkey, don't use as shortcut to open menu - mAltKeyTrigger = false; - } - - if(!result - && (key == KEY_F10 && mask == MASK_CONTROL) - && !gKeyboard->getKeyRepeated(key) - && isInVisibleChain()) - { - if (getHighlightedItem()) - { - clearHoverItem(); - LLMenuGL::setKeyboardMode(false); - } - else - { - // close menus originating from other menu bars when first opening menu via keyboard - LLMenuGL::sMenuContainer->hideMenus(); - highlightNextItem(NULL); - LLMenuGL::setKeyboardMode(true); - } - return true; - } - - if (result && !getHighlightedItem() && LLMenuGL::sMenuContainer->hasVisibleMenu()) - { - // close menus originating from other menu bars - LLMenuGL::sMenuContainer->hideMenus(); - } - - return result; -} - -bool LLMenuBarGL::handleKeyHere(KEY key, MASK mask) -{ - static LLUICachedControl use_altkey_for_menus ("UseAltKeyForMenus", 0); - if(key == KEY_ALT && !gKeyboard->getKeyRepeated(key) && use_altkey_for_menus) - { - mAltKeyTrigger = true; - } - else // if any key other than ALT hit, clear out waiting for Alt key mode - { - mAltKeyTrigger = false; - } - - if (key == KEY_ESCAPE && mask == MASK_NONE) - { - LLMenuGL::setKeyboardMode(false); - // if any menus are visible, this will return true, stopping further processing of ESCAPE key - return LLMenuGL::sMenuContainer->hideMenus(); - } - - // before processing any other key, check to see if ALT key has triggered menu access - checkMenuTrigger(); - - return LLMenuGL::handleKeyHere(key, mask); -} - -bool LLMenuBarGL::handleJumpKey(KEY key) -{ - // perform case-insensitive comparison - key = toupper(key); - navigation_key_map_t::iterator found_it = mJumpKeys.find(key); - if(found_it != mJumpKeys.end() && found_it->second->getEnabled()) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(true); - - found_it->second->setHighlight(true); - found_it->second->onCommit(); - } - return true; -} - -bool LLMenuBarGL::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // clicks on menu bar closes existing menus from other contexts but leave - // own menu open so that we get toggle behavior - if (!getHighlightedItem() || !getHighlightedItem()->isActive()) - { - LLMenuGL::sMenuContainer->hideMenus(); - } - - return LLMenuGL::handleMouseDown(x, y, mask); -} - -bool LLMenuBarGL::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - return LLMenuGL::handleMouseDown(x, y, mask); -} - -void LLMenuBarGL::draw() -{ - LLMenuItemGL* itemp = getHighlightedItem(); - // If we are in mouse-control mode and the mouse cursor is not hovering over - // the current highlighted menu item and it isn't open, then remove the - // highlight. This is done via a polling mechanism here, as we don't receive - // notifications when the mouse cursor moves off of us - if (itemp && !itemp->isOpen() && !itemp->getHover() && !LLMenuGL::getKeyboardMode()) - { - clearHoverItem(); - } - - checkMenuTrigger(); - - LLMenuGL::draw(); -} - - -void LLMenuBarGL::checkMenuTrigger() -{ - // has the ALT key been pressed and subsequently released? - if (mAltKeyTrigger && !gKeyboard->getKeyDown(KEY_ALT)) - { - // if alt key was released quickly, treat it as a menu access key - // otherwise it was probably an Alt-zoom or similar action - static LLUICachedControl menu_access_key_time ("MenuAccessKeyTime", 0); - if (gKeyboard->getKeyElapsedTime(KEY_ALT) <= menu_access_key_time || - gKeyboard->getKeyElapsedFrameCount(KEY_ALT) < 2) - { - if (getHighlightedItem()) - { - clearHoverItem(); - } - else - { - // close menus originating from other menu bars - LLMenuGL::sMenuContainer->hideMenus(); - - highlightNextItem(NULL); - LLMenuGL::setKeyboardMode(true); - } - } - mAltKeyTrigger = false; - } -} - -bool LLMenuBarGL::jumpKeysActive() -{ - // require user to be in keyboard navigation mode to activate key triggers - // as menu bars are always visible and it is easy to leave the mouse cursor over them - return LLMenuGL::getKeyboardMode() && getHighlightedItem() && LLMenuGL::jumpKeysActive(); -} - -// rearrange the child rects so they fit the shape of the menu bar. -void LLMenuBarGL::arrange( void ) -{ - U32 pos = 0; - LLRect rect( 0, getRect().getHeight(), 0, 0 ); - item_list_t::const_iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - LLMenuItemGL* item = *item_iter; - if (item->getVisible()) - { - rect.mLeft = pos; - pos += item->getNominalWidth(); - rect.mRight = pos; - item->setRect( rect ); - item->buildDrawLabel(); - } - } - reshape(rect.mRight, rect.getHeight()); -} - - -S32 LLMenuBarGL::getRightmostMenuEdge() -{ - // Find the last visible menu - item_list_t::reverse_iterator item_iter; - for (item_iter = mItems.rbegin(); item_iter != mItems.rend(); ++item_iter) - { - if ((*item_iter)->getVisible()) - { - break; - } - } - - if (item_iter == mItems.rend()) - { - return 0; - } - return (*item_iter)->getRect().mRight; -} - -// add a vertical separator to this menu -bool LLMenuBarGL::addSeparator() -{ - LLMenuItemGL* separator = new LLMenuItemVerticalSeparatorGL(); - return append( separator ); -} - -// add a menu - this will create a drop down menu. -bool LLMenuBarGL::appendMenu( LLMenuGL* menu ) -{ - if( menu == this ) - { - LL_ERRS() << "** Attempt to attach menu to itself. This is certainly " - << "a logic error." << LL_ENDL; - } - - bool success = true; - - // *TODO: Hack! Fix this - LLMenuItemBranchDownGL::Params p; - p.name = menu->getName(); - p.label = menu->getLabel(); - p.visible = menu->getVisible(); - p.branch = menu; - p.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); - p.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor"); - p.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); - p.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); - p.font = menu->getFont(); - - LLMenuItemBranchDownGL* branch = LLUICtrlFactory::create(p); - success &= branch->addToAcceleratorList(&mAccelerators); - success &= append( branch ); - branch->setJumpKey(branch->getJumpKey()); - menu->updateParent(LLMenuGL::sMenuContainer); - - return success; -} - -bool LLMenuBarGL::handleHover( S32 x, S32 y, MASK mask ) -{ - bool handled = false; - LLView* active_menu = NULL; - - bool no_mouse_data = mLastMouseX == 0 && mLastMouseY == 0; - S32 mouse_delta_x = no_mouse_data ? 0 : x - mLastMouseX; - S32 mouse_delta_y = no_mouse_data ? 0 : y - mLastMouseY; - mMouseVelX = (mMouseVelX / 2) + (mouse_delta_x / 2); - mMouseVelY = (mMouseVelY / 2) + (mouse_delta_y / 2); - mLastMouseX = x; - mLastMouseY = y; - - // if nothing currently selected or mouse has moved since last call, pick menu item via mouse - // otherwise let keyboard control it - if (!getHighlightedItem() || !LLMenuGL::getKeyboardMode() || llabs(mMouseVelX) > 0 || llabs(mMouseVelY) > 0) - { - // find current active menu - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - if (((LLMenuItemGL*)viewp)->isOpen()) - { - active_menu = viewp; - } - } - - // check for new active menu - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if( viewp->getVisible() && - viewp->getEnabled() && - viewp->pointInView(local_x, local_y) && - viewp->handleHover(local_x, local_y, mask)) - { - ((LLMenuItemGL*)viewp)->setHighlight(true); - handled = true; - if (active_menu && active_menu != viewp) - { - ((LLMenuItemGL*)viewp)->onCommit(); - LLMenuGL::setKeyboardMode(false); - } - LLMenuGL::setKeyboardMode(false); - } - } - - if (handled) - { - // set hover false on inactive menus - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if (!viewp->pointInView(local_x, local_y) && ((LLMenuItemGL*)viewp)->getHighlight()) - { - ((LLMenuItemGL*)viewp)->setHighlight(false); - } - } - } - } - - getWindow()->setCursor(UI_CURSOR_ARROW); - - return true; -} - -///============================================================================ -/// Class LLMenuHolderGL -///============================================================================ -LLCoordGL LLMenuHolderGL::sContextMenuSpawnPos(S32_MAX, S32_MAX); - -LLMenuHolderGL::LLMenuHolderGL(const LLMenuHolderGL::Params& p) - : LLPanel(p) -{ - sItemActivationTimer.stop(); - mCanHide = true; -} - -void LLMenuHolderGL::draw() -{ - LLView::draw(); - // now draw last selected item as overlay - LLMenuItemGL* selecteditem = (LLMenuItemGL*)sItemLastSelectedHandle.get(); - if (selecteditem && selecteditem->getVisible() && sItemActivationTimer.getStarted() && sItemActivationTimer.getElapsedTimeF32() < ACTIVATE_HIGHLIGHT_TIME) - { - // make sure toggle items, for example, show the proper state when fading out - selecteditem->buildDrawLabel(); - - LLRect item_rect; - selecteditem->localRectToOtherView(selecteditem->getLocalRect(), &item_rect, this); - - F32 interpolant = sItemActivationTimer.getElapsedTimeF32() / ACTIVATE_HIGHLIGHT_TIME; - - LLUI::pushMatrix(); - { - LLUI::translate((F32)item_rect.mLeft, (F32)item_rect.mBottom); - selecteditem->getMenu()->drawBackground(selecteditem, interpolant); - selecteditem->draw(); - } - LLUI::popMatrix(); - } -} - -bool LLMenuHolderGL::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - bool handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; - if (!handled) - { - LLMenuGL* visible_menu = (LLMenuGL*)getVisibleMenu(); - LLMenuItemGL* parent_menu = visible_menu ? visible_menu->getParentMenuItem() : NULL; - if (parent_menu && parent_menu->getVisible()) - { - // don't hide menu if parent was hit - LLRect parent_rect; - parent_menu->localRectToOtherView(parent_menu->getLocalRect(), &parent_rect, this); - if (!parent_rect.pointInRect(x, y)) - { - // clicked off of menu and parent, hide them all - hideMenus(); - } - } - else - { - // no visible parent, clicked off of menu, hide them all - hideMenus(); - } - } - return handled; -} - -bool LLMenuHolderGL::handleRightMouseDown( S32 x, S32 y, MASK mask ) -{ - bool handled = LLView::childrenHandleRightMouseDown(x, y, mask) != NULL; - if (!handled) - { - // clicked off of menu, hide them all - hideMenus(); - } - return handled; -} - -// This occurs when you mouse-down to spawn a context menu, hold the button -// down, move off the menu, then mouse-up. We want this to close the menu. -bool LLMenuHolderGL::handleRightMouseUp( S32 x, S32 y, MASK mask ) -{ - const S32 SLOP = 2; - S32 spawn_dx = (x - sContextMenuSpawnPos.mX); - S32 spawn_dy = (y - sContextMenuSpawnPos.mY); - if (-SLOP <= spawn_dx && spawn_dx <= SLOP - && -SLOP <= spawn_dy && spawn_dy <= SLOP) - { - // we're still inside the slop region from spawning this menu - // so interpret the mouse-up as a single-click to show and leave on - // screen - sContextMenuSpawnPos.set(S32_MAX, S32_MAX); - return true; - } - - bool handled = LLView::childrenHandleRightMouseUp(x, y, mask) != NULL; - if (!handled) - { - // clicked off of menu, hide them all - hideMenus(); - } - return handled; -} - -bool LLMenuHolderGL::handleKey(KEY key, MASK mask, bool called_from_parent) -{ - bool handled = false; - LLMenuGL* const pMenu = dynamic_cast(getVisibleMenu()); - - if (pMenu) - { - //eat TAB key - EXT-7000 - if (key == KEY_TAB && mask == MASK_NONE) - { - return true; - } - - //handle ESCAPE and RETURN key - handled = LLPanel::handleKey(key, mask, called_from_parent); - if (!handled) - { - if (pMenu->getHighlightedItem()) - { - handled = pMenu->handleKey(key, mask, true); - } - else if (mask == MASK_NONE || (key >= KEY_LEFT && key <= KEY_DOWN)) - { - //highlight first enabled one - if(pMenu->highlightNextItem(NULL)) - { - handled = true; - } - } - } - } - - return handled; - -} - -void LLMenuHolderGL::reshape(S32 width, S32 height, bool called_from_parent) -{ - if (width != getRect().getWidth() || height != getRect().getHeight()) - { - hideMenus(); - } - LLView::reshape(width, height, called_from_parent); -} - -LLView* const LLMenuHolderGL::getVisibleMenu() const -{ - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - if (viewp->getVisible() && dynamic_cast(viewp) != NULL) - { - return viewp; - } - } - return NULL; -} - - -bool LLMenuHolderGL::hideMenus() -{ - if (!mCanHide) - { - return false; - } - LLMenuGL::setKeyboardMode(false); - bool menu_visible = hasVisibleMenu(); - if (menu_visible) - { - // clicked off of menu, hide them all - for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) - { - LLView* viewp = *child_it; - if (dynamic_cast(viewp) != NULL && viewp->getVisible()) - { - viewp->setVisible(false); - } - } - } - //if (gFocusMgr.childHasKeyboardFocus(this)) - //{ - // gFocusMgr.setKeyboardFocus(NULL); - //} - - return menu_visible; -} - -void LLMenuHolderGL::setActivatedItem(LLMenuItemGL* item) -{ - sItemLastSelectedHandle = item->getHandle(); - sItemActivationTimer.start(); -} - -///============================================================================ -/// Class LLTearOffMenu -///============================================================================ -LLTearOffMenu::LLTearOffMenu(LLMenuGL* menup) : - LLFloater(LLSD()), - mQuitRequested(false) -{ - S32 floater_header_size = getHeaderHeight(); - - setName(menup->getName()); - setTitle(menup->getLabel()); - setCanMinimize(false); - // flag menu as being torn off - menup->setTornOff(true); - // update menu layout as torn off menu (no spillover menus) - menup->needsArrange(); - - LLRect rect; - menup->localRectToOtherView(LLRect(-1, menup->getRect().getHeight(), menup->getRect().getWidth() + 3, 0), &rect, gFloaterView); - // make sure this floater is big enough for menu - mTargetHeight = rect.getHeight() + floater_header_size; - reshape(rect.getWidth(), rect.getHeight()); - setRect(rect); - - // attach menu to floater - menup->setFollows( FOLLOWS_LEFT | FOLLOWS_BOTTOM ); - mOldParent = menup->getParent(); - addChild(menup); - menup->setVisible(true); - LLRect menu_rect = menup->getRect(); - menu_rect.setOriginAndSize( 1, 1, - menu_rect.getWidth(), menu_rect.getHeight()); - menup->setRect(menu_rect); - menup->setDropShadowed(false); - - mMenu = menup; - - // highlight first item (tear off item will be disabled) - mMenu->highlightNextItem(NULL); - - // Can't do this in postBuild() because that is only called for floaters - // constructed from XML. - mCloseSignal.connect(boost::bind(&LLTearOffMenu::closeTearOff, this)); -} - -LLTearOffMenu::~LLTearOffMenu() -{ -} - -void LLTearOffMenu::draw() -{ - mMenu->setBackgroundVisible(isBackgroundOpaque()); - - if (getRect().getHeight() != mTargetHeight) - { - // animate towards target height - reshape(getRect().getWidth(), llceil(lerp((F32)getRect().getHeight(), (F32)mTargetHeight, LLSmoothInterpolation::getInterpolant(0.05f)))); - } - mMenu->needsArrange(); - LLFloater::draw(); -} - -void LLTearOffMenu::onFocusReceived() -{ - if (mQuitRequested) - { - return; - } - - // if nothing is highlighted, just highlight first item - if (!mMenu->getHighlightedItem()) - { - mMenu->highlightNextItem(NULL); - } - - // parent menu items get highlights so navigation logic keeps working - LLMenuItemGL* parent_menu_item = mMenu->getParentMenuItem(); - while(parent_menu_item) - { - if (parent_menu_item->getMenu()->getVisible()) - { - parent_menu_item->setHighlight(true); - parent_menu_item = parent_menu_item->getMenu()->getParentMenuItem(); - } - else - { - break; - } - } - LLFloater::onFocusReceived(); -} - -void LLTearOffMenu::onFocusLost() -{ - // remove highlight from parent item and our own menu - mMenu->clearHoverItem(); - LLFloater::onFocusLost(); -} - -bool LLTearOffMenu::handleUnicodeChar(llwchar uni_char, bool called_from_parent) -{ - // pass keystrokes down to menu - return mMenu->handleUnicodeChar(uni_char, true); -} - -bool LLTearOffMenu::handleKeyHere(KEY key, MASK mask) -{ - if (!mMenu->getHighlightedItem()) - { - if (key == KEY_UP) - { - mMenu->highlightPrevItem(NULL); - return true; - } - else if (key == KEY_DOWN) - { - mMenu->highlightNextItem(NULL); - return true; - } - } - // pass keystrokes down to menu - return mMenu->handleKey(key, mask, true); -} - -void LLTearOffMenu::translate(S32 x, S32 y) -{ - if (x != 0 && y != 0) - { - // hide open sub-menus by clearing current hover item - mMenu->clearHoverItem(); - } - LLFloater::translate(x, y); -} - -//static -LLTearOffMenu* LLTearOffMenu::create(LLMenuGL* menup) -{ - LLTearOffMenu* tearoffp = new LLTearOffMenu(menup); - // keep onscreen - gFloaterView->adjustToFitScreen(tearoffp, false); - tearoffp->openFloater(LLSD()); - - return tearoffp; -} - -void LLTearOffMenu::updateSize() -{ - if (mMenu) - { - S32 floater_header_size = getHeaderHeight(); - const LLRect &floater_rect = getRect(); - LLRect new_rect; - mMenu->localRectToOtherView(LLRect(-1, mMenu->getRect().getHeight() + floater_header_size, mMenu->getRect().getWidth() + 3, 0), &new_rect, gFloaterView); - - if (floater_rect.getWidth() != new_rect.getWidth() - || mTargetHeight != new_rect.getHeight()) - { - // make sure this floater is big enough for menu - mTargetHeight = new_rect.getHeight(); - reshape(new_rect.getWidth(), mTargetHeight); - - // Restore menu position - LLRect menu_rect = mMenu->getRect(); - menu_rect.setOriginAndSize(1, 1, - menu_rect.getWidth(), menu_rect.getHeight()); - mMenu->setRect(menu_rect); - } - } -} - -void LLTearOffMenu::closeTearOff() -{ - removeChild(mMenu); - mOldParent->addChild(mMenu); - mMenu->clearHoverItem(); - mMenu->setFollowsNone(); - mMenu->setBackgroundVisible(true); - mMenu->setVisible(false); - mMenu->setTornOff(false); - mMenu->setDropShadowed(true); - mQuitRequested = true; -} - -LLContextMenuBranch::LLContextMenuBranch(const LLContextMenuBranch::Params& p) -: LLMenuItemGL(p) -{ - LLContextMenu* branch = static_cast(p.branch); - if (branch) - { - mBranch = branch->getHandle(); - branch->hide(); - branch->setParentMenuItem(this); - } -} - -LLContextMenuBranch::~LLContextMenuBranch() -{ - if (mBranch.get()) - { - mBranch.get()->die(); - } -} - -// called to rebuild the draw label -void LLContextMenuBranch::buildDrawLabel( void ) -{ - auto menu = getBranch(); - if (menu) - { - // default enablement is this -- if any of the subitems are - // enabled, this item is enabled. JC - U32 sub_count = menu->getItemCount(); - U32 i; - bool any_enabled = false; - for (i = 0; i < sub_count; i++) - { - LLMenuItemGL* item = menu->getItem(i); - item->buildDrawLabel(); - if (item->getEnabled() && !item->getDrawTextDisabled() ) - { - any_enabled = true; - break; - } - } - setDrawTextDisabled(!any_enabled); - setEnabled(true); - } - - mDrawAccelLabel.clear(); - std::string st = mDrawAccelLabel; - appendAcceleratorString( st ); - mDrawAccelLabel = st; - - mDrawBranchLabel = LLMenuGL::BRANCH_SUFFIX; -} - -void LLContextMenuBranch::showSubMenu() -{ - auto menu = getBranch(); - if(menu) - { - LLMenuItemGL* menu_item = menu->getParentMenuItem(); - if (menu_item != NULL && menu_item->getVisible()) - { - S32 center_x; - S32 center_y; - localPointToScreen(getRect().getWidth(), getRect().getHeight(), ¢er_x, ¢er_y); - menu->show(center_x, center_y); - } - } -} - -// onCommit() - do the primary funcationality of the menu item. -void LLContextMenuBranch::onCommit( void ) -{ - showSubMenu(); - -} -void LLContextMenuBranch::setHighlight( bool highlight ) -{ - if (highlight == getHighlight()) return; - LLMenuItemGL::setHighlight(highlight); - auto menu = getBranch(); - if (menu) - { - if (highlight) - { - showSubMenu(); - } - else - { - menu->hide(); - } - } -} - - -/////////////////////////////////////////////////////////////////////////////////////////////////////////// -//----------------------------------------------------------------------------- -// class LLContextMenu -// A context menu -//----------------------------------------------------------------------------- -static LLDefaultChildRegistry::Register context_menu_register("context_menu"); -static MenuRegistry::Register context_menu_register2("context_menu"); - - -LLContextMenu::LLContextMenu(const Params& p) -: LLMenuGL(p), - mHoveredAnyItem(false), - mHoverItem(NULL) -{ - //setBackgroundVisible(true); -} - -void LLContextMenu::setVisible(bool visible) -{ - if (!visible) - hide(); -} - -// Takes cursor position in screen space? -void LLContextMenu::show(S32 x, S32 y, LLView* spawning_view) -{ - if (getChildList()->empty()) - { - // nothing to show, so abort - return; - } - // Save click point for detecting cursor moves before mouse-up. - // Must be in local coords to compare with mouseUp events. - // If the mouse doesn't move, the menu will stay open ala the Mac. - // See also LLMenuGL::showPopup() - LLMenuHolderGL::sContextMenuSpawnPos.set(x,y); - - arrangeAndClear(); - - S32 width = getRect().getWidth(); - S32 height = getRect().getHeight(); - const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect(); - LLView* parent_view = getParent(); - - // Open upwards if menu extends past bottom - if (y - height < menu_region_rect.mBottom) - { - if (getParentMenuItem()) // Adjust if this is a submenu - { - y += height - getParentMenuItem()->getNominalHeight(); - } - else - { - y += height; - } - } - - // Open out to the left if menu extends past right edge - if (x + width > menu_region_rect.mRight) - { - if (getParentMenuItem()) - { - x -= getParentMenuItem()->getRect().getWidth() + width; - } - else - { - x -= width; - } - } - - S32 local_x, local_y; - parent_view->screenPointToLocal(x, y, &local_x, &local_y); - - LLRect rect; - rect.setLeftTopAndSize(local_x, local_y, width, height); - setRect(rect); - arrange(); - - if (spawning_view) - { - mSpawningViewHandle = spawning_view->getHandle(); - } - else - { - mSpawningViewHandle.markDead(); - } - LLView::setVisible(true); -} - -void LLContextMenu::hide() -{ - if (!getVisible()) return; - - LLView::setVisible(false); - - if (mHoverItem) - { - mHoverItem->setHighlight( false ); - } - mHoverItem = NULL; -} - - -bool LLContextMenu::handleHover( S32 x, S32 y, MASK mask ) -{ - LLMenuGL::handleHover(x,y,mask); - - bool handled = false; - - LLMenuItemGL *item = getHighlightedItem(); - - if (item && item->getEnabled()) - { - getWindow()->setCursor(UI_CURSOR_ARROW); - handled = true; - - if (item != mHoverItem) - { - if (mHoverItem) - { - mHoverItem->setHighlight( false ); - } - mHoverItem = item; - mHoverItem->setHighlight( true ); - } - mHoveredAnyItem = true; - } - else - { - // clear out our selection - if (mHoverItem) - { - mHoverItem->setHighlight(false); - mHoverItem = NULL; - } - } - - if( !handled && pointInView( x, y ) ) - { - getWindow()->setCursor(UI_CURSOR_ARROW); - handled = true; - } - - return handled; -} - -// handleMouseDown and handleMouseUp are handled by LLMenuGL - - -bool LLContextMenu::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // The click was somewhere within our rectangle - LLMenuItemGL *item = getHighlightedItem(); - - S32 local_x = x - getRect().mLeft; - S32 local_y = y - getRect().mBottom; - - bool clicked_in_menu = pointInView(local_x, local_y) ; - - // grab mouse if right clicking anywhere within pie (even deadzone in middle), to detect drag outside of pie - if (clicked_in_menu) - { - // capture mouse cursor as if on initial menu show - handled = true; - } - - if (item) - { - // lie to the item about where the click happened - // to make sure it's within the item's rectangle - if (item->handleMouseDown( 0, 0, mask )) - { - handled = true; - } - } - - return handled; -} - -bool LLContextMenu::handleRightMouseUp( S32 x, S32 y, MASK mask ) -{ - S32 local_x = x - getRect().mLeft; - S32 local_y = y - getRect().mBottom; - - if (!mHoveredAnyItem && !pointInView(local_x, local_y)) - { - sMenuContainer->hideMenus(); - return true; - } - - - bool result = handleMouseUp( x, y, mask ); - mHoveredAnyItem = false; - - return result; -} - -bool LLContextMenu::addChild(LLView* view, S32 tab_group) -{ - return addContextChild(view, tab_group); -} - +/** + * @file llmenugl.cpp + * @brief LLMenuItemGL base 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$ + */ + +//***************************************************************************** +// +// This file contains the opengl based menu implementation. +// +// NOTES: A menu label is split into 4 columns. The left column, the +// label colum, the accelerator column, and the right column. The left +// column is used for displaying boolean values for toggle and check +// controls. The right column is used for submenus. +// +//***************************************************************************** + +//#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "llmenugl.h" + +#include "llgl.h" +#include "llmath.h" +#include "llrender.h" +#include "llfocusmgr.h" +#include "llcoord.h" +#include "llwindow.h" +#include "llcriticaldamp.h" +#include "lluictrlfactory.h" + +#include "llbutton.h" +#include "llfontgl.h" +#include "llresmgr.h" +#include "lltrans.h" +#include "llui.h" + +#include "llstl.h" + +#include "v2math.h" +#include +#include + +// static +LLMenuHolderGL *LLMenuGL::sMenuContainer = NULL; +view_listener_t::listener_map_t view_listener_t::sListeners; + +S32 MENU_BAR_HEIGHT = 18; +S32 MENU_BAR_WIDTH = 410; + +///============================================================================ +/// Local function declarations, constants, enums, and typedefs +///============================================================================ + +const S32 LABEL_BOTTOM_PAD_PIXELS = 2; + +const U32 LEFT_PAD_PIXELS = 3; +const U32 LEFT_WIDTH_PIXELS = 15; +const U32 LEFT_PLAIN_PIXELS = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS; + +const U32 RIGHT_PAD_PIXELS = 7; +const U32 RIGHT_WIDTH_PIXELS = 15; +const U32 RIGHT_PLAIN_PIXELS = RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS; + +const U32 PLAIN_PAD_PIXELS = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS + RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS; + +const U32 BRIEF_PAD_PIXELS = 2; + +const U32 SEPARATOR_HEIGHT_PIXELS = 8; +const S32 TEAROFF_SEPARATOR_HEIGHT_PIXELS = 10; +const S32 MENU_ITEM_PADDING = 4; + +const std::string SEPARATOR_NAME("separator"); +const std::string VERTICAL_SEPARATOR_LABEL( "|" ); + +const std::string LLMenuGL::BOOLEAN_TRUE_PREFIX( "\xE2\x9C\x94" ); // U+2714 HEAVY CHECK MARK +const std::string LLMenuGL::BRANCH_SUFFIX( "\xe2\x96\xb8" ); // U+25B6 BLACK RIGHT-POINTING TRIANGLE +const std::string LLMenuGL::ARROW_UP ("^^^^^^^"); +const std::string LLMenuGL::ARROW_DOWN("vvvvvvv"); + +const F32 MAX_MOUSE_SLOPE_SUB_MENU = 0.9f; + +bool LLMenuGL::sKeyboardMode = false; + +LLHandle LLMenuHolderGL::sItemLastSelectedHandle; +LLFrameTimer LLMenuHolderGL::sItemActivationTimer; + +const F32 ACTIVATE_HIGHLIGHT_TIME = 0.3f; + +static MenuRegistry::Register register_menu_item("menu_item"); +static MenuRegistry::Register register_separator("menu_item_separator"); +static MenuRegistry::Register register_menu_item_call("menu_item_call"); +static MenuRegistry::Register register_menu_item_check("menu_item_check"); +// Created programmatically but we need to specify custom colors in xml +static MenuRegistry::Register register_menu_item_tear_off("menu_item_tear_off"); +static MenuRegistry::Register register_menu("menu"); + +static LLDefaultChildRegistry::Register register_menu_default("menu"); + + + +///============================================================================ +/// Class LLMenuItemGL +///============================================================================ + +LLMenuItemGL::Params::Params() +: shortcut("shortcut"), + jump_key("jump_key", KEY_NONE), + use_mac_ctrl("use_mac_ctrl", false), + allow_key_repeat("allow_key_repeat", false), + rect("rect"), + left("left"), + top("top"), + right("right"), + bottom("bottom"), + width("width"), + height("height"), + bottom_delta("bottom_delta"), + left_delta("left_delta"), + enabled_color("enabled_color"), + disabled_color("disabled_color"), + highlight_bg_color("highlight_bg_color"), + highlight_fg_color("highlight_fg_color") +{ + changeDefault(mouse_opaque, true); +} + +// Default constructor +LLMenuItemGL::LLMenuItemGL(const LLMenuItemGL::Params& p) +: LLUICtrl(p), + mJumpKey(p.jump_key), + mAllowKeyRepeat(p.allow_key_repeat), + mHighlight( false ), + mGotHover( false ), + mBriefItem( false ), + mDrawTextDisabled( false ), + mFont(p.font), + mAcceleratorKey(KEY_NONE), + mAcceleratorMask(MASK_NONE), + mLabel(p.label.isProvided() ? p.label() : p.name()), + mEnabledColor(p.enabled_color()), + mDisabledColor(p.disabled_color()), + mHighlightBackground(p.highlight_bg_color()), + mHighlightForeground(p.highlight_fg_color()) +{ +#ifdef LL_DARWIN + // See if this Mac accelerator should really use the ctrl key and not get mapped to cmd + bool useMacCtrl = p.use_mac_ctrl; +#endif // LL_DARWIN + + std::string shortcut = p.shortcut; + if (shortcut.find("control") != shortcut.npos) + { +#ifdef LL_DARWIN + if ( useMacCtrl ) + { + mAcceleratorMask |= MASK_MAC_CONTROL; + } +#endif // LL_DARWIN + mAcceleratorMask |= MASK_CONTROL; + } + if (shortcut.find("alt") != shortcut.npos) + { + mAcceleratorMask |= MASK_ALT; + } + if (shortcut.find("shift") != shortcut.npos) + { + mAcceleratorMask |= MASK_SHIFT; + } + S32 pipe_pos = shortcut.rfind("|"); + std::string key_str = shortcut.substr(pipe_pos+1); + + LLKeyboard::keyFromString(key_str, &mAcceleratorKey); + + LL_DEBUGS("HotKeys") << "Process short cut key: shortcut: " << shortcut + << ", key str: " << key_str + << ", accelerator mask: " << mAcceleratorMask + << ", accelerator key: " << mAcceleratorKey + << LL_ENDL; +} + +//virtual +void LLMenuItemGL::setValue(const LLSD& value) +{ + setLabel(value.asString()); +} + +//virtual +LLSD LLMenuItemGL::getValue() const +{ + return getLabel(); +} + +//virtual +bool LLMenuItemGL::hasAccelerator(const KEY &key, const MASK &mask) const +{ + return (mAcceleratorKey == key) && (mAcceleratorMask == mask); +} + +//virtual +bool LLMenuItemGL::handleAcceleratorKey(KEY key, MASK mask) +{ + if( getEnabled() && (!gKeyboard->getKeyRepeated(key) || mAllowKeyRepeat) && (key == mAcceleratorKey) && (mask == (mAcceleratorMask & MASK_NORMALKEYS)) ) + { + onCommit(); + return true; + } + return false; +} + +bool LLMenuItemGL::handleHover(S32 x, S32 y, MASK mask) +{ + getWindow()->setCursor(UI_CURSOR_ARROW); + return true; +} + +//virtual +bool LLMenuItemGL::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + return LLUICtrl::handleRightMouseDown(x,y,mask); +} + +void LLMenuItemGL::onMouseEnter(S32 x, S32 y, MASK mask) +{ + setHover(true); + LLUICtrl::onMouseEnter(x,y,mask); +} + +void LLMenuItemGL::onMouseLeave(S32 x, S32 y, MASK mask) +{ + setHover(false); + LLUICtrl::onMouseLeave(x,y,mask); +} + +//virtual +bool LLMenuItemGL::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + // If this event came from a right-click context menu spawn, + // process as a left-click to allow menu items to be hit + if (LLMenuHolderGL::sContextMenuSpawnPos.mX != S32_MAX + || LLMenuHolderGL::sContextMenuSpawnPos.mY != S32_MAX) + { + bool handled = handleMouseUp(x, y, mask); + return handled; + } + return LLUICtrl::handleRightMouseUp(x,y,mask); +} + +// This function checks to see if the accelerator key is already in use; +// if not, it will be added to the list +bool LLMenuItemGL::addToAcceleratorList(std::list *listp) +{ + LLMenuKeyboardBinding *accelerator = NULL; + + if (mAcceleratorKey != KEY_NONE) + { + std::list::iterator list_it; + for (list_it = listp->begin(); list_it != listp->end(); ++list_it) + { + accelerator = *list_it; + if ((accelerator->mKey == mAcceleratorKey) && (accelerator->mMask == (mAcceleratorMask & MASK_NORMALKEYS))) + { + + // *NOTE: get calling code to throw up warning or route + // warning messages back to app-provided output + // std::string warning; + // warning.append("Duplicate key binding <"); + // appendAcceleratorString( warning ); + // warning.append("> for menu items:\n "); + // warning.append(accelerator->mName); + // warning.append("\n "); + // warning.append(mLabel); + + // LL_WARNS() << warning << LL_ENDL; + // LLAlertDialog::modalAlert(warning); + return false; + } + } + if (!accelerator) + { + accelerator = new LLMenuKeyboardBinding; + if (accelerator) + { + accelerator->mKey = mAcceleratorKey; + accelerator->mMask = (mAcceleratorMask & MASK_NORMALKEYS); +// accelerator->mName = mLabel; + } + listp->push_back(accelerator);//addData(accelerator); + } + } + return true; +} + +// This function appends the character string representation of +// the current accelerator key and mask to the provided string. +void LLMenuItemGL::appendAcceleratorString( std::string& st ) const +{ + st = LLKeyboard::stringFromAccelerator( mAcceleratorMask, mAcceleratorKey ); + LL_DEBUGS("HotKeys") << "appendAcceleratorString: " << st << LL_ENDL; +} + +void LLMenuItemGL::setJumpKey(KEY key) +{ + mJumpKey = LLStringOps::toUpper((char)key); +} + + +// virtual +U32 LLMenuItemGL::getNominalHeight( void ) const +{ + return mFont->getLineHeight() + MENU_ITEM_PADDING; +} + +//virtual +void LLMenuItemGL::setBriefItem(bool brief) +{ + mBriefItem = brief; +} + +//virtual +bool LLMenuItemGL::isBriefItem() const +{ + return mBriefItem; +} + +// Get the parent menu for this item +LLMenuGL* LLMenuItemGL::getMenu() const +{ + return (LLMenuGL*) getParent(); +} + + +// getNominalWidth() - returns the normal width of this control in +// pixels - this is used for calculating the widest item, as well as +// for horizontal arrangement. +U32 LLMenuItemGL::getNominalWidth( void ) const +{ + U32 width; + + if (mBriefItem) + { + width = BRIEF_PAD_PIXELS; + } + else + { + width = PLAIN_PAD_PIXELS; + } + + if( KEY_NONE != mAcceleratorKey ) + { + width += getMenu()->getShortcutPad(); + std::string temp; + appendAcceleratorString( temp ); + width += mFont->getWidth( temp ); + } + width += mFont->getWidth( mLabel.getWString().c_str() ); + return width; +} + +// called to rebuild the draw label +void LLMenuItemGL::buildDrawLabel( void ) +{ + mDrawAccelLabel.clear(); + std::string st = mDrawAccelLabel.getString(); + appendAcceleratorString( st ); + mDrawAccelLabel = st; +} + +void LLMenuItemGL::onCommit( void ) +{ + // Check torn-off status to allow left-arrow keyboard navigation back + // to parent menu. + // Also, don't hide if item triggered by keyboard shortcut (and hence + // parent not visible). + if (!getMenu()->getTornOff() + && getMenu()->getVisible()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + + LLUICtrl::onCommit(); +} + +// set the hover status (called by it's menu) + void LLMenuItemGL::setHighlight( bool highlight ) +{ + if (highlight) + { + getMenu()->clearHoverItem(); + } + + if (mHighlight != highlight) + { + dirtyRect(); + } + + mHighlight = highlight; +} + + +bool LLMenuItemGL::handleKeyHere( KEY key, MASK mask ) +{ + if (getHighlight() && + getMenu()->isOpen()) + { + if (key == KEY_UP) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + getMenu()->highlightPrevItem(this); + return true; + } + else if (key == KEY_DOWN) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + getMenu()->highlightNextItem(this); + return true; + } + else if (key == KEY_RETURN && mask == MASK_NONE) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + onCommit(); + return true; + } + } + + return false; +} + +bool LLMenuItemGL::handleMouseUp( S32 x, S32 y, MASK mask) +{ + // switch to mouse navigation mode + LLMenuGL::setKeyboardMode(false); + + onCommit(); + make_ui_sound("UISndClickRelease"); + return LLView::handleMouseUp(x, y, mask); +} + +bool LLMenuItemGL::handleMouseDown( S32 x, S32 y, MASK mask) +{ + // switch to mouse navigation mode + LLMenuGL::setKeyboardMode(false); + + setHighlight(true); + return LLView::handleMouseDown(x, y, mask); +} + +bool LLMenuItemGL::handleScrollWheel( S32 x, S32 y, S32 clicks ) +{ + // If the menu is scrollable let it handle the wheel event. + return !getMenu()->isScrollable(); +} + +void LLMenuItemGL::draw( void ) +{ + // *FIX: This can be optimized by using switches. Want to avoid + // that until the functionality is finalized. + + // HACK: Brief items don't highlight. Pie menu takes care of it. JC + // let disabled items be highlighted, just don't draw them as such + if( getEnabled() && getHighlight() && !mBriefItem) + { + gGL.color4fv( mHighlightBackground.get().mV ); + + gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); + } + + LLColor4 color; + + if ( getEnabled() && getHighlight() ) + { + color = mHighlightForeground.get(); + } + else if( getEnabled() && !mDrawTextDisabled ) + { + color = mEnabledColor.get(); + } + else + { + color = mDisabledColor.get(); + } + + // Highlight if needed + if( ll::ui::SearchableControl::getHighlighted() ) + color = ll::ui::SearchableControl::getHighlightColor(); + + // Draw the text on top. + if (mBriefItem) + { + mFont->render( mLabel, 0, BRIEF_PAD_PIXELS / 2, 0, color, + LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL); + } + else + { + if( !mDrawBoolLabel.empty() ) + { + mFont->render( mDrawBoolLabel.getWString(), 0, (F32)LEFT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, + LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); + } + mFont->render( mLabel.getWString(), 0, (F32)LEFT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, + LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); + if( !mDrawAccelLabel.empty() ) + { + mFont->render( mDrawAccelLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PLAIN_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, + LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); + } + if( !mDrawBranchLabel.empty() ) + { + mFont->render( mDrawBranchLabel.getWString(), 0, (F32)getRect().mRight - (F32)RIGHT_PAD_PIXELS, ((F32)MENU_ITEM_PADDING / 2.f), color, + LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, false ); + } + } + + // underline "jump" key only when keyboard navigation has been initiated + if (getMenu()->jumpKeysActive() && LLMenuGL::getKeyboardMode()) + { + std::string upper_case_label = mLabel.getString(); + LLStringUtil::toUpper(upper_case_label); + std::string::size_type offset = upper_case_label.find(mJumpKey); + if (offset != std::string::npos) + { + S32 x_begin = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel, 0, offset); + S32 x_end = LEFT_PLAIN_PIXELS + mFont->getWidth(mLabel, 0, offset + 1); + gl_line_2d(x_begin, (MENU_ITEM_PADDING / 2) + 1, x_end, (MENU_ITEM_PADDING / 2) + 1); + } + } +} + +bool LLMenuItemGL::setLabelArg( const std::string& key, const LLStringExplicit& text ) +{ + mLabel.setArg(key, text); + return true; +} + +void LLMenuItemGL::onVisibilityChange(bool new_visibility) +{ + if (getMenu()) + { + getMenu()->needsArrange(); + } + LLView::onVisibilityChange(new_visibility); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemSeparatorGL +// +// This class represents a separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LLMenuItemSeparatorGL::Params::Params() + : on_visible("on_visible") +{ +} + +LLMenuItemSeparatorGL::LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p) : + LLMenuItemGL( p ) +{ + if (p.on_visible.isProvided()) + { + mVisibleSignal.connect(initEnableCallback(p.on_visible)); + } +} + +//virtual +U32 LLMenuItemSeparatorGL::getNominalHeight( void ) const +{ + return SEPARATOR_HEIGHT_PIXELS; +} + +void LLMenuItemSeparatorGL::draw( void ) +{ + gGL.color4fv( mDisabledColor.get().mV ); + const S32 y = getRect().getHeight() / 2; + const S32 PAD = 6; + gl_line_2d( PAD, y, getRect().getWidth() - PAD, y ); +} + +void LLMenuItemSeparatorGL::buildDrawLabel( void ) +{ + if (mVisibleSignal.num_slots() > 0) + { + bool visible = mVisibleSignal(this, LLSD()); + setVisible(visible); + } +} + +bool LLMenuItemSeparatorGL::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLMenuGL* parent_menu = getMenu(); + if (y > getRect().getHeight() / 2) + { + // the menu items are in the child list in bottom up order + LLView* prev_menu_item = parent_menu->findNextSibling(this); + return (prev_menu_item && prev_menu_item->getVisible() && prev_menu_item->getEnabled()) ? prev_menu_item->handleMouseDown(x, prev_menu_item->getRect().getHeight(), mask) : false; + } + else + { + LLView* next_menu_item = parent_menu->findPrevSibling(this); + return (next_menu_item && next_menu_item->getVisible() && next_menu_item->getEnabled()) ? next_menu_item->handleMouseDown(x, 0, mask) : false; + } +} + +bool LLMenuItemSeparatorGL::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LLMenuGL* parent_menu = getMenu(); + if (y > getRect().getHeight() / 2) + { + LLView* prev_menu_item = parent_menu->findNextSibling(this); + return (prev_menu_item && prev_menu_item->getVisible() && prev_menu_item->getEnabled()) ? prev_menu_item->handleMouseUp(x, prev_menu_item->getRect().getHeight(), mask) : false; + } + else + { + LLView* next_menu_item = parent_menu->findPrevSibling(this); + return (next_menu_item && next_menu_item->getVisible() && next_menu_item->getEnabled()) ? next_menu_item->handleMouseUp(x, 0, mask) : false; + } +} + +bool LLMenuItemSeparatorGL::handleHover(S32 x, S32 y, MASK mask) +{ + LLMenuGL* parent_menu = getMenu(); + if (y > getRect().getHeight() / 2) + { + parent_menu->highlightPrevItem(this, false); + return false; + } + else + { + parent_menu->highlightNextItem(this, false); + return false; + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemVerticalSeparatorGL +// +// This class represents a vertical separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemVerticalSeparatorGL +: public LLMenuItemSeparatorGL +{ +public: + LLMenuItemVerticalSeparatorGL( void ); + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) { return false; } +}; + +LLMenuItemVerticalSeparatorGL::LLMenuItemVerticalSeparatorGL( void ) +{ + setLabel( VERTICAL_SEPARATOR_LABEL ); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemTearOffGL +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LLMenuItemTearOffGL::LLMenuItemTearOffGL(const LLMenuItemTearOffGL::Params& p) +: LLMenuItemGL(p) +{ +} + +// Returns the first floater ancestor if there is one +LLFloater* LLMenuItemTearOffGL::getParentFloater() +{ + LLView* parent_view = getMenu(); + + while (parent_view) + { + if (dynamic_cast(parent_view)) + { + return dynamic_cast(parent_view); + } + + bool parent_is_menu = dynamic_cast(parent_view) && !dynamic_cast(parent_view); + + if (parent_is_menu) + { + // use menu parent + parent_view = dynamic_cast(parent_view)->getParentMenuItem(); + } + else + { + // just use regular view parent + parent_view = parent_view->getParent(); + } + } + + return NULL; +} + +void LLMenuItemTearOffGL::onCommit() +{ + if (getMenu()->getTornOff()) + { + LLTearOffMenu * torn_off_menu = dynamic_cast(getMenu()->getParent()); + if (torn_off_menu) + { + torn_off_menu->closeFloater(); + } + } + else + { + // transfer keyboard focus and highlight to first real item in list + if (getHighlight()) + { + getMenu()->highlightNextItem(this); + } + + getMenu()->needsArrange(); + + LLFloater* parent_floater = getParentFloater(); + LLFloater* tear_off_menu = LLTearOffMenu::create(getMenu()); + + if (tear_off_menu) + { + if (parent_floater) + { + parent_floater->addDependentFloater(tear_off_menu, false); + } + + // give focus to torn off menu because it will have + // been taken away when parent menu closes + tear_off_menu->setFocus(true); + } + } + LLMenuItemGL::onCommit(); +} + +void LLMenuItemTearOffGL::draw() +{ + // disabled items can be highlighted, but shouldn't render as such + if( getEnabled() && getHighlight() && !isBriefItem()) + { + gGL.color4fv( mHighlightBackground.get().mV ); + gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); + } + + if (getEnabled()) + { + gGL.color4fv( mEnabledColor.get().mV ); + } + else + { + gGL.color4fv( mDisabledColor.get().mV ); + } + const S32 y = getRect().getHeight() / 3; + const S32 PAD = 6; + gl_line_2d( PAD, y, getRect().getWidth() - PAD, y ); + gl_line_2d( PAD, y * 2, getRect().getWidth() - PAD, y * 2 ); +} + +U32 LLMenuItemTearOffGL::getNominalHeight( void ) const +{ + return TEAROFF_SEPARATOR_HEIGHT_PIXELS; +} + +///============================================================================ +/// Class LLMenuItemCallGL +///============================================================================ + +LLMenuItemCallGL::LLMenuItemCallGL(const LLMenuItemCallGL::Params& p) +: LLMenuItemGL(p) +{ +} + +void LLMenuItemCallGL::initFromParams(const Params& p) +{ + if (p.on_visible.isProvided()) + { + mVisibleSignal.connect(initEnableCallback(p.on_visible)); + } + if (p.on_enable.isProvided()) + { + setEnableCallback(initEnableCallback(p.on_enable)); + // Set the enabled control variable (for backwards compatability) + if (p.on_enable.control_name.isProvided() && !p.on_enable.control_name().empty()) + { + LLControlVariable* control = findControl(p.on_enable.control_name()); + if (control) + { + setEnabledControlVariable(control); + } + else + { + LL_WARNS() << "Failed to assign 'enabled' control variable to menu " << getName() + << ": control " << p.on_enable.control_name() + << " does not exist." << LL_ENDL; + } + } + } + if (p.on_click.isProvided()) + { + setCommitCallback(initCommitCallback(p.on_click)); + } + + LLUICtrl::initFromParams(p); +} + +void LLMenuItemCallGL::onCommit( void ) +{ + // RN: menu item can be deleted in callback, so beware + getMenu()->setItemLastSelected( this ); + + LLMenuItemGL::onCommit(); +} + +void LLMenuItemCallGL::updateEnabled( void ) +{ + if (mEnableSignal.num_slots() > 0) + { + bool enabled = mEnableSignal(this, LLSD()); + if (mEnabledControlVariable) + { + if (!enabled) + { + // callback overrides control variable; this will call setEnabled() + mEnabledControlVariable->set(false); + } + } + else + { + setEnabled(enabled); + } + } +} + +void LLMenuItemCallGL::updateVisible( void ) +{ + if (mVisibleSignal.num_slots() > 0) + { + bool visible = mVisibleSignal(this, LLSD()); + setVisible(visible); + } +} + +void LLMenuItemCallGL::buildDrawLabel( void ) +{ + updateEnabled(); + updateVisible(); + LLMenuItemGL::buildDrawLabel(); +} + +bool LLMenuItemCallGL::handleKeyHere( KEY key, MASK mask ) +{ + return LLMenuItemGL::handleKeyHere(key, mask); +} + +bool LLMenuItemCallGL::handleAcceleratorKey( KEY key, MASK mask ) +{ + if( (!gKeyboard->getKeyRepeated(key) || getAllowKeyRepeat()) && (key == mAcceleratorKey) && (mask == (mAcceleratorMask & MASK_NORMALKEYS)) ) + { + updateEnabled(); + if (getEnabled()) + { + onCommit(); + return true; + } + } + return false; +} + +// handleRightMouseUp moved into base class LLMenuItemGL so clicks are +// handled for all menu item types + +///============================================================================ +/// Class LLMenuItemCheckGL +///============================================================================ +LLMenuItemCheckGL::LLMenuItemCheckGL (const LLMenuItemCheckGL::Params& p) +: LLMenuItemCallGL(p) +{ +} + +void LLMenuItemCheckGL::initFromParams(const Params& p) +{ + if (p.on_check.isProvided()) + { + setCheckCallback(initEnableCallback(p.on_check)); + // Set the control name (for backwards compatability) + if (p.on_check.control_name.isProvided() && !p.on_check.control_name().empty()) + { + setControlName(p.on_check.control_name()); + } + } + + LLMenuItemCallGL::initFromParams(p); +} + +void LLMenuItemCheckGL::onCommit( void ) +{ + LLMenuItemCallGL::onCommit(); +} + +//virtual +void LLMenuItemCheckGL::setValue(const LLSD& value) +{ + LLUICtrl::setValue(value); + if(value.asBoolean()) + { + mDrawBoolLabel = LLMenuGL::BOOLEAN_TRUE_PREFIX; + } + else + { + mDrawBoolLabel.clear(); + } +} + +//virtual +LLSD LLMenuItemCheckGL::getValue() const +{ + // Get our boolean value from the view model. + // If we don't override this method then the implementation from + // LLMenuItemGL will return a string. (EXT-8501) + return LLUICtrl::getValue(); +} + +// called to rebuild the draw label +void LLMenuItemCheckGL::buildDrawLabel( void ) +{ + // Note: mCheckSignal() returns true if no callbacks are set + bool checked = mCheckSignal(this, LLSD()); + if (mControlVariable) + { + if (!checked) + setControlValue(false); // callback overrides control variable; this will call setValue() + } + else + { + setValue(checked); + } + if(getValue().asBoolean()) + { + mDrawBoolLabel = LLMenuGL::BOOLEAN_TRUE_PREFIX; + } + else + { + mDrawBoolLabel.clear(); + } + LLMenuItemCallGL::buildDrawLabel(); +} + +///============================================================================ +/// Class LLMenuItemBranchGL +///============================================================================ +LLMenuItemBranchGL::LLMenuItemBranchGL(const LLMenuItemBranchGL::Params& p) + : LLMenuItemGL(p) +{ + LLMenuGL* branch = p.branch; + if (branch) + { + mBranchHandle = branch->getHandle(); + branch->setVisible(false); + branch->setParentMenuItem(this); + } +} + +LLMenuItemBranchGL::~LLMenuItemBranchGL() +{ + if (mBranchHandle.get()) + { + mBranchHandle.get()->die(); + } +} + + + +// virtual +LLView* LLMenuItemBranchGL::getChildView(const std::string& name, bool recurse) const +{ + LLMenuGL* branch = getBranch(); + if (branch) + { + if (branch->getName() == name) + { + return branch; + } + + // Always recurse on branches + return branch->getChildView(name, recurse); + } + + return LLView::getChildView(name, recurse); +} + +LLView* LLMenuItemBranchGL::findChildView(const std::string& name, bool recurse) const +{ + LLMenuGL* branch = getBranch(); + if (branch) + { + if (branch->getName() == name) + { + return branch; + } + + // Always recurse on branches + return branch->findChildView(name, recurse); + } + + return LLView::findChildView(name, recurse); +} + +// virtual +bool LLMenuItemBranchGL::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // switch to mouse navigation mode + LLMenuGL::setKeyboardMode(false); + + onCommit(); + make_ui_sound("UISndClickRelease"); + return true; +} + +bool LLMenuItemBranchGL::hasAccelerator(const KEY &key, const MASK &mask) const +{ + return getBranch() && getBranch()->hasAccelerator(key, mask); +} + +bool LLMenuItemBranchGL::handleAcceleratorKey(KEY key, MASK mask) +{ + return getBranch() && getBranch()->handleAcceleratorKey(key, mask); +} + +// This function checks to see if the accelerator key is already in use; +// if not, it will be added to the list +bool LLMenuItemBranchGL::addToAcceleratorList(std::list *listp) +{ + LLMenuGL* branch = getBranch(); + if (!branch) + return false; + + U32 item_count = branch->getItemCount(); + LLMenuItemGL *item; + + while (item_count--) + { + if ((item = branch->getItem(item_count))) + { + return item->addToAcceleratorList(listp); + } + } + + return false; +} + + +// called to rebuild the draw label +void LLMenuItemBranchGL::buildDrawLabel( void ) +{ + mDrawAccelLabel.clear(); + std::string st = mDrawAccelLabel; + appendAcceleratorString( st ); + mDrawAccelLabel = st; + mDrawBranchLabel = LLMenuGL::BRANCH_SUFFIX; +} + +void LLMenuItemBranchGL::onCommit( void ) +{ + openMenu(); + + // keyboard navigation automatically propagates highlight to sub-menu + // to facilitate fast menu control via jump keys + if (LLMenuGL::getKeyboardMode() && getBranch() && !getBranch()->getHighlightedItem()) + { + getBranch()->highlightNextItem(NULL); + } + + LLUICtrl::onCommit(); +} + +bool LLMenuItemBranchGL::handleKey(KEY key, MASK mask, bool called_from_parent) +{ + bool handled = false; + if (getBranch() && called_from_parent) + { + handled = getBranch()->handleKey(key, mask, called_from_parent); + } + + if (!handled) + { + handled = LLMenuItemGL::handleKey(key, mask, called_from_parent); + } + + return handled; +} + +bool LLMenuItemBranchGL::handleUnicodeChar(llwchar uni_char, bool called_from_parent) +{ + bool handled = false; + if (getBranch() && called_from_parent) + { + handled = getBranch()->handleUnicodeChar(uni_char, true); + } + + if (!handled) + { + handled = LLMenuItemGL::handleUnicodeChar(uni_char, called_from_parent); + } + + return handled; +} + + +void LLMenuItemBranchGL::setHighlight( bool highlight ) +{ + if (highlight == getHighlight()) + return; + + LLMenuGL* branch = getBranch(); + if (!branch) + return; + + bool auto_open = getEnabled() && (!branch->getVisible() || branch->getTornOff()); + // torn off menus don't open sub menus on hover unless they have focus + LLFloater * menu_parent = dynamic_cast(getMenu()->getParent()); + if (getMenu()->getTornOff() && menu_parent && !menu_parent->hasFocus()) + { + auto_open = false; + } + // don't auto open torn off sub-menus (need to explicitly active menu item to give them focus) + if (branch->getTornOff()) + { + auto_open = false; + } + LLMenuItemGL::setHighlight(highlight); + if( highlight ) + { + if(auto_open) + { + openMenu(); + } + } + else + { + if (branch->getTornOff()) + { + LLFloater * branch_parent = dynamic_cast(branch->getParent()); + if (branch_parent) + { + branch_parent->setFocus(false); + } + branch->clearHoverItem(); + } + else + { + branch->setVisible( false ); + } + } +} + +void LLMenuItemBranchGL::draw() +{ + LLMenuItemGL::draw(); + if (getBranch() && getBranch()->getVisible() && !getBranch()->getTornOff()) + { + setHighlight(true); + } +} + +void LLMenuItemBranchGL::updateBranchParent(LLView* parentp) +{ + if (getBranch() && getBranch()->getParent() == NULL) + { + // make the branch menu a sibling of my parent menu + getBranch()->updateParent(parentp); + } +} + +void LLMenuItemBranchGL::onVisibilityChange(bool new_visibility) +{ + if (!new_visibility && getBranch() && !getBranch()->getTornOff()) + { + getBranch()->setVisible(false); + } + LLMenuItemGL::onVisibilityChange(new_visibility); +} + +bool LLMenuItemBranchGL::handleKeyHere(KEY key, MASK mask) +{ + LLMenuGL* branch = getBranch(); + if (!branch) + return LLMenuItemGL::handleKeyHere(key, mask); + + // an item is highlighted, my menu is open, and I have an active sub menu or we are in + // keyboard navigation mode + if (getHighlight() + && getMenu()->isOpen() + && (isActive() || LLMenuGL::getKeyboardMode())) + { + if (branch->getVisible() && key == KEY_LEFT) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + bool handled = branch->clearHoverItem(); + if (branch->getTornOff()) + { + LLFloater * branch_parent = dynamic_cast(branch->getParent()); + if (branch_parent) + { + branch_parent->setFocus(false); + } + } + if (handled && getMenu()->getTornOff()) + { + LLFloater * menu_parent = dynamic_cast(getMenu()->getParent()); + if (menu_parent) + { + menu_parent->setFocus(true); + } + } + return handled; + } + + if (key == KEY_RIGHT && !branch->getHighlightedItem()) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + LLMenuItemGL* itemp = branch->highlightNextItem(NULL); + if (itemp) + { + return true; + } + } + } + return LLMenuItemGL::handleKeyHere(key, mask); +} + +//virtual +bool LLMenuItemBranchGL::isActive() const +{ + return isOpen() && getBranch() && getBranch()->getHighlightedItem(); +} + +//virtual +bool LLMenuItemBranchGL::isOpen() const +{ + return getBranch() && getBranch()->isOpen(); +} + +void LLMenuItemBranchGL::openMenu() +{ + LLMenuGL* branch = getBranch(); + if (!branch) + return; + + if (branch->getTornOff()) + { + LLFloater * branch_parent = dynamic_cast(branch->getParent()); + if (branch_parent) + { + gFloaterView->bringToFront(branch_parent); + // this might not be necessary, as torn off branches don't get focus and hence no highligth + branch->highlightNextItem(NULL); + } + } + else if( !branch->getVisible() ) + { + // get valid rectangle for menus + const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect(); + + branch->arrange(); + + LLRect branch_rect = branch->getRect(); + // calculate root-view relative position for branch menu + S32 left = getRect().mRight; + S32 top = getRect().mTop - getRect().mBottom; + + localPointToOtherView(left, top, &left, &top, branch->getParent()); + + branch_rect.setLeftTopAndSize( left, top, + branch_rect.getWidth(), branch_rect.getHeight() ); + + if (branch->getCanTearOff()) + { + branch_rect.translate(0, TEAROFF_SEPARATOR_HEIGHT_PIXELS); + } + branch->setRect( branch_rect ); + + // if branch extends outside of menu region change the direction it opens in + S32 x, y; + S32 delta_x = 0; + S32 delta_y = 0; + branch->localPointToOtherView( 0, 0, &x, &y, branch->getParent() ); + if( y < menu_region_rect.mBottom ) + { + // open upwards if menu extends past bottom + // adjust by the height of the menu item branch since it is a submenu + if (y + 2 * branch_rect.getHeight() - getRect().getHeight() > menu_region_rect.mTop) + { + // overlaps with top border, align with top + delta_y = menu_region_rect.mTop - y - branch_rect.getHeight(); + } + else + { + delta_y = branch_rect.getHeight() - getRect().getHeight(); + } + } + + if( x + branch_rect.getWidth() > menu_region_rect.mRight ) + { + // move sub-menu over to left side + delta_x = llmax(-x, ( -(branch_rect.getWidth() + getRect().getWidth()))); + } + branch->translate( delta_x, delta_y ); + + branch->setVisible( true ); + branch->getParent()->sendChildToFront(branch); + + dirtyRect(); + } +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemBranchDownGL +// +// The LLMenuItemBranchDownGL represents a menu item that has a +// sub-menu. This is used to make menu bar menus. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemBranchDownGL : public LLMenuItemBranchGL +{ +protected: + +public: + LLMenuItemBranchDownGL( const Params& ); + + // returns the normal width of this control in pixels - this is + // used for calculating the widest item, as well as for horizontal + // arrangement. + virtual U32 getNominalWidth( void ) const; + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // handles opening, positioning, and arranging the menu branch associated with this item + virtual void openMenu( void ); + + // set the hover status (called by it's menu) and if the object is + // active. This is used for behavior transfer. + virtual void setHighlight( bool highlight ); + + virtual bool isActive( void ) const; + + // LLView functionality + virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); + virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); + virtual void draw( void ); + virtual bool handleKeyHere(KEY key, MASK mask); + + virtual bool handleAcceleratorKey(KEY key, MASK mask); + + virtual void onFocusLost(); + virtual void setFocus(bool b); +}; + +LLMenuItemBranchDownGL::LLMenuItemBranchDownGL( const Params& p) : + LLMenuItemBranchGL(p) +{ +} + +// returns the normal width of this control in pixels - this is used +// for calculating the widest item, as well as for horizontal +// arrangement. +U32 LLMenuItemBranchDownGL::getNominalWidth( void ) const +{ + U32 width = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS + RIGHT_PAD_PIXELS; + width += getFont()->getWidth( mLabel.getWString().c_str() ); + return width; +} + +// called to rebuild the draw label +void LLMenuItemBranchDownGL::buildDrawLabel( void ) +{ + mDrawAccelLabel.clear(); + std::string st = mDrawAccelLabel; + appendAcceleratorString( st ); + mDrawAccelLabel = st; +} + +void LLMenuItemBranchDownGL::openMenu( void ) +{ + LLMenuGL* branch = getBranch(); + if( branch->getVisible() && !branch->getTornOff() ) + { + branch->setVisible( false ); + } + else + { + if (branch->getTornOff()) + { + LLFloater * branch_parent = dynamic_cast(branch->getParent()); + if (branch_parent) + { + gFloaterView->bringToFront(branch_parent); + } + } + else + { + // We're showing the drop-down menu, so patch up its labels/rects + branch->arrange(); + + LLRect rect = branch->getRect(); + S32 left = 0; + S32 top = getRect().mBottom; + localPointToOtherView(left, top, &left, &top, branch->getParent()); + + rect.setLeftTopAndSize( left, top, + rect.getWidth(), rect.getHeight() ); + branch->setRect( rect ); + S32 x = 0; + S32 y = 0; + branch->localPointToScreen( 0, 0, &x, &y ); + S32 delta_x = 0; + + LLCoordScreen window_size; + LLWindow* windowp = getWindow(); + windowp->getSize(&window_size); + + S32 window_width = window_size.mX; + if( x > window_width - rect.getWidth() ) + { + delta_x = (window_width - rect.getWidth()) - x; + } + branch->translate( delta_x, 0 ); + + setHighlight(true); + branch->setVisible( true ); + branch->getParent()->sendChildToFront(branch); + } + } +} + +// set the hover status (called by it's menu) +void LLMenuItemBranchDownGL::setHighlight( bool highlight ) +{ + if (highlight == getHighlight()) + return; + + //NOTE: Purposely calling all the way to the base to bypass auto-open. + LLMenuItemGL::setHighlight(highlight); + + LLMenuGL* branch = getBranch(); + if (!branch) + return; + + if( !highlight) + { + if (branch->getTornOff()) + { + LLFloater * branch_parent = dynamic_cast(branch->getParent()); + if (branch_parent) + { + branch_parent->setFocus(false); + } + branch->clearHoverItem(); + } + else + { + branch->setVisible( false ); + } + } +} + +bool LLMenuItemBranchDownGL::isActive() const +{ + // for top level menus, being open is sufficient to be considered + // active, because clicking on them with the mouse will open + // them, without moving keyboard focus to them + return isOpen(); +} + +bool LLMenuItemBranchDownGL::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + // switch to mouse control mode + LLMenuGL::setKeyboardMode(false); + + if (getVisible() && isOpen()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + else + { + onCommit(); + } + + make_ui_sound("UISndClick"); + return true; +} + +bool LLMenuItemBranchDownGL::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + return true; +} + + +bool LLMenuItemBranchDownGL::handleAcceleratorKey(KEY key, MASK mask) +{ + bool branch_visible = getBranch()->getVisible(); + bool handled = getBranch()->handleAcceleratorKey(key, mask); + if (handled && !branch_visible && isInVisibleChain()) + { + // flash this menu entry because we triggered an invisible menu item + LLMenuHolderGL::setActivatedItem(this); + } + + return handled; +} +void LLMenuItemBranchDownGL::onFocusLost() +{ + // needed for tab-based selection + LLMenuItemBranchGL::onFocusLost(); + LLMenuGL::setKeyboardMode(false); + setHighlight(false); +} + +void LLMenuItemBranchDownGL::setFocus(bool b) +{ + // needed for tab-based selection + LLMenuItemBranchGL::setFocus(b); + LLMenuGL::setKeyboardMode(b); + setHighlight(b); +} + +bool LLMenuItemBranchDownGL::handleKeyHere(KEY key, MASK mask) +{ + bool menu_open = getBranch()->getVisible(); + // don't do keyboard navigation of top-level menus unless in keyboard mode, or menu expanded + if (getHighlight() && getMenu()->isOpen() && (isActive() || LLMenuGL::getKeyboardMode())) + { + if (key == KEY_LEFT) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + LLMenuItemGL* itemp = getMenu()->highlightPrevItem(this); + // open new menu only if previous menu was open + if (itemp && itemp->getEnabled() && menu_open) + { + itemp->onCommit(); + } + + return true; + } + else if (key == KEY_RIGHT) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + LLMenuItemGL* itemp = getMenu()->highlightNextItem(this); + // open new menu only if previous menu was open + if (itemp && itemp->getEnabled() && menu_open) + { + itemp->onCommit(); + } + + return true; + } + else if (key == KEY_DOWN) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + if (!isActive()) + { + onCommit(); + } + getBranch()->highlightNextItem(NULL); + return true; + } + else if (key == KEY_UP) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + if (!isActive()) + { + onCommit(); + } + getBranch()->highlightPrevItem(NULL); + return true; + } + } + + return false; +} + +void LLMenuItemBranchDownGL::draw( void ) +{ + //FIXME: try removing this + if (getBranch()->getVisible() && !getBranch()->getTornOff()) + { + setHighlight(true); + } + + if( getHighlight() ) + { + gGL.color4fv( mHighlightBackground.get().mV ); + gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); + } + + LLColor4 color; + if (getHighlight()) + { + color = mHighlightForeground.get(); + } + else if( getEnabled() ) + { + color = mEnabledColor.get(); + } + else + { + color = mDisabledColor.get(); + } + getFont()->render( mLabel.getWString(), 0, (F32)getRect().getWidth() / 2.f, (F32)LABEL_BOTTOM_PAD_PIXELS, color, + LLFontGL::HCENTER, LLFontGL::BOTTOM, LLFontGL::NORMAL); + + + // underline navigation key only when keyboard navigation has been initiated + if (getMenu()->jumpKeysActive() && LLMenuGL::getKeyboardMode()) + { + std::string upper_case_label = mLabel.getString(); + LLStringUtil::toUpper(upper_case_label); + std::string::size_type offset = upper_case_label.find(getJumpKey()); + if (offset != std::string::npos) + { + S32 x_offset = ll_round((F32)getRect().getWidth() / 2.f - getFont()->getWidthF32(mLabel.getString(), 0, S32_MAX) / 2.f); + S32 x_begin = x_offset + getFont()->getWidth(mLabel, 0, offset); + S32 x_end = x_offset + getFont()->getWidth(mLabel, 0, offset + 1); + gl_line_2d(x_begin, LABEL_BOTTOM_PAD_PIXELS, x_end, LABEL_BOTTOM_PAD_PIXELS); + } + } +} + + +class LLMenuScrollItem : public LLMenuItemCallGL +{ +public: + enum EArrowType + { + ARROW_DOWN, + ARROW_UP + }; + struct ArrowTypes : public LLInitParam::TypeValuesHelper + { + static void declareValues() + { + declare("up", ARROW_UP); + declare("down", ARROW_DOWN); + } + }; + + struct Params : public LLInitParam::Block + { + Optional arrow_type; + Optional scroll_callback; + }; + +protected: + LLMenuScrollItem(const Params&); + friend class LLUICtrlFactory; + +public: + /*virtual*/ void draw(); + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent); + /*virtual*/ void setEnabled(bool enabled); + virtual void onCommit( void ); + +private: + LLButton* mArrowBtn; +}; + +LLMenuScrollItem::LLMenuScrollItem(const Params& p) +: LLMenuItemCallGL(p) +{ + std::string icon; + if (p.arrow_type.isProvided() && p.arrow_type == ARROW_UP) + { + icon = "arrow_up.tga"; + } + else + { + icon = "arrow_down.tga"; + } + + LLButton::Params bparams; + + // Disabled the Return key handling by LLMenuScrollItem instead of + // passing the key press to the currently selected menu item. See STORM-385. + bparams.commit_on_return(false); + bparams.mouse_opaque(true); + bparams.scale_image(false); + bparams.click_callback(p.scroll_callback); + bparams.mouse_held_callback(p.scroll_callback); + bparams.follows.flags(FOLLOWS_ALL); + std::string background = "transparent.j2c"; + bparams.image_unselected.name(background); + bparams.image_disabled.name(background); + bparams.image_selected.name(background); + bparams.image_hover_selected.name(background); + bparams.image_disabled_selected.name(background); + bparams.image_hover_unselected.name(background); + bparams.image_overlay.name(icon); + + mArrowBtn = LLUICtrlFactory::create(bparams); + addChild(mArrowBtn); +} + +/*virtual*/ +void LLMenuScrollItem::draw() +{ + LLUICtrl::draw(); +} + +/*virtual*/ +void LLMenuScrollItem::reshape(S32 width, S32 height, bool called_from_parent) +{ + mArrowBtn->reshape(width, height, called_from_parent); + LLView::reshape(width, height, called_from_parent); +} + +/*virtual*/ +void LLMenuScrollItem::setEnabled(bool enabled) +{ + mArrowBtn->setEnabled(enabled); + LLView::setEnabled(enabled); +} + +void LLMenuScrollItem::onCommit( void ) +{ + LLUICtrl::onCommit(); +} + +///============================================================================ +/// Class LLMenuGL +///============================================================================ + +LLMenuGL::LLMenuGL(const LLMenuGL::Params& p) +: LLUICtrl(p), + mBackgroundColor( p.bg_color() ), + mBgVisible( p.bg_visible ), + mDropShadowed( p.drop_shadow ), + mHasSelection(false), + mHorizontalLayout( p.horizontal_layout ), + mScrollable(mHorizontalLayout ? false : p.scrollable), // Scrolling is supported only for vertical layout + mMaxScrollableItems(p.max_scrollable_items), + mPreferredWidth(p.preferred_width), + mKeepFixedSize( p.keep_fixed_size ), + mLabel (p.label), + mLastMouseX(0), + mLastMouseY(0), + mMouseVelX(0), + mMouseVelY(0), + mTornOff(false), + mTearOffItem(NULL), + mSpilloverBranch(NULL), + mFirstVisibleItem(NULL), + mArrowUpItem(NULL), + mArrowDownItem(NULL), + mSpilloverMenu(NULL), + mJumpKey(p.jump_key), + mCreateJumpKeys(p.create_jump_keys), + mNeedsArrange(false), + mAlwaysShowMenu(false), + mResetScrollPositionOnShow(true), + mShortcutPad(p.shortcut_pad), + mFont(p.font) +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("_"); + tokenizer tokens(p.label(), sep); + tokenizer::iterator token_iter; + + S32 token_count = 0; + std::string new_menu_label; + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + new_menu_label += (*token_iter); + if (token_count > 0) + { + setJumpKey((*token_iter).c_str()[0]); + } + ++token_count; + } + setLabel(new_menu_label); + + mFadeTimer.stop(); +} + +void LLMenuGL::initFromParams(const LLMenuGL::Params& p) +{ + LLUICtrl::initFromParams(p); + setCanTearOff(p.can_tear_off); +} + +// Destroys the object +LLMenuGL::~LLMenuGL( void ) +{ + // delete the branch, as it might not be in view hierarchy + // leave the menu, because it is always in view hierarchy + delete mSpilloverBranch; + mJumpKeys.clear(); +} + +void LLMenuGL::setCanTearOff(bool tear_off) +{ + if (tear_off && mTearOffItem == NULL) + { + LLMenuItemTearOffGL::Params p; + mTearOffItem = LLUICtrlFactory::create(p); + addChild(mTearOffItem); + } + else if (!tear_off && mTearOffItem != NULL) + { + mItems.remove(mTearOffItem); + removeChild(mTearOffItem); + delete mTearOffItem; + mTearOffItem = NULL; + needsArrange(); + } +} + +bool LLMenuGL::addChild(LLView* view, S32 tab_group) +{ + LLMenuGL* menup = dynamic_cast(view); + if (menup) + { + return appendMenu(menup); + } + + LLMenuItemGL* itemp = dynamic_cast(view); + if (itemp) + { + return append(itemp); + } + + return false; +} + +// Used in LLContextMenu and in LLTogleableMenu + +// Add an item to the context menu branch +bool LLMenuGL::addContextChild(LLView* view, S32 tab_group) +{ + LLContextMenu* context = dynamic_cast(view); + if (context) + { + return appendContextSubMenu(context); + } + + LLMenuItemSeparatorGL* separator = dynamic_cast(view); + if (separator) + { + return append(separator); + } + + LLMenuItemGL* item = dynamic_cast(view); + if (item) + { + return append(item); + } + + LLMenuGL* menup = dynamic_cast(view); + if (menup) + { + return appendMenu(menup); + } + + return false; +} + + +void LLMenuGL::deleteAllChildren() +{ + mItems.clear(); + LLUICtrl::deleteAllChildren(); +} + +void LLMenuGL::removeChild( LLView* ctrl) +{ + // previously a dynamic_cast with if statement to check validity + // unfortunately removeChild is called by ~LLView, and at that point the + // object being deleted is no longer a LLMenuItemGL so a dynamic_cast will fail + LLMenuItemGL* itemp = static_cast(ctrl); + + item_list_t::iterator found_it = std::find(mItems.begin(), mItems.end(), (itemp)); + if (found_it != mItems.end()) + { + mItems.erase(found_it); + } + + return LLUICtrl::removeChild(ctrl); +} + +bool LLMenuGL::postBuild() +{ + createJumpKeys(); + return LLUICtrl::postBuild(); +} + +// are we the childmost active menu and hence our jump keys should be enabled? +// or are we a free-standing torn-off menu (which uses jump keys too) +bool LLMenuGL::jumpKeysActive() +{ + LLMenuItemGL* highlighted_item = getHighlightedItem(); + bool active = getVisible() && getEnabled(); + + if (active) + { + if (getTornOff()) + { + // activation of jump keys on torn off menus controlled by keyboard focus + LLFloater * parent = dynamic_cast(getParent()); + if (parent) + { + active = parent->hasFocus(); + } + } + else + { + // Are we the terminal active menu? + // Yes, if parent menu item deems us to be active (just being visible is sufficient for top-level menus) + // and we don't have a highlighted menu item pointing to an active sub-menu + active = (!getParentMenuItem() || getParentMenuItem()->isActive()) // I have a parent that is active... + && (!highlighted_item || !highlighted_item->isActive()); //... but no child that is active + } + } + + return active; +} + +bool LLMenuGL::isOpen() +{ + if (getTornOff()) + { + LLMenuItemGL* itemp = getHighlightedItem(); + // if we have an open sub-menu, then we are considered part of + // the open menu chain even if we don't have focus + if (itemp && itemp->isOpen()) + { + return true; + } + // otherwise we are only active if we have keyboard focus + LLFloater * parent = dynamic_cast(getParent()); + if (parent) + { + return parent->hasFocus(); + } + return false; + } + else + { + // normally, menus are hidden as soon as the user focuses + // on another menu, so just use the visibility criterion + return getVisible(); + } +} + + + +bool LLMenuGL::scrollItems(EScrollingDirection direction) +{ + // Slowing down items scrolling when arrow button is held + if (mScrollItemsTimer.hasExpired() && NULL != mFirstVisibleItem) + { + mScrollItemsTimer.setTimerExpirySec(.033f); + } + else + { + return false; + } + + switch (direction) + { + case SD_UP: + { + item_list_t::iterator cur_item_iter; + item_list_t::iterator prev_item_iter; + for (cur_item_iter = mItems.begin(), prev_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) + { + if( (*cur_item_iter) == mFirstVisibleItem) + { + break; + } + if ((*cur_item_iter)->getVisible()) + { + prev_item_iter = cur_item_iter; + } + } + + if ((*prev_item_iter)->getVisible()) + { + mFirstVisibleItem = *prev_item_iter; + } + break; + } + case SD_DOWN: + { + if (NULL == mFirstVisibleItem) + { + mFirstVisibleItem = *mItems.begin(); + } + + item_list_t::iterator cur_item_iter; + + for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) + { + if( (*cur_item_iter) == mFirstVisibleItem) + { + break; + } + } + + item_list_t::iterator next_item_iter; + + if (cur_item_iter != mItems.end()) + { + for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++) + { + if( (*next_item_iter)->getVisible()) + { + break; + } + } + + if (next_item_iter != mItems.end() && + (*next_item_iter)->getVisible()) + { + mFirstVisibleItem = *next_item_iter; + } + } + break; + } + case SD_BEGIN: + { + mFirstVisibleItem = *mItems.begin(); + break; + } + case SD_END: + { + item_list_t::reverse_iterator first_visible_item_iter = mItems.rend(); + + // Need to scroll through number of actual existing items in menu. + // Otherwise viewer will hang for a time needed to scroll U32_MAX + // times in std::advance(). STORM-659. + size_t nitems = mItems.size(); + U32 scrollable_items = nitems < mMaxScrollableItems ? nitems : mMaxScrollableItems; + + // Advance by mMaxScrollableItems back from the end of the list + // to make the last item visible. + std::advance(first_visible_item_iter, scrollable_items); + mFirstVisibleItem = *first_visible_item_iter; + break; + } + default: + LL_WARNS() << "Unknown scrolling direction: " << direction << LL_ENDL; + } + + mNeedsArrange = true; + arrangeAndClear(); + + return true; +} + +// rearrange the child rects so they fit the shape of the menu. +void LLMenuGL::arrange( void ) +{ + // calculate the height & width, and set our rect based on that + // information. + const LLRect& initial_rect = getRect(); + + U32 width = 0, height = MENU_ITEM_PADDING; + + cleanupSpilloverBranch(); + + if( mItems.size() ) + { + const LLRect menu_region_rect = LLMenuGL::sMenuContainer ? LLMenuGL::sMenuContainer->getMenuRect() : LLRect(0, S32_MAX, S32_MAX, 0); + + // torn off menus are not constrained to the size of the screen + U32 max_width = getTornOff() ? U32_MAX : menu_region_rect.getWidth(); + U32 max_height = getTornOff() ? U32_MAX: menu_region_rect.getHeight(); + + // *FIX: create the item first and then ask for its dimensions? + S32 spillover_item_width = PLAIN_PAD_PIXELS + LLFontGL::getFontSansSerif()->getWidth( std::string("More") ); // *TODO: Translate + S32 spillover_item_height = LLFontGL::getFontSansSerif()->getLineHeight() + MENU_ITEM_PADDING; + + // Scrolling support + item_list_t::iterator first_visible_item_iter; + item_list_t::iterator first_hidden_item_iter = mItems.end(); + S32 height_before_first_visible_item = -1; + S32 visible_items_height = 0; + U32 scrollable_items_cnt = 0; + + if (mHorizontalLayout) + { + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + // do first so LLMenuGLItemCall can call on_visible to determine if visible + (*item_iter)->buildDrawLabel(); + + if ((*item_iter)->getVisible()) + { + if (!getTornOff() + && *item_iter != mSpilloverBranch + && width + (*item_iter)->getNominalWidth() > max_width - spillover_item_width) + { + // no room for any more items + createSpilloverBranch(); + + std::vector items_to_remove; + std::copy(item_iter, mItems.end(), std::back_inserter(items_to_remove)); + std::vector::iterator spillover_iter; + for (spillover_iter= items_to_remove.begin(); spillover_iter != items_to_remove.end(); ++spillover_iter) + { + LLMenuItemGL* itemp = (*spillover_iter); + removeChild(itemp); + mSpilloverMenu->addChild(itemp); + } + + addChild(mSpilloverBranch); + + height = llmax(height, mSpilloverBranch->getNominalHeight()); + width += mSpilloverBranch->getNominalWidth(); + + break; + } + else + { + // track our rect + height = llmax(height, (*item_iter)->getNominalHeight()); + width += (*item_iter)->getNominalWidth(); + } + } + } + } + else + { + for (LLMenuItemGL* itemp : mItems) + { + // do first so LLMenuGLItemCall can call on_visible to determine if visible + itemp->buildDrawLabel(); + } + item_list_t::iterator item_iter; + + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter)->getVisible()) + { + if (!getTornOff() + && !mScrollable + && *item_iter != mSpilloverBranch + && height + (*item_iter)->getNominalHeight() > max_height - spillover_item_height) + { + // don't show only one item + int visible_items = 0; + item_list_t::iterator count_iter; + for (count_iter = item_iter; count_iter != mItems.end(); ++count_iter) + { + if((*count_iter)->getVisible()) + visible_items++; + } + if (visible_items>1) + { + // no room for any more items + createSpilloverBranch(); + + std::vector items_to_remove; + std::copy(item_iter, mItems.end(), std::back_inserter(items_to_remove)); + std::vector::iterator spillover_iter; + for (spillover_iter= items_to_remove.begin(); spillover_iter != items_to_remove.end(); ++spillover_iter) + { + LLMenuItemGL* itemp = (*spillover_iter); + removeChild(itemp); + mSpilloverMenu->addChild(itemp); + } + + + addChild(mSpilloverBranch); + + height += mSpilloverBranch->getNominalHeight(); + width = llmax( width, mSpilloverBranch->getNominalWidth() ); + + break; + } + } + + // track our rect + height += (*item_iter)->getNominalHeight(); + width = llmax( width, (*item_iter)->getNominalWidth() ); + + if (mScrollable) + { + // Determining visible items boundaries + if (NULL == mFirstVisibleItem) + { + mFirstVisibleItem = *item_iter; + } + + if (*item_iter == mFirstVisibleItem) + { + height_before_first_visible_item = height - (*item_iter)->getNominalHeight(); + first_visible_item_iter = item_iter; + scrollable_items_cnt = 0; + } + + if (-1 != height_before_first_visible_item && 0 == visible_items_height && + (++scrollable_items_cnt > mMaxScrollableItems || + height - height_before_first_visible_item > max_height - spillover_item_height * 2 )) + { + first_hidden_item_iter = item_iter; + visible_items_height = height - height_before_first_visible_item - (*item_iter)->getNominalHeight(); + scrollable_items_cnt--; + } + } + } + } + + if (mPreferredWidth < U32_MAX) + width = llmin(mPreferredWidth, max_width); + + if (mScrollable) + { + S32 max_items_height = max_height - spillover_item_height * 2; + + if (visible_items_height == 0) + visible_items_height = height - height_before_first_visible_item; + + // Fix mFirstVisibleItem value, if it doesn't allow to display all items, that can fit + if (visible_items_height < max_items_height && scrollable_items_cnt < mMaxScrollableItems) + { + item_list_t::iterator tmp_iter(first_visible_item_iter); + while (visible_items_height < max_items_height && + scrollable_items_cnt < mMaxScrollableItems && + first_visible_item_iter != mItems.begin()) + { + if ((*first_visible_item_iter)->getVisible()) + { + // It keeps visible item, after first_visible_item_iter + tmp_iter = first_visible_item_iter; + } + + first_visible_item_iter--; + + if ((*first_visible_item_iter)->getVisible()) + { + visible_items_height += (*first_visible_item_iter)->getNominalHeight(); + height_before_first_visible_item -= (*first_visible_item_iter)->getNominalHeight(); + scrollable_items_cnt++; + } + } + + // Roll back one item, that doesn't fit + if (visible_items_height > max_items_height) + { + visible_items_height -= (*first_visible_item_iter)->getNominalHeight(); + height_before_first_visible_item += (*first_visible_item_iter)->getNominalHeight(); + scrollable_items_cnt--; + first_visible_item_iter = tmp_iter; + } + if (!(*first_visible_item_iter)->getVisible()) + { + first_visible_item_iter = tmp_iter; + } + + mFirstVisibleItem = *first_visible_item_iter; + } + } + } + + S32 cur_height = (S32)llmin(max_height, height); + + if (mScrollable && + (height_before_first_visible_item > MENU_ITEM_PADDING || + height_before_first_visible_item + visible_items_height < (S32)height)) + { + // Reserving 2 extra slots for arrow items + cur_height = visible_items_height + spillover_item_height * 2; + } + + setRect(LLRect(getRect().mLeft, getRect().mTop, getRect().mLeft + width, getRect().mTop - cur_height)); + + S32 cur_width = 0; + S32 offset = 0; + if (mScrollable) + { + // No space for all items, creating arrow items + if (height_before_first_visible_item > MENU_ITEM_PADDING || + height_before_first_visible_item + visible_items_height < (S32)height) + { + if (NULL == mArrowUpItem) + { + LLMenuScrollItem::Params item_params; + item_params.name(ARROW_UP); + item_params.arrow_type(LLMenuScrollItem::ARROW_UP); + item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_UP)); + + mArrowUpItem = LLUICtrlFactory::create(item_params); + LLUICtrl::addChild(mArrowUpItem); + + } + if (NULL == mArrowDownItem) + { + LLMenuScrollItem::Params item_params; + item_params.name(ARROW_DOWN); + item_params.arrow_type(LLMenuScrollItem::ARROW_DOWN); + item_params.scroll_callback.function(boost::bind(&LLMenuGL::scrollItems, this, SD_DOWN)); + + mArrowDownItem = LLUICtrlFactory::create(item_params); + LLUICtrl::addChild(mArrowDownItem); + } + + LLRect rect; + mArrowUpItem->setRect(rect.setLeftTopAndSize( 0, cur_height, width, mArrowUpItem->getNominalHeight())); + mArrowUpItem->setVisible(true); + mArrowUpItem->setEnabled(height_before_first_visible_item > MENU_ITEM_PADDING); + mArrowUpItem->reshape(width, mArrowUpItem->getNominalHeight()); + mArrowDownItem->setRect(rect.setLeftTopAndSize( 0, mArrowDownItem->getNominalHeight(), width, mArrowDownItem->getNominalHeight())); + mArrowDownItem->setVisible(true); + mArrowDownItem->setEnabled(height_before_first_visible_item + visible_items_height < (S32)height); + mArrowDownItem->reshape(width, mArrowDownItem->getNominalHeight()); + + cur_height -= mArrowUpItem->getNominalHeight(); + + offset = menu_region_rect.mRight; // This moves items behind visible area + } + else + { + if (NULL != mArrowUpItem) + { + mArrowUpItem->setVisible(false); + } + if (NULL != mArrowDownItem) + { + mArrowDownItem->setVisible(false); + } + } + + } + + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter)->getVisible()) + { + if (mScrollable) + { + if (item_iter == first_visible_item_iter) + { + offset = 0; + } + else if (item_iter == first_hidden_item_iter) + { + offset = menu_region_rect.mRight; // This moves items behind visible area + } + } + + // setup item rect to hold label + LLRect rect; + if (mHorizontalLayout) + { + rect.setLeftTopAndSize( cur_width, height, (*item_iter)->getNominalWidth(), height); + cur_width += (*item_iter)->getNominalWidth(); + } + else + { + rect.setLeftTopAndSize( 0 + offset, cur_height, width, (*item_iter)->getNominalHeight()); + if (offset == 0) + { + cur_height -= (*item_iter)->getNominalHeight(); + } + } + (*item_iter)->setRect( rect ); + } + } + + + if (getTornOff()) + { + LLTearOffMenu * torn_off_menu = dynamic_cast(getParent()); + if (torn_off_menu) + { + torn_off_menu->updateSize(); + } + } + } + if (mKeepFixedSize) + { + reshape(initial_rect.getWidth(), initial_rect.getHeight()); + } +} + +void LLMenuGL::arrangeAndClear( void ) +{ + if (mNeedsArrange) + { + arrange(); + mNeedsArrange = false; + } +} + +void LLMenuGL::createSpilloverBranch() +{ + if (!mSpilloverBranch) + { + // should be NULL but delete anyway + delete mSpilloverMenu; + // technically, you can't tear off spillover menus, but we're passing the handle + // along just to be safe + LLMenuGL::Params p; + std::string label = LLTrans::getString("More"); + p.name("More"); + p.label(label); + p.bg_color(mBackgroundColor); + p.bg_visible(true); + p.can_tear_off(false); + mSpilloverMenu = new LLMenuGL(p); + mSpilloverMenu->updateParent(LLMenuGL::sMenuContainer); + + LLMenuItemBranchGL::Params branch_params; + branch_params.name = "More"; + branch_params.label = label; + branch_params.branch = mSpilloverMenu; + branch_params.font.style = "italic"; + branch_params.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); + branch_params.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); + branch_params.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); + + mSpilloverBranch = LLUICtrlFactory::create(branch_params); + } +} + +void LLMenuGL::cleanupSpilloverBranch() +{ + if (mSpilloverBranch && mSpilloverBranch->getParent() == this) + { + // head-recursion to propagate items back up to root menu + mSpilloverMenu->cleanupSpilloverBranch(); + + // pop off spillover items + while (mSpilloverMenu->getItemCount()) + { + LLMenuItemGL* itemp = mSpilloverMenu->getItem(0); + mSpilloverMenu->removeChild(itemp); + // put them at the end of our own list + addChild(itemp); + } + + // Delete the branch, and since the branch will delete the menu, + // set the menu* to null. + delete mSpilloverBranch; + mSpilloverBranch = NULL; + mSpilloverMenu = NULL; + } +} + +void LLMenuGL::createJumpKeys() +{ + if (!mCreateJumpKeys) return; + mCreateJumpKeys = false; + + mJumpKeys.clear(); + + std::set unique_words; + std::set shared_words; + + item_list_t::iterator item_it; + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(" "); + + for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) + { + std::string uppercase_label = (*item_it)->getLabel(); + LLStringUtil::toUpper(uppercase_label); + + tokenizer tokens(uppercase_label, sep); + tokenizer::iterator token_iter; + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + if (unique_words.find(*token_iter) != unique_words.end()) + { + // this word exists in more than one menu instance + shared_words.insert(*token_iter); + } + else + { + // we have a new word, keep track of it + unique_words.insert(*token_iter); + } + } + } + + // pre-assign specified jump keys + for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) + { + KEY jump_key = (*item_it)->getJumpKey(); + if(jump_key != KEY_NONE) + { + if (mJumpKeys.find(jump_key) == mJumpKeys.end()) + { + mJumpKeys.insert(std::pair(jump_key, (*item_it))); + } + else + { + // this key is already spoken for, + // so we need to reassign it below + (*item_it)->setJumpKey(KEY_NONE); + } + } + } + + for(item_it = mItems.begin(); item_it != mItems.end(); ++item_it) + { + // skip over items that already have assigned jump keys + if ((*item_it)->getJumpKey() != KEY_NONE) + { + continue; + } + std::string uppercase_label = (*item_it)->getLabel(); + LLStringUtil::toUpper(uppercase_label); + + tokenizer tokens(uppercase_label, sep); + tokenizer::iterator token_iter; + + bool found_key = false; + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + std::string uppercase_word = *token_iter; + + // this word is not shared with other menu entries... + if (shared_words.find(*token_iter) == shared_words.end()) + { + S32 i; + for(i = 0; i < (S32)uppercase_word.size(); i++) + { + char jump_key = uppercase_word[i]; + + if (LLStringOps::isDigit(jump_key) || (LLStringOps::isUpper(jump_key) && + mJumpKeys.find(jump_key) == mJumpKeys.end())) + { + mJumpKeys.insert(std::pair(jump_key, (*item_it))); + (*item_it)->setJumpKey(jump_key); + found_key = true; + break; + } + } + } + if (found_key) + { + break; + } + } + } +} + +// remove all items on the menu +void LLMenuGL::empty( void ) +{ + cleanupSpilloverBranch(); + + mItems.clear(); + mFirstVisibleItem = NULL; + mArrowUpItem = NULL; + mArrowDownItem = NULL; + + deleteAllChildren(); +} + +// erase group of items from menu +void LLMenuGL::erase( S32 begin, S32 end, bool arrange/* = true*/) +{ + S32 items = mItems.size(); + + if ( items == 0 || begin >= end || begin < 0 || end > items ) + { + return; + } + + item_list_t::iterator start_position = mItems.begin(); + std::advance(start_position, begin); + + item_list_t::iterator end_position = mItems.begin(); + std::advance(end_position, end); + + for (item_list_t::iterator position_iter = start_position; position_iter != end_position; position_iter++) + { + LLUICtrl::removeChild(*position_iter); + } + + mItems.erase(start_position, end_position); + + if (arrange) + { + needsArrange(); + } +} + +// add new item at position +void LLMenuGL::insert( S32 position, LLView * ctrl, bool arrange /*= true*/ ) +{ + LLMenuItemGL * item = dynamic_cast(ctrl); + + if (NULL == item || position < 0 || position >= mItems.size()) + { + return; + } + + item_list_t::iterator position_iter = mItems.begin(); + std::advance(position_iter, position); + mItems.insert(position_iter, item); + LLUICtrl::addChild(item); + + if (arrange) + { + needsArrange(); + } +} + +// Adjust rectangle of the menu +void LLMenuGL::setLeftAndBottom(S32 left, S32 bottom) +{ + setRect(LLRect(left, getRect().mTop, getRect().mRight, bottom)); + needsArrange(); +} + +bool LLMenuGL::handleJumpKey(KEY key) +{ + // must perform case-insensitive comparison, so just switch to uppercase input key + key = toupper(key); + navigation_key_map_t::iterator found_it = mJumpKeys.find(key); + if(found_it != mJumpKeys.end() && found_it->second->getEnabled()) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + // force highlight to close old menus and open and sub-menus + found_it->second->setHighlight(true); + found_it->second->onCommit(); + + } + // if we are navigating the menus, we need to eat the keystroke + // so rest of UI doesn't handle it + return true; +} + + +// Add the menu item to this menu. +bool LLMenuGL::append( LLMenuItemGL* item ) +{ + if (!item) return false; + mItems.push_back( item ); + LLUICtrl::addChild(item); + needsArrange(); + return true; +} + +// add a separator to this menu +bool LLMenuGL::addSeparator() +{ + LLMenuItemSeparatorGL::Params p; + LLMenuItemGL* separator = LLUICtrlFactory::create(p); + return addChild(separator); +} + +// add a menu - this will create a cascading menu +bool LLMenuGL::appendMenu( LLMenuGL* menu ) +{ + if( menu == this ) + { + LL_ERRS() << "** Attempt to attach menu to itself. This is certainly " + << "a logic error." << LL_ENDL; + } + bool success = true; + + LLMenuItemBranchGL::Params p; + p.name = menu->getName(); + p.label = menu->getLabel(); + p.branch = menu; + p.jump_key = menu->getJumpKey(); + p.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); + p.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor"); + p.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); + p.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); + + LLMenuItemBranchGL* branch = LLUICtrlFactory::create(p); + success &= append( branch ); + + // Inherit colors + menu->setBackgroundColor( mBackgroundColor ); + menu->updateParent(LLMenuGL::sMenuContainer); + return success; +} + +// add a context menu branch +bool LLMenuGL::appendContextSubMenu(LLMenuGL *menu) +{ + if (menu == this) + { + LL_ERRS() << "Can't attach a context menu to itself" << LL_ENDL; + } + + LLContextMenuBranch *item; + LLContextMenuBranch::Params p; + p.name = menu->getName(); + p.label = menu->getLabel(); + p.branch = (LLContextMenu *)menu; + p.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); + p.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor"); + p.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); + p.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); + + item = LLUICtrlFactory::create(p); + LLMenuGL::sMenuContainer->addChild(item->getBranch()); + + return append( item ); +} + +void LLMenuGL::setEnabledSubMenus(bool enable) +{ + setEnabled(enable); + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + (*item_iter)->setEnabledSubMenus( enable ); + } +} + +// setItemEnabled() - pass the label and the enable flag for a menu +// item. true will make sure it's enabled, false will disable it. +void LLMenuGL::setItemEnabled( const std::string& name, bool enable ) +{ + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if( (*item_iter)->getName() == name ) + { + (*item_iter)->setEnabled( enable ); + (*item_iter)->setEnabledSubMenus( enable ); + break; + } + } +} + +void LLMenuGL::setItemVisible( const std::string& name, bool visible ) +{ + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if( (*item_iter)->getName() == name ) + { + (*item_iter)->setVisible( visible ); + needsArrange(); + break; + } + } +} + + +void LLMenuGL::setItemLabel(const std::string &name, const std::string &label) +{ + LLMenuItemGL *item = getItem(name); + + if (item) + item->setLabel(label); +} + +void LLMenuGL::setItemLastSelected(LLMenuItemGL* item) +{ + if (getVisible()) + { + LLMenuHolderGL::setActivatedItem(item); + } + + // update enabled and checkmark status + item->buildDrawLabel(); +} + +// Set whether drop shadowed +void LLMenuGL::setDropShadowed( const bool shadowed ) +{ + mDropShadowed = shadowed; +} + +void LLMenuGL::setTornOff(bool torn_off) +{ + mTornOff = torn_off; +} + +U32 LLMenuGL::getItemCount() +{ + return mItems.size(); +} + +LLMenuItemGL* LLMenuGL::getItem(S32 number) +{ + if (number >= 0 && number < (S32)mItems.size()) + { + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if (number == 0) + { + return (*item_iter); + } + number--; + } + } + return NULL; +} + +LLMenuItemGL* LLMenuGL::getItem(std::string name) +{ + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter)->getName() == name) + { + return (*item_iter); + } + } + return NULL; +} + +LLMenuItemGL* LLMenuGL::getHighlightedItem() +{ + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + if ((*item_iter)->getHighlight()) + { + return (*item_iter); + } + } + return NULL; +} + +LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, bool skip_disabled) +{ + if (mItems.empty()) return NULL; + // highlighting first item on a torn off menu is the + // same as giving focus to it + if (!cur_item && getTornOff()) + { + LLFloater * parent = dynamic_cast(getParent()); + if (parent) + { + parent->setFocus(true); + } + } + + // Current item position in the items list + item_list_t::iterator cur_item_iter = std::find(mItems.begin(), mItems.end(), cur_item); + + item_list_t::iterator next_item_iter; + if (cur_item_iter == mItems.end()) + { + next_item_iter = mItems.begin(); + } + else + { + next_item_iter = cur_item_iter; + next_item_iter++; + + // First visible item position in the items list + item_list_t::iterator first_visible_item_iter = std::find(mItems.begin(), mItems.end(), mFirstVisibleItem); + + if (next_item_iter == mItems.end()) + { + next_item_iter = mItems.begin(); + + // If current item is the last in the list, the menu is scrolled to the beginning + // and the first item is highlighted. + if (mScrollable && !scrollItems(SD_BEGIN)) + { + return NULL; + } + } + // If current item is the last visible, the menu is scrolled one item down + // and the next item is highlighted. + else if (mScrollable && + (U32)std::abs(std::distance(first_visible_item_iter, next_item_iter)) >= mMaxScrollableItems) + { + // Call highlightNextItem() recursively only if the menu was successfully scrolled down. + // If scroll timer hasn't expired yet the menu won't be scrolled and calling + // highlightNextItem() will result in an endless recursion. + if (scrollItems(SD_DOWN)) + { + return highlightNextItem(cur_item, skip_disabled); + } + else + { + return NULL; + } + } + } + + // when first highlighting a menu, skip over tear off menu item + if (mTearOffItem && !cur_item) + { + // we know the first item is the tear off menu item + cur_item_iter = mItems.begin(); + next_item_iter++; + if (next_item_iter == mItems.end()) + { + next_item_iter = mItems.begin(); + } + } + + while(1) + { + // skip separators and disabled/invisible items + if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getVisible() && !dynamic_cast(*next_item_iter)) + { + if (cur_item) + { + cur_item->setHighlight(false); + } + (*next_item_iter)->setHighlight(true); + return (*next_item_iter); + } + + + if (!skip_disabled || next_item_iter == cur_item_iter) + { + break; + } + + next_item_iter++; + if (next_item_iter == mItems.end()) + { + if (cur_item_iter == mItems.end()) + { + break; + } + next_item_iter = mItems.begin(); + } + } + + return NULL; +} + +LLMenuItemGL* LLMenuGL::highlightPrevItem(LLMenuItemGL* cur_item, bool skip_disabled) +{ + if (mItems.empty()) return NULL; + + // highlighting first item on a torn off menu is the + // same as giving focus to it + if (!cur_item && getTornOff()) + { + LLFloater * parent = dynamic_cast(getParent()); + if (parent) + { + parent->setFocus(true); + } + } + + // Current item reverse position from the end of the list + item_list_t::reverse_iterator cur_item_iter = std::find(mItems.rbegin(), mItems.rend(), cur_item); + + item_list_t::reverse_iterator prev_item_iter; + if (cur_item_iter == mItems.rend()) + { + prev_item_iter = mItems.rbegin(); + } + else + { + prev_item_iter = cur_item_iter; + prev_item_iter++; + + // First visible item reverse position in the items list + item_list_t::reverse_iterator first_visible_item_iter = std::find(mItems.rbegin(), mItems.rend(), mFirstVisibleItem); + + if (prev_item_iter == mItems.rend()) + { + prev_item_iter = mItems.rbegin(); + + // If current item is the first in the list, the menu is scrolled to the end + // and the last item is highlighted. + if (mScrollable && !scrollItems(SD_END)) + { + return NULL; + } + } + // If current item is the first visible, the menu is scrolled one item up + // and the previous item is highlighted. + else if (mScrollable && + std::distance(first_visible_item_iter, cur_item_iter) <= 0) + { + // Call highlightNextItem() only if the menu was successfully scrolled up. + // If scroll timer hasn't expired yet the menu won't be scrolled and calling + // highlightNextItem() will result in an endless recursion. + if (scrollItems(SD_UP)) + { + return highlightPrevItem(cur_item, skip_disabled); + } + else + { + return NULL; + } + } + } + + while(1) + { + // skip separators and disabled/invisible items + if ((*prev_item_iter)->getEnabled() && (*prev_item_iter)->getVisible() && (*prev_item_iter)->getName() != SEPARATOR_NAME) + { + (*prev_item_iter)->setHighlight(true); + return (*prev_item_iter); + } + + if (!skip_disabled || prev_item_iter == cur_item_iter) + { + break; + } + + prev_item_iter++; + if (prev_item_iter == mItems.rend()) + { + if (cur_item_iter == mItems.rend()) + { + break; + } + + prev_item_iter = mItems.rbegin(); + } + } + + return NULL; +} + +void LLMenuGL::buildDrawLabels() +{ + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + (*item_iter)->buildDrawLabel(); + } +} + +void LLMenuGL::updateParent(LLView* parentp) +{ + if (getParent()) + { + getParent()->removeChild(this); + } + if (parentp) + { + parentp->addChild(this); + } + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + (*item_iter)->updateBranchParent(parentp); + } +} + +bool LLMenuGL::hasAccelerator(const KEY &key, const MASK &mask) const +{ + if (key == KEY_NONE) + { + return false; + } + // Note: checking this way because mAccelerators seems to be broken + // mAccelerators probably needs to be cleaned up or fixed + // It was used for dupplicate accelerator avoidance. + item_list_t::const_iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + LLMenuItemGL* itemp = *item_iter; + if (itemp->hasAccelerator(key, mask)) + { + return true; + } + } + return false; +} + +bool LLMenuGL::handleAcceleratorKey(KEY key, MASK mask) +{ + // don't handle if not enabled + if(!getEnabled()) + { + return false; + } + + // Pass down even if not visible + item_list_t::iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + LLMenuItemGL* itemp = *item_iter; + if (itemp->handleAcceleratorKey(key, mask)) + { + return true; + } + } + + return false; +} + +bool LLMenuGL::handleUnicodeCharHere( llwchar uni_char ) +{ + if (jumpKeysActive()) + { + return handleJumpKey((KEY)uni_char); + } + return false; +} + +bool LLMenuGL::handleHover( S32 x, S32 y, MASK mask ) +{ + // leave submenu in place if slope of mouse < MAX_MOUSE_SLOPE_SUB_MENU + bool no_mouse_data = mLastMouseX == 0 && mLastMouseY == 0; + S32 mouse_delta_x = no_mouse_data ? 0 : x - mLastMouseX; + S32 mouse_delta_y = no_mouse_data ? 0 : y - mLastMouseY; + LLVector2 mouse_dir((F32)mouse_delta_x, (F32)mouse_delta_y); + mouse_dir.normVec(); + LLVector2 mouse_avg_dir((F32)mMouseVelX, (F32)mMouseVelY); + mouse_avg_dir.normVec(); + F32 interp = 0.5f * (llclamp(mouse_dir * mouse_avg_dir, 0.f, 1.f)); + mMouseVelX = ll_round(lerp((F32)mouse_delta_x, (F32)mMouseVelX, interp)); + mMouseVelY = ll_round(lerp((F32)mouse_delta_y, (F32)mMouseVelY, interp)); + mLastMouseX = x; + mLastMouseY = y; + + // don't change menu focus unless mouse is moving or alt key is not held down + if ((llabs(mMouseVelX) > 0 || + llabs(mMouseVelY) > 0) && + (!mHasSelection || + //(mouse_delta_x == 0 && mouse_delta_y == 0) || + (mMouseVelX < 0) || + llabs((F32)mMouseVelY) / llabs((F32)mMouseVelX) > MAX_MOUSE_SLOPE_SUB_MENU)) + { + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if (!viewp->pointInView(local_x, local_y) && ((LLMenuItemGL*)viewp)->getHighlight()) + { + // moving mouse always highlights new item + if (mouse_delta_x != 0 || mouse_delta_y != 0) + { + ((LLMenuItemGL*)viewp)->setHighlight(false); + } + } + } + + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + //RN: always call handleHover to track mGotHover status + // but only set highlight when mouse is moving + if( viewp->getVisible() && + //RN: allow disabled items to be highlighted to preserve "active" menus when + // moving mouse through them + //viewp->getEnabled() && + viewp->pointInView(local_x, local_y) && + viewp->handleHover(local_x, local_y, mask)) + { + // moving mouse always highlights new item + if (mouse_delta_x != 0 || mouse_delta_y != 0) + { + ((LLMenuItemGL*)viewp)->setHighlight(true); + LLMenuGL::setKeyboardMode(false); + } + mHasSelection = true; + } + } + } + getWindow()->setCursor(UI_CURSOR_ARROW); + + // *HACK Release the mouse capture + // This is done to release the mouse after the Navigation Bar "Back" or "Forward" button + // drop-down menu is shown. Otherwise any other view won't be able to handle mouse events + // until the user chooses one of the drop-down menu items. + + return true; +} + +bool LLMenuGL::handleScrollWheel( S32 x, S32 y, S32 clicks ) +{ + if (!mScrollable) + return blockMouseEvent(x, y); + + if( clicks > 0 ) + { + while( clicks-- ) + scrollItems(SD_DOWN); + } + else + { + while( clicks++ ) + scrollItems(SD_UP); + } + + return true; +} + + +void LLMenuGL::draw( void ) +{ + if (mNeedsArrange) + { + arrange(); + mNeedsArrange = false; + } + if (mDropShadowed && !mTornOff) + { + static LLUIColor color_drop_shadow = LLUIColorTable::instance().getColor("ColorDropShadow"); + gl_drop_shadow(0, getRect().getHeight(), getRect().getWidth(), 0, + color_drop_shadow, DROP_SHADOW_FLOATER); + } + + if( mBgVisible ) + { + gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0, mBackgroundColor.get() ); + } + LLView::draw(); +} + +void LLMenuGL::drawBackground(LLMenuItemGL* itemp, F32 alpha) +{ + LLColor4 color = itemp->getHighlightBgColor() % alpha; + gGL.color4fv( color.mV ); + LLRect item_rect = itemp->getRect(); + gl_rect_2d( 0, item_rect.getHeight(), item_rect.getWidth(), 0); +} + +void LLMenuGL::setVisible(bool visible) +{ + if (visible != getVisible()) + { + if (!visible) + { + mFadeTimer.start(); + clearHoverItem(); + // reset last known mouse coordinates so + // we don't spoof a mouse move next time we're opened + mLastMouseX = 0; + mLastMouseY = 0; + } + else + { + mHasSelection = true; + mFadeTimer.stop(); + } + + LLView::setVisible(visible); + } +} + +LLMenuGL* LLMenuGL::findChildMenuByName(const std::string& name, bool recurse) const +{ + LLView* view = findChildView(name, recurse); + if (view) + { + LLMenuItemBranchGL* branch = dynamic_cast(view); + if (branch) + { + return branch->getBranch(); + } + + LLMenuGL* menup = dynamic_cast(view); + if (menup) + { + return menup; + } + } + LL_WARNS() << "Child Menu " << name << " not found in menu " << getName() << LL_ENDL; + return NULL; +} + +bool LLMenuGL::clearHoverItem() +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLMenuItemGL* itemp = (LLMenuItemGL*)*child_it; + if (itemp->getHighlight()) + { + itemp->setHighlight(false); + return true; + } + } + return false; +} + +void hide_top_view( LLView* view ) +{ + if( view ) view->setVisible( false ); +} + + +// x and y are the desired location for the popup, in the spawning_view's +// coordinate frame, NOT necessarily the mouse location +// static +void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y, S32 mouse_x, S32 mouse_y) +{ + const S32 CURSOR_HEIGHT = 22; // Approximate "normal" cursor size + const S32 CURSOR_WIDTH = 12; + + if (menu->getChildList()->empty()) + { + return; + } + + menu->setVisible( true ); + + if(!menu->getAlwaysShowMenu()) + { + //Do not show menu if all menu items are disabled + bool item_enabled = false; + for (LLView::child_list_t::const_iterator itor = menu->getChildList()->begin(); + itor != menu->getChildList()->end(); + ++itor) + { + LLView *menu_item = (*itor); + item_enabled = item_enabled || menu_item->getEnabled(); + } + + if(!item_enabled) + { + menu->setVisible( false ); + return; + } + } + + // Resetting scrolling position + if (menu->isScrollable() && menu->isScrollPositionOnShowReset()) + { + menu->mFirstVisibleItem = NULL; + } + + // Fix menu rect if needed. + menu->needsArrange(); + menu->arrangeAndClear(); + + if ((mouse_x == 0) || (mouse_y == 0)) + + { + // Save click point for detecting cursor moves before mouse-up. + // Must be in local coords to compare with mouseUp events. + // If the mouse doesn't move, the menu will stay open ala the Mac. + // See also LLContextMenu::show() + + LLUI::getInstance()->getMousePositionLocal(menu->getParent(), &mouse_x, &mouse_y); + } + + + LLMenuHolderGL::sContextMenuSpawnPos.set(mouse_x,mouse_y); + + const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getRect(); + + const S32 HPAD = 2; + LLRect rect = menu->getRect(); + S32 left = x + HPAD; + S32 top = y; + spawning_view->localPointToOtherView(left, top, &left, &top, menu->getParent()); + rect.setLeftTopAndSize( left, top, + rect.getWidth(), rect.getHeight() ); + menu->setRect( rect ); + + + // Adjust context menu to fit onscreen + LLRect mouse_rect; + const S32 MOUSE_CURSOR_PADDING = 5; + mouse_rect.setLeftTopAndSize(mouse_x - MOUSE_CURSOR_PADDING, + mouse_y + MOUSE_CURSOR_PADDING, + CURSOR_WIDTH + MOUSE_CURSOR_PADDING * 2, + CURSOR_HEIGHT + MOUSE_CURSOR_PADDING * 2); + menu->translateIntoRectWithExclusion( menu_region_rect, mouse_rect ); + if (menu->getRect().mTop > menu_region_rect.mTop) + { + // not enough space: align with top, ignore exclusion + menu->translateIntoRect( menu_region_rect ); + } + menu->getParent()->sendChildToFront(menu); +} + +///============================================================================ +/// Class LLMenuBarGL +///============================================================================ + +static LLDefaultChildRegistry::Register r2("menu_bar"); + +LLMenuBarGL::LLMenuBarGL( const Params& p ) +: LLMenuGL(p), + mAltKeyTrigger(false) +{} + +// Default destructor +LLMenuBarGL::~LLMenuBarGL() +{ + std::for_each(mAccelerators.begin(), mAccelerators.end(), DeletePointer()); + mAccelerators.clear(); +} + +bool LLMenuBarGL::handleAcceleratorKey(KEY key, MASK mask) +{ + if (getHighlightedItem() && mask == MASK_NONE) + { + // unmodified key accelerators are ignored when navigating menu + // (but are used as jump keys so will still work when appropriate menu is up) + return false; + } + bool result = LLMenuGL::handleAcceleratorKey(key, mask); + if (result && mask & MASK_ALT) + { + // ALT key used to trigger hotkey, don't use as shortcut to open menu + mAltKeyTrigger = false; + } + + if(!result + && (key == KEY_F10 && mask == MASK_CONTROL) + && !gKeyboard->getKeyRepeated(key) + && isInVisibleChain()) + { + if (getHighlightedItem()) + { + clearHoverItem(); + LLMenuGL::setKeyboardMode(false); + } + else + { + // close menus originating from other menu bars when first opening menu via keyboard + LLMenuGL::sMenuContainer->hideMenus(); + highlightNextItem(NULL); + LLMenuGL::setKeyboardMode(true); + } + return true; + } + + if (result && !getHighlightedItem() && LLMenuGL::sMenuContainer->hasVisibleMenu()) + { + // close menus originating from other menu bars + LLMenuGL::sMenuContainer->hideMenus(); + } + + return result; +} + +bool LLMenuBarGL::handleKeyHere(KEY key, MASK mask) +{ + static LLUICachedControl use_altkey_for_menus ("UseAltKeyForMenus", 0); + if(key == KEY_ALT && !gKeyboard->getKeyRepeated(key) && use_altkey_for_menus) + { + mAltKeyTrigger = true; + } + else // if any key other than ALT hit, clear out waiting for Alt key mode + { + mAltKeyTrigger = false; + } + + if (key == KEY_ESCAPE && mask == MASK_NONE) + { + LLMenuGL::setKeyboardMode(false); + // if any menus are visible, this will return true, stopping further processing of ESCAPE key + return LLMenuGL::sMenuContainer->hideMenus(); + } + + // before processing any other key, check to see if ALT key has triggered menu access + checkMenuTrigger(); + + return LLMenuGL::handleKeyHere(key, mask); +} + +bool LLMenuBarGL::handleJumpKey(KEY key) +{ + // perform case-insensitive comparison + key = toupper(key); + navigation_key_map_t::iterator found_it = mJumpKeys.find(key); + if(found_it != mJumpKeys.end() && found_it->second->getEnabled()) + { + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(true); + + found_it->second->setHighlight(true); + found_it->second->onCommit(); + } + return true; +} + +bool LLMenuBarGL::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // clicks on menu bar closes existing menus from other contexts but leave + // own menu open so that we get toggle behavior + if (!getHighlightedItem() || !getHighlightedItem()->isActive()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + + return LLMenuGL::handleMouseDown(x, y, mask); +} + +bool LLMenuBarGL::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + return LLMenuGL::handleMouseDown(x, y, mask); +} + +void LLMenuBarGL::draw() +{ + LLMenuItemGL* itemp = getHighlightedItem(); + // If we are in mouse-control mode and the mouse cursor is not hovering over + // the current highlighted menu item and it isn't open, then remove the + // highlight. This is done via a polling mechanism here, as we don't receive + // notifications when the mouse cursor moves off of us + if (itemp && !itemp->isOpen() && !itemp->getHover() && !LLMenuGL::getKeyboardMode()) + { + clearHoverItem(); + } + + checkMenuTrigger(); + + LLMenuGL::draw(); +} + + +void LLMenuBarGL::checkMenuTrigger() +{ + // has the ALT key been pressed and subsequently released? + if (mAltKeyTrigger && !gKeyboard->getKeyDown(KEY_ALT)) + { + // if alt key was released quickly, treat it as a menu access key + // otherwise it was probably an Alt-zoom or similar action + static LLUICachedControl menu_access_key_time ("MenuAccessKeyTime", 0); + if (gKeyboard->getKeyElapsedTime(KEY_ALT) <= menu_access_key_time || + gKeyboard->getKeyElapsedFrameCount(KEY_ALT) < 2) + { + if (getHighlightedItem()) + { + clearHoverItem(); + } + else + { + // close menus originating from other menu bars + LLMenuGL::sMenuContainer->hideMenus(); + + highlightNextItem(NULL); + LLMenuGL::setKeyboardMode(true); + } + } + mAltKeyTrigger = false; + } +} + +bool LLMenuBarGL::jumpKeysActive() +{ + // require user to be in keyboard navigation mode to activate key triggers + // as menu bars are always visible and it is easy to leave the mouse cursor over them + return LLMenuGL::getKeyboardMode() && getHighlightedItem() && LLMenuGL::jumpKeysActive(); +} + +// rearrange the child rects so they fit the shape of the menu bar. +void LLMenuBarGL::arrange( void ) +{ + U32 pos = 0; + LLRect rect( 0, getRect().getHeight(), 0, 0 ); + item_list_t::const_iterator item_iter; + for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + { + LLMenuItemGL* item = *item_iter; + if (item->getVisible()) + { + rect.mLeft = pos; + pos += item->getNominalWidth(); + rect.mRight = pos; + item->setRect( rect ); + item->buildDrawLabel(); + } + } + reshape(rect.mRight, rect.getHeight()); +} + + +S32 LLMenuBarGL::getRightmostMenuEdge() +{ + // Find the last visible menu + item_list_t::reverse_iterator item_iter; + for (item_iter = mItems.rbegin(); item_iter != mItems.rend(); ++item_iter) + { + if ((*item_iter)->getVisible()) + { + break; + } + } + + if (item_iter == mItems.rend()) + { + return 0; + } + return (*item_iter)->getRect().mRight; +} + +// add a vertical separator to this menu +bool LLMenuBarGL::addSeparator() +{ + LLMenuItemGL* separator = new LLMenuItemVerticalSeparatorGL(); + return append( separator ); +} + +// add a menu - this will create a drop down menu. +bool LLMenuBarGL::appendMenu( LLMenuGL* menu ) +{ + if( menu == this ) + { + LL_ERRS() << "** Attempt to attach menu to itself. This is certainly " + << "a logic error." << LL_ENDL; + } + + bool success = true; + + // *TODO: Hack! Fix this + LLMenuItemBranchDownGL::Params p; + p.name = menu->getName(); + p.label = menu->getLabel(); + p.visible = menu->getVisible(); + p.branch = menu; + p.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); + p.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor"); + p.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); + p.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); + p.font = menu->getFont(); + + LLMenuItemBranchDownGL* branch = LLUICtrlFactory::create(p); + success &= branch->addToAcceleratorList(&mAccelerators); + success &= append( branch ); + branch->setJumpKey(branch->getJumpKey()); + menu->updateParent(LLMenuGL::sMenuContainer); + + return success; +} + +bool LLMenuBarGL::handleHover( S32 x, S32 y, MASK mask ) +{ + bool handled = false; + LLView* active_menu = NULL; + + bool no_mouse_data = mLastMouseX == 0 && mLastMouseY == 0; + S32 mouse_delta_x = no_mouse_data ? 0 : x - mLastMouseX; + S32 mouse_delta_y = no_mouse_data ? 0 : y - mLastMouseY; + mMouseVelX = (mMouseVelX / 2) + (mouse_delta_x / 2); + mMouseVelY = (mMouseVelY / 2) + (mouse_delta_y / 2); + mLastMouseX = x; + mLastMouseY = y; + + // if nothing currently selected or mouse has moved since last call, pick menu item via mouse + // otherwise let keyboard control it + if (!getHighlightedItem() || !LLMenuGL::getKeyboardMode() || llabs(mMouseVelX) > 0 || llabs(mMouseVelY) > 0) + { + // find current active menu + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (((LLMenuItemGL*)viewp)->isOpen()) + { + active_menu = viewp; + } + } + + // check for new active menu + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if( viewp->getVisible() && + viewp->getEnabled() && + viewp->pointInView(local_x, local_y) && + viewp->handleHover(local_x, local_y, mask)) + { + ((LLMenuItemGL*)viewp)->setHighlight(true); + handled = true; + if (active_menu && active_menu != viewp) + { + ((LLMenuItemGL*)viewp)->onCommit(); + LLMenuGL::setKeyboardMode(false); + } + LLMenuGL::setKeyboardMode(false); + } + } + + if (handled) + { + // set hover false on inactive menus + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if (!viewp->pointInView(local_x, local_y) && ((LLMenuItemGL*)viewp)->getHighlight()) + { + ((LLMenuItemGL*)viewp)->setHighlight(false); + } + } + } + } + + getWindow()->setCursor(UI_CURSOR_ARROW); + + return true; +} + +///============================================================================ +/// Class LLMenuHolderGL +///============================================================================ +LLCoordGL LLMenuHolderGL::sContextMenuSpawnPos(S32_MAX, S32_MAX); + +LLMenuHolderGL::LLMenuHolderGL(const LLMenuHolderGL::Params& p) + : LLPanel(p) +{ + sItemActivationTimer.stop(); + mCanHide = true; +} + +void LLMenuHolderGL::draw() +{ + LLView::draw(); + // now draw last selected item as overlay + LLMenuItemGL* selecteditem = (LLMenuItemGL*)sItemLastSelectedHandle.get(); + if (selecteditem && selecteditem->getVisible() && sItemActivationTimer.getStarted() && sItemActivationTimer.getElapsedTimeF32() < ACTIVATE_HIGHLIGHT_TIME) + { + // make sure toggle items, for example, show the proper state when fading out + selecteditem->buildDrawLabel(); + + LLRect item_rect; + selecteditem->localRectToOtherView(selecteditem->getLocalRect(), &item_rect, this); + + F32 interpolant = sItemActivationTimer.getElapsedTimeF32() / ACTIVATE_HIGHLIGHT_TIME; + + LLUI::pushMatrix(); + { + LLUI::translate((F32)item_rect.mLeft, (F32)item_rect.mBottom); + selecteditem->getMenu()->drawBackground(selecteditem, interpolant); + selecteditem->draw(); + } + LLUI::popMatrix(); + } +} + +bool LLMenuHolderGL::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + bool handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + if (!handled) + { + LLMenuGL* visible_menu = (LLMenuGL*)getVisibleMenu(); + LLMenuItemGL* parent_menu = visible_menu ? visible_menu->getParentMenuItem() : NULL; + if (parent_menu && parent_menu->getVisible()) + { + // don't hide menu if parent was hit + LLRect parent_rect; + parent_menu->localRectToOtherView(parent_menu->getLocalRect(), &parent_rect, this); + if (!parent_rect.pointInRect(x, y)) + { + // clicked off of menu and parent, hide them all + hideMenus(); + } + } + else + { + // no visible parent, clicked off of menu, hide them all + hideMenus(); + } + } + return handled; +} + +bool LLMenuHolderGL::handleRightMouseDown( S32 x, S32 y, MASK mask ) +{ + bool handled = LLView::childrenHandleRightMouseDown(x, y, mask) != NULL; + if (!handled) + { + // clicked off of menu, hide them all + hideMenus(); + } + return handled; +} + +// This occurs when you mouse-down to spawn a context menu, hold the button +// down, move off the menu, then mouse-up. We want this to close the menu. +bool LLMenuHolderGL::handleRightMouseUp( S32 x, S32 y, MASK mask ) +{ + const S32 SLOP = 2; + S32 spawn_dx = (x - sContextMenuSpawnPos.mX); + S32 spawn_dy = (y - sContextMenuSpawnPos.mY); + if (-SLOP <= spawn_dx && spawn_dx <= SLOP + && -SLOP <= spawn_dy && spawn_dy <= SLOP) + { + // we're still inside the slop region from spawning this menu + // so interpret the mouse-up as a single-click to show and leave on + // screen + sContextMenuSpawnPos.set(S32_MAX, S32_MAX); + return true; + } + + bool handled = LLView::childrenHandleRightMouseUp(x, y, mask) != NULL; + if (!handled) + { + // clicked off of menu, hide them all + hideMenus(); + } + return handled; +} + +bool LLMenuHolderGL::handleKey(KEY key, MASK mask, bool called_from_parent) +{ + bool handled = false; + LLMenuGL* const pMenu = dynamic_cast(getVisibleMenu()); + + if (pMenu) + { + //eat TAB key - EXT-7000 + if (key == KEY_TAB && mask == MASK_NONE) + { + return true; + } + + //handle ESCAPE and RETURN key + handled = LLPanel::handleKey(key, mask, called_from_parent); + if (!handled) + { + if (pMenu->getHighlightedItem()) + { + handled = pMenu->handleKey(key, mask, true); + } + else if (mask == MASK_NONE || (key >= KEY_LEFT && key <= KEY_DOWN)) + { + //highlight first enabled one + if(pMenu->highlightNextItem(NULL)) + { + handled = true; + } + } + } + } + + return handled; + +} + +void LLMenuHolderGL::reshape(S32 width, S32 height, bool called_from_parent) +{ + if (width != getRect().getWidth() || height != getRect().getHeight()) + { + hideMenus(); + } + LLView::reshape(width, height, called_from_parent); +} + +LLView* const LLMenuHolderGL::getVisibleMenu() const +{ + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (viewp->getVisible() && dynamic_cast(viewp) != NULL) + { + return viewp; + } + } + return NULL; +} + + +bool LLMenuHolderGL::hideMenus() +{ + if (!mCanHide) + { + return false; + } + LLMenuGL::setKeyboardMode(false); + bool menu_visible = hasVisibleMenu(); + if (menu_visible) + { + // clicked off of menu, hide them all + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) + { + LLView* viewp = *child_it; + if (dynamic_cast(viewp) != NULL && viewp->getVisible()) + { + viewp->setVisible(false); + } + } + } + //if (gFocusMgr.childHasKeyboardFocus(this)) + //{ + // gFocusMgr.setKeyboardFocus(NULL); + //} + + return menu_visible; +} + +void LLMenuHolderGL::setActivatedItem(LLMenuItemGL* item) +{ + sItemLastSelectedHandle = item->getHandle(); + sItemActivationTimer.start(); +} + +///============================================================================ +/// Class LLTearOffMenu +///============================================================================ +LLTearOffMenu::LLTearOffMenu(LLMenuGL* menup) : + LLFloater(LLSD()), + mQuitRequested(false) +{ + S32 floater_header_size = getHeaderHeight(); + + setName(menup->getName()); + setTitle(menup->getLabel()); + setCanMinimize(false); + // flag menu as being torn off + menup->setTornOff(true); + // update menu layout as torn off menu (no spillover menus) + menup->needsArrange(); + + LLRect rect; + menup->localRectToOtherView(LLRect(-1, menup->getRect().getHeight(), menup->getRect().getWidth() + 3, 0), &rect, gFloaterView); + // make sure this floater is big enough for menu + mTargetHeight = rect.getHeight() + floater_header_size; + reshape(rect.getWidth(), rect.getHeight()); + setRect(rect); + + // attach menu to floater + menup->setFollows( FOLLOWS_LEFT | FOLLOWS_BOTTOM ); + mOldParent = menup->getParent(); + addChild(menup); + menup->setVisible(true); + LLRect menu_rect = menup->getRect(); + menu_rect.setOriginAndSize( 1, 1, + menu_rect.getWidth(), menu_rect.getHeight()); + menup->setRect(menu_rect); + menup->setDropShadowed(false); + + mMenu = menup; + + // highlight first item (tear off item will be disabled) + mMenu->highlightNextItem(NULL); + + // Can't do this in postBuild() because that is only called for floaters + // constructed from XML. + mCloseSignal.connect(boost::bind(&LLTearOffMenu::closeTearOff, this)); +} + +LLTearOffMenu::~LLTearOffMenu() +{ +} + +void LLTearOffMenu::draw() +{ + mMenu->setBackgroundVisible(isBackgroundOpaque()); + + if (getRect().getHeight() != mTargetHeight) + { + // animate towards target height + reshape(getRect().getWidth(), llceil(lerp((F32)getRect().getHeight(), (F32)mTargetHeight, LLSmoothInterpolation::getInterpolant(0.05f)))); + } + mMenu->needsArrange(); + LLFloater::draw(); +} + +void LLTearOffMenu::onFocusReceived() +{ + if (mQuitRequested) + { + return; + } + + // if nothing is highlighted, just highlight first item + if (!mMenu->getHighlightedItem()) + { + mMenu->highlightNextItem(NULL); + } + + // parent menu items get highlights so navigation logic keeps working + LLMenuItemGL* parent_menu_item = mMenu->getParentMenuItem(); + while(parent_menu_item) + { + if (parent_menu_item->getMenu()->getVisible()) + { + parent_menu_item->setHighlight(true); + parent_menu_item = parent_menu_item->getMenu()->getParentMenuItem(); + } + else + { + break; + } + } + LLFloater::onFocusReceived(); +} + +void LLTearOffMenu::onFocusLost() +{ + // remove highlight from parent item and our own menu + mMenu->clearHoverItem(); + LLFloater::onFocusLost(); +} + +bool LLTearOffMenu::handleUnicodeChar(llwchar uni_char, bool called_from_parent) +{ + // pass keystrokes down to menu + return mMenu->handleUnicodeChar(uni_char, true); +} + +bool LLTearOffMenu::handleKeyHere(KEY key, MASK mask) +{ + if (!mMenu->getHighlightedItem()) + { + if (key == KEY_UP) + { + mMenu->highlightPrevItem(NULL); + return true; + } + else if (key == KEY_DOWN) + { + mMenu->highlightNextItem(NULL); + return true; + } + } + // pass keystrokes down to menu + return mMenu->handleKey(key, mask, true); +} + +void LLTearOffMenu::translate(S32 x, S32 y) +{ + if (x != 0 && y != 0) + { + // hide open sub-menus by clearing current hover item + mMenu->clearHoverItem(); + } + LLFloater::translate(x, y); +} + +//static +LLTearOffMenu* LLTearOffMenu::create(LLMenuGL* menup) +{ + LLTearOffMenu* tearoffp = new LLTearOffMenu(menup); + // keep onscreen + gFloaterView->adjustToFitScreen(tearoffp, false); + tearoffp->openFloater(LLSD()); + + return tearoffp; +} + +void LLTearOffMenu::updateSize() +{ + if (mMenu) + { + S32 floater_header_size = getHeaderHeight(); + const LLRect &floater_rect = getRect(); + LLRect new_rect; + mMenu->localRectToOtherView(LLRect(-1, mMenu->getRect().getHeight() + floater_header_size, mMenu->getRect().getWidth() + 3, 0), &new_rect, gFloaterView); + + if (floater_rect.getWidth() != new_rect.getWidth() + || mTargetHeight != new_rect.getHeight()) + { + // make sure this floater is big enough for menu + mTargetHeight = new_rect.getHeight(); + reshape(new_rect.getWidth(), mTargetHeight); + + // Restore menu position + LLRect menu_rect = mMenu->getRect(); + menu_rect.setOriginAndSize(1, 1, + menu_rect.getWidth(), menu_rect.getHeight()); + mMenu->setRect(menu_rect); + } + } +} + +void LLTearOffMenu::closeTearOff() +{ + removeChild(mMenu); + mOldParent->addChild(mMenu); + mMenu->clearHoverItem(); + mMenu->setFollowsNone(); + mMenu->setBackgroundVisible(true); + mMenu->setVisible(false); + mMenu->setTornOff(false); + mMenu->setDropShadowed(true); + mQuitRequested = true; +} + +LLContextMenuBranch::LLContextMenuBranch(const LLContextMenuBranch::Params& p) +: LLMenuItemGL(p) +{ + LLContextMenu* branch = static_cast(p.branch); + if (branch) + { + mBranch = branch->getHandle(); + branch->hide(); + branch->setParentMenuItem(this); + } +} + +LLContextMenuBranch::~LLContextMenuBranch() +{ + if (mBranch.get()) + { + mBranch.get()->die(); + } +} + +// called to rebuild the draw label +void LLContextMenuBranch::buildDrawLabel( void ) +{ + auto menu = getBranch(); + if (menu) + { + // default enablement is this -- if any of the subitems are + // enabled, this item is enabled. JC + U32 sub_count = menu->getItemCount(); + U32 i; + bool any_enabled = false; + for (i = 0; i < sub_count; i++) + { + LLMenuItemGL* item = menu->getItem(i); + item->buildDrawLabel(); + if (item->getEnabled() && !item->getDrawTextDisabled() ) + { + any_enabled = true; + break; + } + } + setDrawTextDisabled(!any_enabled); + setEnabled(true); + } + + mDrawAccelLabel.clear(); + std::string st = mDrawAccelLabel; + appendAcceleratorString( st ); + mDrawAccelLabel = st; + + mDrawBranchLabel = LLMenuGL::BRANCH_SUFFIX; +} + +void LLContextMenuBranch::showSubMenu() +{ + auto menu = getBranch(); + if(menu) + { + LLMenuItemGL* menu_item = menu->getParentMenuItem(); + if (menu_item != NULL && menu_item->getVisible()) + { + S32 center_x; + S32 center_y; + localPointToScreen(getRect().getWidth(), getRect().getHeight(), ¢er_x, ¢er_y); + menu->show(center_x, center_y); + } + } +} + +// onCommit() - do the primary funcationality of the menu item. +void LLContextMenuBranch::onCommit( void ) +{ + showSubMenu(); + +} +void LLContextMenuBranch::setHighlight( bool highlight ) +{ + if (highlight == getHighlight()) return; + LLMenuItemGL::setHighlight(highlight); + auto menu = getBranch(); + if (menu) + { + if (highlight) + { + showSubMenu(); + } + else + { + menu->hide(); + } + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +//----------------------------------------------------------------------------- +// class LLContextMenu +// A context menu +//----------------------------------------------------------------------------- +static LLDefaultChildRegistry::Register context_menu_register("context_menu"); +static MenuRegistry::Register context_menu_register2("context_menu"); + + +LLContextMenu::LLContextMenu(const Params& p) +: LLMenuGL(p), + mHoveredAnyItem(false), + mHoverItem(NULL) +{ + //setBackgroundVisible(true); +} + +void LLContextMenu::setVisible(bool visible) +{ + if (!visible) + hide(); +} + +// Takes cursor position in screen space? +void LLContextMenu::show(S32 x, S32 y, LLView* spawning_view) +{ + if (getChildList()->empty()) + { + // nothing to show, so abort + return; + } + // Save click point for detecting cursor moves before mouse-up. + // Must be in local coords to compare with mouseUp events. + // If the mouse doesn't move, the menu will stay open ala the Mac. + // See also LLMenuGL::showPopup() + LLMenuHolderGL::sContextMenuSpawnPos.set(x,y); + + arrangeAndClear(); + + S32 width = getRect().getWidth(); + S32 height = getRect().getHeight(); + const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect(); + LLView* parent_view = getParent(); + + // Open upwards if menu extends past bottom + if (y - height < menu_region_rect.mBottom) + { + if (getParentMenuItem()) // Adjust if this is a submenu + { + y += height - getParentMenuItem()->getNominalHeight(); + } + else + { + y += height; + } + } + + // Open out to the left if menu extends past right edge + if (x + width > menu_region_rect.mRight) + { + if (getParentMenuItem()) + { + x -= getParentMenuItem()->getRect().getWidth() + width; + } + else + { + x -= width; + } + } + + S32 local_x, local_y; + parent_view->screenPointToLocal(x, y, &local_x, &local_y); + + LLRect rect; + rect.setLeftTopAndSize(local_x, local_y, width, height); + setRect(rect); + arrange(); + + if (spawning_view) + { + mSpawningViewHandle = spawning_view->getHandle(); + } + else + { + mSpawningViewHandle.markDead(); + } + LLView::setVisible(true); +} + +void LLContextMenu::hide() +{ + if (!getVisible()) return; + + LLView::setVisible(false); + + if (mHoverItem) + { + mHoverItem->setHighlight( false ); + } + mHoverItem = NULL; +} + + +bool LLContextMenu::handleHover( S32 x, S32 y, MASK mask ) +{ + LLMenuGL::handleHover(x,y,mask); + + bool handled = false; + + LLMenuItemGL *item = getHighlightedItem(); + + if (item && item->getEnabled()) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + handled = true; + + if (item != mHoverItem) + { + if (mHoverItem) + { + mHoverItem->setHighlight( false ); + } + mHoverItem = item; + mHoverItem->setHighlight( true ); + } + mHoveredAnyItem = true; + } + else + { + // clear out our selection + if (mHoverItem) + { + mHoverItem->setHighlight(false); + mHoverItem = NULL; + } + } + + if( !handled && pointInView( x, y ) ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + handled = true; + } + + return handled; +} + +// handleMouseDown and handleMouseUp are handled by LLMenuGL + + +bool LLContextMenu::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // The click was somewhere within our rectangle + LLMenuItemGL *item = getHighlightedItem(); + + S32 local_x = x - getRect().mLeft; + S32 local_y = y - getRect().mBottom; + + bool clicked_in_menu = pointInView(local_x, local_y) ; + + // grab mouse if right clicking anywhere within pie (even deadzone in middle), to detect drag outside of pie + if (clicked_in_menu) + { + // capture mouse cursor as if on initial menu show + handled = true; + } + + if (item) + { + // lie to the item about where the click happened + // to make sure it's within the item's rectangle + if (item->handleMouseDown( 0, 0, mask )) + { + handled = true; + } + } + + return handled; +} + +bool LLContextMenu::handleRightMouseUp( S32 x, S32 y, MASK mask ) +{ + S32 local_x = x - getRect().mLeft; + S32 local_y = y - getRect().mBottom; + + if (!mHoveredAnyItem && !pointInView(local_x, local_y)) + { + sMenuContainer->hideMenus(); + return true; + } + + + bool result = handleMouseUp( x, y, mask ); + mHoveredAnyItem = false; + + return result; +} + +bool LLContextMenu::addChild(LLView* view, S32 tab_group) +{ + return addContextChild(view, tab_group); +} + diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 9b22a6e0f2..51766afe85 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -1,978 +1,978 @@ -/** - * @file llmenugl.h - * @brief Declaration of the opengl based menu system. - * - * $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$ - */ - -#ifndef LL_LLMENUGL_H -#define LL_LLMENUGL_H - -#include - -#include "llstring.h" -#include "v4color.h" -#include "llframetimer.h" - -#include "llkeyboard.h" -#include "llfloater.h" -#include "lluistring.h" -#include "llview.h" -#include - -extern S32 MENU_BAR_HEIGHT; -extern S32 MENU_BAR_WIDTH; - -class LLMenuKeyboardBinding -{ -public: - KEY mKey; - MASK mMask; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemGL -// -// The LLMenuItemGL represents a single menu item in a menu. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuItemGL: public LLUICtrl, public ll::ui::SearchableControl -{ -public: - struct Params : public LLInitParam::Block - { - Optional shortcut; - Optional jump_key; - Optional use_mac_ctrl, - allow_key_repeat; - - Ignored rect, - left, - top, - right, - bottom, - width, - height, - bottom_delta, - left_delta; - - Optional enabled_color, - disabled_color, - highlight_bg_color, - highlight_fg_color; - - - Params(); - }; - -protected: - LLMenuItemGL(const Params&); - friend class LLUICtrlFactory; -public: - // LLView overrides - /*virtual*/ void onVisibilityChange(bool new_visibility); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask); - - // LLUICtrl overrides - /*virtual*/ void setValue(const LLSD& value); - /*virtual*/ LLSD getValue() const; - - virtual bool hasAccelerator(const KEY &key, const MASK &mask) const; - virtual bool handleAcceleratorKey(KEY key, MASK mask); - - LLColor4 getHighlightBgColor() { return mHighlightBackground.get(); } - - void setJumpKey(KEY key); - KEY getJumpKey() const { return mJumpKey; } - - // set the font used by this item. - void setFont(const LLFontGL* font) { mFont = font; } - const LLFontGL* getFont() const { return mFont; } - - // returns the height in pixels for the current font. - virtual U32 getNominalHeight( void ) const; - - // Marks item as not needing space for check marks or accelerator keys - virtual void setBriefItem(bool brief); - virtual bool isBriefItem() const; - - virtual bool addToAcceleratorList(std::list *listp); - void setAllowKeyRepeat(bool allow) { mAllowKeyRepeat = allow; } - bool getAllowKeyRepeat() const { return mAllowKeyRepeat; } - - // change the label - void setLabel( const LLStringExplicit& label ) { mLabel = label; } - std::string getLabel( void ) const { return mLabel.getString(); } - virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); - - // Get the parent menu for this item - virtual class LLMenuGL* getMenu() const; - - // returns the normal width of this control in pixels - this is - // used for calculating the widest item, as well as for horizontal - // arrangement. - virtual U32 getNominalWidth( void ) const; - - // buildDrawLabel() - constructs the string used during the draw() - // function. This reduces the overall string manipulation, but can - // lead to visual errors if the state of the object changes - // without the knowledge of the menu item. For example, if a - // boolean being watched is changed outside of the menu item's - // onCommit() function, the draw buffer will not be updated and will - // reflect the wrong value. If this ever becomes an issue, there - // are ways to fix this. - // Returns the enabled state of the item. - virtual void buildDrawLabel( void ); - - // for branching menu items, bring sub menus up to root level of menu hierarchy - virtual void updateBranchParent( LLView* parentp ){}; - - virtual void onCommit( void ); - - virtual void setHighlight( bool highlight ); - virtual bool getHighlight() const { return mHighlight; } - - // determine if this represents an active sub-menu - virtual bool isActive( void ) const { return false; } - - // determine if this represents an open sub-menu - virtual bool isOpen( void ) const { return false; } - - virtual void setEnabledSubMenus(bool enable){}; - - // LLView Functionality - virtual bool handleKeyHere( KEY key, MASK mask ); - virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); - virtual bool handleScrollWheel( S32 x, S32 y, S32 clicks ); - - virtual void onMouseEnter(S32 x, S32 y, MASK mask); - virtual void onMouseLeave(S32 x, S32 y, MASK mask); - - virtual void draw( void ); - - bool getHover() const { return mGotHover; } - - void setDrawTextDisabled(bool disabled) { mDrawTextDisabled = disabled; } - bool getDrawTextDisabled() const { return mDrawTextDisabled; } - -protected: - void setHover(bool hover) { mGotHover = hover; } - - // This function appends the character string representation of - // the current accelerator key and mask to the provided string. - void appendAcceleratorString( std::string& st ) const; - - virtual std::string _getSearchText() const - { - return mLabel.getString(); - } - -protected: - KEY mAcceleratorKey; - MASK mAcceleratorMask; - // mLabel contains the actual label specified by the user. - LLUIString mLabel; - - // The draw labels contain some of the labels that we draw during - // the draw() routine. This optimizes away some of the string - // manipulation. - LLUIString mDrawBoolLabel; - LLUIString mDrawAccelLabel; - LLUIString mDrawBranchLabel; - - LLUIColor mEnabledColor; - LLUIColor mDisabledColor; - LLUIColor mHighlightBackground; - LLUIColor mHighlightForeground; - - bool mHighlight; -private: - // Keyboard and mouse variables - bool mAllowKeyRepeat; - bool mGotHover; - - // If true, suppress normal space for check marks on the left and accelerator - // keys on the right. - bool mBriefItem; - - // Font for this item - const LLFontGL* mFont; - bool mDrawTextDisabled; - - KEY mJumpKey; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemSeparatorGL -// -// This class represents a separator. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLMenuItemSeparatorGL : public LLMenuItemGL -{ -public: - struct Params : public LLInitParam::Block - { - Optional on_visible; - Params(); - }; - - LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p = LLMenuItemSeparatorGL::Params()); - - /*virtual*/ void draw( void ); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - - virtual void buildDrawLabel(); - - /*virtual*/ U32 getNominalHeight( void ) const; - -private: - enable_signal_t mVisibleSignal; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemCallGL -// -// The LLMenuItemCallerGL represents a single menu item in a menu that -// calls a user defined callback. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuItemCallGL : public LLMenuItemGL -{ -public: - struct Params : public LLInitParam::Block - { - Optional on_enable; - Optional on_click; - Optional on_visible; - Params() - : on_enable("on_enable"), - on_click("on_click"), - on_visible("on_visible") - {} - }; -protected: - LLMenuItemCallGL(const Params&); - friend class LLUICtrlFactory; - void updateEnabled( void ); - void updateVisible( void ); - -public: - void initFromParams(const Params& p); - - // called to rebuild the draw label - virtual void buildDrawLabel( void ); - - virtual void onCommit( void ); - - virtual bool handleAcceleratorKey(KEY key, MASK mask); - virtual bool handleKeyHere(KEY key, MASK mask); - - //virtual void draw(); - - boost::signals2::connection setClickCallback( const commit_signal_t::slot_type& cb ) - { - return setCommitCallback(cb); - } - - boost::signals2::connection setEnableCallback( const enable_signal_t::slot_type& cb ) - { - return mEnableSignal.connect(cb); - } - -private: - enable_signal_t mEnableSignal; - enable_signal_t mVisibleSignal; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemCheckGL -// -// The LLMenuItemCheckGL is an extension of the LLMenuItemCallGL -// class, by allowing another method to be specified which determines -// if the menu item should consider itself checked as true or not. Be -// careful that the provided callback is fast - it needs to be VERY -// EFFICIENT because it may need to be checked a lot. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuItemCheckGL -: public LLMenuItemCallGL -{ -public: - struct Params : public LLInitParam::Block - { - Optional on_check; - Params() - : on_check("on_check") - {} - }; - -protected: - LLMenuItemCheckGL(const Params&); - friend class LLUICtrlFactory; -public: - - void initFromParams(const Params& p); - - virtual void onCommit( void ); - - virtual void setValue(const LLSD& value); - virtual LLSD getValue() const; - - // called to rebuild the draw label - virtual void buildDrawLabel( void ); - - boost::signals2::connection setCheckCallback( const enable_signal_t::slot_type& cb ) - { - return mCheckSignal.connect(cb); - } - -private: - enable_signal_t mCheckSignal; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuGL -// -// The Menu class represents a normal rectangular menu somewhere on -// screen. A Menu can have menu items (described above) or sub-menus -// attached to it. Sub-menus are implemented via a specialized -// menu-item type known as a branch. Since it's easy to do wrong, I've -// taken the branch functionality out of public view, and encapsulate -// it in the appendMenu() method. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -// child widget registry -struct MenuRegistry : public LLChildRegistry -{ - LLSINGLETON_EMPTY_CTOR(MenuRegistry); -}; - - -class LLMenuGL -: public LLUICtrl -{ -public: - struct Params : public LLInitParam::Block - { - Optional jump_key; - Optional horizontal_layout, - can_tear_off, - drop_shadow, - bg_visible, - create_jump_keys, - keep_fixed_size, - scrollable; - Optional max_scrollable_items; - Optional preferred_width; - Optional bg_color; - Optional shortcut_pad; - - Params() - : jump_key("jump_key", KEY_NONE), - horizontal_layout("horizontal_layout"), - can_tear_off("tear_off", false), - drop_shadow("drop_shadow", true), - bg_visible("bg_visible", true), - create_jump_keys("create_jump_keys", false), - keep_fixed_size("keep_fixed_size", false), - bg_color("bg_color", LLUIColorTable::instance().getColor( "MenuDefaultBgColor" )), - scrollable("scrollable", false), - max_scrollable_items("max_scrollable_items", U32_MAX), - preferred_width("preferred_width", U32_MAX), - shortcut_pad("shortcut_pad") - { - addSynonym(bg_visible, "opaque"); - addSynonym(bg_color, "color"); - addSynonym(can_tear_off, "can_tear_off"); - } - }; - - // my valid children are contained in MenuRegistry - typedef MenuRegistry child_registry_t; - - void initFromParams(const Params&); - - // textual artwork which menugl-imitators may want to match - static const std::string BOOLEAN_TRUE_PREFIX; - static const std::string BRANCH_SUFFIX; - static const std::string ARROW_UP; - static const std::string ARROW_DOWN; - - // for scrollable menus - typedef enum e_scrolling_direction - { - SD_UP = 0, - SD_DOWN = 1, - SD_BEGIN = 2, - SD_END = 3 - } EScrollingDirection; - -protected: - LLMenuGL(const LLMenuGL::Params& p); - friend class LLUICtrlFactory; - // let branching menu items use my protected traversal methods - friend class LLMenuItemBranchGL; -public: - virtual ~LLMenuGL( void ); - - void parseChildXML(LLXMLNodePtr child, LLView* parent); - - // LLView Functionality - /*virtual*/ bool handleUnicodeCharHere( llwchar uni_char ); - /*virtual*/ bool handleHover( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleScrollWheel( S32 x, S32 y, S32 clicks ); - /*virtual*/ void draw( void ); - /*virtual*/ void drawBackground(LLMenuItemGL* itemp, F32 alpha); - /*virtual*/ void setVisible(bool visible); - /*virtual*/ bool addChild(LLView* view, S32 tab_group = 0); - /*virtual*/ void deleteAllChildren(); - /*virtual*/ void removeChild( LLView* ctrl); - /*virtual*/ bool postBuild(); - - virtual bool hasAccelerator(const KEY &key, const MASK &mask) const; - virtual bool handleAcceleratorKey(KEY key, MASK mask); - - LLMenuGL* findChildMenuByName(const std::string& name, bool recurse) const; - - bool clearHoverItem(); - - // return the name label - const std::string& getLabel( void ) const { return mLabel.getString(); } - void setLabel(const LLStringExplicit& label) { mLabel = label; } - - // background colors - void setBackgroundColor( const LLUIColor& color ) { mBackgroundColor = color; } - const LLUIColor& getBackgroundColor() const { return mBackgroundColor; } - void setBackgroundVisible( bool b ) { mBgVisible = b; } - void setCanTearOff(bool tear_off); - - // add a separator to this menu - virtual bool addSeparator(); - - // for branching menu items, bring sub menus up to root level of menu hierarchy - virtual void updateParent( LLView* parentp ); - - // setItemEnabled() - pass the name and the enable flag for a - // menu item. true will make sure it's enabled, false will disable - // it. - void setItemEnabled( const std::string& name, bool enable ); - - // propagate message to submenus - void setEnabledSubMenus(bool enable); - - void setItemVisible( const std::string& name, bool visible); - - void setItemLabel(const std::string &name, const std::string &label); - - // sets the left,bottom corner of menu, useful for popups - void setLeftAndBottom(S32 left, S32 bottom); - - virtual bool handleJumpKey(KEY key); - - virtual bool jumpKeysActive(); - - virtual bool isOpen(); - - void needsArrange() { mNeedsArrange = true; } - // Shape this menu to fit the current state of the children, and - // adjust the child rects to fit. This is called automatically - // when you add items. *FIX: We may need to deal with visibility - // arrangement. - virtual void arrange( void ); - void arrangeAndClear( void ); - - // remove all items on the menu - void empty( void ); - - // erase group of items from menu - void erase( S32 begin, S32 end, bool arrange = true ); - - // add new item at position - void insert( S32 begin, LLView * ctrl, bool arrange = true ); - - void setItemLastSelected(LLMenuItemGL* item); // must be in menu - U32 getItemCount(); // number of menu items - LLMenuItemGL* getItem(S32 number); // 0 = first item - LLMenuItemGL* getItem(std::string name); - LLMenuItemGL* getHighlightedItem(); - - LLMenuItemGL* highlightNextItem(LLMenuItemGL* cur_item, bool skip_disabled = true); - LLMenuItemGL* highlightPrevItem(LLMenuItemGL* cur_item, bool skip_disabled = true); - - void buildDrawLabels(); - void createJumpKeys(); - - // Show popup at a specific location, in the spawn_view's coordinate frame - static void showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y, S32 mouse_x = 0, S32 mouse_y = 0); - - // Whether to drop shadow menu bar - void setDropShadowed( const bool shadowed ); - - void setParentMenuItem( LLMenuItemGL* parent_menu_item ) { mParentMenuItem = parent_menu_item->getHandle(); } - LLMenuItemGL* getParentMenuItem() const { return dynamic_cast(mParentMenuItem.get()); } - - void setTornOff(bool torn_off); - bool getTornOff() { return mTornOff; } - - bool getCanTearOff() { return mTearOffItem != NULL; } - - KEY getJumpKey() const { return mJumpKey; } - void setJumpKey(KEY key) { mJumpKey = key; } - - static void setKeyboardMode(bool mode) { sKeyboardMode = mode; } - static bool getKeyboardMode() { return sKeyboardMode; } - - S32 getShortcutPad() { return mShortcutPad; } - - bool scrollItems(EScrollingDirection direction); - bool isScrollable() const { return mScrollable; } - - static class LLMenuHolderGL* sMenuContainer; - - void resetScrollPositionOnShow(bool reset_scroll_pos) { mResetScrollPositionOnShow = reset_scroll_pos; } - bool isScrollPositionOnShowReset() { return mResetScrollPositionOnShow; } - - void setAlwaysShowMenu(bool show) { mAlwaysShowMenu = show; } - bool getAlwaysShowMenu() { return mAlwaysShowMenu; } - - // add a context menu branch - bool appendContextSubMenu(LLMenuGL *menu); - - const LLFontGL *getFont() const { return mFont; } - - protected: - void createSpilloverBranch(); - void cleanupSpilloverBranch(); - // Add the menu item to this menu. - virtual bool append( LLMenuItemGL* item ); - - // add a menu - this will create a cascading menu - virtual bool appendMenu( LLMenuGL* menu ); - - // Used in LLContextMenu and in LLTogleableMenu - // to add an item of context menu branch - bool addContextChild(LLView* view, S32 tab_group); - - // TODO: create accessor methods for these? - typedef std::list< LLMenuItemGL* > item_list_t; - item_list_t mItems; - LLMenuItemGL*mFirstVisibleItem; - LLMenuItemGL *mArrowUpItem, *mArrowDownItem; - - typedef std::map navigation_key_map_t; - navigation_key_map_t mJumpKeys; - S32 mLastMouseX; - S32 mLastMouseY; - S32 mMouseVelX; - S32 mMouseVelY; - U32 mMaxScrollableItems; - U32 mPreferredWidth; - bool mHorizontalLayout; - bool mScrollable; - bool mKeepFixedSize; - bool mNeedsArrange; - - // Font for top menu items only - const LLFontGL* mFont; - -private: - - - static LLColor4 sDefaultBackgroundColor; - static bool sKeyboardMode; - - bool mAlwaysShowMenu; - - LLUIColor mBackgroundColor; - bool mBgVisible; - LLHandle mParentMenuItem; - LLUIString mLabel; - bool mDropShadowed; // Whether to drop shadow - bool mHasSelection; - LLFrameTimer mFadeTimer; - LLTimer mScrollItemsTimer; - bool mTornOff; - class LLMenuItemTearOffGL* mTearOffItem; - class LLMenuItemBranchGL* mSpilloverBranch; - LLMenuGL* mSpilloverMenu; - KEY mJumpKey; - bool mCreateJumpKeys; - S32 mShortcutPad; - bool mResetScrollPositionOnShow; -}; // end class LLMenuGL - - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemBranchGL -// -// The LLMenuItemBranchGL represents a menu item that has a -// sub-menu. This is used to make cascading menus. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuItemBranchGL : public LLMenuItemGL -{ -public: - struct Params : public LLInitParam::Block - { - Optional branch; - }; - -protected: - LLMenuItemBranchGL(const Params&); - friend class LLUICtrlFactory; -public: - virtual ~LLMenuItemBranchGL(); - - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - - virtual bool hasAccelerator(const KEY &key, const MASK &mask) const; - virtual bool handleAcceleratorKey(KEY key, MASK mask); - - // check if we've used these accelerators already - virtual bool addToAcceleratorList(std::list *listp); - - // called to rebuild the draw label - virtual void buildDrawLabel( void ); - - virtual void onCommit( void ); - - virtual bool handleKey(KEY key, MASK mask, bool called_from_parent); - virtual bool handleUnicodeChar(llwchar uni_char, bool called_from_parent); - - // set the hover status (called by it's menu) and if the object is - // active. This is used for behavior transfer. - virtual void setHighlight( bool highlight ); - - virtual bool handleKeyHere(KEY key, MASK mask); - - virtual bool isActive() const; - - virtual bool isOpen() const; - - LLMenuGL* getBranch() const { return (LLMenuGL*)mBranchHandle.get(); } - - virtual void updateBranchParent( LLView* parentp ); - - // LLView Functionality - virtual void onVisibilityChange( bool curVisibilityIn ); - - virtual void draw(); - - virtual void setEnabledSubMenus(bool enabled) { if (getBranch()) getBranch()->setEnabledSubMenus(enabled); } - - virtual void openMenu(); - - virtual LLView* getChildView(const std::string& name, bool recurse = true) const; - virtual LLView* findChildView(const std::string& name, bool recurse = true) const; - -private: - LLHandle mBranchHandle; -}; // end class LLMenuItemBranchGL - - -//----------------------------------------------------------------------------- -// class LLContextMenu -// A context menu -//----------------------------------------------------------------------------- - -class LLContextMenu -: public LLMenuGL -{ -public: - struct Params : public LLInitParam::Block - { - Params() - { - changeDefault(visible, false); - } - }; - -protected: - LLContextMenu(const Params& p); - friend class LLUICtrlFactory; - -public: - virtual ~LLContextMenu() {} - - // LLView Functionality - // can't set visibility directly, must call show or hide - virtual void setVisible (bool visible); - - virtual void show (S32 x, S32 y, LLView* spawning_view = NULL); - virtual void hide (); - - virtual bool handleHover ( S32 x, S32 y, MASK mask ); - virtual bool handleRightMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleRightMouseUp ( S32 x, S32 y, MASK mask ); - - virtual bool addChild (LLView* view, S32 tab_group = 0); - - LLHandle getHandle() { return getDerivedHandle(); } - - LLView* getSpawningView() const { return mSpawningViewHandle.get(); } - void setSpawningView(LLHandle spawning_view) { mSpawningViewHandle = spawning_view; } - -protected: - bool mHoveredAnyItem; - LLMenuItemGL* mHoverItem; - LLRootHandle mHandle; - LLHandle mSpawningViewHandle; -}; - -//----------------------------------------------------------------------------- -// class LLContextMenuBranch -// A branch to another context menu -//----------------------------------------------------------------------------- -class LLContextMenuBranch : public LLMenuItemGL -{ -public: - struct Params : public LLInitParam::Block - { - Mandatory branch; - }; - - LLContextMenuBranch(const Params&); - - virtual ~LLContextMenuBranch(); - - // called to rebuild the draw label - virtual void buildDrawLabel( void ); - - // onCommit() - do the primary funcationality of the menu item. - virtual void onCommit( void ); - - LLContextMenu* getBranch() { return mBranch.get(); } - void setHighlight( bool highlight ); - -protected: - void showSubMenu(); - - LLHandle mBranch; -}; - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuBarGL -// -// A menu bar displays menus horizontally. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuBarGL : public LLMenuGL -{ -public: - struct Params : public LLInitParam::Block - {}; - LLMenuBarGL( const Params& p ); - virtual ~LLMenuBarGL(); - - /*virtual*/ bool handleAcceleratorKey(KEY key, MASK mask); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - /*virtual*/ bool handleJumpKey(KEY key); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - - /*virtual*/ void draw(); - /*virtual*/ bool jumpKeysActive(); - - // add a vertical separator to this menu - virtual bool addSeparator(); - - // LLView Functionality - virtual bool handleHover( S32 x, S32 y, MASK mask ); - - // Returns x position of rightmost child, usually Help menu - S32 getRightmostMenuEdge(); - - void resetMenuTrigger() { mAltKeyTrigger = false; } - -private: - // add a menu - this will create a drop down menu. - virtual bool appendMenu( LLMenuGL* menu ); - // rearrange the child rects so they fit the shape of the menu - // bar. - virtual void arrange( void ); - - void checkMenuTrigger(); - - std::list mAccelerators; - bool mAltKeyTrigger; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuHolderGL -// -// High level view that serves as parent for all menus -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLMenuHolderGL : public LLPanel -{ -public: - struct Params : public LLInitParam::Block - {}; - LLMenuHolderGL(const Params& p); - virtual ~LLMenuHolderGL() {} - - virtual bool hideMenus(); - void reshape(S32 width, S32 height, bool called_from_parent = true); - void setCanHide(bool can_hide) { mCanHide = can_hide; } - - // LLView functionality - virtual void draw(); - virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleRightMouseDown( S32 x, S32 y, MASK mask ); - - // Close context menus on right mouse up not handled by menus. - /*virtual*/ bool handleRightMouseUp( S32 x, S32 y, MASK mask ); - - virtual bool handleKey(KEY key, MASK mask, bool called_from_parent); - virtual const LLRect getMenuRect() const { return getLocalRect(); } - LLView*const getVisibleMenu() const; - virtual bool hasVisibleMenu() const {return getVisibleMenu() != NULL;} - - static void setActivatedItem(LLMenuItemGL* item); - - // Need to detect if mouse-up after context menu spawn has moved. - // If not, need to keep the menu up. - static LLCoordGL sContextMenuSpawnPos; - -private: - static LLHandle sItemLastSelectedHandle; - static LLFrameTimer sItemActivationTimer; - - bool mCanHide; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLTearOffMenu -// -// Floater that hosts a menu -// https://wiki.lindenlab.com/mediawiki/index.php?title=LLTearOffMenu&oldid=81344 -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLTearOffMenu : public LLFloater -{ -public: - static LLTearOffMenu* create(LLMenuGL* menup); - virtual ~LLTearOffMenu(); - - virtual void draw(void); - virtual void onFocusReceived(); - virtual void onFocusLost(); - virtual bool handleUnicodeChar(llwchar uni_char, bool called_from_parent); - virtual bool handleKeyHere(KEY key, MASK mask); - virtual void translate(S32 x, S32 y); - - void updateSize(); - -private: - LLTearOffMenu(LLMenuGL* menup); - - void closeTearOff(); - - LLView* mOldParent; - LLMenuGL* mMenu; - S32 mTargetHeight; - bool mQuitRequested; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemTearOffGL -// -// This class represents a separator. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuItemTearOffGL : public LLMenuItemGL -{ -public: - struct Params : public LLInitParam::Block - {}; - - LLMenuItemTearOffGL( const Params& ); - - virtual void onCommit(void); - virtual void draw(void); - virtual U32 getNominalHeight() const; - - LLFloater* getParentFloater(); -}; - - -// *TODO: this is currently working, so finish implementation -class LLEditMenuHandlerMgr -{ -public: - LLEditMenuHandlerMgr& getInstance() { - static LLEditMenuHandlerMgr instance; - return instance; - } - virtual ~LLEditMenuHandlerMgr() {} -private: - LLEditMenuHandlerMgr() {}; -}; - - -// *TODO: Eliminate -// For backwards compatability only; generally just use boost::bind -class view_listener_t : public boost::signals2::trackable -{ -public: - virtual bool handleEvent(const LLSD& userdata) = 0; - view_listener_t() { sListeners.insert(this); } - virtual ~view_listener_t() { sListeners.erase(this); } - - static void addEnable(view_listener_t* listener, const std::string& name) - { - LLUICtrl::EnableCallbackRegistry::currentRegistrar().add(name, boost::bind(&view_listener_t::handleEvent, listener, _2)); - } - - static void addCommit(view_listener_t* listener, const std::string& name) - { - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add(name, boost::bind(&view_listener_t::handleEvent, listener, _2)); - } - - static void addMenu(view_listener_t* listener, const std::string& name) - { - // For now, add to both click and enable registries - addEnable(listener, name); - addCommit(listener, name); - } - - static void cleanup() - { - listener_vector_t listeners(sListeners.begin(), sListeners.end()); - sListeners.clear(); - - std::for_each(listeners.begin(), listeners.end(), DeletePointer()); - listeners.clear(); - } - -private: - typedef std::set listener_map_t; - typedef std::vector listener_vector_t; - static listener_map_t sListeners; -}; - -#endif // LL_LLMENUGL_H +/** + * @file llmenugl.h + * @brief Declaration of the opengl based menu system. + * + * $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$ + */ + +#ifndef LL_LLMENUGL_H +#define LL_LLMENUGL_H + +#include + +#include "llstring.h" +#include "v4color.h" +#include "llframetimer.h" + +#include "llkeyboard.h" +#include "llfloater.h" +#include "lluistring.h" +#include "llview.h" +#include + +extern S32 MENU_BAR_HEIGHT; +extern S32 MENU_BAR_WIDTH; + +class LLMenuKeyboardBinding +{ +public: + KEY mKey; + MASK mMask; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemGL +// +// The LLMenuItemGL represents a single menu item in a menu. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemGL: public LLUICtrl, public ll::ui::SearchableControl +{ +public: + struct Params : public LLInitParam::Block + { + Optional shortcut; + Optional jump_key; + Optional use_mac_ctrl, + allow_key_repeat; + + Ignored rect, + left, + top, + right, + bottom, + width, + height, + bottom_delta, + left_delta; + + Optional enabled_color, + disabled_color, + highlight_bg_color, + highlight_fg_color; + + + Params(); + }; + +protected: + LLMenuItemGL(const Params&); + friend class LLUICtrlFactory; +public: + // LLView overrides + /*virtual*/ void onVisibilityChange(bool new_visibility); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask); + + // LLUICtrl overrides + /*virtual*/ void setValue(const LLSD& value); + /*virtual*/ LLSD getValue() const; + + virtual bool hasAccelerator(const KEY &key, const MASK &mask) const; + virtual bool handleAcceleratorKey(KEY key, MASK mask); + + LLColor4 getHighlightBgColor() { return mHighlightBackground.get(); } + + void setJumpKey(KEY key); + KEY getJumpKey() const { return mJumpKey; } + + // set the font used by this item. + void setFont(const LLFontGL* font) { mFont = font; } + const LLFontGL* getFont() const { return mFont; } + + // returns the height in pixels for the current font. + virtual U32 getNominalHeight( void ) const; + + // Marks item as not needing space for check marks or accelerator keys + virtual void setBriefItem(bool brief); + virtual bool isBriefItem() const; + + virtual bool addToAcceleratorList(std::list *listp); + void setAllowKeyRepeat(bool allow) { mAllowKeyRepeat = allow; } + bool getAllowKeyRepeat() const { return mAllowKeyRepeat; } + + // change the label + void setLabel( const LLStringExplicit& label ) { mLabel = label; } + std::string getLabel( void ) const { return mLabel.getString(); } + virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); + + // Get the parent menu for this item + virtual class LLMenuGL* getMenu() const; + + // returns the normal width of this control in pixels - this is + // used for calculating the widest item, as well as for horizontal + // arrangement. + virtual U32 getNominalWidth( void ) const; + + // buildDrawLabel() - constructs the string used during the draw() + // function. This reduces the overall string manipulation, but can + // lead to visual errors if the state of the object changes + // without the knowledge of the menu item. For example, if a + // boolean being watched is changed outside of the menu item's + // onCommit() function, the draw buffer will not be updated and will + // reflect the wrong value. If this ever becomes an issue, there + // are ways to fix this. + // Returns the enabled state of the item. + virtual void buildDrawLabel( void ); + + // for branching menu items, bring sub menus up to root level of menu hierarchy + virtual void updateBranchParent( LLView* parentp ){}; + + virtual void onCommit( void ); + + virtual void setHighlight( bool highlight ); + virtual bool getHighlight() const { return mHighlight; } + + // determine if this represents an active sub-menu + virtual bool isActive( void ) const { return false; } + + // determine if this represents an open sub-menu + virtual bool isOpen( void ) const { return false; } + + virtual void setEnabledSubMenus(bool enable){}; + + // LLView Functionality + virtual bool handleKeyHere( KEY key, MASK mask ); + virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); + virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); + virtual bool handleScrollWheel( S32 x, S32 y, S32 clicks ); + + virtual void onMouseEnter(S32 x, S32 y, MASK mask); + virtual void onMouseLeave(S32 x, S32 y, MASK mask); + + virtual void draw( void ); + + bool getHover() const { return mGotHover; } + + void setDrawTextDisabled(bool disabled) { mDrawTextDisabled = disabled; } + bool getDrawTextDisabled() const { return mDrawTextDisabled; } + +protected: + void setHover(bool hover) { mGotHover = hover; } + + // This function appends the character string representation of + // the current accelerator key and mask to the provided string. + void appendAcceleratorString( std::string& st ) const; + + virtual std::string _getSearchText() const + { + return mLabel.getString(); + } + +protected: + KEY mAcceleratorKey; + MASK mAcceleratorMask; + // mLabel contains the actual label specified by the user. + LLUIString mLabel; + + // The draw labels contain some of the labels that we draw during + // the draw() routine. This optimizes away some of the string + // manipulation. + LLUIString mDrawBoolLabel; + LLUIString mDrawAccelLabel; + LLUIString mDrawBranchLabel; + + LLUIColor mEnabledColor; + LLUIColor mDisabledColor; + LLUIColor mHighlightBackground; + LLUIColor mHighlightForeground; + + bool mHighlight; +private: + // Keyboard and mouse variables + bool mAllowKeyRepeat; + bool mGotHover; + + // If true, suppress normal space for check marks on the left and accelerator + // keys on the right. + bool mBriefItem; + + // Font for this item + const LLFontGL* mFont; + bool mDrawTextDisabled; + + KEY mJumpKey; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemSeparatorGL +// +// This class represents a separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLMenuItemSeparatorGL : public LLMenuItemGL +{ +public: + struct Params : public LLInitParam::Block + { + Optional on_visible; + Params(); + }; + + LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p = LLMenuItemSeparatorGL::Params()); + + /*virtual*/ void draw( void ); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + + virtual void buildDrawLabel(); + + /*virtual*/ U32 getNominalHeight( void ) const; + +private: + enable_signal_t mVisibleSignal; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemCallGL +// +// The LLMenuItemCallerGL represents a single menu item in a menu that +// calls a user defined callback. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemCallGL : public LLMenuItemGL +{ +public: + struct Params : public LLInitParam::Block + { + Optional on_enable; + Optional on_click; + Optional on_visible; + Params() + : on_enable("on_enable"), + on_click("on_click"), + on_visible("on_visible") + {} + }; +protected: + LLMenuItemCallGL(const Params&); + friend class LLUICtrlFactory; + void updateEnabled( void ); + void updateVisible( void ); + +public: + void initFromParams(const Params& p); + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + virtual void onCommit( void ); + + virtual bool handleAcceleratorKey(KEY key, MASK mask); + virtual bool handleKeyHere(KEY key, MASK mask); + + //virtual void draw(); + + boost::signals2::connection setClickCallback( const commit_signal_t::slot_type& cb ) + { + return setCommitCallback(cb); + } + + boost::signals2::connection setEnableCallback( const enable_signal_t::slot_type& cb ) + { + return mEnableSignal.connect(cb); + } + +private: + enable_signal_t mEnableSignal; + enable_signal_t mVisibleSignal; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemCheckGL +// +// The LLMenuItemCheckGL is an extension of the LLMenuItemCallGL +// class, by allowing another method to be specified which determines +// if the menu item should consider itself checked as true or not. Be +// careful that the provided callback is fast - it needs to be VERY +// EFFICIENT because it may need to be checked a lot. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemCheckGL +: public LLMenuItemCallGL +{ +public: + struct Params : public LLInitParam::Block + { + Optional on_check; + Params() + : on_check("on_check") + {} + }; + +protected: + LLMenuItemCheckGL(const Params&); + friend class LLUICtrlFactory; +public: + + void initFromParams(const Params& p); + + virtual void onCommit( void ); + + virtual void setValue(const LLSD& value); + virtual LLSD getValue() const; + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + boost::signals2::connection setCheckCallback( const enable_signal_t::slot_type& cb ) + { + return mCheckSignal.connect(cb); + } + +private: + enable_signal_t mCheckSignal; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuGL +// +// The Menu class represents a normal rectangular menu somewhere on +// screen. A Menu can have menu items (described above) or sub-menus +// attached to it. Sub-menus are implemented via a specialized +// menu-item type known as a branch. Since it's easy to do wrong, I've +// taken the branch functionality out of public view, and encapsulate +// it in the appendMenu() method. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +// child widget registry +struct MenuRegistry : public LLChildRegistry +{ + LLSINGLETON_EMPTY_CTOR(MenuRegistry); +}; + + +class LLMenuGL +: public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block + { + Optional jump_key; + Optional horizontal_layout, + can_tear_off, + drop_shadow, + bg_visible, + create_jump_keys, + keep_fixed_size, + scrollable; + Optional max_scrollable_items; + Optional preferred_width; + Optional bg_color; + Optional shortcut_pad; + + Params() + : jump_key("jump_key", KEY_NONE), + horizontal_layout("horizontal_layout"), + can_tear_off("tear_off", false), + drop_shadow("drop_shadow", true), + bg_visible("bg_visible", true), + create_jump_keys("create_jump_keys", false), + keep_fixed_size("keep_fixed_size", false), + bg_color("bg_color", LLUIColorTable::instance().getColor( "MenuDefaultBgColor" )), + scrollable("scrollable", false), + max_scrollable_items("max_scrollable_items", U32_MAX), + preferred_width("preferred_width", U32_MAX), + shortcut_pad("shortcut_pad") + { + addSynonym(bg_visible, "opaque"); + addSynonym(bg_color, "color"); + addSynonym(can_tear_off, "can_tear_off"); + } + }; + + // my valid children are contained in MenuRegistry + typedef MenuRegistry child_registry_t; + + void initFromParams(const Params&); + + // textual artwork which menugl-imitators may want to match + static const std::string BOOLEAN_TRUE_PREFIX; + static const std::string BRANCH_SUFFIX; + static const std::string ARROW_UP; + static const std::string ARROW_DOWN; + + // for scrollable menus + typedef enum e_scrolling_direction + { + SD_UP = 0, + SD_DOWN = 1, + SD_BEGIN = 2, + SD_END = 3 + } EScrollingDirection; + +protected: + LLMenuGL(const LLMenuGL::Params& p); + friend class LLUICtrlFactory; + // let branching menu items use my protected traversal methods + friend class LLMenuItemBranchGL; +public: + virtual ~LLMenuGL( void ); + + void parseChildXML(LLXMLNodePtr child, LLView* parent); + + // LLView Functionality + /*virtual*/ bool handleUnicodeCharHere( llwchar uni_char ); + /*virtual*/ bool handleHover( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleScrollWheel( S32 x, S32 y, S32 clicks ); + /*virtual*/ void draw( void ); + /*virtual*/ void drawBackground(LLMenuItemGL* itemp, F32 alpha); + /*virtual*/ void setVisible(bool visible); + /*virtual*/ bool addChild(LLView* view, S32 tab_group = 0); + /*virtual*/ void deleteAllChildren(); + /*virtual*/ void removeChild( LLView* ctrl); + /*virtual*/ bool postBuild(); + + virtual bool hasAccelerator(const KEY &key, const MASK &mask) const; + virtual bool handleAcceleratorKey(KEY key, MASK mask); + + LLMenuGL* findChildMenuByName(const std::string& name, bool recurse) const; + + bool clearHoverItem(); + + // return the name label + const std::string& getLabel( void ) const { return mLabel.getString(); } + void setLabel(const LLStringExplicit& label) { mLabel = label; } + + // background colors + void setBackgroundColor( const LLUIColor& color ) { mBackgroundColor = color; } + const LLUIColor& getBackgroundColor() const { return mBackgroundColor; } + void setBackgroundVisible( bool b ) { mBgVisible = b; } + void setCanTearOff(bool tear_off); + + // add a separator to this menu + virtual bool addSeparator(); + + // for branching menu items, bring sub menus up to root level of menu hierarchy + virtual void updateParent( LLView* parentp ); + + // setItemEnabled() - pass the name and the enable flag for a + // menu item. true will make sure it's enabled, false will disable + // it. + void setItemEnabled( const std::string& name, bool enable ); + + // propagate message to submenus + void setEnabledSubMenus(bool enable); + + void setItemVisible( const std::string& name, bool visible); + + void setItemLabel(const std::string &name, const std::string &label); + + // sets the left,bottom corner of menu, useful for popups + void setLeftAndBottom(S32 left, S32 bottom); + + virtual bool handleJumpKey(KEY key); + + virtual bool jumpKeysActive(); + + virtual bool isOpen(); + + void needsArrange() { mNeedsArrange = true; } + // Shape this menu to fit the current state of the children, and + // adjust the child rects to fit. This is called automatically + // when you add items. *FIX: We may need to deal with visibility + // arrangement. + virtual void arrange( void ); + void arrangeAndClear( void ); + + // remove all items on the menu + void empty( void ); + + // erase group of items from menu + void erase( S32 begin, S32 end, bool arrange = true ); + + // add new item at position + void insert( S32 begin, LLView * ctrl, bool arrange = true ); + + void setItemLastSelected(LLMenuItemGL* item); // must be in menu + U32 getItemCount(); // number of menu items + LLMenuItemGL* getItem(S32 number); // 0 = first item + LLMenuItemGL* getItem(std::string name); + LLMenuItemGL* getHighlightedItem(); + + LLMenuItemGL* highlightNextItem(LLMenuItemGL* cur_item, bool skip_disabled = true); + LLMenuItemGL* highlightPrevItem(LLMenuItemGL* cur_item, bool skip_disabled = true); + + void buildDrawLabels(); + void createJumpKeys(); + + // Show popup at a specific location, in the spawn_view's coordinate frame + static void showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y, S32 mouse_x = 0, S32 mouse_y = 0); + + // Whether to drop shadow menu bar + void setDropShadowed( const bool shadowed ); + + void setParentMenuItem( LLMenuItemGL* parent_menu_item ) { mParentMenuItem = parent_menu_item->getHandle(); } + LLMenuItemGL* getParentMenuItem() const { return dynamic_cast(mParentMenuItem.get()); } + + void setTornOff(bool torn_off); + bool getTornOff() { return mTornOff; } + + bool getCanTearOff() { return mTearOffItem != NULL; } + + KEY getJumpKey() const { return mJumpKey; } + void setJumpKey(KEY key) { mJumpKey = key; } + + static void setKeyboardMode(bool mode) { sKeyboardMode = mode; } + static bool getKeyboardMode() { return sKeyboardMode; } + + S32 getShortcutPad() { return mShortcutPad; } + + bool scrollItems(EScrollingDirection direction); + bool isScrollable() const { return mScrollable; } + + static class LLMenuHolderGL* sMenuContainer; + + void resetScrollPositionOnShow(bool reset_scroll_pos) { mResetScrollPositionOnShow = reset_scroll_pos; } + bool isScrollPositionOnShowReset() { return mResetScrollPositionOnShow; } + + void setAlwaysShowMenu(bool show) { mAlwaysShowMenu = show; } + bool getAlwaysShowMenu() { return mAlwaysShowMenu; } + + // add a context menu branch + bool appendContextSubMenu(LLMenuGL *menu); + + const LLFontGL *getFont() const { return mFont; } + + protected: + void createSpilloverBranch(); + void cleanupSpilloverBranch(); + // Add the menu item to this menu. + virtual bool append( LLMenuItemGL* item ); + + // add a menu - this will create a cascading menu + virtual bool appendMenu( LLMenuGL* menu ); + + // Used in LLContextMenu and in LLTogleableMenu + // to add an item of context menu branch + bool addContextChild(LLView* view, S32 tab_group); + + // TODO: create accessor methods for these? + typedef std::list< LLMenuItemGL* > item_list_t; + item_list_t mItems; + LLMenuItemGL*mFirstVisibleItem; + LLMenuItemGL *mArrowUpItem, *mArrowDownItem; + + typedef std::map navigation_key_map_t; + navigation_key_map_t mJumpKeys; + S32 mLastMouseX; + S32 mLastMouseY; + S32 mMouseVelX; + S32 mMouseVelY; + U32 mMaxScrollableItems; + U32 mPreferredWidth; + bool mHorizontalLayout; + bool mScrollable; + bool mKeepFixedSize; + bool mNeedsArrange; + + // Font for top menu items only + const LLFontGL* mFont; + +private: + + + static LLColor4 sDefaultBackgroundColor; + static bool sKeyboardMode; + + bool mAlwaysShowMenu; + + LLUIColor mBackgroundColor; + bool mBgVisible; + LLHandle mParentMenuItem; + LLUIString mLabel; + bool mDropShadowed; // Whether to drop shadow + bool mHasSelection; + LLFrameTimer mFadeTimer; + LLTimer mScrollItemsTimer; + bool mTornOff; + class LLMenuItemTearOffGL* mTearOffItem; + class LLMenuItemBranchGL* mSpilloverBranch; + LLMenuGL* mSpilloverMenu; + KEY mJumpKey; + bool mCreateJumpKeys; + S32 mShortcutPad; + bool mResetScrollPositionOnShow; +}; // end class LLMenuGL + + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemBranchGL +// +// The LLMenuItemBranchGL represents a menu item that has a +// sub-menu. This is used to make cascading menus. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemBranchGL : public LLMenuItemGL +{ +public: + struct Params : public LLInitParam::Block + { + Optional branch; + }; + +protected: + LLMenuItemBranchGL(const Params&); + friend class LLUICtrlFactory; +public: + virtual ~LLMenuItemBranchGL(); + + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + + virtual bool hasAccelerator(const KEY &key, const MASK &mask) const; + virtual bool handleAcceleratorKey(KEY key, MASK mask); + + // check if we've used these accelerators already + virtual bool addToAcceleratorList(std::list *listp); + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + virtual void onCommit( void ); + + virtual bool handleKey(KEY key, MASK mask, bool called_from_parent); + virtual bool handleUnicodeChar(llwchar uni_char, bool called_from_parent); + + // set the hover status (called by it's menu) and if the object is + // active. This is used for behavior transfer. + virtual void setHighlight( bool highlight ); + + virtual bool handleKeyHere(KEY key, MASK mask); + + virtual bool isActive() const; + + virtual bool isOpen() const; + + LLMenuGL* getBranch() const { return (LLMenuGL*)mBranchHandle.get(); } + + virtual void updateBranchParent( LLView* parentp ); + + // LLView Functionality + virtual void onVisibilityChange( bool curVisibilityIn ); + + virtual void draw(); + + virtual void setEnabledSubMenus(bool enabled) { if (getBranch()) getBranch()->setEnabledSubMenus(enabled); } + + virtual void openMenu(); + + virtual LLView* getChildView(const std::string& name, bool recurse = true) const; + virtual LLView* findChildView(const std::string& name, bool recurse = true) const; + +private: + LLHandle mBranchHandle; +}; // end class LLMenuItemBranchGL + + +//----------------------------------------------------------------------------- +// class LLContextMenu +// A context menu +//----------------------------------------------------------------------------- + +class LLContextMenu +: public LLMenuGL +{ +public: + struct Params : public LLInitParam::Block + { + Params() + { + changeDefault(visible, false); + } + }; + +protected: + LLContextMenu(const Params& p); + friend class LLUICtrlFactory; + +public: + virtual ~LLContextMenu() {} + + // LLView Functionality + // can't set visibility directly, must call show or hide + virtual void setVisible (bool visible); + + virtual void show (S32 x, S32 y, LLView* spawning_view = NULL); + virtual void hide (); + + virtual bool handleHover ( S32 x, S32 y, MASK mask ); + virtual bool handleRightMouseDown( S32 x, S32 y, MASK mask ); + virtual bool handleRightMouseUp ( S32 x, S32 y, MASK mask ); + + virtual bool addChild (LLView* view, S32 tab_group = 0); + + LLHandle getHandle() { return getDerivedHandle(); } + + LLView* getSpawningView() const { return mSpawningViewHandle.get(); } + void setSpawningView(LLHandle spawning_view) { mSpawningViewHandle = spawning_view; } + +protected: + bool mHoveredAnyItem; + LLMenuItemGL* mHoverItem; + LLRootHandle mHandle; + LLHandle mSpawningViewHandle; +}; + +//----------------------------------------------------------------------------- +// class LLContextMenuBranch +// A branch to another context menu +//----------------------------------------------------------------------------- +class LLContextMenuBranch : public LLMenuItemGL +{ +public: + struct Params : public LLInitParam::Block + { + Mandatory branch; + }; + + LLContextMenuBranch(const Params&); + + virtual ~LLContextMenuBranch(); + + // called to rebuild the draw label + virtual void buildDrawLabel( void ); + + // onCommit() - do the primary funcationality of the menu item. + virtual void onCommit( void ); + + LLContextMenu* getBranch() { return mBranch.get(); } + void setHighlight( bool highlight ); + +protected: + void showSubMenu(); + + LLHandle mBranch; +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuBarGL +// +// A menu bar displays menus horizontally. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuBarGL : public LLMenuGL +{ +public: + struct Params : public LLInitParam::Block + {}; + LLMenuBarGL( const Params& p ); + virtual ~LLMenuBarGL(); + + /*virtual*/ bool handleAcceleratorKey(KEY key, MASK mask); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + /*virtual*/ bool handleJumpKey(KEY key); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + + /*virtual*/ void draw(); + /*virtual*/ bool jumpKeysActive(); + + // add a vertical separator to this menu + virtual bool addSeparator(); + + // LLView Functionality + virtual bool handleHover( S32 x, S32 y, MASK mask ); + + // Returns x position of rightmost child, usually Help menu + S32 getRightmostMenuEdge(); + + void resetMenuTrigger() { mAltKeyTrigger = false; } + +private: + // add a menu - this will create a drop down menu. + virtual bool appendMenu( LLMenuGL* menu ); + // rearrange the child rects so they fit the shape of the menu + // bar. + virtual void arrange( void ); + + void checkMenuTrigger(); + + std::list mAccelerators; + bool mAltKeyTrigger; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuHolderGL +// +// High level view that serves as parent for all menus +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLMenuHolderGL : public LLPanel +{ +public: + struct Params : public LLInitParam::Block + {}; + LLMenuHolderGL(const Params& p); + virtual ~LLMenuHolderGL() {} + + virtual bool hideMenus(); + void reshape(S32 width, S32 height, bool called_from_parent = true); + void setCanHide(bool can_hide) { mCanHide = can_hide; } + + // LLView functionality + virtual void draw(); + virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); + virtual bool handleRightMouseDown( S32 x, S32 y, MASK mask ); + + // Close context menus on right mouse up not handled by menus. + /*virtual*/ bool handleRightMouseUp( S32 x, S32 y, MASK mask ); + + virtual bool handleKey(KEY key, MASK mask, bool called_from_parent); + virtual const LLRect getMenuRect() const { return getLocalRect(); } + LLView*const getVisibleMenu() const; + virtual bool hasVisibleMenu() const {return getVisibleMenu() != NULL;} + + static void setActivatedItem(LLMenuItemGL* item); + + // Need to detect if mouse-up after context menu spawn has moved. + // If not, need to keep the menu up. + static LLCoordGL sContextMenuSpawnPos; + +private: + static LLHandle sItemLastSelectedHandle; + static LLFrameTimer sItemActivationTimer; + + bool mCanHide; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLTearOffMenu +// +// Floater that hosts a menu +// https://wiki.lindenlab.com/mediawiki/index.php?title=LLTearOffMenu&oldid=81344 +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLTearOffMenu : public LLFloater +{ +public: + static LLTearOffMenu* create(LLMenuGL* menup); + virtual ~LLTearOffMenu(); + + virtual void draw(void); + virtual void onFocusReceived(); + virtual void onFocusLost(); + virtual bool handleUnicodeChar(llwchar uni_char, bool called_from_parent); + virtual bool handleKeyHere(KEY key, MASK mask); + virtual void translate(S32 x, S32 y); + + void updateSize(); + +private: + LLTearOffMenu(LLMenuGL* menup); + + void closeTearOff(); + + LLView* mOldParent; + LLMenuGL* mMenu; + S32 mTargetHeight; + bool mQuitRequested; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemTearOffGL +// +// This class represents a separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMenuItemTearOffGL : public LLMenuItemGL +{ +public: + struct Params : public LLInitParam::Block + {}; + + LLMenuItemTearOffGL( const Params& ); + + virtual void onCommit(void); + virtual void draw(void); + virtual U32 getNominalHeight() const; + + LLFloater* getParentFloater(); +}; + + +// *TODO: this is currently working, so finish implementation +class LLEditMenuHandlerMgr +{ +public: + LLEditMenuHandlerMgr& getInstance() { + static LLEditMenuHandlerMgr instance; + return instance; + } + virtual ~LLEditMenuHandlerMgr() {} +private: + LLEditMenuHandlerMgr() {}; +}; + + +// *TODO: Eliminate +// For backwards compatability only; generally just use boost::bind +class view_listener_t : public boost::signals2::trackable +{ +public: + virtual bool handleEvent(const LLSD& userdata) = 0; + view_listener_t() { sListeners.insert(this); } + virtual ~view_listener_t() { sListeners.erase(this); } + + static void addEnable(view_listener_t* listener, const std::string& name) + { + LLUICtrl::EnableCallbackRegistry::currentRegistrar().add(name, boost::bind(&view_listener_t::handleEvent, listener, _2)); + } + + static void addCommit(view_listener_t* listener, const std::string& name) + { + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add(name, boost::bind(&view_listener_t::handleEvent, listener, _2)); + } + + static void addMenu(view_listener_t* listener, const std::string& name) + { + // For now, add to both click and enable registries + addEnable(listener, name); + addCommit(listener, name); + } + + static void cleanup() + { + listener_vector_t listeners(sListeners.begin(), sListeners.end()); + sListeners.clear(); + + std::for_each(listeners.begin(), listeners.end(), DeletePointer()); + listeners.clear(); + } + +private: + typedef std::set listener_map_t; + typedef std::vector listener_vector_t; + static listener_map_t sListeners; +}; + +#endif // LL_LLMENUGL_H diff --git a/indra/llui/llmodaldialog.cpp b/indra/llui/llmodaldialog.cpp index 087ac325c8..c5c31f7252 100644 --- a/indra/llui/llmodaldialog.cpp +++ b/indra/llui/llmodaldialog.cpp @@ -1,348 +1,348 @@ -/** - * @file llmodaldialog.cpp - * @brief LLModalDialog base class - * - * $LicenseInfo:firstyear=2002&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 "llmodaldialog.h" - -#include "llfocusmgr.h" -#include "v4color.h" -#include "v2math.h" -#include "llui.h" -#include "llwindow.h" -#include "llkeyboard.h" -#include "llmenugl.h" -// static -std::list LLModalDialog::sModalStack; - -LLModalDialog::LLModalDialog( const LLSD& key, bool modal ) - : LLFloater(key), - mModal( modal ) -{ - if (modal) - { - setCanMinimize(false); - setCanClose(false); - } - setVisible( false ); - setBackgroundVisible(true); - setBackgroundOpaque(true); - centerOnScreen(); // default position - mCloseSignal.connect(boost::bind(&LLModalDialog::stopModal, this)); -} - -LLModalDialog::~LLModalDialog() -{ - // don't unlock focus unless we have it - if (gFocusMgr.childHasKeyboardFocus(this)) - { - gFocusMgr.unlockFocus(); - } - - std::list::iterator iter = std::find(sModalStack.begin(), sModalStack.end(), this); - if (iter != sModalStack.end()) - { - LL_ERRS() << "Attempt to delete dialog while still in sModalStack!" << LL_ENDL; - } - - LLUI::getInstance()->removePopup(this); -} - -// virtual -bool LLModalDialog::postBuild() -{ - return LLFloater::postBuild(); -} - -// virtual -void LLModalDialog::openFloater(const LLSD& key) -{ - // SJB: Hack! Make sure we don't ever host a modal dialog - LLMultiFloater* thost = LLFloater::getFloaterHost(); - LLFloater::setFloaterHost(NULL); - LLFloater::openFloater(key); - LLFloater::setFloaterHost(thost); -} - -void LLModalDialog::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLFloater::reshape(width, height, called_from_parent); - centerOnScreen(); -} - -// virtual -void LLModalDialog::onOpen(const LLSD& key) -{ - if (mModal) - { - // If Modal, Hide the active modal dialog - if (!sModalStack.empty()) - { - LLModalDialog* front = sModalStack.front(); - if (front != this) - { - front->setVisible(false); - } - } - - // This is a modal dialog. It sucks up all mouse and keyboard operations. - gFocusMgr.setMouseCapture( this ); - LLUI::getInstance()->addPopup(this); - setFocus(true); - - std::list::iterator iter = std::find(sModalStack.begin(), sModalStack.end(), this); - if (iter != sModalStack.end()) - { - // if already present, we want to move it to front. - sModalStack.erase(iter); - } - - sModalStack.push_front(this); - } -} - -void LLModalDialog::stopModal() -{ - gFocusMgr.unlockFocus(); - gFocusMgr.releaseFocusIfNeeded( this ); - - if (mModal) - { - std::list::iterator iter = std::find(sModalStack.begin(), sModalStack.end(), this); - if (iter != sModalStack.end()) - { - sModalStack.erase(iter); - } - else - { - LL_WARNS() << "LLModalDialog::stopModal not in list!" << LL_ENDL; - } - } - if (!sModalStack.empty()) - { - LLModalDialog* front = sModalStack.front(); - front->setVisible(true); - } -} - - -void LLModalDialog::setVisible( bool visible ) -{ - if (mModal) - { - if( visible ) - { - // This is a modal dialog. It sucks up all mouse and keyboard operations. - gFocusMgr.setMouseCapture( this ); - - // The dialog view is a root view - LLUI::getInstance()->addPopup(this); - setFocus( true ); - } - else - { - gFocusMgr.releaseFocusIfNeeded( this ); - } - } - - LLFloater::setVisible( visible ); -} - -bool LLModalDialog::handleMouseDown(S32 x, S32 y, MASK mask) -{ - LLView* popup_menu = LLMenuGL::sMenuContainer->getVisibleMenu(); - if (popup_menu != NULL) - { - S32 mx, my; - LLUI::getInstance()->getMousePositionScreen(&mx, &my); - LLRect menu_screen_rc = popup_menu->calcScreenRect(); - if(!menu_screen_rc.pointInRect(mx, my)) - { - LLMenuGL::sMenuContainer->hideMenus(); - } - } - - if (mModal) - { - if (!LLFloater::handleMouseDown(x, y, mask)) - { - // Click was outside the panel - make_ui_sound("UISndInvalidOp"); - } - } - else - { - LLFloater::handleMouseDown(x, y, mask); - } - - - return true; -} - -bool LLModalDialog::handleHover(S32 x, S32 y, MASK mask) -{ - if( childrenHandleHover(x, y, mask) == NULL ) - { - getWindow()->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << LL_ENDL; - } - - LLView* popup_menu = LLMenuGL::sMenuContainer->getVisibleMenu(); - if (popup_menu != NULL) - { - S32 mx, my; - LLUI::getInstance()->getMousePositionScreen(&mx, &my); - LLRect menu_screen_rc = popup_menu->calcScreenRect(); - if(menu_screen_rc.pointInRect(mx, my)) - { - S32 local_x = mx - popup_menu->getRect().mLeft; - S32 local_y = my - popup_menu->getRect().mBottom; - popup_menu->handleHover(local_x, local_y, mask); - gFocusMgr.setMouseCapture(NULL); - } - } - - return true; -} - -bool LLModalDialog::handleMouseUp(S32 x, S32 y, MASK mask) -{ - childrenHandleMouseUp(x, y, mask); - return true; -} - -bool LLModalDialog::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - childrenHandleScrollWheel(x, y, clicks); - return true; -} - -bool LLModalDialog::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if (!LLFloater::handleDoubleClick(x, y, mask)) - { - // Click outside the panel - make_ui_sound("UISndInvalidOp"); - } - return true; -} - -bool LLModalDialog::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - LLMenuGL::sMenuContainer->hideMenus(); - childrenHandleRightMouseDown(x, y, mask); - return true; -} - - -bool LLModalDialog::handleKeyHere(KEY key, MASK mask ) -{ - LLFloater::handleKeyHere(key, mask ); - - if (mModal) - { - // Suck up all keystokes except CTRL-Q. - bool is_quit = ('Q' == key) && (MASK_CONTROL == mask); - return !is_quit; - } - else - { - // don't process escape key until message box has been on screen a minimal amount of time - // to avoid accidentally destroying the message box when user is hitting escape at the time it appears - bool enough_time_elapsed = mVisibleTime.getElapsedTimeF32() > 1.0f; - if (enough_time_elapsed && key == KEY_ESCAPE) - { - closeFloater(); - return true; - } - return false; - } -} - -// virtual -void LLModalDialog::draw() -{ - static LLUIColor shadow_color = LLUIColorTable::instance().getColor("ColorDropShadow"); - - gl_drop_shadow( 0, getRect().getHeight(), getRect().getWidth(), 0, - shadow_color, DROP_SHADOW_FLOATER); - - LLFloater::draw(); - - // Focus retrieval moved to LLFloaterView::refresh() -} - -void LLModalDialog::centerOnScreen() -{ - LLVector2 window_size = LLUI::getInstance()->getWindowSize(); - centerWithin(LLRect(0, 0, ll_round(window_size.mV[VX]), ll_round(window_size.mV[VY]))); -} - - -// static -void LLModalDialog::onAppFocusLost() -{ - if( !sModalStack.empty() ) - { - LLModalDialog* instance = LLModalDialog::sModalStack.front(); - if( gFocusMgr.childHasMouseCapture( instance ) ) - { - gFocusMgr.setMouseCapture( NULL ); - } - - instance->setFocus(false); - } -} - -// static -void LLModalDialog::onAppFocusGained() -{ - if( !sModalStack.empty() ) - { - LLModalDialog* instance = LLModalDialog::sModalStack.front(); - - // This is a modal dialog. It sucks up all mouse and keyboard operations. - gFocusMgr.setMouseCapture( instance ); - instance->setFocus(true); - LLUI::getInstance()->addPopup(instance); - - instance->centerOnScreen(); - } -} - -void LLModalDialog::shutdownModals() -{ - // This method is only for use during app shutdown. ~LLModalDialog() - // checks sModalStack, and if the dialog instance is still there, it - // crumps with "Attempt to delete dialog while still in sModalStack!" But - // at app shutdown, all bets are off. If the user asks to shut down the - // app, we shouldn't have to care WHAT's open. Put differently, if a modal - // dialog is so crucial that we can't let the user terminate until s/he - // addresses it, we should reject a termination request. The current state - // of affairs is that we accept it, but then produce an LL_ERRS() popup that - // simply makes our software look unreliable. - sModalStack.clear(); -} +/** + * @file llmodaldialog.cpp + * @brief LLModalDialog base class + * + * $LicenseInfo:firstyear=2002&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 "llmodaldialog.h" + +#include "llfocusmgr.h" +#include "v4color.h" +#include "v2math.h" +#include "llui.h" +#include "llwindow.h" +#include "llkeyboard.h" +#include "llmenugl.h" +// static +std::list LLModalDialog::sModalStack; + +LLModalDialog::LLModalDialog( const LLSD& key, bool modal ) + : LLFloater(key), + mModal( modal ) +{ + if (modal) + { + setCanMinimize(false); + setCanClose(false); + } + setVisible( false ); + setBackgroundVisible(true); + setBackgroundOpaque(true); + centerOnScreen(); // default position + mCloseSignal.connect(boost::bind(&LLModalDialog::stopModal, this)); +} + +LLModalDialog::~LLModalDialog() +{ + // don't unlock focus unless we have it + if (gFocusMgr.childHasKeyboardFocus(this)) + { + gFocusMgr.unlockFocus(); + } + + std::list::iterator iter = std::find(sModalStack.begin(), sModalStack.end(), this); + if (iter != sModalStack.end()) + { + LL_ERRS() << "Attempt to delete dialog while still in sModalStack!" << LL_ENDL; + } + + LLUI::getInstance()->removePopup(this); +} + +// virtual +bool LLModalDialog::postBuild() +{ + return LLFloater::postBuild(); +} + +// virtual +void LLModalDialog::openFloater(const LLSD& key) +{ + // SJB: Hack! Make sure we don't ever host a modal dialog + LLMultiFloater* thost = LLFloater::getFloaterHost(); + LLFloater::setFloaterHost(NULL); + LLFloater::openFloater(key); + LLFloater::setFloaterHost(thost); +} + +void LLModalDialog::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLFloater::reshape(width, height, called_from_parent); + centerOnScreen(); +} + +// virtual +void LLModalDialog::onOpen(const LLSD& key) +{ + if (mModal) + { + // If Modal, Hide the active modal dialog + if (!sModalStack.empty()) + { + LLModalDialog* front = sModalStack.front(); + if (front != this) + { + front->setVisible(false); + } + } + + // This is a modal dialog. It sucks up all mouse and keyboard operations. + gFocusMgr.setMouseCapture( this ); + LLUI::getInstance()->addPopup(this); + setFocus(true); + + std::list::iterator iter = std::find(sModalStack.begin(), sModalStack.end(), this); + if (iter != sModalStack.end()) + { + // if already present, we want to move it to front. + sModalStack.erase(iter); + } + + sModalStack.push_front(this); + } +} + +void LLModalDialog::stopModal() +{ + gFocusMgr.unlockFocus(); + gFocusMgr.releaseFocusIfNeeded( this ); + + if (mModal) + { + std::list::iterator iter = std::find(sModalStack.begin(), sModalStack.end(), this); + if (iter != sModalStack.end()) + { + sModalStack.erase(iter); + } + else + { + LL_WARNS() << "LLModalDialog::stopModal not in list!" << LL_ENDL; + } + } + if (!sModalStack.empty()) + { + LLModalDialog* front = sModalStack.front(); + front->setVisible(true); + } +} + + +void LLModalDialog::setVisible( bool visible ) +{ + if (mModal) + { + if( visible ) + { + // This is a modal dialog. It sucks up all mouse and keyboard operations. + gFocusMgr.setMouseCapture( this ); + + // The dialog view is a root view + LLUI::getInstance()->addPopup(this); + setFocus( true ); + } + else + { + gFocusMgr.releaseFocusIfNeeded( this ); + } + } + + LLFloater::setVisible( visible ); +} + +bool LLModalDialog::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLView* popup_menu = LLMenuGL::sMenuContainer->getVisibleMenu(); + if (popup_menu != NULL) + { + S32 mx, my; + LLUI::getInstance()->getMousePositionScreen(&mx, &my); + LLRect menu_screen_rc = popup_menu->calcScreenRect(); + if(!menu_screen_rc.pointInRect(mx, my)) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + } + + if (mModal) + { + if (!LLFloater::handleMouseDown(x, y, mask)) + { + // Click was outside the panel + make_ui_sound("UISndInvalidOp"); + } + } + else + { + LLFloater::handleMouseDown(x, y, mask); + } + + + return true; +} + +bool LLModalDialog::handleHover(S32 x, S32 y, MASK mask) +{ + if( childrenHandleHover(x, y, mask) == NULL ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << LL_ENDL; + } + + LLView* popup_menu = LLMenuGL::sMenuContainer->getVisibleMenu(); + if (popup_menu != NULL) + { + S32 mx, my; + LLUI::getInstance()->getMousePositionScreen(&mx, &my); + LLRect menu_screen_rc = popup_menu->calcScreenRect(); + if(menu_screen_rc.pointInRect(mx, my)) + { + S32 local_x = mx - popup_menu->getRect().mLeft; + S32 local_y = my - popup_menu->getRect().mBottom; + popup_menu->handleHover(local_x, local_y, mask); + gFocusMgr.setMouseCapture(NULL); + } + } + + return true; +} + +bool LLModalDialog::handleMouseUp(S32 x, S32 y, MASK mask) +{ + childrenHandleMouseUp(x, y, mask); + return true; +} + +bool LLModalDialog::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + childrenHandleScrollWheel(x, y, clicks); + return true; +} + +bool LLModalDialog::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (!LLFloater::handleDoubleClick(x, y, mask)) + { + // Click outside the panel + make_ui_sound("UISndInvalidOp"); + } + return true; +} + +bool LLModalDialog::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLMenuGL::sMenuContainer->hideMenus(); + childrenHandleRightMouseDown(x, y, mask); + return true; +} + + +bool LLModalDialog::handleKeyHere(KEY key, MASK mask ) +{ + LLFloater::handleKeyHere(key, mask ); + + if (mModal) + { + // Suck up all keystokes except CTRL-Q. + bool is_quit = ('Q' == key) && (MASK_CONTROL == mask); + return !is_quit; + } + else + { + // don't process escape key until message box has been on screen a minimal amount of time + // to avoid accidentally destroying the message box when user is hitting escape at the time it appears + bool enough_time_elapsed = mVisibleTime.getElapsedTimeF32() > 1.0f; + if (enough_time_elapsed && key == KEY_ESCAPE) + { + closeFloater(); + return true; + } + return false; + } +} + +// virtual +void LLModalDialog::draw() +{ + static LLUIColor shadow_color = LLUIColorTable::instance().getColor("ColorDropShadow"); + + gl_drop_shadow( 0, getRect().getHeight(), getRect().getWidth(), 0, + shadow_color, DROP_SHADOW_FLOATER); + + LLFloater::draw(); + + // Focus retrieval moved to LLFloaterView::refresh() +} + +void LLModalDialog::centerOnScreen() +{ + LLVector2 window_size = LLUI::getInstance()->getWindowSize(); + centerWithin(LLRect(0, 0, ll_round(window_size.mV[VX]), ll_round(window_size.mV[VY]))); +} + + +// static +void LLModalDialog::onAppFocusLost() +{ + if( !sModalStack.empty() ) + { + LLModalDialog* instance = LLModalDialog::sModalStack.front(); + if( gFocusMgr.childHasMouseCapture( instance ) ) + { + gFocusMgr.setMouseCapture( NULL ); + } + + instance->setFocus(false); + } +} + +// static +void LLModalDialog::onAppFocusGained() +{ + if( !sModalStack.empty() ) + { + LLModalDialog* instance = LLModalDialog::sModalStack.front(); + + // This is a modal dialog. It sucks up all mouse and keyboard operations. + gFocusMgr.setMouseCapture( instance ); + instance->setFocus(true); + LLUI::getInstance()->addPopup(instance); + + instance->centerOnScreen(); + } +} + +void LLModalDialog::shutdownModals() +{ + // This method is only for use during app shutdown. ~LLModalDialog() + // checks sModalStack, and if the dialog instance is still there, it + // crumps with "Attempt to delete dialog while still in sModalStack!" But + // at app shutdown, all bets are off. If the user asks to shut down the + // app, we shouldn't have to care WHAT's open. Put differently, if a modal + // dialog is so crucial that we can't let the user terminate until s/he + // addresses it, we should reject a termination request. The current state + // of affairs is that we accept it, but then produce an LL_ERRS() popup that + // simply makes our software look unreliable. + sModalStack.clear(); +} diff --git a/indra/llui/llmodaldialog.h b/indra/llui/llmodaldialog.h index 37637bcb18..58c253c3f4 100644 --- a/indra/llui/llmodaldialog.h +++ b/indra/llui/llmodaldialog.h @@ -1,83 +1,83 @@ -/** - * @file llmodaldialog.h - * @brief LLModalDialog base class - * - * $LicenseInfo:firstyear=2002&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$ - */ - -#ifndef LL_LLMODALDIALOG_H -#define LL_LLMODALDIALOG_H - -#include "llfloater.h" -#include "llframetimer.h" - -class LLModalDialog; - -// By default, a ModalDialog is modal, i.e. no other window can have focus -// However, for the sake of code reuse and simplicity, if mModal == false, -// the dialog behaves like a normal floater -// https://wiki.lindenlab.com/mediawiki/index.php?title=LLModalDialog&oldid=81385 -class LLModalDialog : public LLFloater -{ -public: - LLModalDialog( const LLSD& key, bool modal = true ); - virtual ~LLModalDialog(); - - /*virtual*/ bool postBuild(); - - /*virtual*/ void openFloater(const LLSD& key = LLSD()); - /*virtual*/ void onOpen(const LLSD& key); - - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask ); - - /*virtual*/ void setVisible(bool visible); - /*virtual*/ void draw(); - - bool isModal() const { return mModal; } - void stopModal(); - - static void onAppFocusLost(); - static void onAppFocusGained(); - - static S32 activeCount() { return sModalStack.size(); } - static void shutdownModals(); - -protected: - void centerOnScreen(); - -private: - - LLFrameTimer mVisibleTime; - const bool mModal; - - static std::list sModalStack; // Top of stack is currently being displayed -}; - -#endif // LL_LLMODALDIALOG_H +/** + * @file llmodaldialog.h + * @brief LLModalDialog base class + * + * $LicenseInfo:firstyear=2002&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$ + */ + +#ifndef LL_LLMODALDIALOG_H +#define LL_LLMODALDIALOG_H + +#include "llfloater.h" +#include "llframetimer.h" + +class LLModalDialog; + +// By default, a ModalDialog is modal, i.e. no other window can have focus +// However, for the sake of code reuse and simplicity, if mModal == false, +// the dialog behaves like a normal floater +// https://wiki.lindenlab.com/mediawiki/index.php?title=LLModalDialog&oldid=81385 +class LLModalDialog : public LLFloater +{ +public: + LLModalDialog( const LLSD& key, bool modal = true ); + virtual ~LLModalDialog(); + + /*virtual*/ bool postBuild(); + + /*virtual*/ void openFloater(const LLSD& key = LLSD()); + /*virtual*/ void onOpen(const LLSD& key); + + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask ); + + /*virtual*/ void setVisible(bool visible); + /*virtual*/ void draw(); + + bool isModal() const { return mModal; } + void stopModal(); + + static void onAppFocusLost(); + static void onAppFocusGained(); + + static S32 activeCount() { return sModalStack.size(); } + static void shutdownModals(); + +protected: + void centerOnScreen(); + +private: + + LLFrameTimer mVisibleTime; + const bool mModal; + + static std::list sModalStack; // Top of stack is currently being displayed +}; + +#endif // LL_LLMODALDIALOG_H diff --git a/indra/llui/llmultifloater.cpp b/indra/llui/llmultifloater.cpp index 08e9c763f4..a7f9b8b2d9 100644 --- a/indra/llui/llmultifloater.cpp +++ b/indra/llui/llmultifloater.cpp @@ -1,522 +1,522 @@ -/** - * @file llmultifloater.cpp - * @brief LLFloater that hosts other floaters - * - * $LicenseInfo:firstyear=2002&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$ - */ - -// Floating "windows" within the GL display, like the inventory floater, -// mini-map floater, etc. - -#include "linden_common.h" - -#include "llmultifloater.h" -#include "llresizehandle.h" - -// -// LLMultiFloater -// - -LLMultiFloater::LLMultiFloater(const LLSD& key, const LLFloater::Params& params) - : LLFloater(key), - mTabContainer(NULL), - mTabPos(LLTabContainer::TOP), - mAutoResize(true), - mOrigMinWidth(params.min_width), - mOrigMinHeight(params.min_height) -{ -} - -void LLMultiFloater::buildTabContainer() -{ - const LLFloater::Params& default_params = LLFloater::getDefaultParams(); - S32 floater_header_size = default_params.header_height; - - LLTabContainer::Params p; - p.name(std::string("Preview Tabs")); - p.rect(LLRect(LLPANEL_BORDER_WIDTH, getRect().getHeight() - floater_header_size, getRect().getWidth() - LLPANEL_BORDER_WIDTH, 0)); - p.tab_position(mTabPos); - p.follows.flags(FOLLOWS_ALL); - p.commit_callback.function(boost::bind(&LLMultiFloater::onTabSelected, this)); - - mTabContainer = LLUICtrlFactory::create(p); - addChild(mTabContainer); - - if (isResizable()) - { - mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); - } -} - -void LLMultiFloater::onClose(bool app_quitting) -{ - if(isMinimized()) - { - setMinimized(false); - } - LLFloater::onClose(app_quitting); -} - -void LLMultiFloater::draw() -{ - if (mTabContainer->getTabCount() == 0) - { - //RN: could this potentially crash in draw hierarchy? - closeFloater(); - } - else - { - LLFloater::draw(); - } -} - -bool LLMultiFloater::closeAllFloaters() -{ - S32 tabToClose = 0; - S32 lastTabCount = mTabContainer->getTabCount(); - while (tabToClose < mTabContainer->getTabCount()) - { - LLFloater* first_floater = (LLFloater*)mTabContainer->getPanelByIndex(tabToClose); - first_floater->closeFloater(); - if(lastTabCount == mTabContainer->getTabCount()) - { - //Tab did not actually close, possibly due to a pending Save Confirmation dialog.. - //so try and close the next one in the list... - tabToClose++; - } - else - { - //Tab closed ok. - lastTabCount = mTabContainer->getTabCount(); - } - } - if( mTabContainer->getTabCount() != 0 ) - return false; // Couldn't close all the tabs (pending save dialog?) so return false. - return true; //else all tabs were successfully closed... -} - -void LLMultiFloater::growToFit(S32 content_width, S32 content_height) -{ - static LLUICachedControl tabcntr_close_btn_size ("UITabCntrCloseBtnSize", 0); - const LLFloater::Params& default_params = LLFloater::getDefaultParams(); - S32 floater_header_size = default_params.header_height; - S32 tabcntr_header_height = LLPANEL_BORDER_WIDTH + tabcntr_close_btn_size; - S32 new_width = llmax(getRect().getWidth(), content_width + LLPANEL_BORDER_WIDTH * 2); - S32 new_height = llmax(getRect().getHeight(), content_height + floater_header_size + tabcntr_header_height); - - if (isMinimized()) - { - LLRect newrect; - newrect.setLeftTopAndSize(getExpandedRect().mLeft, getExpandedRect().mTop, new_width, new_height); - setExpandedRect(newrect); - } - else - { - S32 old_height = getRect().getHeight(); - reshape(new_width, new_height); - // keep top left corner in same position - translate(0, old_height - new_height); - } -} - -/** - void addFloater(LLFloater* floaterp, bool select_added_floater) - - Adds the LLFloater pointed to by floaterp to this. - If floaterp is already hosted by this, then it is re-added to get - new titles, etc. - If select_added_floater is true, the LLFloater pointed to by floaterp will - become the selected tab in this - - Affects: mTabContainer, floaterp -**/ -void LLMultiFloater::addFloater(LLFloater* floaterp, bool select_added_floater, LLTabContainer::eInsertionPoint insertion_point) -{ - if (!floaterp) - { - return; - } - - if (!mTabContainer) - { - LL_ERRS() << "Tab Container used without having been initialized." << LL_ENDL; - return; - } - - if (floaterp->getHost() == this) - { - // already hosted by me, remove - // do this so we get updated title, etc. - mFloaterDataMap.erase(floaterp->getHandle()); - mTabContainer->removeTabPanel(floaterp); - } - else if (floaterp->getHost()) - { - // floaterp is hosted by somebody else and - // this is adding it, so remove it from its old host - floaterp->getHost()->removeFloater(floaterp); - } - else if (floaterp->getParent() == gFloaterView) - { - // rehost preview floater as child panel - gFloaterView->removeChild(floaterp); - } - - // store original configuration - LLFloaterData floater_data; - floater_data.mWidth = floaterp->getRect().getWidth(); - floater_data.mHeight = floaterp->getRect().getHeight(); - floater_data.mCanMinimize = floaterp->isMinimizeable(); - floater_data.mCanResize = floaterp->isResizable(); - floater_data.mSaveRect = floaterp->mSaveRect; - - // remove minimize and close buttons - floaterp->setCanMinimize(false); - floaterp->setCanResize(false); - floaterp->setCanDrag(false); - floaterp->mSaveRect = false; - floaterp->storeRectControl(); - // avoid double rendering of floater background (makes it more opaque) - floaterp->setBackgroundVisible(false); - - if (mAutoResize) - { - growToFit(floater_data.mWidth, floater_data.mHeight); - } - - //add the panel, add it to proper maps - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams() - .panel(floaterp) - .label(floaterp->getShortTitle()) - .insert_at(insertion_point)); - mFloaterDataMap[floaterp->getHandle()] = floater_data; - - updateResizeLimits(); - - if ( select_added_floater ) - { - mTabContainer->selectTabPanel(floaterp); - } - else - { - // reassert visible tab (hiding new floater if necessary) - mTabContainer->selectTab(mTabContainer->getCurrentPanelIndex()); - } - - floaterp->setHost(this); - if (isMinimized()) - { - floaterp->setVisible(false); - } - - // Tabs sometimes overlap resize handle - moveResizeHandlesToFront(); -} - -void LLMultiFloater::updateFloaterTitle(LLFloater* floaterp) -{ - S32 index = mTabContainer->getIndexForPanel(floaterp); - if (index != -1) - { - mTabContainer->setPanelTitle(index, floaterp->getShortTitle()); - } -} - - -/** - bool selectFloater(LLFloater* floaterp) - - If the LLFloater pointed to by floaterp is hosted by this, - then its tab is selected and returns true. Otherwise returns false. - - Affects: mTabContainer -**/ -bool LLMultiFloater::selectFloater(LLFloater* floaterp) -{ - return mTabContainer->selectTabPanel(floaterp); -} - -// virtual -void LLMultiFloater::selectNextFloater() -{ - mTabContainer->selectNextTab(); -} - -// virtual -void LLMultiFloater::selectPrevFloater() -{ - mTabContainer->selectPrevTab(); -} - -void LLMultiFloater::showFloater(LLFloater* floaterp, LLTabContainer::eInsertionPoint insertion_point) -{ - if(!floaterp) return; - // we won't select a panel that already is selected - // it is hard to do this internally to tab container - // as tab selection is handled via index and the tab at a given - // index might have changed - if (floaterp != mTabContainer->getCurrentPanel() && - !mTabContainer->selectTabPanel(floaterp)) - { - addFloater(floaterp, true, insertion_point); - } -} - -void LLMultiFloater::removeFloater(LLFloater* floaterp) -{ - if (!floaterp || floaterp->getHost() != this ) - return; - - floater_data_map_t::iterator found_data_it = mFloaterDataMap.find(floaterp->getHandle()); - if (found_data_it != mFloaterDataMap.end()) - { - LLFloaterData& floater_data = found_data_it->second; - floaterp->setCanMinimize(floater_data.mCanMinimize); - floaterp->mSaveRect = floater_data.mSaveRect; - if (!floater_data.mCanResize) - { - // restore original size - floaterp->reshape(floater_data.mWidth, floater_data.mHeight); - } - floaterp->setCanResize(floater_data.mCanResize); - mFloaterDataMap.erase(found_data_it); - } - mTabContainer->removeTabPanel(floaterp); - floaterp->setBackgroundVisible(true); - floaterp->setCanDrag(true); - floaterp->setHost(NULL); - floaterp->applyRectControl(); - - updateResizeLimits(); - - tabOpen((LLFloater*)mTabContainer->getCurrentPanel(), false); -} - -void LLMultiFloater::tabOpen(LLFloater* opened_floater, bool from_click) -{ - // default implementation does nothing -} - -void LLMultiFloater::tabClose() -{ - if (mTabContainer->getTabCount() == 0) - { - // no more children, close myself - closeFloater(); - } -} - -void LLMultiFloater::setVisible(bool visible) -{ - // *FIX: shouldn't have to do this, fix adding to minimized multifloater - LLFloater::setVisible(visible); - - if (mTabContainer) - { - LLPanel* cur_floaterp = mTabContainer->getCurrentPanel(); - - if (cur_floaterp) - { - cur_floaterp->setVisible(visible); - } - - // if no tab selected, and we're being shown, - // select last tab to be added - if (visible && !cur_floaterp) - { - mTabContainer->selectLastTab(); - } - } -} - -bool LLMultiFloater::handleKeyHere(KEY key, MASK mask) -{ - if (key == 'W' && mask == MASK_CONTROL) - { - LLFloater* floater = getActiveFloater(); - // is user closeable and is system closeable - if (floater && floater->canClose() && floater->isCloseable()) - { - floater->closeFloater(); - - // EXT-5695 (Tabbed IM window loses focus if close any tabs by Ctrl+W) - // bring back focus on tab container if there are any tab left - if(mTabContainer->getTabCount() > 0) - { - mTabContainer->setFocus(true); - } - } - return true; - } - - return LLFloater::handleKeyHere(key, mask); -} - -bool LLMultiFloater::addChild(LLView* child, S32 tab_group) -{ - LLTabContainer* tab_container = dynamic_cast(child); - if (tab_container) - { - // store pointer to tab container - setTabContainer(tab_container); - } - - // then go ahead and add child as usual - return LLFloater::addChild(child, tab_group); -} - -LLFloater* LLMultiFloater::getActiveFloater() -{ - return (LLFloater*)mTabContainer->getCurrentPanel(); -} - -S32 LLMultiFloater::getFloaterCount() -{ - return mTabContainer->getTabCount(); -} - -/** - bool isFloaterFlashing(LLFloater* floaterp) - - Returns true if the LLFloater pointed to by floaterp - is currently in a flashing state and is hosted by this. - False otherwise. - - Requires: floaterp != NULL -**/ -bool LLMultiFloater::isFloaterFlashing(LLFloater* floaterp) -{ - if ( floaterp && floaterp->getHost() == this ) - return mTabContainer->getTabPanelFlashing(floaterp); - - return false; -} - -/** - bool setFloaterFlashing(LLFloater* floaterp, bool flashing) - - Sets the current flashing state of the LLFloater pointed - to by floaterp to be the bool flashing if the LLFloater pointed - to by floaterp is hosted by this. - - Requires: floaterp != NULL -**/ -void LLMultiFloater::setFloaterFlashing(LLFloater* floaterp, bool flashing) -{ - if ( floaterp && floaterp->getHost() == this ) - mTabContainer->setTabPanelFlashing(floaterp, flashing); -} - -void LLMultiFloater::onTabSelected() -{ - LLFloater* floaterp = dynamic_cast(mTabContainer->getCurrentPanel()); - if (floaterp) - { - tabOpen(floaterp, true); - } -} - -void LLMultiFloater::setCanResize(bool can_resize) -{ - LLFloater::setCanResize(can_resize); - if (!mTabContainer) return; - if (isResizable() && mTabContainer->getTabPosition() == LLTabContainer::BOTTOM) - { - mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); - } - else - { - mTabContainer->setRightTabBtnOffset(0); - } -} - -bool LLMultiFloater::postBuild() -{ - mCloseSignal.connect(boost::bind(&LLMultiFloater::closeAllFloaters, this)); - - // remember any original xml minimum size - getResizeLimits(&mOrigMinWidth, &mOrigMinHeight); - - if (mTabContainer) - { - return true; - } - - mTabContainer = getChild("Preview Tabs"); - - setCanResize(mResizable); - return true; -} - -void LLMultiFloater::updateResizeLimits() -{ - // initialize minimum size constraint to the original xml values. - S32 new_min_width = mOrigMinWidth; - S32 new_min_height = mOrigMinHeight; - - computeResizeLimits(new_min_width, new_min_height); - - setResizeLimits(new_min_width, new_min_height); - - S32 cur_height = getRect().getHeight(); - S32 new_width = llmax(getRect().getWidth(), new_min_width); - S32 new_height = llmax(getRect().getHeight(), new_min_height); - - if (isMinimized()) - { - const LLRect& expanded = getExpandedRect(); - LLRect newrect; - newrect.setLeftTopAndSize(expanded.mLeft, expanded.mTop, llmax(expanded.getWidth(), new_width), llmax(expanded.getHeight(), new_height)); - setExpandedRect(newrect); - } - else - { - reshape(new_width, new_height); - - // make sure upper left corner doesn't move - translate(0, cur_height - getRect().getHeight()); - - // make sure this window is visible on screen when it has been modified - // (tab added, etc) - gFloaterView->adjustToFitScreen(this, true); - } -} - -void LLMultiFloater::computeResizeLimits(S32& new_min_width, S32& new_min_height) -{ - static LLUICachedControl tabcntr_close_btn_size ("UITabCntrCloseBtnSize", 0); - const LLFloater::Params& default_params = LLFloater::getDefaultParams(); - S32 floater_header_size = default_params.header_height; - S32 tabcntr_header_height = LLPANEL_BORDER_WIDTH + tabcntr_close_btn_size; - - // possibly increase minimum size constraint due to children's minimums. - for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) - { - LLFloater* floaterp = (LLFloater*)mTabContainer->getPanelByIndex(tab_idx); - if (floaterp) - { - new_min_width = llmax(new_min_width, floaterp->getMinWidth() + LLPANEL_BORDER_WIDTH * 2); - new_min_height = llmax(new_min_height, floaterp->getMinHeight() + floater_header_size + tabcntr_header_height); - } - } -} +/** + * @file llmultifloater.cpp + * @brief LLFloater that hosts other floaters + * + * $LicenseInfo:firstyear=2002&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$ + */ + +// Floating "windows" within the GL display, like the inventory floater, +// mini-map floater, etc. + +#include "linden_common.h" + +#include "llmultifloater.h" +#include "llresizehandle.h" + +// +// LLMultiFloater +// + +LLMultiFloater::LLMultiFloater(const LLSD& key, const LLFloater::Params& params) + : LLFloater(key), + mTabContainer(NULL), + mTabPos(LLTabContainer::TOP), + mAutoResize(true), + mOrigMinWidth(params.min_width), + mOrigMinHeight(params.min_height) +{ +} + +void LLMultiFloater::buildTabContainer() +{ + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + + LLTabContainer::Params p; + p.name(std::string("Preview Tabs")); + p.rect(LLRect(LLPANEL_BORDER_WIDTH, getRect().getHeight() - floater_header_size, getRect().getWidth() - LLPANEL_BORDER_WIDTH, 0)); + p.tab_position(mTabPos); + p.follows.flags(FOLLOWS_ALL); + p.commit_callback.function(boost::bind(&LLMultiFloater::onTabSelected, this)); + + mTabContainer = LLUICtrlFactory::create(p); + addChild(mTabContainer); + + if (isResizable()) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } +} + +void LLMultiFloater::onClose(bool app_quitting) +{ + if(isMinimized()) + { + setMinimized(false); + } + LLFloater::onClose(app_quitting); +} + +void LLMultiFloater::draw() +{ + if (mTabContainer->getTabCount() == 0) + { + //RN: could this potentially crash in draw hierarchy? + closeFloater(); + } + else + { + LLFloater::draw(); + } +} + +bool LLMultiFloater::closeAllFloaters() +{ + S32 tabToClose = 0; + S32 lastTabCount = mTabContainer->getTabCount(); + while (tabToClose < mTabContainer->getTabCount()) + { + LLFloater* first_floater = (LLFloater*)mTabContainer->getPanelByIndex(tabToClose); + first_floater->closeFloater(); + if(lastTabCount == mTabContainer->getTabCount()) + { + //Tab did not actually close, possibly due to a pending Save Confirmation dialog.. + //so try and close the next one in the list... + tabToClose++; + } + else + { + //Tab closed ok. + lastTabCount = mTabContainer->getTabCount(); + } + } + if( mTabContainer->getTabCount() != 0 ) + return false; // Couldn't close all the tabs (pending save dialog?) so return false. + return true; //else all tabs were successfully closed... +} + +void LLMultiFloater::growToFit(S32 content_width, S32 content_height) +{ + static LLUICachedControl tabcntr_close_btn_size ("UITabCntrCloseBtnSize", 0); + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + S32 tabcntr_header_height = LLPANEL_BORDER_WIDTH + tabcntr_close_btn_size; + S32 new_width = llmax(getRect().getWidth(), content_width + LLPANEL_BORDER_WIDTH * 2); + S32 new_height = llmax(getRect().getHeight(), content_height + floater_header_size + tabcntr_header_height); + + if (isMinimized()) + { + LLRect newrect; + newrect.setLeftTopAndSize(getExpandedRect().mLeft, getExpandedRect().mTop, new_width, new_height); + setExpandedRect(newrect); + } + else + { + S32 old_height = getRect().getHeight(); + reshape(new_width, new_height); + // keep top left corner in same position + translate(0, old_height - new_height); + } +} + +/** + void addFloater(LLFloater* floaterp, bool select_added_floater) + + Adds the LLFloater pointed to by floaterp to this. + If floaterp is already hosted by this, then it is re-added to get + new titles, etc. + If select_added_floater is true, the LLFloater pointed to by floaterp will + become the selected tab in this + + Affects: mTabContainer, floaterp +**/ +void LLMultiFloater::addFloater(LLFloater* floaterp, bool select_added_floater, LLTabContainer::eInsertionPoint insertion_point) +{ + if (!floaterp) + { + return; + } + + if (!mTabContainer) + { + LL_ERRS() << "Tab Container used without having been initialized." << LL_ENDL; + return; + } + + if (floaterp->getHost() == this) + { + // already hosted by me, remove + // do this so we get updated title, etc. + mFloaterDataMap.erase(floaterp->getHandle()); + mTabContainer->removeTabPanel(floaterp); + } + else if (floaterp->getHost()) + { + // floaterp is hosted by somebody else and + // this is adding it, so remove it from its old host + floaterp->getHost()->removeFloater(floaterp); + } + else if (floaterp->getParent() == gFloaterView) + { + // rehost preview floater as child panel + gFloaterView->removeChild(floaterp); + } + + // store original configuration + LLFloaterData floater_data; + floater_data.mWidth = floaterp->getRect().getWidth(); + floater_data.mHeight = floaterp->getRect().getHeight(); + floater_data.mCanMinimize = floaterp->isMinimizeable(); + floater_data.mCanResize = floaterp->isResizable(); + floater_data.mSaveRect = floaterp->mSaveRect; + + // remove minimize and close buttons + floaterp->setCanMinimize(false); + floaterp->setCanResize(false); + floaterp->setCanDrag(false); + floaterp->mSaveRect = false; + floaterp->storeRectControl(); + // avoid double rendering of floater background (makes it more opaque) + floaterp->setBackgroundVisible(false); + + if (mAutoResize) + { + growToFit(floater_data.mWidth, floater_data.mHeight); + } + + //add the panel, add it to proper maps + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams() + .panel(floaterp) + .label(floaterp->getShortTitle()) + .insert_at(insertion_point)); + mFloaterDataMap[floaterp->getHandle()] = floater_data; + + updateResizeLimits(); + + if ( select_added_floater ) + { + mTabContainer->selectTabPanel(floaterp); + } + else + { + // reassert visible tab (hiding new floater if necessary) + mTabContainer->selectTab(mTabContainer->getCurrentPanelIndex()); + } + + floaterp->setHost(this); + if (isMinimized()) + { + floaterp->setVisible(false); + } + + // Tabs sometimes overlap resize handle + moveResizeHandlesToFront(); +} + +void LLMultiFloater::updateFloaterTitle(LLFloater* floaterp) +{ + S32 index = mTabContainer->getIndexForPanel(floaterp); + if (index != -1) + { + mTabContainer->setPanelTitle(index, floaterp->getShortTitle()); + } +} + + +/** + bool selectFloater(LLFloater* floaterp) + + If the LLFloater pointed to by floaterp is hosted by this, + then its tab is selected and returns true. Otherwise returns false. + + Affects: mTabContainer +**/ +bool LLMultiFloater::selectFloater(LLFloater* floaterp) +{ + return mTabContainer->selectTabPanel(floaterp); +} + +// virtual +void LLMultiFloater::selectNextFloater() +{ + mTabContainer->selectNextTab(); +} + +// virtual +void LLMultiFloater::selectPrevFloater() +{ + mTabContainer->selectPrevTab(); +} + +void LLMultiFloater::showFloater(LLFloater* floaterp, LLTabContainer::eInsertionPoint insertion_point) +{ + if(!floaterp) return; + // we won't select a panel that already is selected + // it is hard to do this internally to tab container + // as tab selection is handled via index and the tab at a given + // index might have changed + if (floaterp != mTabContainer->getCurrentPanel() && + !mTabContainer->selectTabPanel(floaterp)) + { + addFloater(floaterp, true, insertion_point); + } +} + +void LLMultiFloater::removeFloater(LLFloater* floaterp) +{ + if (!floaterp || floaterp->getHost() != this ) + return; + + floater_data_map_t::iterator found_data_it = mFloaterDataMap.find(floaterp->getHandle()); + if (found_data_it != mFloaterDataMap.end()) + { + LLFloaterData& floater_data = found_data_it->second; + floaterp->setCanMinimize(floater_data.mCanMinimize); + floaterp->mSaveRect = floater_data.mSaveRect; + if (!floater_data.mCanResize) + { + // restore original size + floaterp->reshape(floater_data.mWidth, floater_data.mHeight); + } + floaterp->setCanResize(floater_data.mCanResize); + mFloaterDataMap.erase(found_data_it); + } + mTabContainer->removeTabPanel(floaterp); + floaterp->setBackgroundVisible(true); + floaterp->setCanDrag(true); + floaterp->setHost(NULL); + floaterp->applyRectControl(); + + updateResizeLimits(); + + tabOpen((LLFloater*)mTabContainer->getCurrentPanel(), false); +} + +void LLMultiFloater::tabOpen(LLFloater* opened_floater, bool from_click) +{ + // default implementation does nothing +} + +void LLMultiFloater::tabClose() +{ + if (mTabContainer->getTabCount() == 0) + { + // no more children, close myself + closeFloater(); + } +} + +void LLMultiFloater::setVisible(bool visible) +{ + // *FIX: shouldn't have to do this, fix adding to minimized multifloater + LLFloater::setVisible(visible); + + if (mTabContainer) + { + LLPanel* cur_floaterp = mTabContainer->getCurrentPanel(); + + if (cur_floaterp) + { + cur_floaterp->setVisible(visible); + } + + // if no tab selected, and we're being shown, + // select last tab to be added + if (visible && !cur_floaterp) + { + mTabContainer->selectLastTab(); + } + } +} + +bool LLMultiFloater::handleKeyHere(KEY key, MASK mask) +{ + if (key == 'W' && mask == MASK_CONTROL) + { + LLFloater* floater = getActiveFloater(); + // is user closeable and is system closeable + if (floater && floater->canClose() && floater->isCloseable()) + { + floater->closeFloater(); + + // EXT-5695 (Tabbed IM window loses focus if close any tabs by Ctrl+W) + // bring back focus on tab container if there are any tab left + if(mTabContainer->getTabCount() > 0) + { + mTabContainer->setFocus(true); + } + } + return true; + } + + return LLFloater::handleKeyHere(key, mask); +} + +bool LLMultiFloater::addChild(LLView* child, S32 tab_group) +{ + LLTabContainer* tab_container = dynamic_cast(child); + if (tab_container) + { + // store pointer to tab container + setTabContainer(tab_container); + } + + // then go ahead and add child as usual + return LLFloater::addChild(child, tab_group); +} + +LLFloater* LLMultiFloater::getActiveFloater() +{ + return (LLFloater*)mTabContainer->getCurrentPanel(); +} + +S32 LLMultiFloater::getFloaterCount() +{ + return mTabContainer->getTabCount(); +} + +/** + bool isFloaterFlashing(LLFloater* floaterp) + + Returns true if the LLFloater pointed to by floaterp + is currently in a flashing state and is hosted by this. + False otherwise. + + Requires: floaterp != NULL +**/ +bool LLMultiFloater::isFloaterFlashing(LLFloater* floaterp) +{ + if ( floaterp && floaterp->getHost() == this ) + return mTabContainer->getTabPanelFlashing(floaterp); + + return false; +} + +/** + bool setFloaterFlashing(LLFloater* floaterp, bool flashing) + + Sets the current flashing state of the LLFloater pointed + to by floaterp to be the bool flashing if the LLFloater pointed + to by floaterp is hosted by this. + + Requires: floaterp != NULL +**/ +void LLMultiFloater::setFloaterFlashing(LLFloater* floaterp, bool flashing) +{ + if ( floaterp && floaterp->getHost() == this ) + mTabContainer->setTabPanelFlashing(floaterp, flashing); +} + +void LLMultiFloater::onTabSelected() +{ + LLFloater* floaterp = dynamic_cast(mTabContainer->getCurrentPanel()); + if (floaterp) + { + tabOpen(floaterp, true); + } +} + +void LLMultiFloater::setCanResize(bool can_resize) +{ + LLFloater::setCanResize(can_resize); + if (!mTabContainer) return; + if (isResizable() && mTabContainer->getTabPosition() == LLTabContainer::BOTTOM) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } + else + { + mTabContainer->setRightTabBtnOffset(0); + } +} + +bool LLMultiFloater::postBuild() +{ + mCloseSignal.connect(boost::bind(&LLMultiFloater::closeAllFloaters, this)); + + // remember any original xml minimum size + getResizeLimits(&mOrigMinWidth, &mOrigMinHeight); + + if (mTabContainer) + { + return true; + } + + mTabContainer = getChild("Preview Tabs"); + + setCanResize(mResizable); + return true; +} + +void LLMultiFloater::updateResizeLimits() +{ + // initialize minimum size constraint to the original xml values. + S32 new_min_width = mOrigMinWidth; + S32 new_min_height = mOrigMinHeight; + + computeResizeLimits(new_min_width, new_min_height); + + setResizeLimits(new_min_width, new_min_height); + + S32 cur_height = getRect().getHeight(); + S32 new_width = llmax(getRect().getWidth(), new_min_width); + S32 new_height = llmax(getRect().getHeight(), new_min_height); + + if (isMinimized()) + { + const LLRect& expanded = getExpandedRect(); + LLRect newrect; + newrect.setLeftTopAndSize(expanded.mLeft, expanded.mTop, llmax(expanded.getWidth(), new_width), llmax(expanded.getHeight(), new_height)); + setExpandedRect(newrect); + } + else + { + reshape(new_width, new_height); + + // make sure upper left corner doesn't move + translate(0, cur_height - getRect().getHeight()); + + // make sure this window is visible on screen when it has been modified + // (tab added, etc) + gFloaterView->adjustToFitScreen(this, true); + } +} + +void LLMultiFloater::computeResizeLimits(S32& new_min_width, S32& new_min_height) +{ + static LLUICachedControl tabcntr_close_btn_size ("UITabCntrCloseBtnSize", 0); + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + S32 tabcntr_header_height = LLPANEL_BORDER_WIDTH + tabcntr_close_btn_size; + + // possibly increase minimum size constraint due to children's minimums. + for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) + { + LLFloater* floaterp = (LLFloater*)mTabContainer->getPanelByIndex(tab_idx); + if (floaterp) + { + new_min_width = llmax(new_min_width, floaterp->getMinWidth() + LLPANEL_BORDER_WIDTH * 2); + new_min_height = llmax(new_min_height, floaterp->getMinHeight() + floater_header_size + tabcntr_header_height); + } + } +} diff --git a/indra/llui/llmultifloater.h b/indra/llui/llmultifloater.h index 82cff6084a..eb0f917695 100644 --- a/indra/llui/llmultifloater.h +++ b/indra/llui/llmultifloater.h @@ -1,102 +1,102 @@ -/** - * @file llmultifloater.h - * @brief LLFloater that hosts other floaters - * - * $LicenseInfo:firstyear=2002&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$ - */ - -// Floating "windows" within the GL display, like the inventory floater, -// mini-map floater, etc. - - -#ifndef LL_MULTI_FLOATER_H -#define LL_MULTI_FLOATER_H - -#include "llfloater.h" -#include "lltabcontainer.h" // for LLTabContainer::eInsertionPoint - -// https://wiki.lindenlab.com/mediawiki/index.php?title=LLMultiFloater&oldid=81376 -class LLMultiFloater : public LLFloater -{ -public: - LLMultiFloater(const LLSD& key, const Params& params = getDefaultParams()); - virtual ~LLMultiFloater() {}; - - void buildTabContainer(); - - virtual bool postBuild(); - /*virtual*/ void onClose(bool app_quitting); - virtual void draw(); - virtual void setVisible(bool visible); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - /*virtual*/ bool addChild(LLView* view, S32 tab_group = 0); - - virtual void setCanResize(bool can_resize); - virtual void growToFit(S32 content_width, S32 content_height); - virtual void addFloater(LLFloater* floaterp, bool select_added_floater, LLTabContainer::eInsertionPoint insertion_point = LLTabContainer::END); - - virtual void showFloater(LLFloater* floaterp, LLTabContainer::eInsertionPoint insertion_point = LLTabContainer::END); - virtual void removeFloater(LLFloater* floaterp); - - virtual void tabOpen(LLFloater* opened_floater, bool from_click); - virtual void tabClose(); - - virtual bool selectFloater(LLFloater* floaterp); - virtual void selectNextFloater(); - virtual void selectPrevFloater(); - - virtual LLFloater* getActiveFloater(); - virtual bool isFloaterFlashing(LLFloater* floaterp); - virtual S32 getFloaterCount(); - - virtual void setFloaterFlashing(LLFloater* floaterp, bool flashing); - virtual bool closeAllFloaters(); //Returns false if the floater could not be closed due to pending confirmation dialogs - void setTabContainer(LLTabContainer* tab_container) { if (!mTabContainer) mTabContainer = tab_container; } - void onTabSelected(); - - virtual void updateResizeLimits(); - virtual void updateFloaterTitle(LLFloater* floaterp); - -protected: - struct LLFloaterData - { - S32 mWidth; - S32 mHeight; - bool mCanMinimize; - bool mCanResize; - bool mSaveRect; - }; - - LLTabContainer* mTabContainer; - - typedef std::map, LLFloaterData> floater_data_map_t; - floater_data_map_t mFloaterDataMap; - - LLTabContainer::TabPosition mTabPos; - bool mAutoResize; - S32 mOrigMinWidth, mOrigMinHeight; // logically const but initialized late - -private: - virtual void computeResizeLimits(S32& new_min_width, S32& new_min_height); -}; - -#endif // LL_MULTI_FLOATER_H +/** + * @file llmultifloater.h + * @brief LLFloater that hosts other floaters + * + * $LicenseInfo:firstyear=2002&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$ + */ + +// Floating "windows" within the GL display, like the inventory floater, +// mini-map floater, etc. + + +#ifndef LL_MULTI_FLOATER_H +#define LL_MULTI_FLOATER_H + +#include "llfloater.h" +#include "lltabcontainer.h" // for LLTabContainer::eInsertionPoint + +// https://wiki.lindenlab.com/mediawiki/index.php?title=LLMultiFloater&oldid=81376 +class LLMultiFloater : public LLFloater +{ +public: + LLMultiFloater(const LLSD& key, const Params& params = getDefaultParams()); + virtual ~LLMultiFloater() {}; + + void buildTabContainer(); + + virtual bool postBuild(); + /*virtual*/ void onClose(bool app_quitting); + virtual void draw(); + virtual void setVisible(bool visible); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + /*virtual*/ bool addChild(LLView* view, S32 tab_group = 0); + + virtual void setCanResize(bool can_resize); + virtual void growToFit(S32 content_width, S32 content_height); + virtual void addFloater(LLFloater* floaterp, bool select_added_floater, LLTabContainer::eInsertionPoint insertion_point = LLTabContainer::END); + + virtual void showFloater(LLFloater* floaterp, LLTabContainer::eInsertionPoint insertion_point = LLTabContainer::END); + virtual void removeFloater(LLFloater* floaterp); + + virtual void tabOpen(LLFloater* opened_floater, bool from_click); + virtual void tabClose(); + + virtual bool selectFloater(LLFloater* floaterp); + virtual void selectNextFloater(); + virtual void selectPrevFloater(); + + virtual LLFloater* getActiveFloater(); + virtual bool isFloaterFlashing(LLFloater* floaterp); + virtual S32 getFloaterCount(); + + virtual void setFloaterFlashing(LLFloater* floaterp, bool flashing); + virtual bool closeAllFloaters(); //Returns false if the floater could not be closed due to pending confirmation dialogs + void setTabContainer(LLTabContainer* tab_container) { if (!mTabContainer) mTabContainer = tab_container; } + void onTabSelected(); + + virtual void updateResizeLimits(); + virtual void updateFloaterTitle(LLFloater* floaterp); + +protected: + struct LLFloaterData + { + S32 mWidth; + S32 mHeight; + bool mCanMinimize; + bool mCanResize; + bool mSaveRect; + }; + + LLTabContainer* mTabContainer; + + typedef std::map, LLFloaterData> floater_data_map_t; + floater_data_map_t mFloaterDataMap; + + LLTabContainer::TabPosition mTabPos; + bool mAutoResize; + S32 mOrigMinWidth, mOrigMinHeight; // logically const but initialized late + +private: + virtual void computeResizeLimits(S32& new_min_width, S32& new_min_height); +}; + +#endif // LL_MULTI_FLOATER_H diff --git a/indra/llui/llmultislider.cpp b/indra/llui/llmultislider.cpp index a29ccab737..cfbf491610 100644 --- a/indra/llui/llmultislider.cpp +++ b/indra/llui/llmultislider.cpp @@ -1,880 +1,880 @@ -/** - * @file llmultisldr.cpp - * @brief LLMultiSlider base class - * - * $LicenseInfo:firstyear=2007&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 "llmultislider.h" -#include "llui.h" - -#include "llgl.h" -#include "llwindow.h" -#include "llfocusmgr.h" -#include "llkeyboard.h" // for the MASK constants -#include "llcontrol.h" -#include "lluictrlfactory.h" -#include "lluiimage.h" - -#include - -static LLDefaultChildRegistry::Register r("multi_slider_bar"); - -const F32 FLOAT_THRESHOLD = 0.00001f; - -S32 LLMultiSlider::mNameCounter = 0; - -LLMultiSlider::SliderParams::SliderParams() -: name("name"), - value("value", 0.f) -{ -} - -LLMultiSlider::Params::Params() -: max_sliders("max_sliders", 1), - allow_overlap("allow_overlap", false), - loop_overlap("loop_overlap", false), - orientation("orientation"), - overlap_threshold("overlap_threshold", 0), - draw_track("draw_track", true), - use_triangle("use_triangle", false), - track_color("track_color"), - thumb_disabled_color("thumb_disabled_color"), - thumb_highlight_color("thumb_highlight_color"), - thumb_outline_color("thumb_outline_color"), - thumb_center_color("thumb_center_color"), - thumb_center_selected_color("thumb_center_selected_color"), - thumb_image("thumb_image"), - triangle_color("triangle_color"), - mouse_down_callback("mouse_down_callback"), - mouse_up_callback("mouse_up_callback"), - thumb_width("thumb_width"), - sliders("slider") -{} - -LLMultiSlider::LLMultiSlider(const LLMultiSlider::Params& p) -: LLF32UICtrl(p), - mMouseOffset( 0 ), - mMaxNumSliders(p.max_sliders), - mAllowOverlap(p.allow_overlap), - mLoopOverlap(p.loop_overlap), - mDrawTrack(p.draw_track), - mUseTriangle(p.use_triangle), - mTrackColor(p.track_color()), - mThumbOutlineColor(p.thumb_outline_color()), - mThumbCenterColor(p.thumb_center_color()), - mThumbCenterSelectedColor(p.thumb_center_selected_color()), - mDisabledThumbColor(p.thumb_disabled_color()), - mTriangleColor(p.triangle_color()), - mThumbWidth(p.thumb_width), - mOrientation((p.orientation() == "vertical") ? VERTICAL : HORIZONTAL), - mMouseDownSignal(NULL), - mMouseUpSignal(NULL) -{ - mValue = LLSD::emptyMap(); - mCurSlider = LLStringUtil::null; - - if (mOrientation == HORIZONTAL) - { - mDragStartThumbRect = LLRect(0, getRect().getHeight(), p.thumb_width, 0); - } - else - { - mDragStartThumbRect = LLRect(0, p.thumb_width, getRect().getWidth(), 0); - } - - if (p.mouse_down_callback.isProvided()) - { - setMouseDownCallback(initCommitCallback(p.mouse_down_callback)); - } - if (p.mouse_up_callback.isProvided()) - { - setMouseUpCallback(initCommitCallback(p.mouse_up_callback)); - } - - if (p.overlap_threshold.isProvided() && p.overlap_threshold > mIncrement) - { - mOverlapThreshold = p.overlap_threshold - mIncrement; - } - else - { - mOverlapThreshold = 0; - } - - for (LLInitParam::ParamIterator::const_iterator it = p.sliders.begin(); - it != p.sliders.end(); - ++it) - { - if (it->name.isProvided()) - { - addSlider(it->value, it->name); - } - else - { - addSlider(it->value); - } - } - - mRoundedSquareImgp = LLUI::getUIImage("Rounded_Square"); - if (p.thumb_image.isProvided()) - { - mThumbImagep = LLUI::getUIImage(p.thumb_image()); - } - mThumbHighlightColor = p.thumb_highlight_color.isProvided() ? p.thumb_highlight_color() : static_cast(gFocusMgr.getFocusColor()); -} - -LLMultiSlider::~LLMultiSlider() -{ - delete mMouseDownSignal; - delete mMouseUpSignal; -} - -F32 LLMultiSlider::getNearestIncrement(F32 value) const -{ - value = llclamp(value, mMinValue, mMaxValue); - - // Round to nearest increment (bias towards rounding down) - value -= mMinValue; - value += mIncrement / 2.0001f; - value -= fmod(value, mIncrement); - return mMinValue + value; -} - -void LLMultiSlider::setSliderValue(const std::string& name, F32 value, bool from_event) -{ - // exit if not there - if(!mValue.has(name)) { - return; - } - - F32 newValue = getNearestIncrement(value); - - // now, make sure no overlap - // if we want that - if(!mAllowOverlap) { - bool hit = false; - - // look at the current spot - // and see if anything is there - LLSD::map_iterator mIt = mValue.beginMap(); - - // increment is our distance between points, use to eliminate round error - F32 threshold = mOverlapThreshold + (mIncrement / 4); - // If loop overlap is enabled, check if we overlap with points 'after' max value (project to lower) - F32 loop_up_check = (mLoopOverlap && (value + threshold) > mMaxValue) ? (value + threshold - mMaxValue + mMinValue) : mMinValue - 1.0f; - // If loop overlap is enabled, check if we overlap with points 'before' min value (project to upper) - F32 loop_down_check = (mLoopOverlap && (value - threshold) < mMinValue) ? (value - threshold - mMinValue + mMaxValue) : mMaxValue + 1.0f; - - for(;mIt != mValue.endMap(); mIt++) - { - F32 locationVal = (F32)mIt->second.asReal(); - // Check nearby values - F32 testVal = locationVal - newValue; - if (testVal > -threshold - && testVal < threshold - && mIt->first != name) - { - hit = true; - break; - } - if (mLoopOverlap) - { - // Check edge overlap values - if (locationVal < loop_up_check) - { - hit = true; - break; - } - if (locationVal > loop_down_check) - { - hit = true; - break; - } - } - } - - // if none found, stop - if(hit) { - return; - } - } - - - // now set it in the map - mValue[name] = newValue; - - // set the control if it's the current slider and not from an event - if (!from_event && name == mCurSlider) - { - setControlValue(mValue); - } - - F32 t = (newValue - mMinValue) / (mMaxValue - mMinValue); - if (mOrientation == HORIZONTAL) - { - S32 left_edge = mThumbWidth/2; - S32 right_edge = getRect().getWidth() - (mThumbWidth/2); - - S32 x = left_edge + S32( t * (right_edge - left_edge) ); - - mThumbRects[name].mLeft = x - (mThumbWidth / 2); - mThumbRects[name].mRight = x + (mThumbWidth / 2); - } - else - { - S32 bottom_edge = mThumbWidth/2; - S32 top_edge = getRect().getHeight() - (mThumbWidth/2); - - S32 x = bottom_edge + S32( t * (top_edge - bottom_edge) ); - - mThumbRects[name].mTop = x + (mThumbWidth / 2); - mThumbRects[name].mBottom = x - (mThumbWidth / 2); - } -} - -void LLMultiSlider::setValue(const LLSD& value) -{ - // only do if it's a map - if(value.isMap()) { - - // add each value... the first in the map becomes the current - LLSD::map_const_iterator mIt = value.beginMap(); - mCurSlider = mIt->first; - - for(; mIt != value.endMap(); mIt++) { - setSliderValue(mIt->first, (F32)mIt->second.asReal(), true); - } - } -} - -F32 LLMultiSlider::getSliderValue(const std::string& name) const -{ - if (mValue.has(name)) - { - return (F32)mValue[name].asReal(); - } - return 0; -} - -void LLMultiSlider::setCurSlider(const std::string& name) -{ - if(mValue.has(name)) { - mCurSlider = name; - } -} - -F32 LLMultiSlider::getSliderValueFromPos(S32 xpos, S32 ypos) const -{ - F32 t = 0; - if (mOrientation == HORIZONTAL) - { - S32 left_edge = mThumbWidth / 2; - S32 right_edge = getRect().getWidth() - (mThumbWidth / 2); - - xpos += mMouseOffset; - xpos = llclamp(xpos, left_edge, right_edge); - - t = F32(xpos - left_edge) / (right_edge - left_edge); - } - else - { - S32 bottom_edge = mThumbWidth / 2; - S32 top_edge = getRect().getHeight() - (mThumbWidth / 2); - - ypos += mMouseOffset; - ypos = llclamp(ypos, bottom_edge, top_edge); - - t = F32(ypos - bottom_edge) / (top_edge - bottom_edge); - } - - return((t * (mMaxValue - mMinValue)) + mMinValue); -} - - -LLRect LLMultiSlider::getSliderThumbRect(const std::string& name) const -{ - auto it = mThumbRects.find(name); - if (it != mThumbRects.end()) - return (*it).second; - return LLRect(); -} - -void LLMultiSlider::setSliderThumbImage(const std::string &name) -{ - if (!name.empty()) - { - mThumbImagep = LLUI::getUIImage(name); - } - else - clearSliderThumbImage(); -} - -void LLMultiSlider::clearSliderThumbImage() -{ - mThumbImagep = NULL; -} - -void LLMultiSlider::resetCurSlider() -{ - mCurSlider = LLStringUtil::null; -} - -const std::string& LLMultiSlider::addSlider() -{ - return addSlider(mInitialValue); -} - -const std::string& LLMultiSlider::addSlider(F32 val) -{ - std::stringstream newName; - F32 initVal = val; - - if(mValue.size() >= mMaxNumSliders) { - return LLStringUtil::null; - } - - // create a new name - newName << "sldr" << mNameCounter; - mNameCounter++; - - bool foundOne = findUnusedValue(initVal); - if(!foundOne) { - return LLStringUtil::null; - } - - // add a new thumb rect - if (mOrientation == HORIZONTAL) - { - mThumbRects[newName.str()] = LLRect(0, getRect().getHeight(), mThumbWidth, 0); - } - else - { - mThumbRects[newName.str()] = LLRect(0, mThumbWidth, getRect().getWidth(), 0); - } - - // add the value and set the current slider to this one - mValue.insert(newName.str(), initVal); - mCurSlider = newName.str(); - - // move the slider - setSliderValue(mCurSlider, initVal, true); - - return mCurSlider; -} - -bool LLMultiSlider::addSlider(F32 val, const std::string& name) -{ - F32 initVal = val; - - if(mValue.size() >= mMaxNumSliders) { - return false; - } - - bool foundOne = findUnusedValue(initVal); - if(!foundOne) { - return false; - } - - // add a new thumb rect - if (mOrientation == HORIZONTAL) - { - mThumbRects[name] = LLRect(0, getRect().getHeight(), mThumbWidth, 0); - } - else - { - mThumbRects[name] = LLRect(0, mThumbWidth, getRect().getWidth(), 0); - } - - // add the value and set the current slider to this one - mValue.insert(name, initVal); - mCurSlider = name; - - // move the slider - setSliderValue(mCurSlider, initVal, true); - - return true; -} - -bool LLMultiSlider::findUnusedValue(F32& initVal) -{ - bool firstTry = true; - - // find the first open slot starting with - // the initial value - while(true) { - - bool hit = false; - - // look at the current spot - // and see if anything is there - F32 threshold = mAllowOverlap ? FLOAT_THRESHOLD : mOverlapThreshold + (mIncrement / 4); - LLSD::map_iterator mIt = mValue.beginMap(); - for(;mIt != mValue.endMap(); mIt++) { - - F32 testVal = (F32)mIt->second.asReal() - initVal; - if(testVal > -threshold && testVal < threshold) - { - hit = true; - break; - } - } - - // if we found one - if(!hit) { - break; - } - - // increment and wrap if need be - initVal += mIncrement; - if(initVal > mMaxValue) { - initVal = mMinValue; - } - - // stop if it's filled - if(initVal == mInitialValue && !firstTry) { - LL_WARNS() << "Whoa! Too many multi slider elements to add one to" << LL_ENDL; - return false; - } - - firstTry = false; - continue; - } - - return true; -} - - -void LLMultiSlider::deleteSlider(const std::string& name) -{ - // can't delete last slider - if(mValue.size() <= 0) { - return; - } - - // get rid of value from mValue and its thumb rect - mValue.erase(name); - mThumbRects.erase(name); - - // set to the last created - if(mValue.size() > 0) { - std::map::iterator mIt = mThumbRects.end(); - mIt--; - mCurSlider = mIt->first; - } -} - -void LLMultiSlider::clear() -{ - while(mThumbRects.size() > 0 && mValue.size() > 0) { - deleteCurSlider(); - } - - if (mThumbRects.size() > 0 || mValue.size() > 0) - { - LL_WARNS() << "Failed to fully clear Multi slider" << LL_ENDL; - } - - LLF32UICtrl::clear(); -} - -bool LLMultiSlider::handleHover(S32 x, S32 y, MASK mask) -{ - if( gFocusMgr.getMouseCapture() == this ) - { - setCurSliderValue(getSliderValueFromPos(x, y)); - onCommit(); - - getWindow()->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; - } - else - { - if (getEnabled()) - { - mHoverSlider.clear(); - std::map::iterator mIt = mThumbRects.begin(); - for (; mIt != mThumbRects.end(); mIt++) - { - if (mIt->second.pointInRect(x, y)) - { - mHoverSlider = mIt->first; - break; - } - } - } - else - { - mHoverSlider.clear(); - } - - getWindow()->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; - } - return true; -} - -bool LLMultiSlider::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - if( gFocusMgr.getMouseCapture() == this ) - { - gFocusMgr.setMouseCapture( NULL ); - - if (mMouseUpSignal) - (*mMouseUpSignal)( this, LLSD() ); - - handled = true; - make_ui_sound("UISndClickRelease"); - } - else - { - handled = true; - } - - return handled; -} - -bool LLMultiSlider::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // only do sticky-focus on non-chrome widgets - if (!getIsChrome()) - { - setFocus(true); - } - if (mMouseDownSignal) - (*mMouseDownSignal)( this, LLSD() ); - - if (MASK_CONTROL & mask) // if CTRL is modifying - { - setCurSliderValue(mInitialValue); - onCommit(); - } - else - { - // scroll through thumbs to see if we have a new one selected and select that one - std::map::iterator mIt = mThumbRects.begin(); - for(; mIt != mThumbRects.end(); mIt++) { - - // check if inside. If so, set current slider and continue - if(mIt->second.pointInRect(x,y)) { - mCurSlider = mIt->first; - break; - } - } - - if (!mCurSlider.empty()) - { - // Find the offset of the actual mouse location from the center of the thumb. - if (mThumbRects[mCurSlider].pointInRect(x,y)) - { - if (mOrientation == HORIZONTAL) - { - mMouseOffset = (mThumbRects[mCurSlider].mLeft + mThumbWidth / 2) - x; - } - else - { - mMouseOffset = (mThumbRects[mCurSlider].mBottom + mThumbWidth / 2) - y; - } - } - else - { - mMouseOffset = 0; - } - - // Start dragging the thumb - // No handler needed for focus lost since this class has no state that depends on it. - gFocusMgr.setMouseCapture( this ); - mDragStartThumbRect = mThumbRects[mCurSlider]; - } - } - make_ui_sound("UISndClick"); - - return true; -} - -bool LLMultiSlider::handleKeyHere(KEY key, MASK mask) -{ - bool handled = false; - switch(key) - { - case KEY_UP: - case KEY_DOWN: - // eat up and down keys to be consistent - handled = true; - break; - case KEY_LEFT: - setCurSliderValue(getCurSliderValue() - getIncrement()); - onCommit(); - handled = true; - break; - case KEY_RIGHT: - setCurSliderValue(getCurSliderValue() + getIncrement()); - onCommit(); - handled = true; - break; - default: - break; - } - return handled; -} - -/*virtual*/ -void LLMultiSlider::onMouseLeave(S32 x, S32 y, MASK mask) -{ - mHoverSlider.clear(); - LLF32UICtrl::onMouseLeave(x, y, mask); -} - -void LLMultiSlider::draw() -{ - static LLUICachedControl extra_triangle_height ("UIExtraTriangleHeight", 0); - static LLUICachedControl extra_triangle_width ("UIExtraTriangleWidth", 0); - LLColor4 curThumbColor; - - std::map::iterator mIt; - std::map::iterator curSldrIt; - std::map::iterator hoverSldrIt; - - // Draw background and thumb. - - // drawing solids requires texturing be disabled - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - LLRect rect(mDragStartThumbRect); - - F32 opacity = getEnabled() ? 1.f : 0.3f; - - // Track - static LLUICachedControl multi_track_height_width ("UIMultiTrackHeight", 0); - S32 height_offset = 0; - S32 width_offset = 0; - if (mOrientation == HORIZONTAL) - { - height_offset = (getRect().getHeight() - multi_track_height_width) / 2; - } - else - { - width_offset = (getRect().getWidth() - multi_track_height_width) / 2; - } - LLRect track_rect(width_offset, getRect().getHeight() - height_offset, getRect().getWidth() - width_offset, height_offset); - - - if(mDrawTrack) - { - track_rect.stretch(-1); - mRoundedSquareImgp->draw(track_rect, mTrackColor.get() % opacity); - } - - // if we're supposed to use a drawn triangle - // simple gl call for the triangle - if(mUseTriangle) { - - for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) { - - gl_triangle_2d( - mIt->second.mLeft - extra_triangle_width, - mIt->second.mTop + extra_triangle_height, - mIt->second.mRight + extra_triangle_width, - mIt->second.mTop + extra_triangle_height, - mIt->second.mLeft + mIt->second.getWidth() / 2, - mIt->second.mBottom - extra_triangle_height, - mTriangleColor.get() % opacity, true); - } - } - else if (!mRoundedSquareImgp && !mThumbImagep) - { - // draw all the thumbs - curSldrIt = mThumbRects.end(); - hoverSldrIt = mThumbRects.end(); - for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) { - - // choose the color - curThumbColor = mThumbCenterColor.get(); - if(mIt->first == mCurSlider) { - - curSldrIt = mIt; - continue; - } - if (mIt->first == mHoverSlider && getEnabled() && gFocusMgr.getMouseCapture() != this) - { - // draw last, after current one - hoverSldrIt = mIt; - continue; - } - - // the draw command - gl_rect_2d(mIt->second, curThumbColor, true); - } - - // now draw the current and hover sliders - if(curSldrIt != mThumbRects.end()) - { - gl_rect_2d(curSldrIt->second, mThumbCenterSelectedColor.get(), true); - } - - // and draw the drag start - if (gFocusMgr.getMouseCapture() == this) - { - gl_rect_2d(mDragStartThumbRect, mThumbCenterColor.get() % opacity, false); - } - else if (hoverSldrIt != mThumbRects.end()) - { - gl_rect_2d(hoverSldrIt->second, mThumbCenterSelectedColor.get(), true); - } - } - else - { - LLMouseHandler* capture = gFocusMgr.getMouseCapture(); - if (capture == this) - { - // draw drag start (ghost) - if (mThumbImagep) - { - mThumbImagep->draw(mDragStartThumbRect, mThumbCenterColor.get() % 0.3f); - } - else - { - mRoundedSquareImgp->drawSolid(mDragStartThumbRect, mThumbCenterColor.get() % 0.3f); - } - } - - // draw the highlight - if (hasFocus()) - { - if (!mCurSlider.empty()) - { - if (mThumbImagep) - { - mThumbImagep->drawBorder(mThumbRects[mCurSlider], mThumbHighlightColor, gFocusMgr.getFocusFlashWidth()); - } - else - { - mRoundedSquareImgp->drawBorder(mThumbRects[mCurSlider], gFocusMgr.getFocusColor(), gFocusMgr.getFocusFlashWidth()); - } - } - } - if (!mHoverSlider.empty()) - { - if (mThumbImagep) - { - mThumbImagep->drawBorder(mThumbRects[mHoverSlider], mThumbHighlightColor, gFocusMgr.getFocusFlashWidth()); - } - else - { - mRoundedSquareImgp->drawBorder(mThumbRects[mHoverSlider], gFocusMgr.getFocusColor(), gFocusMgr.getFocusFlashWidth()); - } - } - - // draw the thumbs - curSldrIt = mThumbRects.end(); - hoverSldrIt = mThumbRects.end(); - for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) - { - // choose the color - curThumbColor = mThumbCenterColor.get(); - if(mIt->first == mCurSlider) - { - // don't draw now, draw last - curSldrIt = mIt; - continue; - } - if (mIt->first == mHoverSlider && getEnabled() && gFocusMgr.getMouseCapture() != this) - { - // don't draw now, draw last, after current one - hoverSldrIt = mIt; - continue; - } - - // the draw command - if (mThumbImagep) - { - if (getEnabled()) - { - mThumbImagep->draw(mIt->second); - } - else - { - mThumbImagep->draw(mIt->second, LLColor4::grey % 0.8f); - } - } - else if (capture == this) - { - mRoundedSquareImgp->drawSolid(mIt->second, curThumbColor); - } - else - { - mRoundedSquareImgp->drawSolid(mIt->second, curThumbColor % opacity); - } - } - - // draw cur and hover slider last - if(curSldrIt != mThumbRects.end()) - { - if (mThumbImagep) - { - if (getEnabled()) - { - mThumbImagep->draw(curSldrIt->second); - } - else - { - mThumbImagep->draw(curSldrIt->second, LLColor4::grey % 0.8f); - } - } - else if (capture == this) - { - mRoundedSquareImgp->drawSolid(curSldrIt->second, mThumbCenterSelectedColor.get()); - } - else - { - mRoundedSquareImgp->drawSolid(curSldrIt->second, mThumbCenterSelectedColor.get() % opacity); - } - } - if(hoverSldrIt != mThumbRects.end()) - { - if (mThumbImagep) - { - mThumbImagep->draw(hoverSldrIt->second); - } - else - { - mRoundedSquareImgp->drawSolid(hoverSldrIt->second, mThumbCenterSelectedColor.get()); - } - } - } - - LLF32UICtrl::draw(); -} -boost::signals2::connection LLMultiSlider::setMouseDownCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t(); - return mMouseDownSignal->connect(cb); -} - -boost::signals2::connection LLMultiSlider::setMouseUpCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mMouseUpSignal) mMouseUpSignal = new commit_signal_t(); - return mMouseUpSignal->connect(cb); -} +/** + * @file llmultisldr.cpp + * @brief LLMultiSlider base class + * + * $LicenseInfo:firstyear=2007&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 "llmultislider.h" +#include "llui.h" + +#include "llgl.h" +#include "llwindow.h" +#include "llfocusmgr.h" +#include "llkeyboard.h" // for the MASK constants +#include "llcontrol.h" +#include "lluictrlfactory.h" +#include "lluiimage.h" + +#include + +static LLDefaultChildRegistry::Register r("multi_slider_bar"); + +const F32 FLOAT_THRESHOLD = 0.00001f; + +S32 LLMultiSlider::mNameCounter = 0; + +LLMultiSlider::SliderParams::SliderParams() +: name("name"), + value("value", 0.f) +{ +} + +LLMultiSlider::Params::Params() +: max_sliders("max_sliders", 1), + allow_overlap("allow_overlap", false), + loop_overlap("loop_overlap", false), + orientation("orientation"), + overlap_threshold("overlap_threshold", 0), + draw_track("draw_track", true), + use_triangle("use_triangle", false), + track_color("track_color"), + thumb_disabled_color("thumb_disabled_color"), + thumb_highlight_color("thumb_highlight_color"), + thumb_outline_color("thumb_outline_color"), + thumb_center_color("thumb_center_color"), + thumb_center_selected_color("thumb_center_selected_color"), + thumb_image("thumb_image"), + triangle_color("triangle_color"), + mouse_down_callback("mouse_down_callback"), + mouse_up_callback("mouse_up_callback"), + thumb_width("thumb_width"), + sliders("slider") +{} + +LLMultiSlider::LLMultiSlider(const LLMultiSlider::Params& p) +: LLF32UICtrl(p), + mMouseOffset( 0 ), + mMaxNumSliders(p.max_sliders), + mAllowOverlap(p.allow_overlap), + mLoopOverlap(p.loop_overlap), + mDrawTrack(p.draw_track), + mUseTriangle(p.use_triangle), + mTrackColor(p.track_color()), + mThumbOutlineColor(p.thumb_outline_color()), + mThumbCenterColor(p.thumb_center_color()), + mThumbCenterSelectedColor(p.thumb_center_selected_color()), + mDisabledThumbColor(p.thumb_disabled_color()), + mTriangleColor(p.triangle_color()), + mThumbWidth(p.thumb_width), + mOrientation((p.orientation() == "vertical") ? VERTICAL : HORIZONTAL), + mMouseDownSignal(NULL), + mMouseUpSignal(NULL) +{ + mValue = LLSD::emptyMap(); + mCurSlider = LLStringUtil::null; + + if (mOrientation == HORIZONTAL) + { + mDragStartThumbRect = LLRect(0, getRect().getHeight(), p.thumb_width, 0); + } + else + { + mDragStartThumbRect = LLRect(0, p.thumb_width, getRect().getWidth(), 0); + } + + if (p.mouse_down_callback.isProvided()) + { + setMouseDownCallback(initCommitCallback(p.mouse_down_callback)); + } + if (p.mouse_up_callback.isProvided()) + { + setMouseUpCallback(initCommitCallback(p.mouse_up_callback)); + } + + if (p.overlap_threshold.isProvided() && p.overlap_threshold > mIncrement) + { + mOverlapThreshold = p.overlap_threshold - mIncrement; + } + else + { + mOverlapThreshold = 0; + } + + for (LLInitParam::ParamIterator::const_iterator it = p.sliders.begin(); + it != p.sliders.end(); + ++it) + { + if (it->name.isProvided()) + { + addSlider(it->value, it->name); + } + else + { + addSlider(it->value); + } + } + + mRoundedSquareImgp = LLUI::getUIImage("Rounded_Square"); + if (p.thumb_image.isProvided()) + { + mThumbImagep = LLUI::getUIImage(p.thumb_image()); + } + mThumbHighlightColor = p.thumb_highlight_color.isProvided() ? p.thumb_highlight_color() : static_cast(gFocusMgr.getFocusColor()); +} + +LLMultiSlider::~LLMultiSlider() +{ + delete mMouseDownSignal; + delete mMouseUpSignal; +} + +F32 LLMultiSlider::getNearestIncrement(F32 value) const +{ + value = llclamp(value, mMinValue, mMaxValue); + + // Round to nearest increment (bias towards rounding down) + value -= mMinValue; + value += mIncrement / 2.0001f; + value -= fmod(value, mIncrement); + return mMinValue + value; +} + +void LLMultiSlider::setSliderValue(const std::string& name, F32 value, bool from_event) +{ + // exit if not there + if(!mValue.has(name)) { + return; + } + + F32 newValue = getNearestIncrement(value); + + // now, make sure no overlap + // if we want that + if(!mAllowOverlap) { + bool hit = false; + + // look at the current spot + // and see if anything is there + LLSD::map_iterator mIt = mValue.beginMap(); + + // increment is our distance between points, use to eliminate round error + F32 threshold = mOverlapThreshold + (mIncrement / 4); + // If loop overlap is enabled, check if we overlap with points 'after' max value (project to lower) + F32 loop_up_check = (mLoopOverlap && (value + threshold) > mMaxValue) ? (value + threshold - mMaxValue + mMinValue) : mMinValue - 1.0f; + // If loop overlap is enabled, check if we overlap with points 'before' min value (project to upper) + F32 loop_down_check = (mLoopOverlap && (value - threshold) < mMinValue) ? (value - threshold - mMinValue + mMaxValue) : mMaxValue + 1.0f; + + for(;mIt != mValue.endMap(); mIt++) + { + F32 locationVal = (F32)mIt->second.asReal(); + // Check nearby values + F32 testVal = locationVal - newValue; + if (testVal > -threshold + && testVal < threshold + && mIt->first != name) + { + hit = true; + break; + } + if (mLoopOverlap) + { + // Check edge overlap values + if (locationVal < loop_up_check) + { + hit = true; + break; + } + if (locationVal > loop_down_check) + { + hit = true; + break; + } + } + } + + // if none found, stop + if(hit) { + return; + } + } + + + // now set it in the map + mValue[name] = newValue; + + // set the control if it's the current slider and not from an event + if (!from_event && name == mCurSlider) + { + setControlValue(mValue); + } + + F32 t = (newValue - mMinValue) / (mMaxValue - mMinValue); + if (mOrientation == HORIZONTAL) + { + S32 left_edge = mThumbWidth/2; + S32 right_edge = getRect().getWidth() - (mThumbWidth/2); + + S32 x = left_edge + S32( t * (right_edge - left_edge) ); + + mThumbRects[name].mLeft = x - (mThumbWidth / 2); + mThumbRects[name].mRight = x + (mThumbWidth / 2); + } + else + { + S32 bottom_edge = mThumbWidth/2; + S32 top_edge = getRect().getHeight() - (mThumbWidth/2); + + S32 x = bottom_edge + S32( t * (top_edge - bottom_edge) ); + + mThumbRects[name].mTop = x + (mThumbWidth / 2); + mThumbRects[name].mBottom = x - (mThumbWidth / 2); + } +} + +void LLMultiSlider::setValue(const LLSD& value) +{ + // only do if it's a map + if(value.isMap()) { + + // add each value... the first in the map becomes the current + LLSD::map_const_iterator mIt = value.beginMap(); + mCurSlider = mIt->first; + + for(; mIt != value.endMap(); mIt++) { + setSliderValue(mIt->first, (F32)mIt->second.asReal(), true); + } + } +} + +F32 LLMultiSlider::getSliderValue(const std::string& name) const +{ + if (mValue.has(name)) + { + return (F32)mValue[name].asReal(); + } + return 0; +} + +void LLMultiSlider::setCurSlider(const std::string& name) +{ + if(mValue.has(name)) { + mCurSlider = name; + } +} + +F32 LLMultiSlider::getSliderValueFromPos(S32 xpos, S32 ypos) const +{ + F32 t = 0; + if (mOrientation == HORIZONTAL) + { + S32 left_edge = mThumbWidth / 2; + S32 right_edge = getRect().getWidth() - (mThumbWidth / 2); + + xpos += mMouseOffset; + xpos = llclamp(xpos, left_edge, right_edge); + + t = F32(xpos - left_edge) / (right_edge - left_edge); + } + else + { + S32 bottom_edge = mThumbWidth / 2; + S32 top_edge = getRect().getHeight() - (mThumbWidth / 2); + + ypos += mMouseOffset; + ypos = llclamp(ypos, bottom_edge, top_edge); + + t = F32(ypos - bottom_edge) / (top_edge - bottom_edge); + } + + return((t * (mMaxValue - mMinValue)) + mMinValue); +} + + +LLRect LLMultiSlider::getSliderThumbRect(const std::string& name) const +{ + auto it = mThumbRects.find(name); + if (it != mThumbRects.end()) + return (*it).second; + return LLRect(); +} + +void LLMultiSlider::setSliderThumbImage(const std::string &name) +{ + if (!name.empty()) + { + mThumbImagep = LLUI::getUIImage(name); + } + else + clearSliderThumbImage(); +} + +void LLMultiSlider::clearSliderThumbImage() +{ + mThumbImagep = NULL; +} + +void LLMultiSlider::resetCurSlider() +{ + mCurSlider = LLStringUtil::null; +} + +const std::string& LLMultiSlider::addSlider() +{ + return addSlider(mInitialValue); +} + +const std::string& LLMultiSlider::addSlider(F32 val) +{ + std::stringstream newName; + F32 initVal = val; + + if(mValue.size() >= mMaxNumSliders) { + return LLStringUtil::null; + } + + // create a new name + newName << "sldr" << mNameCounter; + mNameCounter++; + + bool foundOne = findUnusedValue(initVal); + if(!foundOne) { + return LLStringUtil::null; + } + + // add a new thumb rect + if (mOrientation == HORIZONTAL) + { + mThumbRects[newName.str()] = LLRect(0, getRect().getHeight(), mThumbWidth, 0); + } + else + { + mThumbRects[newName.str()] = LLRect(0, mThumbWidth, getRect().getWidth(), 0); + } + + // add the value and set the current slider to this one + mValue.insert(newName.str(), initVal); + mCurSlider = newName.str(); + + // move the slider + setSliderValue(mCurSlider, initVal, true); + + return mCurSlider; +} + +bool LLMultiSlider::addSlider(F32 val, const std::string& name) +{ + F32 initVal = val; + + if(mValue.size() >= mMaxNumSliders) { + return false; + } + + bool foundOne = findUnusedValue(initVal); + if(!foundOne) { + return false; + } + + // add a new thumb rect + if (mOrientation == HORIZONTAL) + { + mThumbRects[name] = LLRect(0, getRect().getHeight(), mThumbWidth, 0); + } + else + { + mThumbRects[name] = LLRect(0, mThumbWidth, getRect().getWidth(), 0); + } + + // add the value and set the current slider to this one + mValue.insert(name, initVal); + mCurSlider = name; + + // move the slider + setSliderValue(mCurSlider, initVal, true); + + return true; +} + +bool LLMultiSlider::findUnusedValue(F32& initVal) +{ + bool firstTry = true; + + // find the first open slot starting with + // the initial value + while(true) { + + bool hit = false; + + // look at the current spot + // and see if anything is there + F32 threshold = mAllowOverlap ? FLOAT_THRESHOLD : mOverlapThreshold + (mIncrement / 4); + LLSD::map_iterator mIt = mValue.beginMap(); + for(;mIt != mValue.endMap(); mIt++) { + + F32 testVal = (F32)mIt->second.asReal() - initVal; + if(testVal > -threshold && testVal < threshold) + { + hit = true; + break; + } + } + + // if we found one + if(!hit) { + break; + } + + // increment and wrap if need be + initVal += mIncrement; + if(initVal > mMaxValue) { + initVal = mMinValue; + } + + // stop if it's filled + if(initVal == mInitialValue && !firstTry) { + LL_WARNS() << "Whoa! Too many multi slider elements to add one to" << LL_ENDL; + return false; + } + + firstTry = false; + continue; + } + + return true; +} + + +void LLMultiSlider::deleteSlider(const std::string& name) +{ + // can't delete last slider + if(mValue.size() <= 0) { + return; + } + + // get rid of value from mValue and its thumb rect + mValue.erase(name); + mThumbRects.erase(name); + + // set to the last created + if(mValue.size() > 0) { + std::map::iterator mIt = mThumbRects.end(); + mIt--; + mCurSlider = mIt->first; + } +} + +void LLMultiSlider::clear() +{ + while(mThumbRects.size() > 0 && mValue.size() > 0) { + deleteCurSlider(); + } + + if (mThumbRects.size() > 0 || mValue.size() > 0) + { + LL_WARNS() << "Failed to fully clear Multi slider" << LL_ENDL; + } + + LLF32UICtrl::clear(); +} + +bool LLMultiSlider::handleHover(S32 x, S32 y, MASK mask) +{ + if( gFocusMgr.getMouseCapture() == this ) + { + setCurSliderValue(getSliderValueFromPos(x, y)); + onCommit(); + + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; + } + else + { + if (getEnabled()) + { + mHoverSlider.clear(); + std::map::iterator mIt = mThumbRects.begin(); + for (; mIt != mThumbRects.end(); mIt++) + { + if (mIt->second.pointInRect(x, y)) + { + mHoverSlider = mIt->first; + break; + } + } + } + else + { + mHoverSlider.clear(); + } + + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; + } + return true; +} + +bool LLMultiSlider::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + if( gFocusMgr.getMouseCapture() == this ) + { + gFocusMgr.setMouseCapture( NULL ); + + if (mMouseUpSignal) + (*mMouseUpSignal)( this, LLSD() ); + + handled = true; + make_ui_sound("UISndClickRelease"); + } + else + { + handled = true; + } + + return handled; +} + +bool LLMultiSlider::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // only do sticky-focus on non-chrome widgets + if (!getIsChrome()) + { + setFocus(true); + } + if (mMouseDownSignal) + (*mMouseDownSignal)( this, LLSD() ); + + if (MASK_CONTROL & mask) // if CTRL is modifying + { + setCurSliderValue(mInitialValue); + onCommit(); + } + else + { + // scroll through thumbs to see if we have a new one selected and select that one + std::map::iterator mIt = mThumbRects.begin(); + for(; mIt != mThumbRects.end(); mIt++) { + + // check if inside. If so, set current slider and continue + if(mIt->second.pointInRect(x,y)) { + mCurSlider = mIt->first; + break; + } + } + + if (!mCurSlider.empty()) + { + // Find the offset of the actual mouse location from the center of the thumb. + if (mThumbRects[mCurSlider].pointInRect(x,y)) + { + if (mOrientation == HORIZONTAL) + { + mMouseOffset = (mThumbRects[mCurSlider].mLeft + mThumbWidth / 2) - x; + } + else + { + mMouseOffset = (mThumbRects[mCurSlider].mBottom + mThumbWidth / 2) - y; + } + } + else + { + mMouseOffset = 0; + } + + // Start dragging the thumb + // No handler needed for focus lost since this class has no state that depends on it. + gFocusMgr.setMouseCapture( this ); + mDragStartThumbRect = mThumbRects[mCurSlider]; + } + } + make_ui_sound("UISndClick"); + + return true; +} + +bool LLMultiSlider::handleKeyHere(KEY key, MASK mask) +{ + bool handled = false; + switch(key) + { + case KEY_UP: + case KEY_DOWN: + // eat up and down keys to be consistent + handled = true; + break; + case KEY_LEFT: + setCurSliderValue(getCurSliderValue() - getIncrement()); + onCommit(); + handled = true; + break; + case KEY_RIGHT: + setCurSliderValue(getCurSliderValue() + getIncrement()); + onCommit(); + handled = true; + break; + default: + break; + } + return handled; +} + +/*virtual*/ +void LLMultiSlider::onMouseLeave(S32 x, S32 y, MASK mask) +{ + mHoverSlider.clear(); + LLF32UICtrl::onMouseLeave(x, y, mask); +} + +void LLMultiSlider::draw() +{ + static LLUICachedControl extra_triangle_height ("UIExtraTriangleHeight", 0); + static LLUICachedControl extra_triangle_width ("UIExtraTriangleWidth", 0); + LLColor4 curThumbColor; + + std::map::iterator mIt; + std::map::iterator curSldrIt; + std::map::iterator hoverSldrIt; + + // Draw background and thumb. + + // drawing solids requires texturing be disabled + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLRect rect(mDragStartThumbRect); + + F32 opacity = getEnabled() ? 1.f : 0.3f; + + // Track + static LLUICachedControl multi_track_height_width ("UIMultiTrackHeight", 0); + S32 height_offset = 0; + S32 width_offset = 0; + if (mOrientation == HORIZONTAL) + { + height_offset = (getRect().getHeight() - multi_track_height_width) / 2; + } + else + { + width_offset = (getRect().getWidth() - multi_track_height_width) / 2; + } + LLRect track_rect(width_offset, getRect().getHeight() - height_offset, getRect().getWidth() - width_offset, height_offset); + + + if(mDrawTrack) + { + track_rect.stretch(-1); + mRoundedSquareImgp->draw(track_rect, mTrackColor.get() % opacity); + } + + // if we're supposed to use a drawn triangle + // simple gl call for the triangle + if(mUseTriangle) { + + for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) { + + gl_triangle_2d( + mIt->second.mLeft - extra_triangle_width, + mIt->second.mTop + extra_triangle_height, + mIt->second.mRight + extra_triangle_width, + mIt->second.mTop + extra_triangle_height, + mIt->second.mLeft + mIt->second.getWidth() / 2, + mIt->second.mBottom - extra_triangle_height, + mTriangleColor.get() % opacity, true); + } + } + else if (!mRoundedSquareImgp && !mThumbImagep) + { + // draw all the thumbs + curSldrIt = mThumbRects.end(); + hoverSldrIt = mThumbRects.end(); + for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) { + + // choose the color + curThumbColor = mThumbCenterColor.get(); + if(mIt->first == mCurSlider) { + + curSldrIt = mIt; + continue; + } + if (mIt->first == mHoverSlider && getEnabled() && gFocusMgr.getMouseCapture() != this) + { + // draw last, after current one + hoverSldrIt = mIt; + continue; + } + + // the draw command + gl_rect_2d(mIt->second, curThumbColor, true); + } + + // now draw the current and hover sliders + if(curSldrIt != mThumbRects.end()) + { + gl_rect_2d(curSldrIt->second, mThumbCenterSelectedColor.get(), true); + } + + // and draw the drag start + if (gFocusMgr.getMouseCapture() == this) + { + gl_rect_2d(mDragStartThumbRect, mThumbCenterColor.get() % opacity, false); + } + else if (hoverSldrIt != mThumbRects.end()) + { + gl_rect_2d(hoverSldrIt->second, mThumbCenterSelectedColor.get(), true); + } + } + else + { + LLMouseHandler* capture = gFocusMgr.getMouseCapture(); + if (capture == this) + { + // draw drag start (ghost) + if (mThumbImagep) + { + mThumbImagep->draw(mDragStartThumbRect, mThumbCenterColor.get() % 0.3f); + } + else + { + mRoundedSquareImgp->drawSolid(mDragStartThumbRect, mThumbCenterColor.get() % 0.3f); + } + } + + // draw the highlight + if (hasFocus()) + { + if (!mCurSlider.empty()) + { + if (mThumbImagep) + { + mThumbImagep->drawBorder(mThumbRects[mCurSlider], mThumbHighlightColor, gFocusMgr.getFocusFlashWidth()); + } + else + { + mRoundedSquareImgp->drawBorder(mThumbRects[mCurSlider], gFocusMgr.getFocusColor(), gFocusMgr.getFocusFlashWidth()); + } + } + } + if (!mHoverSlider.empty()) + { + if (mThumbImagep) + { + mThumbImagep->drawBorder(mThumbRects[mHoverSlider], mThumbHighlightColor, gFocusMgr.getFocusFlashWidth()); + } + else + { + mRoundedSquareImgp->drawBorder(mThumbRects[mHoverSlider], gFocusMgr.getFocusColor(), gFocusMgr.getFocusFlashWidth()); + } + } + + // draw the thumbs + curSldrIt = mThumbRects.end(); + hoverSldrIt = mThumbRects.end(); + for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) + { + // choose the color + curThumbColor = mThumbCenterColor.get(); + if(mIt->first == mCurSlider) + { + // don't draw now, draw last + curSldrIt = mIt; + continue; + } + if (mIt->first == mHoverSlider && getEnabled() && gFocusMgr.getMouseCapture() != this) + { + // don't draw now, draw last, after current one + hoverSldrIt = mIt; + continue; + } + + // the draw command + if (mThumbImagep) + { + if (getEnabled()) + { + mThumbImagep->draw(mIt->second); + } + else + { + mThumbImagep->draw(mIt->second, LLColor4::grey % 0.8f); + } + } + else if (capture == this) + { + mRoundedSquareImgp->drawSolid(mIt->second, curThumbColor); + } + else + { + mRoundedSquareImgp->drawSolid(mIt->second, curThumbColor % opacity); + } + } + + // draw cur and hover slider last + if(curSldrIt != mThumbRects.end()) + { + if (mThumbImagep) + { + if (getEnabled()) + { + mThumbImagep->draw(curSldrIt->second); + } + else + { + mThumbImagep->draw(curSldrIt->second, LLColor4::grey % 0.8f); + } + } + else if (capture == this) + { + mRoundedSquareImgp->drawSolid(curSldrIt->second, mThumbCenterSelectedColor.get()); + } + else + { + mRoundedSquareImgp->drawSolid(curSldrIt->second, mThumbCenterSelectedColor.get() % opacity); + } + } + if(hoverSldrIt != mThumbRects.end()) + { + if (mThumbImagep) + { + mThumbImagep->draw(hoverSldrIt->second); + } + else + { + mRoundedSquareImgp->drawSolid(hoverSldrIt->second, mThumbCenterSelectedColor.get()); + } + } + } + + LLF32UICtrl::draw(); +} +boost::signals2::connection LLMultiSlider::setMouseDownCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t(); + return mMouseDownSignal->connect(cb); +} + +boost::signals2::connection LLMultiSlider::setMouseUpCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseUpSignal) mMouseUpSignal = new commit_signal_t(); + return mMouseUpSignal->connect(cb); +} diff --git a/indra/llui/llmultislider.h b/indra/llui/llmultislider.h index e0ac563e75..630e45dddb 100644 --- a/indra/llui/llmultislider.h +++ b/indra/llui/llmultislider.h @@ -1,161 +1,161 @@ -/** - * @file llmultislider.h - * @brief A simple multislider - * - * $LicenseInfo:firstyear=2007&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$ - */ - -#ifndef LL_MULTI_SLIDER_H -#define LL_MULTI_SLIDER_H - -#include "llf32uictrl.h" -#include "v4color.h" - -class LLUICtrlFactory; - -class LLMultiSlider : public LLF32UICtrl -{ -public: - struct SliderParams : public LLInitParam::Block - { - Optional name; - Mandatory value; - SliderParams(); - }; - - struct Params : public LLInitParam::Block - { - Optional max_sliders; - - Optional allow_overlap, - loop_overlap, - draw_track, - use_triangle; - - Optional overlap_threshold; - - Optional track_color, - thumb_disabled_color, - thumb_highlight_color, - thumb_outline_color, - thumb_center_color, - thumb_center_selected_color, - triangle_color; - - Optional orientation, - thumb_image; - - Optional mouse_down_callback, - mouse_up_callback; - Optional thumb_width; - - Multiple sliders; - Params(); - }; - -protected: - LLMultiSlider(const Params&); - friend class LLUICtrlFactory; -public: - virtual ~LLMultiSlider(); - - // Multi-slider rounds values to nearest increments (bias towards rounding down) - F32 getNearestIncrement(F32 value) const; - - void setSliderValue(const std::string& name, F32 value, bool from_event = false); - F32 getSliderValue(const std::string& name) const; - F32 getSliderValueFromPos(S32 xpos, S32 ypos) const; - LLRect getSliderThumbRect(const std::string& name) const; - - void setSliderThumbImage(const std::string &name); - void clearSliderThumbImage(); - - - const std::string& getCurSlider() const { return mCurSlider; } - F32 getCurSliderValue() const { return getSliderValue(mCurSlider); } - void setCurSlider(const std::string& name); - void resetCurSlider(); - void setCurSliderValue(F32 val, bool from_event = false) { setSliderValue(mCurSlider, val, from_event); } - - /*virtual*/ void setValue(const LLSD& value) override; - /*virtual*/ LLSD getValue() const override { return mValue; } - - boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ); - boost::signals2::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ); - - bool findUnusedValue(F32& initVal); - const std::string& addSlider(); - const std::string& addSlider(F32 val); - bool addSlider(F32 val, const std::string& name); - void deleteSlider(const std::string& name); - void deleteCurSlider() { deleteSlider(mCurSlider); } - /*virtual*/ void clear() override; - - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleKeyHere(KEY key, MASK mask) override; - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask) override; - /*virtual*/ void draw() override; - - S32 getMaxNumSliders() { return mMaxNumSliders; } - S32 getCurNumSliders() { return mValue.size(); } - F32 getOverlapThreshold() { return mOverlapThreshold; } - bool canAddSliders() { return mValue.size() < mMaxNumSliders; } - - -protected: - LLSD mValue; - std::string mCurSlider; - std::string mHoverSlider; - static S32 mNameCounter; - - S32 mMaxNumSliders; - bool mAllowOverlap; - bool mLoopOverlap; - F32 mOverlapThreshold; - bool mDrawTrack; - bool mUseTriangle; /// hacked in toggle to use a triangle - - S32 mMouseOffset; - LLRect mDragStartThumbRect; - S32 mThumbWidth; - - std::map - mThumbRects; - LLUIColor mTrackColor; - LLUIColor mThumbOutlineColor; - LLUIColor mThumbHighlightColor; - LLUIColor mThumbCenterColor; - LLUIColor mThumbCenterSelectedColor; - LLUIColor mDisabledThumbColor; - LLUIColor mTriangleColor; - LLUIImagePtr mThumbImagep; //blimps on the slider, for now no 'disabled' support - LLUIImagePtr mRoundedSquareImgp; //blimps on the slider, for now no 'disabled' support - - const EOrientation mOrientation; - - commit_signal_t* mMouseDownSignal; - commit_signal_t* mMouseUpSignal; -}; - -#endif // LL_MULTI_SLIDER_H +/** + * @file llmultislider.h + * @brief A simple multislider + * + * $LicenseInfo:firstyear=2007&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$ + */ + +#ifndef LL_MULTI_SLIDER_H +#define LL_MULTI_SLIDER_H + +#include "llf32uictrl.h" +#include "v4color.h" + +class LLUICtrlFactory; + +class LLMultiSlider : public LLF32UICtrl +{ +public: + struct SliderParams : public LLInitParam::Block + { + Optional name; + Mandatory value; + SliderParams(); + }; + + struct Params : public LLInitParam::Block + { + Optional max_sliders; + + Optional allow_overlap, + loop_overlap, + draw_track, + use_triangle; + + Optional overlap_threshold; + + Optional track_color, + thumb_disabled_color, + thumb_highlight_color, + thumb_outline_color, + thumb_center_color, + thumb_center_selected_color, + triangle_color; + + Optional orientation, + thumb_image; + + Optional mouse_down_callback, + mouse_up_callback; + Optional thumb_width; + + Multiple sliders; + Params(); + }; + +protected: + LLMultiSlider(const Params&); + friend class LLUICtrlFactory; +public: + virtual ~LLMultiSlider(); + + // Multi-slider rounds values to nearest increments (bias towards rounding down) + F32 getNearestIncrement(F32 value) const; + + void setSliderValue(const std::string& name, F32 value, bool from_event = false); + F32 getSliderValue(const std::string& name) const; + F32 getSliderValueFromPos(S32 xpos, S32 ypos) const; + LLRect getSliderThumbRect(const std::string& name) const; + + void setSliderThumbImage(const std::string &name); + void clearSliderThumbImage(); + + + const std::string& getCurSlider() const { return mCurSlider; } + F32 getCurSliderValue() const { return getSliderValue(mCurSlider); } + void setCurSlider(const std::string& name); + void resetCurSlider(); + void setCurSliderValue(F32 val, bool from_event = false) { setSliderValue(mCurSlider, val, from_event); } + + /*virtual*/ void setValue(const LLSD& value) override; + /*virtual*/ LLSD getValue() const override { return mValue; } + + boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ); + + bool findUnusedValue(F32& initVal); + const std::string& addSlider(); + const std::string& addSlider(F32 val); + bool addSlider(F32 val, const std::string& name); + void deleteSlider(const std::string& name); + void deleteCurSlider() { deleteSlider(mCurSlider); } + /*virtual*/ void clear() override; + + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleKeyHere(KEY key, MASK mask) override; + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask) override; + /*virtual*/ void draw() override; + + S32 getMaxNumSliders() { return mMaxNumSliders; } + S32 getCurNumSliders() { return mValue.size(); } + F32 getOverlapThreshold() { return mOverlapThreshold; } + bool canAddSliders() { return mValue.size() < mMaxNumSliders; } + + +protected: + LLSD mValue; + std::string mCurSlider; + std::string mHoverSlider; + static S32 mNameCounter; + + S32 mMaxNumSliders; + bool mAllowOverlap; + bool mLoopOverlap; + F32 mOverlapThreshold; + bool mDrawTrack; + bool mUseTriangle; /// hacked in toggle to use a triangle + + S32 mMouseOffset; + LLRect mDragStartThumbRect; + S32 mThumbWidth; + + std::map + mThumbRects; + LLUIColor mTrackColor; + LLUIColor mThumbOutlineColor; + LLUIColor mThumbHighlightColor; + LLUIColor mThumbCenterColor; + LLUIColor mThumbCenterSelectedColor; + LLUIColor mDisabledThumbColor; + LLUIColor mTriangleColor; + LLUIImagePtr mThumbImagep; //blimps on the slider, for now no 'disabled' support + LLUIImagePtr mRoundedSquareImgp; //blimps on the slider, for now no 'disabled' support + + const EOrientation mOrientation; + + commit_signal_t* mMouseDownSignal; + commit_signal_t* mMouseUpSignal; +}; + +#endif // LL_MULTI_SLIDER_H diff --git a/indra/llui/llmultisliderctrl.cpp b/indra/llui/llmultisliderctrl.cpp index 879fdde254..01e2fc6ac9 100644 --- a/indra/llui/llmultisliderctrl.cpp +++ b/indra/llui/llmultisliderctrl.cpp @@ -1,525 +1,525 @@ -/** - * @file llmultisliderctrl.cpp - * @brief LLMultiSliderCtrl base class - * - * $LicenseInfo:firstyear=2007&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 "llmultisliderctrl.h" - -#include "llmath.h" -#include "llfontgl.h" -#include "llgl.h" -#include "llkeyboard.h" -#include "lllineeditor.h" -#include "llmultislider.h" -#include "llstring.h" -#include "lltextbox.h" -#include "llui.h" -#include "lluiconstants.h" -#include "llcontrol.h" -#include "llfocusmgr.h" -#include "llresmgr.h" -#include "lluictrlfactory.h" - -static LLDefaultChildRegistry::Register r("multi_slider"); - -const U32 MAX_STRING_LENGTH = 10; -LLMultiSliderCtrl::Params::Params() -: text_width("text_width"), - label_width("label_width"), - show_text("show_text", true), - can_edit_text("can_edit_text", false), - max_sliders("max_sliders", 1), - allow_overlap("allow_overlap", false), - loop_overlap("loop_overlap", false), - orientation("orientation"), - thumb_image("thumb_image"), - thumb_width("thumb_width"), - thumb_highlight_color("thumb_highlight_color"), - overlap_threshold("overlap_threshold", 0), - draw_track("draw_track", true), - use_triangle("use_triangle", false), - decimal_digits("decimal_digits", 3), - text_color("text_color"), - text_disabled_color("text_disabled_color"), - mouse_down_callback("mouse_down_callback"), - mouse_up_callback("mouse_up_callback"), - sliders("slider") -{ - mouse_opaque = true; -} - -LLMultiSliderCtrl::LLMultiSliderCtrl(const LLMultiSliderCtrl::Params& p) -: LLF32UICtrl(p), - mLabelBox( NULL ), - mEditor( NULL ), - mTextBox( NULL ), - mTextEnabledColor(p.text_color()), - mTextDisabledColor(p.text_disabled_color()) -{ - static LLUICachedControl multi_sliderctrl_spacing ("UIMultiSliderctrlSpacing", 0); - - S32 top = getRect().getHeight(); - S32 bottom = 0; - S32 left = 0; - - S32 label_width = p.label_width; - S32 text_width = p.text_width; - - // Label - if( !p.label().empty() ) - { - if (p.label_width == 0) - { - label_width = p.font()->getWidth(p.label); - } - LLRect label_rect( left, top, label_width, bottom ); - LLTextBox::Params params; - params.name("MultiSliderCtrl Label"); - params.rect(label_rect); - params.initial_value(p.label()); - params.font(p.font); - mLabelBox = LLUICtrlFactory::create (params); - addChild(mLabelBox); - } - - S32 slider_right = getRect().getWidth(); - - if (p.show_text) - { - if (!p.text_width.isProvided()) - { - text_width = 0; - // calculate the size of the text box (log max_value is number of digits - 1 so plus 1) - if ( p.max_value() ) - text_width = p.font()->getWidth(std::string("0")) * ( static_cast < S32 > ( log10 ( p.max_value ) ) + p.decimal_digits + 1 ); - - if ( p.increment < 1.0f ) - text_width += p.font()->getWidth(std::string(".")); // (mostly) take account of decimal point in value - - if ( p.min_value < 0.0f || p.max_value < 0.0f ) - text_width += p.font()->getWidth(std::string("-")); // (mostly) take account of minus sign - - // padding to make things look nicer - text_width += 8; - } - S32 text_left = getRect().getWidth() - text_width; - - slider_right = text_left - multi_sliderctrl_spacing; - - LLRect text_rect( text_left, top, getRect().getWidth(), bottom ); - if( p.can_edit_text ) - { - LLLineEditor::Params params; - params.name("MultiSliderCtrl Editor"); - params.rect(text_rect); - params.font(p.font); - params.max_length.bytes(MAX_STRING_LENGTH); - params.commit_callback.function(LLMultiSliderCtrl::onEditorCommit); - params.prevalidator(&LLTextValidate::validateFloat); - params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); - mEditor = LLUICtrlFactory::create (params); - mEditor->setFocusReceivedCallback( boost::bind(LLMultiSliderCtrl::onEditorGainFocus, _1, this) ); - // don't do this, as selecting the entire text is single clicking in some cases - // and double clicking in others - //mEditor->setSelectAllonFocusReceived(true); - addChild(mEditor); - } - else - { - LLTextBox::Params params; - params.name("MultiSliderCtrl Text"); - params.rect(text_rect); - params.font(p.font); - params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); - mTextBox = LLUICtrlFactory::create (params); - addChild(mTextBox); - } - } - - S32 slider_left = label_width ? label_width + multi_sliderctrl_spacing : 0; - LLRect slider_rect( slider_left, top, slider_right, bottom ); - LLMultiSlider::Params params; - params.sliders = p.sliders; - params.rect(slider_rect); - params.commit_callback.function( LLMultiSliderCtrl::onSliderCommit ); - params.mouse_down_callback( p.mouse_down_callback ); - params.mouse_up_callback( p.mouse_up_callback ); - params.initial_value(p.initial_value()); - params.min_value(p.min_value); - params.max_value(p.max_value); - params.increment(p.increment); - params.max_sliders(p.max_sliders); - params.allow_overlap(p.allow_overlap); - params.loop_overlap(p.loop_overlap); - if (p.overlap_threshold.isProvided()) - { - params.overlap_threshold = p.overlap_threshold; - } - params.orientation(p.orientation); - params.thumb_image(p.thumb_image); - params.thumb_highlight_color(p.thumb_highlight_color); - if (p.thumb_width.isProvided()) - { - // otherwise should be provided by template - params.thumb_width(p.thumb_width); - } - params.draw_track(p.draw_track); - params.use_triangle(p.use_triangle); - params.control_name(p.control_name); - mMultiSlider = LLUICtrlFactory::create (params); - addChild( mMultiSlider ); - mCurValue = mMultiSlider->getCurSliderValue(); - - - updateText(); -} - -LLMultiSliderCtrl::~LLMultiSliderCtrl() -{ - // Children all cleaned up by default view destructor. -} - -// static -void LLMultiSliderCtrl::onEditorGainFocus( LLFocusableElement* caller, void *userdata ) -{ - LLMultiSliderCtrl* self = (LLMultiSliderCtrl*) userdata; - llassert( caller == self->mEditor ); - - self->onFocusReceived(); -} - - -void LLMultiSliderCtrl::setValue(const LLSD& value) -{ - mMultiSlider->setValue(value); - mCurValue = mMultiSlider->getCurSliderValue(); - updateText(); -} - -void LLMultiSliderCtrl::setSliderValue(const std::string& name, F32 v, bool from_event) -{ - mMultiSlider->setSliderValue(name, v, from_event ); - mCurValue = mMultiSlider->getCurSliderValue(); - updateText(); -} - -void LLMultiSliderCtrl::setCurSlider(const std::string& name) -{ - mMultiSlider->setCurSlider(name); - mCurValue = mMultiSlider->getCurSliderValue(); -} - -void LLMultiSliderCtrl::resetCurSlider() -{ - mMultiSlider->resetCurSlider(); -} - -bool LLMultiSliderCtrl::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - bool res = false; - if (mLabelBox) - { - res = mLabelBox->setTextArg(key, text); - if (res && mLabelWidth == 0) - { - S32 label_width = mFont->getWidth(mLabelBox->getText()); - LLRect rect = mLabelBox->getRect(); - S32 prev_right = rect.mRight; - rect.mRight = rect.mLeft + label_width; - mLabelBox->setRect(rect); - - S32 delta = rect.mRight - prev_right; - rect = mMultiSlider->getRect(); - S32 left = rect.mLeft + delta; - static LLUICachedControl multi_slider_ctrl_spacing ("UIMultiSliderctrlSpacing", 0); - left = llclamp(left, 0, rect.mRight - multi_slider_ctrl_spacing); - rect.mLeft = left; - mMultiSlider->setRect(rect); - } - } - return res; -} - -const std::string& LLMultiSliderCtrl::addSlider() -{ - const std::string& name = mMultiSlider->addSlider(); - - // if it returns null, pass it on - if(name == LLStringUtil::null) { - return LLStringUtil::null; - } - - // otherwise, update stuff - mCurValue = mMultiSlider->getCurSliderValue(); - updateText(); - return name; -} - -const std::string& LLMultiSliderCtrl::addSlider(F32 val) -{ - const std::string& name = mMultiSlider->addSlider(val); - - // if it returns null, pass it on - if(name == LLStringUtil::null) { - return LLStringUtil::null; - } - - // otherwise, update stuff - mCurValue = mMultiSlider->getCurSliderValue(); - updateText(); - return name; -} - -bool LLMultiSliderCtrl::addSlider(F32 val, const std::string& name) -{ - bool res = mMultiSlider->addSlider(val, name); - if (res) - { - mCurValue = mMultiSlider->getCurSliderValue(); - updateText(); - } - return res; -} - -void LLMultiSliderCtrl::deleteSlider(const std::string& name) -{ - mMultiSlider->deleteSlider(name); - mCurValue = mMultiSlider->getCurSliderValue(); - updateText(); -} - - -void LLMultiSliderCtrl::clear() -{ - setCurSliderValue(0.0f); - if( mEditor ) - { - mEditor->setText(std::string("")); - } - if( mTextBox ) - { - mTextBox->setText(std::string("")); - } - - // get rid of sliders - mMultiSlider->clear(); - -} - -bool LLMultiSliderCtrl::isMouseHeldDown() -{ - return gFocusMgr.getMouseCapture() == mMultiSlider; -} - -void LLMultiSliderCtrl::updateText() -{ - if( mEditor || mTextBox ) - { - LLLocale locale(LLLocale::USER_LOCALE); - - // Don't display very small negative values as -0.000 - F32 displayed_value = (F32)(floor(getCurSliderValue() * pow(10.0, (F64)mPrecision) + 0.5) / pow(10.0, (F64)mPrecision)); - - std::string format = llformat("%%.%df", mPrecision); - std::string text = llformat(format.c_str(), displayed_value); - if( mEditor ) - { - mEditor->setText( text ); - } - else - { - mTextBox->setText( text ); - } - } -} - -// static -void LLMultiSliderCtrl::onEditorCommit( LLUICtrl* ctrl, const LLSD& userdata) -{ - llassert(ctrl); - if (!ctrl) - return; - - LLMultiSliderCtrl* self = dynamic_cast(ctrl->getParent()); - llassert(self); - if (!self) // cast failed - wrong type! :O - return; - - bool success = false; - F32 val = self->mCurValue; - F32 saved_val = self->mCurValue; - - std::string text = self->mEditor->getText(); - if( LLLineEditor::postvalidateFloat( text ) ) - { - LLLocale locale(LLLocale::USER_LOCALE); - val = (F32) atof( text.c_str() ); - if( self->mMultiSlider->getMinValue() <= val && val <= self->mMultiSlider->getMaxValue() ) - { - self->setCurSliderValue( val ); // set the value temporarily so that the callback can retrieve it. - if( !self->mValidateSignal || (*(self->mValidateSignal))( self, val ) ) - { - success = true; - } - } - } - - if( success ) - { - self->onCommit(); - } - else - { - if( self->getCurSliderValue() != saved_val ) - { - self->setCurSliderValue( saved_val ); - } - self->reportInvalidData(); - } - self->updateText(); -} - -// static -void LLMultiSliderCtrl::onSliderCommit(LLUICtrl* ctrl, const LLSD& userdata) -{ - LLMultiSliderCtrl* self = dynamic_cast(ctrl->getParent()); - if (!self) - return; - - bool success = false; - F32 saved_val = self->mCurValue; - F32 new_val = self->mMultiSlider->getCurSliderValue(); - - self->mCurValue = new_val; // set the value temporarily so that the callback can retrieve it. - if( !self->mValidateSignal || (*(self->mValidateSignal))( self, new_val ) ) - { - success = true; - } - - if( success ) - { - self->onCommit(); - } - else - { - if( self->mCurValue != saved_val ) - { - self->setCurSliderValue( saved_val ); - } - self->reportInvalidData(); - } - self->updateText(); -} - -void LLMultiSliderCtrl::setEnabled(bool b) -{ - LLF32UICtrl::setEnabled( b ); - - if( mLabelBox ) - { - mLabelBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); - } - - mMultiSlider->setEnabled( b ); - - if( mEditor ) - { - mEditor->setEnabled( b ); - } - - if( mTextBox ) - { - mTextBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); - } -} - - -void LLMultiSliderCtrl::setTentative(bool b) -{ - if( mEditor ) - { - mEditor->setTentative(b); - } - LLF32UICtrl::setTentative(b); -} - - -void LLMultiSliderCtrl::onCommit() -{ - setTentative(false); - - if( mEditor ) - { - mEditor->setTentative(false); - } - - setControlValue(getValueF32()); - LLF32UICtrl::onCommit(); -} - - -void LLMultiSliderCtrl::setPrecision(S32 precision) -{ - if (precision < 0 || precision > 10) - { - LL_ERRS() << "LLMultiSliderCtrl::setPrecision - precision out of range" << LL_ENDL; - return; - } - - mPrecision = precision; - updateText(); -} - -boost::signals2::connection LLMultiSliderCtrl::setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ) -{ - return mMultiSlider->setMouseDownCallback( cb ); -} - -boost::signals2::connection LLMultiSliderCtrl::setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ) -{ - return mMultiSlider->setMouseUpCallback( cb ); -} - -void LLMultiSliderCtrl::onTabInto() -{ - if( mEditor ) - { - mEditor->onTabInto(); - } - LLF32UICtrl::onTabInto(); -} - -void LLMultiSliderCtrl::reportInvalidData() -{ - make_ui_sound("UISndBadKeystroke"); -} - -// virtual -void LLMultiSliderCtrl::setControlName(const std::string& control_name, LLView* context) -{ - mMultiSlider->setControlName(control_name, context); -} - +/** + * @file llmultisliderctrl.cpp + * @brief LLMultiSliderCtrl base class + * + * $LicenseInfo:firstyear=2007&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 "llmultisliderctrl.h" + +#include "llmath.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llmultislider.h" +#include "llstring.h" +#include "lltextbox.h" +#include "llui.h" +#include "lluiconstants.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "llresmgr.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register r("multi_slider"); + +const U32 MAX_STRING_LENGTH = 10; +LLMultiSliderCtrl::Params::Params() +: text_width("text_width"), + label_width("label_width"), + show_text("show_text", true), + can_edit_text("can_edit_text", false), + max_sliders("max_sliders", 1), + allow_overlap("allow_overlap", false), + loop_overlap("loop_overlap", false), + orientation("orientation"), + thumb_image("thumb_image"), + thumb_width("thumb_width"), + thumb_highlight_color("thumb_highlight_color"), + overlap_threshold("overlap_threshold", 0), + draw_track("draw_track", true), + use_triangle("use_triangle", false), + decimal_digits("decimal_digits", 3), + text_color("text_color"), + text_disabled_color("text_disabled_color"), + mouse_down_callback("mouse_down_callback"), + mouse_up_callback("mouse_up_callback"), + sliders("slider") +{ + mouse_opaque = true; +} + +LLMultiSliderCtrl::LLMultiSliderCtrl(const LLMultiSliderCtrl::Params& p) +: LLF32UICtrl(p), + mLabelBox( NULL ), + mEditor( NULL ), + mTextBox( NULL ), + mTextEnabledColor(p.text_color()), + mTextDisabledColor(p.text_disabled_color()) +{ + static LLUICachedControl multi_sliderctrl_spacing ("UIMultiSliderctrlSpacing", 0); + + S32 top = getRect().getHeight(); + S32 bottom = 0; + S32 left = 0; + + S32 label_width = p.label_width; + S32 text_width = p.text_width; + + // Label + if( !p.label().empty() ) + { + if (p.label_width == 0) + { + label_width = p.font()->getWidth(p.label); + } + LLRect label_rect( left, top, label_width, bottom ); + LLTextBox::Params params; + params.name("MultiSliderCtrl Label"); + params.rect(label_rect); + params.initial_value(p.label()); + params.font(p.font); + mLabelBox = LLUICtrlFactory::create (params); + addChild(mLabelBox); + } + + S32 slider_right = getRect().getWidth(); + + if (p.show_text) + { + if (!p.text_width.isProvided()) + { + text_width = 0; + // calculate the size of the text box (log max_value is number of digits - 1 so plus 1) + if ( p.max_value() ) + text_width = p.font()->getWidth(std::string("0")) * ( static_cast < S32 > ( log10 ( p.max_value ) ) + p.decimal_digits + 1 ); + + if ( p.increment < 1.0f ) + text_width += p.font()->getWidth(std::string(".")); // (mostly) take account of decimal point in value + + if ( p.min_value < 0.0f || p.max_value < 0.0f ) + text_width += p.font()->getWidth(std::string("-")); // (mostly) take account of minus sign + + // padding to make things look nicer + text_width += 8; + } + S32 text_left = getRect().getWidth() - text_width; + + slider_right = text_left - multi_sliderctrl_spacing; + + LLRect text_rect( text_left, top, getRect().getWidth(), bottom ); + if( p.can_edit_text ) + { + LLLineEditor::Params params; + params.name("MultiSliderCtrl Editor"); + params.rect(text_rect); + params.font(p.font); + params.max_length.bytes(MAX_STRING_LENGTH); + params.commit_callback.function(LLMultiSliderCtrl::onEditorCommit); + params.prevalidator(&LLTextValidate::validateFloat); + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + mEditor = LLUICtrlFactory::create (params); + mEditor->setFocusReceivedCallback( boost::bind(LLMultiSliderCtrl::onEditorGainFocus, _1, this) ); + // don't do this, as selecting the entire text is single clicking in some cases + // and double clicking in others + //mEditor->setSelectAllonFocusReceived(true); + addChild(mEditor); + } + else + { + LLTextBox::Params params; + params.name("MultiSliderCtrl Text"); + params.rect(text_rect); + params.font(p.font); + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + mTextBox = LLUICtrlFactory::create (params); + addChild(mTextBox); + } + } + + S32 slider_left = label_width ? label_width + multi_sliderctrl_spacing : 0; + LLRect slider_rect( slider_left, top, slider_right, bottom ); + LLMultiSlider::Params params; + params.sliders = p.sliders; + params.rect(slider_rect); + params.commit_callback.function( LLMultiSliderCtrl::onSliderCommit ); + params.mouse_down_callback( p.mouse_down_callback ); + params.mouse_up_callback( p.mouse_up_callback ); + params.initial_value(p.initial_value()); + params.min_value(p.min_value); + params.max_value(p.max_value); + params.increment(p.increment); + params.max_sliders(p.max_sliders); + params.allow_overlap(p.allow_overlap); + params.loop_overlap(p.loop_overlap); + if (p.overlap_threshold.isProvided()) + { + params.overlap_threshold = p.overlap_threshold; + } + params.orientation(p.orientation); + params.thumb_image(p.thumb_image); + params.thumb_highlight_color(p.thumb_highlight_color); + if (p.thumb_width.isProvided()) + { + // otherwise should be provided by template + params.thumb_width(p.thumb_width); + } + params.draw_track(p.draw_track); + params.use_triangle(p.use_triangle); + params.control_name(p.control_name); + mMultiSlider = LLUICtrlFactory::create (params); + addChild( mMultiSlider ); + mCurValue = mMultiSlider->getCurSliderValue(); + + + updateText(); +} + +LLMultiSliderCtrl::~LLMultiSliderCtrl() +{ + // Children all cleaned up by default view destructor. +} + +// static +void LLMultiSliderCtrl::onEditorGainFocus( LLFocusableElement* caller, void *userdata ) +{ + LLMultiSliderCtrl* self = (LLMultiSliderCtrl*) userdata; + llassert( caller == self->mEditor ); + + self->onFocusReceived(); +} + + +void LLMultiSliderCtrl::setValue(const LLSD& value) +{ + mMultiSlider->setValue(value); + mCurValue = mMultiSlider->getCurSliderValue(); + updateText(); +} + +void LLMultiSliderCtrl::setSliderValue(const std::string& name, F32 v, bool from_event) +{ + mMultiSlider->setSliderValue(name, v, from_event ); + mCurValue = mMultiSlider->getCurSliderValue(); + updateText(); +} + +void LLMultiSliderCtrl::setCurSlider(const std::string& name) +{ + mMultiSlider->setCurSlider(name); + mCurValue = mMultiSlider->getCurSliderValue(); +} + +void LLMultiSliderCtrl::resetCurSlider() +{ + mMultiSlider->resetCurSlider(); +} + +bool LLMultiSliderCtrl::setLabelArg( const std::string& key, const LLStringExplicit& text ) +{ + bool res = false; + if (mLabelBox) + { + res = mLabelBox->setTextArg(key, text); + if (res && mLabelWidth == 0) + { + S32 label_width = mFont->getWidth(mLabelBox->getText()); + LLRect rect = mLabelBox->getRect(); + S32 prev_right = rect.mRight; + rect.mRight = rect.mLeft + label_width; + mLabelBox->setRect(rect); + + S32 delta = rect.mRight - prev_right; + rect = mMultiSlider->getRect(); + S32 left = rect.mLeft + delta; + static LLUICachedControl multi_slider_ctrl_spacing ("UIMultiSliderctrlSpacing", 0); + left = llclamp(left, 0, rect.mRight - multi_slider_ctrl_spacing); + rect.mLeft = left; + mMultiSlider->setRect(rect); + } + } + return res; +} + +const std::string& LLMultiSliderCtrl::addSlider() +{ + const std::string& name = mMultiSlider->addSlider(); + + // if it returns null, pass it on + if(name == LLStringUtil::null) { + return LLStringUtil::null; + } + + // otherwise, update stuff + mCurValue = mMultiSlider->getCurSliderValue(); + updateText(); + return name; +} + +const std::string& LLMultiSliderCtrl::addSlider(F32 val) +{ + const std::string& name = mMultiSlider->addSlider(val); + + // if it returns null, pass it on + if(name == LLStringUtil::null) { + return LLStringUtil::null; + } + + // otherwise, update stuff + mCurValue = mMultiSlider->getCurSliderValue(); + updateText(); + return name; +} + +bool LLMultiSliderCtrl::addSlider(F32 val, const std::string& name) +{ + bool res = mMultiSlider->addSlider(val, name); + if (res) + { + mCurValue = mMultiSlider->getCurSliderValue(); + updateText(); + } + return res; +} + +void LLMultiSliderCtrl::deleteSlider(const std::string& name) +{ + mMultiSlider->deleteSlider(name); + mCurValue = mMultiSlider->getCurSliderValue(); + updateText(); +} + + +void LLMultiSliderCtrl::clear() +{ + setCurSliderValue(0.0f); + if( mEditor ) + { + mEditor->setText(std::string("")); + } + if( mTextBox ) + { + mTextBox->setText(std::string("")); + } + + // get rid of sliders + mMultiSlider->clear(); + +} + +bool LLMultiSliderCtrl::isMouseHeldDown() +{ + return gFocusMgr.getMouseCapture() == mMultiSlider; +} + +void LLMultiSliderCtrl::updateText() +{ + if( mEditor || mTextBox ) + { + LLLocale locale(LLLocale::USER_LOCALE); + + // Don't display very small negative values as -0.000 + F32 displayed_value = (F32)(floor(getCurSliderValue() * pow(10.0, (F64)mPrecision) + 0.5) / pow(10.0, (F64)mPrecision)); + + std::string format = llformat("%%.%df", mPrecision); + std::string text = llformat(format.c_str(), displayed_value); + if( mEditor ) + { + mEditor->setText( text ); + } + else + { + mTextBox->setText( text ); + } + } +} + +// static +void LLMultiSliderCtrl::onEditorCommit( LLUICtrl* ctrl, const LLSD& userdata) +{ + llassert(ctrl); + if (!ctrl) + return; + + LLMultiSliderCtrl* self = dynamic_cast(ctrl->getParent()); + llassert(self); + if (!self) // cast failed - wrong type! :O + return; + + bool success = false; + F32 val = self->mCurValue; + F32 saved_val = self->mCurValue; + + std::string text = self->mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) + { + LLLocale locale(LLLocale::USER_LOCALE); + val = (F32) atof( text.c_str() ); + if( self->mMultiSlider->getMinValue() <= val && val <= self->mMultiSlider->getMaxValue() ) + { + self->setCurSliderValue( val ); // set the value temporarily so that the callback can retrieve it. + if( !self->mValidateSignal || (*(self->mValidateSignal))( self, val ) ) + { + success = true; + } + } + } + + if( success ) + { + self->onCommit(); + } + else + { + if( self->getCurSliderValue() != saved_val ) + { + self->setCurSliderValue( saved_val ); + } + self->reportInvalidData(); + } + self->updateText(); +} + +// static +void LLMultiSliderCtrl::onSliderCommit(LLUICtrl* ctrl, const LLSD& userdata) +{ + LLMultiSliderCtrl* self = dynamic_cast(ctrl->getParent()); + if (!self) + return; + + bool success = false; + F32 saved_val = self->mCurValue; + F32 new_val = self->mMultiSlider->getCurSliderValue(); + + self->mCurValue = new_val; // set the value temporarily so that the callback can retrieve it. + if( !self->mValidateSignal || (*(self->mValidateSignal))( self, new_val ) ) + { + success = true; + } + + if( success ) + { + self->onCommit(); + } + else + { + if( self->mCurValue != saved_val ) + { + self->setCurSliderValue( saved_val ); + } + self->reportInvalidData(); + } + self->updateText(); +} + +void LLMultiSliderCtrl::setEnabled(bool b) +{ + LLF32UICtrl::setEnabled( b ); + + if( mLabelBox ) + { + mLabelBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); + } + + mMultiSlider->setEnabled( b ); + + if( mEditor ) + { + mEditor->setEnabled( b ); + } + + if( mTextBox ) + { + mTextBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); + } +} + + +void LLMultiSliderCtrl::setTentative(bool b) +{ + if( mEditor ) + { + mEditor->setTentative(b); + } + LLF32UICtrl::setTentative(b); +} + + +void LLMultiSliderCtrl::onCommit() +{ + setTentative(false); + + if( mEditor ) + { + mEditor->setTentative(false); + } + + setControlValue(getValueF32()); + LLF32UICtrl::onCommit(); +} + + +void LLMultiSliderCtrl::setPrecision(S32 precision) +{ + if (precision < 0 || precision > 10) + { + LL_ERRS() << "LLMultiSliderCtrl::setPrecision - precision out of range" << LL_ENDL; + return; + } + + mPrecision = precision; + updateText(); +} + +boost::signals2::connection LLMultiSliderCtrl::setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ) +{ + return mMultiSlider->setMouseDownCallback( cb ); +} + +boost::signals2::connection LLMultiSliderCtrl::setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ) +{ + return mMultiSlider->setMouseUpCallback( cb ); +} + +void LLMultiSliderCtrl::onTabInto() +{ + if( mEditor ) + { + mEditor->onTabInto(); + } + LLF32UICtrl::onTabInto(); +} + +void LLMultiSliderCtrl::reportInvalidData() +{ + make_ui_sound("UISndBadKeystroke"); +} + +// virtual +void LLMultiSliderCtrl::setControlName(const std::string& control_name, LLView* context) +{ + mMultiSlider->setControlName(control_name, context); +} + diff --git a/indra/llui/llmultisliderctrl.h b/indra/llui/llmultisliderctrl.h index a369140660..fee05c92fd 100644 --- a/indra/llui/llmultisliderctrl.h +++ b/indra/llui/llmultisliderctrl.h @@ -1,174 +1,174 @@ -/** - * @file llmultisliderctrl.h - * @brief LLMultiSliderCtrl base class - * - * $LicenseInfo:firstyear=2007&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$ - */ - -#ifndef LL_MULTI_SLIDERCTRL_H -#define LL_MULTI_SLIDERCTRL_H - -#include "llf32uictrl.h" -#include "v4color.h" -#include "llmultislider.h" -#include "lltextbox.h" -#include "llrect.h" - - -// -// Classes -// -class LLFontGL; -class LLLineEditor; -class LLSlider; - - -class LLMultiSliderCtrl : public LLF32UICtrl -{ -public: - struct Params : public LLInitParam::Block - { - Optional label_width, - text_width; - Optional show_text, - can_edit_text; - Optional decimal_digits, - thumb_width; - Optional max_sliders; - Optional allow_overlap, - loop_overlap, - draw_track, - use_triangle; - - Optional orientation, - thumb_image; - - Optional overlap_threshold; - - Optional text_color, - text_disabled_color, - thumb_highlight_color; - - Optional mouse_down_callback, - mouse_up_callback; - - Multiple sliders; - - Params(); - }; - -protected: - LLMultiSliderCtrl(const Params&); - friend class LLUICtrlFactory; -public: - virtual ~LLMultiSliderCtrl(); - - F32 getSliderValue(const std::string& name) const { return mMultiSlider->getSliderValue(name); } - void setSliderValue(const std::string& name, F32 v, bool from_event = false); - - virtual void setValue(const LLSD& value ); - virtual LLSD getValue() const { return mMultiSlider->getValue(); } - virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); - - const std::string& getCurSlider() const { return mMultiSlider->getCurSlider(); } - F32 getCurSliderValue() const { return mCurValue; } - void setCurSlider(const std::string& name); - void resetCurSlider(); - void setCurSliderValue(F32 val, bool from_event = false) { setSliderValue(mMultiSlider->getCurSlider(), val, from_event); } - - virtual void setMinValue(const LLSD& min_value) { setMinValue((F32)min_value.asReal()); } - virtual void setMaxValue(const LLSD& max_value) { setMaxValue((F32)max_value.asReal()); } - - bool isMouseHeldDown(); - - virtual void setEnabled( bool b ); - virtual void clear(); - virtual void setPrecision(S32 precision); - void setMinValue(F32 min_value) {mMultiSlider->setMinValue(min_value);} - void setMaxValue(F32 max_value) {mMultiSlider->setMaxValue(max_value);} - void setIncrement(F32 increment) {mMultiSlider->setIncrement(increment);} - - F32 getNearestIncrement(F32 value) const { return mMultiSlider->getNearestIncrement(value); } - F32 getSliderValueFromPos(S32 x, S32 y) const { return mMultiSlider->getSliderValueFromPos(x, y); } - LLRect getSliderThumbRect(const std::string &name) const { return mMultiSlider->getSliderThumbRect(name); } - - void setSliderThumbImage(const std::string &name) { mMultiSlider->setSliderThumbImage(name); } - void clearSliderThumbImage() { mMultiSlider->clearSliderThumbImage(); } - - /// for adding and deleting sliders - const std::string& addSlider(); - const std::string& addSlider(F32 val); - bool addSlider(F32 val, const std::string& name); - void deleteSlider(const std::string& name); - void deleteCurSlider() { deleteSlider(mMultiSlider->getCurSlider()); } - - F32 getMinValue() const { return mMultiSlider->getMinValue(); } - F32 getMaxValue() const { return mMultiSlider->getMaxValue(); } - - S32 getMaxNumSliders() { return mMultiSlider->getMaxNumSliders(); } - S32 getCurNumSliders() { return mMultiSlider->getCurNumSliders(); } - F32 getOverlapThreshold() { return mMultiSlider->getOverlapThreshold(); } - bool canAddSliders() { return mMultiSlider->canAddSliders(); } - - void setLabel(const std::string& label) { if (mLabelBox) mLabelBox->setText(label); } - void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } - void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } - - boost::signals2::connection setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ); - boost::signals2::connection setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ); - - virtual void onTabInto(); - - virtual void setTentative(bool b); // marks value as tentative - virtual void onCommit(); // mark not tentative, then commit - - virtual void setControlName(const std::string& control_name, LLView* context); - - static void onSliderCommit(LLUICtrl* caller, const LLSD& userdata); - - static void onEditorCommit(LLUICtrl* ctrl, const LLSD& userdata); - static void onEditorGainFocus(LLFocusableElement* caller, void *userdata); - static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); - -private: - void updateText(); - void reportInvalidData(); - -private: - const LLFontGL* mFont; - bool mShowText; - bool mCanEditText; - - S32 mPrecision; - LLTextBox* mLabelBox; - S32 mLabelWidth; - - F32 mCurValue; - LLMultiSlider* mMultiSlider; - LLLineEditor* mEditor; - LLTextBox* mTextBox; - - LLUIColor mTextEnabledColor; - LLUIColor mTextDisabledColor; -}; - -#endif // LL_MULTI_SLIDERCTRL_H +/** + * @file llmultisliderctrl.h + * @brief LLMultiSliderCtrl base class + * + * $LicenseInfo:firstyear=2007&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$ + */ + +#ifndef LL_MULTI_SLIDERCTRL_H +#define LL_MULTI_SLIDERCTRL_H + +#include "llf32uictrl.h" +#include "v4color.h" +#include "llmultislider.h" +#include "lltextbox.h" +#include "llrect.h" + + +// +// Classes +// +class LLFontGL; +class LLLineEditor; +class LLSlider; + + +class LLMultiSliderCtrl : public LLF32UICtrl +{ +public: + struct Params : public LLInitParam::Block + { + Optional label_width, + text_width; + Optional show_text, + can_edit_text; + Optional decimal_digits, + thumb_width; + Optional max_sliders; + Optional allow_overlap, + loop_overlap, + draw_track, + use_triangle; + + Optional orientation, + thumb_image; + + Optional overlap_threshold; + + Optional text_color, + text_disabled_color, + thumb_highlight_color; + + Optional mouse_down_callback, + mouse_up_callback; + + Multiple sliders; + + Params(); + }; + +protected: + LLMultiSliderCtrl(const Params&); + friend class LLUICtrlFactory; +public: + virtual ~LLMultiSliderCtrl(); + + F32 getSliderValue(const std::string& name) const { return mMultiSlider->getSliderValue(name); } + void setSliderValue(const std::string& name, F32 v, bool from_event = false); + + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const { return mMultiSlider->getValue(); } + virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); + + const std::string& getCurSlider() const { return mMultiSlider->getCurSlider(); } + F32 getCurSliderValue() const { return mCurValue; } + void setCurSlider(const std::string& name); + void resetCurSlider(); + void setCurSliderValue(F32 val, bool from_event = false) { setSliderValue(mMultiSlider->getCurSlider(), val, from_event); } + + virtual void setMinValue(const LLSD& min_value) { setMinValue((F32)min_value.asReal()); } + virtual void setMaxValue(const LLSD& max_value) { setMaxValue((F32)max_value.asReal()); } + + bool isMouseHeldDown(); + + virtual void setEnabled( bool b ); + virtual void clear(); + virtual void setPrecision(S32 precision); + void setMinValue(F32 min_value) {mMultiSlider->setMinValue(min_value);} + void setMaxValue(F32 max_value) {mMultiSlider->setMaxValue(max_value);} + void setIncrement(F32 increment) {mMultiSlider->setIncrement(increment);} + + F32 getNearestIncrement(F32 value) const { return mMultiSlider->getNearestIncrement(value); } + F32 getSliderValueFromPos(S32 x, S32 y) const { return mMultiSlider->getSliderValueFromPos(x, y); } + LLRect getSliderThumbRect(const std::string &name) const { return mMultiSlider->getSliderThumbRect(name); } + + void setSliderThumbImage(const std::string &name) { mMultiSlider->setSliderThumbImage(name); } + void clearSliderThumbImage() { mMultiSlider->clearSliderThumbImage(); } + + /// for adding and deleting sliders + const std::string& addSlider(); + const std::string& addSlider(F32 val); + bool addSlider(F32 val, const std::string& name); + void deleteSlider(const std::string& name); + void deleteCurSlider() { deleteSlider(mMultiSlider->getCurSlider()); } + + F32 getMinValue() const { return mMultiSlider->getMinValue(); } + F32 getMaxValue() const { return mMultiSlider->getMaxValue(); } + + S32 getMaxNumSliders() { return mMultiSlider->getMaxNumSliders(); } + S32 getCurNumSliders() { return mMultiSlider->getCurNumSliders(); } + F32 getOverlapThreshold() { return mMultiSlider->getOverlapThreshold(); } + bool canAddSliders() { return mMultiSlider->canAddSliders(); } + + void setLabel(const std::string& label) { if (mLabelBox) mLabelBox->setText(label); } + void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } + void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } + + boost::signals2::connection setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ); + + virtual void onTabInto(); + + virtual void setTentative(bool b); // marks value as tentative + virtual void onCommit(); // mark not tentative, then commit + + virtual void setControlName(const std::string& control_name, LLView* context); + + static void onSliderCommit(LLUICtrl* caller, const LLSD& userdata); + + static void onEditorCommit(LLUICtrl* ctrl, const LLSD& userdata); + static void onEditorGainFocus(LLFocusableElement* caller, void *userdata); + static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); + +private: + void updateText(); + void reportInvalidData(); + +private: + const LLFontGL* mFont; + bool mShowText; + bool mCanEditText; + + S32 mPrecision; + LLTextBox* mLabelBox; + S32 mLabelWidth; + + F32 mCurValue; + LLMultiSlider* mMultiSlider; + LLLineEditor* mEditor; + LLTextBox* mTextBox; + + LLUIColor mTextEnabledColor; + LLUIColor mTextDisabledColor; +}; + +#endif // LL_MULTI_SLIDERCTRL_H diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 9549860972..74e573bb4e 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1,2009 +1,2009 @@ -/** -* @file llnotifications.cpp -* @brief Non-UI queue manager for keeping a prioritized list of notifications -* -* $LicenseInfo:firstyear=2008&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 "llnotifications.h" -#include "llnotificationtemplate.h" -#include "llnotificationvisibilityrule.h" - -#include "llavatarnamecache.h" -#include "llinstantmessage.h" -#include "llcachename.h" -#include "llxmlnode.h" -#include "lluictrl.h" -#include "lluictrlfactory.h" -#include "lldir.h" -#include "llsdserialize.h" -#include "lltrans.h" -#include "llstring.h" -#include "llsdparam.h" -#include "llsdutil.h" - -#include -#include - - -const std::string NOTIFICATION_PERSIST_VERSION = "0.93"; - -void NotificationPriorityValues::declareValues() -{ - declare("low", NOTIFICATION_PRIORITY_LOW); - declare("normal", NOTIFICATION_PRIORITY_NORMAL); - declare("high", NOTIFICATION_PRIORITY_HIGH); - declare("critical", NOTIFICATION_PRIORITY_CRITICAL); -} - -LLNotificationForm::FormElementBase::FormElementBase() -: name("name"), - enabled("enabled", true) -{} - -LLNotificationForm::FormIgnore::FormIgnore() -: text("text"), - control("control"), - invert_control("invert_control", false), - save_option("save_option", false), - session_only("session_only", false), - checkbox_only("checkbox_only", false) -{} - -LLNotificationForm::FormButton::FormButton() -: index("index"), - text("text"), - ignore("ignore"), - is_default("default"), - width("width", 0), - type("type") -{ - // set type here so it gets serialized - type = "button"; -} - -LLNotificationForm::FormInput::FormInput() -: type("type"), - text("text"), - max_length_chars("max_length_chars"), - allow_emoji("allow_emoji"), - width("width", 0), - value("value") -{} - -LLNotificationForm::FormElement::FormElement() -: button("button"), - input("input") -{} - -LLNotificationForm::FormElements::FormElements() -: elements("") -{} - -LLNotificationForm::Params::Params() -: name("name"), - ignore("ignore"), - form_elements("") -{} - - - -bool filterIgnoredNotifications(LLNotificationPtr notification) -{ - LLNotificationFormPtr form = notification->getForm(); - // Check to see if the user wants to ignore this alert - return !notification->getForm()->getIgnored(); -} - -bool handleIgnoredNotification(const LLSD& payload) -{ - if (payload["sigtype"].asString() == "add") - { - LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); - if (!pNotif) return false; - - LLNotificationFormPtr form = pNotif->getForm(); - LLSD response; - switch(form->getIgnoreType()) - { - case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE: - case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE_SESSION_ONLY: - response = pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON); - break; - case LLNotificationForm::IGNORE_WITH_LAST_RESPONSE: - response = LLUI::getInstance()->mSettingGroups["ignores"]->getLLSD("Default" + pNotif->getName()); - break; - case LLNotificationForm::IGNORE_SHOW_AGAIN: - break; - default: - return false; - } - pNotif->setIgnored(true); - pNotif->respond(response); - return true; // don't process this item any further - } - return false; -} - -bool defaultResponse(const LLSD& payload) -{ - if (payload["sigtype"].asString() == "add") - { - LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); - if (pNotif) - { - // supply default response - pNotif->respond(pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON)); - } - } - return false; -} - -bool visibilityRuleMached(const LLSD& payload) -{ - // This is needed because LLNotifications::isVisibleByRules may have cancelled the notification. - // Returning true here makes LLNotificationChannelBase::updateItem do an early out, which prevents things from happening in the wrong order. - return true; -} - - -namespace LLNotificationFilters -{ - // a sample filter - bool includeEverything(LLNotificationPtr p) - { - return true; - } -}; - -LLNotificationForm::LLNotificationForm() -: mIgnore(IGNORE_NO) -{ -} - -LLNotificationForm::LLNotificationForm( const LLNotificationForm& other ) -{ - mFormData = other.mFormData; - mIgnore = other.mIgnore; - mIgnoreMsg = other.mIgnoreMsg; - mIgnoreSetting = other.mIgnoreSetting; - mInvertSetting = other.mInvertSetting; -} - -LLNotificationForm::LLNotificationForm(const std::string& name, const LLNotificationForm::Params& p) -: mIgnore(IGNORE_NO), - mInvertSetting(false) // ignore settings by default mean true=show, false=ignore -{ - if (p.ignore.isProvided()) - { - // For all cases but IGNORE_CHECKBOX_ONLY this is name for use in preferences - mIgnoreMsg = p.ignore.text; - - LLUI *ui_inst = LLUI::getInstance(); - if (p.ignore.checkbox_only) - { - mIgnore = IGNORE_CHECKBOX_ONLY; - } - else if (!p.ignore.save_option) - { - mIgnore = p.ignore.session_only ? IGNORE_WITH_DEFAULT_RESPONSE_SESSION_ONLY : IGNORE_WITH_DEFAULT_RESPONSE; - } - else - { - // remember last option chosen by user and automatically respond with that in the future - mIgnore = IGNORE_WITH_LAST_RESPONSE; - ui_inst->mSettingGroups["ignores"]->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name)); - } - - bool show_notification = true; - if (p.ignore.control.isProvided()) - { - mIgnoreSetting = ui_inst->mSettingGroups["config"]->getControl(p.ignore.control()); - mInvertSetting = p.ignore.invert_control; - } - else if (mIgnore > IGNORE_NO) - { - ui_inst->mSettingGroups["ignores"]->declareBOOL(name, show_notification, "Show notification with this name", LLControlVariable::PERSIST_NONDFT); - mIgnoreSetting = ui_inst->mSettingGroups["ignores"]->getControl(name); - } - } - - LLParamSDParser parser; - parser.writeSD(mFormData, p.form_elements); - - for (LLSD::array_iterator it = mFormData.beginArray(), end_it = mFormData.endArray(); - it != end_it; - ++it) - { - // lift contents of form element up a level, since element type is already encoded in "type" param - if (it->isMap() && it->beginMap() != it->endMap()) - { - *it = it->beginMap()->second; - } - } - - LL_DEBUGS("Notifications") << name << LL_ENDL; - LL_DEBUGS("Notifications") << ll_pretty_print_sd(mFormData) << LL_ENDL; -} - -LLNotificationForm::LLNotificationForm(const LLSD& sd) - : mIgnore(IGNORE_NO) -{ - if (sd.isArray()) - { - mFormData = sd; - } - else - { - LL_WARNS("Notifications") << "Invalid form data " << sd << LL_ENDL; - mFormData = LLSD::emptyArray(); - } -} - -LLSD LLNotificationForm::asLLSD() const -{ - return mFormData; -} - -LLSD LLNotificationForm::getElement(const std::string& element_name) -{ - for (LLSD::array_const_iterator it = mFormData.beginArray(); - it != mFormData.endArray(); - ++it) - { - if ((*it)["name"].asString() == element_name) return (*it); - } - return LLSD(); -} - - -bool LLNotificationForm::hasElement(const std::string& element_name) const -{ - for (LLSD::array_const_iterator it = mFormData.beginArray(); - it != mFormData.endArray(); - ++it) - { - if ((*it)["name"].asString() == element_name) return true; - } - return false; -} - -void LLNotificationForm::getElements(LLSD& elements, S32 offset) -{ - //Finds elements that the template did not add - LLSD::array_const_iterator it = mFormData.beginArray() + offset; - - //Keeps track of only the dynamic elements - for(; it != mFormData.endArray(); ++it) - { - elements.append(*it); - } -} - -bool LLNotificationForm::getElementEnabled(const std::string& element_name) const -{ - for (LLSD::array_const_iterator it = mFormData.beginArray(); - it != mFormData.endArray(); - ++it) - { - if ((*it)["name"].asString() == element_name) - { - return (*it)["enabled"].asBoolean(); - } - } - - return false; -} - -void LLNotificationForm::setElementEnabled(const std::string& element_name, bool enabled) -{ - for (LLSD::array_iterator it = mFormData.beginArray(); - it != mFormData.endArray(); - ++it) - { - if ((*it)["name"].asString() == element_name) - { - (*it)["enabled"] = enabled; - } - } -} - - -void LLNotificationForm::addElement(const std::string& type, const std::string& name, const LLSD& value, bool enabled) -{ - LLSD element; - element["type"] = type; - element["name"] = name; - element["text"] = name; - element["value"] = value; - element["index"] = LLSD::Integer(mFormData.size()); - element["enabled"] = enabled; - mFormData.append(element); -} - -void LLNotificationForm::append(const LLSD& sub_form) -{ - if (sub_form.isArray()) - { - for (LLSD::array_const_iterator it = sub_form.beginArray(); - it != sub_form.endArray(); - ++it) - { - mFormData.append(*it); - } - } -} - -void LLNotificationForm::formatElements(const LLSD& substitutions) -{ - for (LLSD::array_iterator it = mFormData.beginArray(); - it != mFormData.endArray(); - ++it) - { - // format "text" component of each form element - if ((*it).has("text")) - { - std::string text = (*it)["text"].asString(); - LLStringUtil::format(text, substitutions); - (*it)["text"] = text; - } - if ((*it)["type"].asString() == "text" && (*it).has("value")) - { - std::string value = (*it)["value"].asString(); - LLStringUtil::format(value, substitutions); - (*it)["value"] = value; - } - } -} - -std::string LLNotificationForm::getDefaultOption() -{ - for (LLSD::array_const_iterator it = mFormData.beginArray(); - it != mFormData.endArray(); - ++it) - { - if ((*it)["default"]) return (*it)["name"].asString(); - } - return ""; -} - -LLControlVariablePtr LLNotificationForm::getIgnoreSetting() -{ - return mIgnoreSetting; -} - -bool LLNotificationForm::getIgnored() -{ - bool show = true; - if (mIgnore > LLNotificationForm::IGNORE_NO - && mIgnoreSetting) - { - show = mIgnoreSetting->getValue().asBoolean(); - if (mInvertSetting) show = !show; - } - return !show; -} - -void LLNotificationForm::setIgnored(bool ignored) -{ - if (mIgnoreSetting) - { - if (mInvertSetting) ignored = !ignored; - mIgnoreSetting->setValue(!ignored); - } -} - -LLNotificationTemplate::LLNotificationTemplate(const LLNotificationTemplate::Params& p) -: mName(p.name), - mType(p.type), - mMessage(p.value), - mFooter(p.footer.value), - mLabel(p.label), - mIcon(p.icon), - mURL(p.url.value), - mExpireSeconds(p.duration), - mExpireOption(p.expire_option), - mURLOption(p.url.option), - mURLTarget(p.url.target), - mForceUrlsExternal(p.force_urls_external), - mUnique(p.unique.isProvided()), - mCombineBehavior(p.unique.combine), - mPriority(p.priority), - mPersist(p.persist), - mDefaultFunctor(p.functor.isProvided() ? p.functor() : p.name()), - mLogToChat(p.log_to_chat), - mLogToIM(p.log_to_im), - mShowToast(p.show_toast), - mFadeToast(p.fade_toast), - mSoundName("") -{ - if (p.sound.isProvided() - && LLUI::getInstance()->mSettingGroups["config"]->controlExists(p.sound)) - { - mSoundName = p.sound; - } - - for (const LLNotificationTemplate::UniquenessContext& context : p.unique.contexts) - { - mUniqueContext.push_back(context.value); - } - - LL_DEBUGS("Notifications") << "notification \"" << mName << "\": tag count is " << p.tags.size() << LL_ENDL; - - for (const LLNotificationTemplate::Tag& tag : p.tags) - { - LL_DEBUGS("Notifications") << " tag \"" << std::string(tag.value) << "\"" << LL_ENDL; - mTags.push_back(tag.value); - } - - mForm = LLNotificationFormPtr(new LLNotificationForm(p.name, p.form_ref.form)); -} - -LLNotificationVisibilityRule::LLNotificationVisibilityRule(const LLNotificationVisibilityRule::Rule &p) -{ - if (p.show.isChosen()) - { - mType = p.show.type; - mTag = p.show.tag; - mName = p.show.name; - mVisible = true; - } - else if (p.hide.isChosen()) - { - mType = p.hide.type; - mTag = p.hide.tag; - mName = p.hide.name; - mVisible = false; - } - else if (p.respond.isChosen()) - { - mType = p.respond.type; - mTag = p.respond.tag; - mName = p.respond.name; - mVisible = false; - mResponse = p.respond.response; - } -} - -LLNotification::LLNotification(const LLSDParamAdapter& p) : - mTimestamp(p.time_stamp), - mSubstitutions(p.substitutions), - mPayload(p.payload), - mExpiresAt(p.expiry), - mTemporaryResponder(false), - mRespondedTo(false), - mPriority(p.priority), - mCancelled(false), - mIgnored(false), - mResponderObj(NULL), - mId(p.id.isProvided() ? p.id : LLUUID::generateNewID()), - mOfferFromAgent(p.offer_from_agent), - mIsDND(p.is_dnd) -{ - if (p.functor.name.isChosen()) - { - mResponseFunctorName = p.functor.name; - } - else if (p.functor.function.isChosen()) - { - mResponseFunctorName = LLUUID::generateNewID().asString(); - LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, p.functor.function()); - - mTemporaryResponder = true; - } - else if(p.functor.responder.isChosen()) - { - mResponder = p.functor.responder; - } - - if(p.responder.isProvided()) - { - mResponderObj = p.responder; - } - - init(p.name, p.form_elements); -} - - -LLSD LLNotification::asLLSD(bool excludeTemplateElements) -{ - LLParamSDParser parser; - - Params p; - p.id = mId; - p.name = mTemplatep->mName; - p.substitutions = mSubstitutions; - p.payload = mPayload; - p.time_stamp = mTimestamp; - p.expiry = mExpiresAt; - p.priority = mPriority; - - LLNotificationFormPtr templateForm = mTemplatep->mForm; - LLSD formElements = mForm->asLLSD(); - - //All form elements (dynamic or not) - if(!excludeTemplateElements) - { - p.form_elements = formElements; - } - //Only dynamic form elements (exclude template elements) - else if(templateForm->getNumElements() < formElements.size()) - { - LLSD dynamicElements; - //Offset to dynamic elements and store them - mForm->getElements(dynamicElements, templateForm->getNumElements()); - p.form_elements = dynamicElements; - } - - if(mResponder) - { - p.functor.responder_sd = mResponder->asLLSD(); - } - - if(!mResponseFunctorName.empty()) - { - p.functor.name = mResponseFunctorName; - } - - LLSD output; - parser.writeSD(output, p); - return output; -} - -void LLNotification::update() -{ - LLNotifications::instance().update(shared_from_this()); -} - -void LLNotification::updateFrom(LLNotificationPtr other) -{ - // can only update from the same notification type - if (mTemplatep != other->mTemplatep) return; - - // NOTE: do NOT change the ID, since it is the key to - // this given instance, just update all the metadata - //mId = other->mId; - - mPayload = other->mPayload; - mSubstitutions = other->mSubstitutions; - mTimestamp = other->mTimestamp; - mExpiresAt = other->mExpiresAt; - mCancelled = other->mCancelled; - mIgnored = other->mIgnored; - mPriority = other->mPriority; - mForm = other->mForm; - mResponseFunctorName = other->mResponseFunctorName; - mRespondedTo = other->mRespondedTo; - mResponse = other->mResponse; - mTemporaryResponder = other->mTemporaryResponder; - - update(); -} - -const LLNotificationFormPtr LLNotification::getForm() -{ - return mForm; -} - -void LLNotification::cancel() -{ - mCancelled = true; -} - -LLSD LLNotification::getResponseTemplate(EResponseTemplateType type) -{ - LLSD response = LLSD::emptyMap(); - for (S32 element_idx = 0; - element_idx < mForm->getNumElements(); - ++element_idx) - { - LLSD element = mForm->getElement(element_idx); - if (element.has("name")) - { - response[element["name"].asString()] = element["value"]; - } - - if ((type == WITH_DEFAULT_BUTTON) - && element["default"].asBoolean()) - { - response[element["name"].asString()] = true; - } - } - return response; -} - -//static -S32 LLNotification::getSelectedOption(const LLSD& notification, const LLSD& response) -{ - LLNotificationForm form(notification["form"]); - - for (S32 element_idx = 0; - element_idx < form.getNumElements(); - ++element_idx) - { - LLSD element = form.getElement(element_idx); - - // only look at buttons - if (element["type"].asString() == "button" - && response[element["name"].asString()].asBoolean()) - { - return element["index"].asInteger(); - } - } - - return -1; -} - -//static -std::string LLNotification::getSelectedOptionName(const LLSD& response) -{ - for (LLSD::map_const_iterator response_it = response.beginMap(); - response_it != response.endMap(); - ++response_it) - { - if (response_it->second.isBoolean() && response_it->second.asBoolean()) - { - return response_it->first; - } - } - return ""; -} - - -void LLNotification::respond(const LLSD& response) -{ - // *TODO may remove mRespondedTo and use mResponce.isDefined() in isRespondedTo() - mRespondedTo = true; - mResponse = response; - - if(mResponder) - { - mResponder->handleRespond(asLLSD(), response); - } - else if (!mResponseFunctorName.empty()) - { - // look up the functor - LLNotificationFunctorRegistry::ResponseFunctor functor = - LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName); - // and then call it - functor(asLLSD(), response); - } - else if (mCombinedNotifications.empty()) - { - // no registered responder - return; - } - - if (mTemporaryResponder) - { - LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); - mResponseFunctorName = ""; - mTemporaryResponder = false; - } - - if (mForm->getIgnoreType() > LLNotificationForm::IGNORE_NO) - { - mForm->setIgnored(mIgnored); - if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) - { - LLUI::getInstance()->mSettingGroups["ignores"]->setLLSD("Default" + getName(), response); - } - } - - for (std::vector::const_iterator it = mCombinedNotifications.begin(); it != mCombinedNotifications.end(); ++it) - { - if ((*it)) - { - (*it)->respond(response); - } - } - - update(); -} - -void LLNotification::respondWithDefault() -{ - respond(getResponseTemplate(WITH_DEFAULT_BUTTON)); -} - - -const std::string& LLNotification::getName() const -{ - return mTemplatep->mName; -} - -const std::string& LLNotification::getIcon() const -{ - return mTemplatep->mIcon; -} - - -bool LLNotification::isPersistent() const -{ - return mTemplatep->mPersist; -} - -std::string LLNotification::getType() const -{ - return (mTemplatep ? mTemplatep->mType : ""); -} - -S32 LLNotification::getURLOption() const -{ - return (mTemplatep ? mTemplatep->mURLOption : -1); -} - -S32 LLNotification::getURLOpenExternally() const -{ - return(mTemplatep? mTemplatep->mURLTarget == "_external": -1); -} - -bool LLNotification::getForceUrlsExternal() const -{ - return (mTemplatep ? mTemplatep->mForceUrlsExternal : false); -} - -bool LLNotification::hasUniquenessConstraints() const -{ - return (mTemplatep ? mTemplatep->mUnique : false); -} - -bool LLNotification::matchesTag(const std::string& tag) -{ - bool result = false; - - if(mTemplatep) - { - std::list::iterator it; - for(it = mTemplatep->mTags.begin(); it != mTemplatep->mTags.end(); it++) - { - if((*it) == tag) - { - result = true; - break; - } - } - } - - return result; -} - -void LLNotification::setIgnored(bool ignore) -{ - mIgnored = ignore; -} - -void LLNotification::setResponseFunctor(std::string const &responseFunctorName) -{ - if (mTemporaryResponder) - // get rid of the old one - LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); - mResponseFunctorName = responseFunctorName; - mTemporaryResponder = false; -} - -void LLNotification::setResponseFunctor(const LLNotificationFunctorRegistry::ResponseFunctor& cb) -{ - if(mTemporaryResponder) - { - LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); - } - - LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, cb); -} - -void LLNotification::setResponseFunctor(const LLNotificationResponderPtr& responder) -{ - mResponder = responder; -} - -bool LLNotification::isEquivalentTo(LLNotificationPtr that) const -{ - if (this->mTemplatep->mName != that->mTemplatep->mName) - { - return false; // must have the same template name or forget it - } - if (this->mTemplatep->mUnique) - { - const LLSD& these_substitutions = this->getSubstitutions(); - const LLSD& those_substitutions = that->getSubstitutions(); - const LLSD& this_payload = this->getPayload(); - const LLSD& that_payload = that->getPayload(); - - // highlander bit sez there can only be one of these - for (std::vector::const_iterator it = mTemplatep->mUniqueContext.begin(), end_it = mTemplatep->mUniqueContext.end(); - it != end_it; - ++it) - { - // if templates differ in either substitution strings or payload with the given field name - // then they are considered inequivalent - // use of get() avoids converting the LLSD value to a map as the [] operator would - if (these_substitutions.get(*it).asString() != those_substitutions.get(*it).asString() - || this_payload.get(*it).asString() != that_payload.get(*it).asString()) - { - return false; - } - } - return true; - } - - return false; -} - -void LLNotification::init(const std::string& template_name, const LLSD& form_elements) -{ - mTemplatep = LLNotifications::instance().getTemplate(template_name); - if (!mTemplatep) return; - - // add default substitutions - const LLStringUtil::format_map_t& default_args = LLTrans::getDefaultArgs(); - for (LLStringUtil::format_map_t::const_iterator iter = default_args.begin(); - iter != default_args.end(); ++iter) - { - mSubstitutions[iter->first] = iter->second; - } - mSubstitutions["_URL"] = getURL(); - mSubstitutions["_NAME"] = template_name; - // TODO: something like this so that a missing alert is sensible: - //mSubstitutions["_ARGS"] = get_all_arguments_as_text(mSubstitutions); - - mForm = LLNotificationFormPtr(new LLNotificationForm(*mTemplatep->mForm)); - mForm->append(form_elements); - - // apply substitution to form labels - mForm->formatElements(mSubstitutions); - - mIgnored = mForm->getIgnored(); - - LLDate rightnow = LLDate::now(); - if (mTemplatep->mExpireSeconds) - { - mExpiresAt = LLDate(rightnow.secondsSinceEpoch() + mTemplatep->mExpireSeconds); - } - - if (mPriority == NOTIFICATION_PRIORITY_UNSPECIFIED) - { - mPriority = mTemplatep->mPriority; - } -} - -std::string LLNotification::summarize() const -{ - std::string s = "Notification("; - s += getName(); - s += ") : "; - s += mTemplatep ? mTemplatep->mMessage : ""; - // should also include timestamp and expiration time (but probably not payload) - return s; -} - -std::string LLNotification::getMessage() const -{ - // all our callers cache this result, so it gives us more flexibility - // to do the substitution at call time rather than attempting to - // cache it in the notification - if (!mTemplatep) - return std::string(); - - std::string message = mTemplatep->mMessage; - LLStringUtil::format(message, mSubstitutions); - return message; -} - -std::string LLNotification::getFooter() const -{ - if (!mTemplatep) - return std::string(); - - std::string footer = mTemplatep->mFooter; - LLStringUtil::format(footer, mSubstitutions); - return footer; -} - -std::string LLNotification::getLabel() const -{ - std::string label = mTemplatep->mLabel; - LLStringUtil::format(label, mSubstitutions); - return (mTemplatep ? label : ""); -} - -std::string LLNotification::getURL() const -{ - if (!mTemplatep) - return std::string(); - std::string url = mTemplatep->mURL; - LLStringUtil::format(url, mSubstitutions); - return (mTemplatep ? url : ""); -} - -bool LLNotification::canLogToChat() const -{ - return mTemplatep->mLogToChat; -} - -bool LLNotification::canLogToIM() const -{ - return mTemplatep->mLogToIM; -} - -bool LLNotification::canShowToast() const -{ - return mTemplatep->mShowToast; -} - -bool LLNotification::canFadeToast() const -{ - return mTemplatep->mFadeToast; -} - -bool LLNotification::hasFormElements() const -{ - return mTemplatep->mForm->getNumElements() != 0; -} - -void LLNotification::playSound() -{ - make_ui_sound(mTemplatep->mSoundName.c_str()); -} - -LLNotification::ECombineBehavior LLNotification::getCombineBehavior() const -{ - return mTemplatep->mCombineBehavior; -} - -void LLNotification::updateForm( const LLNotificationFormPtr& form ) -{ - mForm = form; -} - -void LLNotification::repost() -{ - mRespondedTo = false; - LLNotifications::instance().update(shared_from_this()); -} - - - -// ========================================================= -// LLNotificationChannel implementation -// --- -LLBoundListener LLNotificationChannelBase::connectChangedImpl(const LLEventListener& slot) -{ - // when someone wants to connect to a channel, we first throw them - // all of the notifications that are already in the channel - // we use a special signal called "load" in case the channel wants to care - // only about new notifications - LLMutexLock lock(&mItemsMutex); - for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) - { - slot(LLSD().with("sigtype", "load").with("id", (*it)->id())); - } - // and then connect the signal so that all future notifications will also be - // forwarded. - return mChanged.connect(slot); -} - -LLBoundListener LLNotificationChannelBase::connectAtFrontChangedImpl(const LLEventListener& slot) -{ - for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) - { - slot(LLSD().with("sigtype", "load").with("id", (*it)->id())); - } - return mChanged.connect(slot, boost::signals2::at_front); -} - -LLBoundListener LLNotificationChannelBase::connectPassedFilterImpl(const LLEventListener& slot) -{ - // these two filters only fire for notifications added after the current one, because - // they don't participate in the hierarchy. - return mPassedFilter.connect(slot); -} - -LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot) -{ - return mFailedFilter.connect(slot); -} - -// external call, conforms to our standard signature -bool LLNotificationChannelBase::updateItem(const LLSD& payload) -{ - // first check to see if it's in the master list - LLNotificationPtr pNotification = LLNotifications::instance().find(payload["id"]); - if (!pNotification) - return false; // not found - - return updateItem(payload, pNotification); -} - - -//FIX QUIT NOT WORKING - - -// internal call, for use in avoiding lookup -bool LLNotificationChannelBase::updateItem(const LLSD& payload, LLNotificationPtr pNotification) -{ - std::string cmd = payload["sigtype"]; - LLNotificationSet::iterator foundItem = mItems.find(pNotification); - bool wasFound = (foundItem != mItems.end()); - bool passesFilter = mFilter ? mFilter(pNotification) : true; - - // first, we offer the result of the filter test to the simple - // signals for pass/fail. One of these is guaranteed to be called. - // If either signal returns true, the change processing is NOT performed - // (so don't return true unless you know what you're doing!) - bool abortProcessing = false; - if (passesFilter) - { - onFilterPass(pNotification); - abortProcessing = mPassedFilter(payload); - } - else - { - onFilterFail(pNotification); - abortProcessing = mFailedFilter(payload); - } - - if (abortProcessing) - { - return true; - } - - if (cmd == "load") - { - // should be no reason we'd ever get a load if we already have it - // if passes filter send a load message, else do nothing - assert(!wasFound); - if (passesFilter) - { - // not in our list, add it and say so - mItems.insert(pNotification); - onLoad(pNotification); - abortProcessing = mChanged(payload); - } - } - else if (cmd == "change") - { - // if it passes filter now and was found, we just send a change message - // if it passes filter now and wasn't found, we have to add it - // if it doesn't pass filter and wasn't found, we do nothing - // if it doesn't pass filter and was found, we need to delete it - if (passesFilter) - { - if (wasFound) - { - // it already existed, so this is a change - // since it changed in place, all we have to do is resend the signal - onChange(pNotification); - abortProcessing = mChanged(payload); - } - else - { - // not in our list, add it and say so - mItems.insert(pNotification); - onChange(pNotification); - // our payload is const, so make a copy before changing it - LLSD newpayload = payload; - newpayload["sigtype"] = "add"; - abortProcessing = mChanged(newpayload); - } - } - else - { - if (wasFound) - { - // it already existed, so this is a delete - mItems.erase(pNotification); - onChange(pNotification); - // our payload is const, so make a copy before changing it - LLSD newpayload = payload; - newpayload["sigtype"] = "delete"; - abortProcessing = mChanged(newpayload); - } - // didn't pass, not on our list, do nothing - } - } - else if (cmd == "add") - { - // should be no reason we'd ever get an add if we already have it - // if passes filter send an add message, else do nothing - assert(!wasFound); - if (passesFilter) - { - // not in our list, add it and say so - mItems.insert(pNotification); - onAdd(pNotification); - abortProcessing = mChanged(payload); - } - } - else if (cmd == "delete") - { - // if we have it in our list, pass on the delete, then delete it, else do nothing - if (wasFound) - { - onDelete(pNotification); - abortProcessing = mChanged(payload); - mItems.erase(pNotification); - } - } - return abortProcessing; -} - -LLNotificationChannel::LLNotificationChannel(const Params& p) -: LLNotificationChannelBase(p.filter()), - LLInstanceTracker(p.name.isProvided() ? p.name : LLUUID::generateNewID().asString()), - mName(p.name.isProvided() ? p.name : LLUUID::generateNewID().asString()) -{ - for (const std::string& source : p.sources) - { - connectToChannel(source); - } -} - - -LLNotificationChannel::LLNotificationChannel(const std::string& name, - const std::string& parent, - LLNotificationFilter filter) -: LLNotificationChannelBase(filter), - LLInstanceTracker(name), - mName(name) -{ - // bind to notification broadcast - connectToChannel(parent); -} - -LLNotificationChannel::~LLNotificationChannel() -{ - for (LLBoundListener &listener : mListeners) - { - listener.disconnect(); - } -} - -bool LLNotificationChannel::isEmpty() const -{ - return mItems.empty(); -} - -S32 LLNotificationChannel::size() const -{ - return mItems.size(); -} - -size_t LLNotificationChannel::size() -{ - return mItems.size(); -} - -void LLNotificationChannel::forEachNotification(NotificationProcess process) -{ - LLMutexLock lock(&mItemsMutex); - std::for_each(mItems.begin(), mItems.end(), process); -} - -std::string LLNotificationChannel::summarize() -{ - std::string s("Channel '"); - s += mName; - s += "'\n "; - LLMutexLock lock(&mItemsMutex); - for (LLNotificationChannel::Iterator it = mItems.begin(); it != mItems.end(); ++it) - { - s += (*it)->summarize(); - s += "\n "; - } - return s; -} - -void LLNotificationChannel::connectToChannel( const std::string& channel_name ) -{ - if (channel_name.empty()) - { - mListeners.push_back(LLNotifications::instance().connectChanged( - boost::bind(&LLNotificationChannelBase::updateItem, this, _1))); - } - else - { - mParents.push_back(channel_name); - LLNotificationChannelPtr p = LLNotifications::instance().getChannel(channel_name); - mListeners.push_back(p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1))); - } -} - -// --- -// END OF LLNotificationChannel implementation -// ========================================================= - - -// ============================================== =========== -// LLNotifications implementation -// --- -LLNotifications::LLNotifications() -: LLNotificationChannelBase(LLNotificationFilters::includeEverything), - mIgnoreAllNotifications(false) -{ - mListener.reset(new LLNotificationsListener(*this)); - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); - - // touch the instance tracker for notification channels, so that it will still be around in our destructor - LLInstanceTracker::instanceCount(); -} - -void LLNotifications::clear() -{ - mDefaultChannels.clear(); -} - -// The expiration channel gets all notifications that are cancelled -bool LLNotifications::expirationFilter(LLNotificationPtr pNotification) -{ - return pNotification->isCancelled() || pNotification->isRespondedTo(); -} - -bool LLNotifications::expirationHandler(const LLSD& payload) -{ - if (payload["sigtype"].asString() != "delete") - { - // anything added to this channel actually should be deleted from the master - cancel(find(payload["id"])); - return true; // don't process this item any further - } - return false; -} - -bool LLNotifications::uniqueFilter(LLNotificationPtr pNotif) -{ - if (!pNotif->hasUniquenessConstraints()) - { - return true; - } - - // checks against existing unique notifications - for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); - existing_it != mUniqueNotifications.end(); - ++existing_it) - { - LLNotificationPtr existing_notification = existing_it->second; - if (pNotif != existing_notification - && pNotif->isEquivalentTo(existing_notification)) - { - if (pNotif->getCombineBehavior() == LLNotification::CANCEL_OLD) - { - cancel(existing_notification); - return true; - } - else - { - return false; - } - } - } - - return true; -} - -bool LLNotifications::uniqueHandler(const LLSD& payload) -{ - std::string cmd = payload["sigtype"]; - - LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); - if (pNotif && pNotif->hasUniquenessConstraints()) - { - if (cmd == "add") - { - // not a duplicate according to uniqueness criteria, so we keep it - // and store it for future uniqueness checks - mUniqueNotifications.insert(std::make_pair(pNotif->getName(), pNotif)); - } - else if (cmd == "delete") - { - mUniqueNotifications.erase(pNotif->getName()); - } - } - - return false; -} - -bool LLNotifications::failedUniquenessTest(const LLSD& payload) -{ - LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); - - std::string cmd = payload["sigtype"]; - - if (!pNotif || cmd != "add") - { - return false; - } - - switch(pNotif->getCombineBehavior()) - { - case LLNotification::REPLACE_WITH_NEW: - // Update the existing unique notification with the data from this particular instance... - // This guarantees that duplicate notifications will be collapsed to the one - // most recently triggered - for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); - existing_it != mUniqueNotifications.end(); - ++existing_it) - { - LLNotificationPtr existing_notification = existing_it->second; - if (pNotif != existing_notification - && pNotif->isEquivalentTo(existing_notification)) - { - // copy notification instance data over to oldest instance - // of this unique notification and update it - existing_notification->updateFrom(pNotif); - // then delete the new one - cancel(pNotif); - } - } - break; - case LLNotification::COMBINE_WITH_NEW: - // Add to the existing unique notification with the data from this particular instance... - // This guarantees that duplicate notifications will be collapsed to the one - // most recently triggered - for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); - existing_it != mUniqueNotifications.end(); - ++existing_it) - { - LLNotificationPtr existing_notification = existing_it->second; - if (pNotif != existing_notification - && pNotif->isEquivalentTo(existing_notification)) - { - // copy the notifications from the newest instance into the oldest - existing_notification->mCombinedNotifications.push_back(pNotif); - existing_notification->mCombinedNotifications.insert(existing_notification->mCombinedNotifications.end(), - pNotif->mCombinedNotifications.begin(), pNotif->mCombinedNotifications.end()); - - // pop up again - existing_notification->update(); - } - } - break; - case LLNotification::KEEP_OLD: - break; - case LLNotification::CANCEL_OLD: - // already handled by filter logic - break; - default: - break; - } - - return false; -} - -LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelName) -{ - return LLNotificationChannelPtr(LLNotificationChannel::getInstance(channelName).get()); -} - - -// this function is called once at construction time, after the object is constructed. -void LLNotifications::initSingleton() -{ - loadTemplates(); - loadVisibilityRules(); - createDefaultChannels(); -} - -void LLNotifications::cleanupSingleton() -{ - clear(); -} - -void LLNotifications::createDefaultChannels() -{ - LL_INFOS("Notifications") << "Generating default notification channels" << LL_ENDL; - // now construct the various channels AFTER loading the notifications, - // because the history channel is going to rewrite the stored notifications file - mDefaultChannels.push_back(new LLNotificationChannel("Enabled", "", - !boost::bind(&LLNotifications::getIgnoreAllNotifications, this))); - mDefaultChannels.push_back(new LLNotificationChannel("Expiration", "Enabled", - boost::bind(&LLNotifications::expirationFilter, this, _1))); - mDefaultChannels.push_back(new LLNotificationChannel("Unexpired", "Enabled", - !boost::bind(&LLNotifications::expirationFilter, this, _1))); // use negated bind - mDefaultChannels.push_back(new LLNotificationChannel("Unique", "Unexpired", - boost::bind(&LLNotifications::uniqueFilter, this, _1))); - mDefaultChannels.push_back(new LLNotificationChannel("Ignore", "Unique", - filterIgnoredNotifications)); - mDefaultChannels.push_back(new LLNotificationChannel("VisibilityRules", "Ignore", - boost::bind(&LLNotifications::isVisibleByRules, this, _1))); - mDefaultChannels.push_back(new LLNotificationChannel("Visible", "VisibilityRules", - &LLNotificationFilters::includeEverything)); - mDefaultChannels.push_back(new LLPersistentNotificationChannel()); - - // connect action methods to these channels - getChannel("Enabled")->connectFailedFilter(&defaultResponse); - getChannel("Expiration")->connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); - // uniqueHandler slot should be added as first slot of the signal due to - // usage LLStopWhenHandled combiner in LLStandardSignal - getChannel("Unique")->connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); - getChannel("Unique")->connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); - getChannel("Ignore")->connectFailedFilter(&handleIgnoredNotification); - getChannel("VisibilityRules")->connectFailedFilter(&visibilityRuleMached); -} - - -LLNotificationTemplatePtr LLNotifications::getTemplate(const std::string& name) -{ - if (mTemplates.count(name)) - { - return mTemplates[name]; - } - else - { - return mTemplates["MissingAlert"]; - } -} - -bool LLNotifications::templateExists(const std::string& name) -{ - return (mTemplates.count(name) != 0); -} - -void LLNotifications::forceResponse(const LLNotification::Params& params, S32 option) -{ - LLNotificationPtr temp_notify(new LLNotification(params)); - LLSD response = temp_notify->getResponseTemplate(); - LLSD selected_item = temp_notify->getForm()->getElement(option); - - if (selected_item.isUndefined()) - { - LL_WARNS("Notifications") << "Invalid option" << option << " for notification " << (std::string)params.name << LL_ENDL; - return; - } - response[selected_item["name"].asString()] = true; - - temp_notify->respond(response); -} - -LLNotifications::TemplateNames LLNotifications::getTemplateNames() const -{ - TemplateNames names; - for (TemplateMap::const_iterator it = mTemplates.begin(); it != mTemplates.end(); ++it) - { - names.push_back(it->first); - } - return names; -} - -typedef std::map StringMap; -void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements) -{ - // walk the list of attributes looking for replacements - for (LLXMLAttribList::iterator it=node->mAttributes.begin(); - it != node->mAttributes.end(); ++it) - { - std::string value = it->second->getValue(); - if (value[0] == '$') - { - value.erase(0, 1); // trim off the $ - std::string replacement; - StringMap::const_iterator found = replacements.find(value); - if (found != replacements.end()) - { - replacement = found->second; - LL_DEBUGS("Notifications") << "replaceSubstitutionStrings: value: \"" << value << "\" repl: \"" << replacement << "\"." << LL_ENDL; - it->second->setValue(replacement); - } - else - { - LL_WARNS("Notifications") << "replaceSubstitutionStrings FAILURE: could not find replacement \"" << value << "\"." << LL_ENDL; - } - } - } - - // now walk the list of children and call this recursively. - for (LLXMLNodePtr child = node->getFirstChild(); - child.notNull(); child = child->getNextSibling()) - { - replaceSubstitutionStrings(child, replacements); - } -} - -void replaceFormText(LLNotificationForm::Params& form, const std::string& pattern, const std::string& replace) -{ - if (form.ignore.isProvided() && form.ignore.text() == pattern) - { - form.ignore.text = replace; - } - - for (LLNotificationForm::FormElement& element : form.form_elements.elements) - { - if (element.button.isChosen() && element.button.text() == pattern) - { - element.button.text = replace; - } - } -} - -void addPathIfExists(const std::string& new_path, std::vector& paths) -{ - if (gDirUtilp->fileExists(new_path)) - { - paths.push_back(new_path); - } -} - -bool LLNotifications::loadTemplates() -{ - LL_INFOS("Notifications") << "Reading notifications template" << LL_ENDL; - // Passing findSkinnedFilenames(constraint=LLDir::ALL_SKINS) makes it - // output all relevant pathnames instead of just the ones from the most - // specific skin. - std::vector search_paths = - gDirUtilp->findSkinnedFilenames(LLDir::XUI, "notifications.xml", LLDir::ALL_SKINS); - if (search_paths.empty()) - { - LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile")); - LL_ERRS() << "Problem finding notifications.xml" << LL_ENDL; - } - - std::string base_filename = search_paths.front(); - LLXMLNodePtr root; - bool success = LLXMLNode::getLayeredXMLNode(root, search_paths); - - if (!success || root.isNull() || !root->hasName( "notifications" )) - { - LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile")); - LL_ERRS() << "Problem reading XML from UI Notifications file: " << base_filename << LL_ENDL; - return false; - } - - LLNotificationTemplate::Notifications params; - LLXUIParser parser; - parser.readXUI(root, params, base_filename); - - if(!params.validateBlock()) - { - LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile")); - LL_ERRS() << "Problem reading XUI from UI Notifications file: " << base_filename << LL_ENDL; - return false; - } - - mTemplates.clear(); - - for (const LLNotificationTemplate::GlobalString& string : params.strings) - { - mGlobalStrings[string.name] = string.value; - } - - std::map form_templates; - - for (const LLNotificationTemplate::Template& notification_template : params.templates) - { - form_templates[notification_template.name] = notification_template.form; - } - - for (LLNotificationTemplate::Params& notification : params.notifications) - { - if (notification.form_ref.form_template.isChosen()) - { - // replace form contents from template - notification.form_ref.form = form_templates[notification.form_ref.form_template.name]; - if(notification.form_ref.form_template.yes_text.isProvided()) - { - replaceFormText(notification.form_ref.form, "$yestext", notification.form_ref.form_template.yes_text); - } - if(notification.form_ref.form_template.no_text.isProvided()) - { - replaceFormText(notification.form_ref.form, "$notext", notification.form_ref.form_template.no_text); - } - if(notification.form_ref.form_template.cancel_text.isProvided()) - { - replaceFormText(notification.form_ref.form, "$canceltext", notification.form_ref.form_template.cancel_text); - } - if(notification.form_ref.form_template.help_text.isProvided()) - { - replaceFormText(notification.form_ref.form, "$helptext", notification.form_ref.form_template.help_text); - } - if(notification.form_ref.form_template.ignore_text.isProvided()) - { - replaceFormText(notification.form_ref.form, "$ignoretext", notification.form_ref.form_template.ignore_text); - } - } - mTemplates[notification.name] = LLNotificationTemplatePtr(new LLNotificationTemplate(notification)); - } - - LL_INFOS("Notifications") << "...done" << LL_ENDL; - - return true; -} - -bool LLNotifications::loadVisibilityRules() -{ - const std::string xml_filename = "notification_visibility.xml"; - // Note that here we're looking for the "en" version, the default - // language, rather than the most localized version of this file. - std::string full_filename = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, xml_filename); - - LLNotificationVisibilityRule::Rules params; - LLSimpleXUIParser parser; - parser.readXUI(full_filename, params); - - if(!params.validateBlock()) - { - LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile")); - LL_ERRS() << "Problem reading UI Notification Visibility Rules file: " << full_filename << LL_ENDL; - return false; - } - - mVisibilityRules.clear(); - - for (const LLNotificationVisibilityRule::Rule& rule : params.rules) - { - mVisibilityRules.push_back(LLNotificationVisibilityRulePtr(new LLNotificationVisibilityRule(rule))); - } - - return true; -} - -// Add a simple notification (from XUI) -void LLNotifications::addFromCallback(const LLSD& name) -{ - add(name.asString(), LLSD(), LLSD()); -} - -LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload) -{ - LLNotification::Params::Functor functor_p; - functor_p.name = name; - return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); -} - -LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload, const std::string& functor_name) -{ - LLNotification::Params::Functor functor_p; - functor_p.name = functor_name; - return add(LLNotification::Params().name(name) - .substitutions(substitutions) - .payload(payload) - .functor(functor_p)); -} - -//virtual -LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload, LLNotificationFunctorRegistry::ResponseFunctor functor) -{ - LLNotification::Params::Functor functor_p; - functor_p.function = functor; - return add(LLNotification::Params().name(name) - .substitutions(substitutions) - .payload(payload) - .functor(functor_p)); -} - -// generalized add function that takes a parameter block object for more complex instantiations -LLNotificationPtr LLNotifications::add(const LLNotification::Params& p) -{ - LLNotificationPtr pNotif(new LLNotification(p)); - add(pNotif); - return pNotif; -} - - -void LLNotifications::add(const LLNotificationPtr pNotif) -{ - if (pNotif == NULL) return; - - // first see if we already have it -- if so, that's a problem - LLNotificationSet::iterator it=mItems.find(pNotif); - if (it != mItems.end()) - { - LL_ERRS() << "Notification added a second time to the master notification channel." << LL_ENDL; - } - - updateItem(LLSD().with("sigtype", "add").with("id", pNotif->id()), pNotif); -} - -void LLNotifications::load(const LLNotificationPtr pNotif) -{ - if (pNotif == NULL) return; - - // first see if we already have it -- if so, that's a problem - LLNotificationSet::iterator it=mItems.find(pNotif); - if (it != mItems.end()) - { - LL_ERRS() << "Notification loaded a second time to the master notification channel." << LL_ENDL; - } - - updateItem(LLSD().with("sigtype", "load").with("id", pNotif->id()), pNotif); -} - -void LLNotifications::cancel(LLNotificationPtr pNotif) -{ - if (pNotif == NULL || pNotif->isCancelled()) return; - - LLNotificationSet::iterator it=mItems.find(pNotif); - if (it != mItems.end()) - { - pNotif->cancel(); - updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif); - } -} - -void LLNotifications::cancelByName(const std::string& name) -{ - LLMutexLock lock(&mItemsMutex); - std::vector notifs_to_cancel; - for (LLNotificationSet::iterator it=mItems.begin(), end_it = mItems.end(); - it != end_it; - ++it) - { - LLNotificationPtr pNotif = *it; - if (pNotif->getName() == name) - { - notifs_to_cancel.push_back(pNotif); - } - } - - for (std::vector::iterator it = notifs_to_cancel.begin(), end_it = notifs_to_cancel.end(); - it != end_it; - ++it) - { - LLNotificationPtr pNotif = *it; - pNotif->cancel(); - updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif); - } -} - -void LLNotifications::cancelByOwner(const LLUUID ownerId) -{ - LLMutexLock lock(&mItemsMutex); - std::vector notifs_to_cancel; - for (LLNotificationSet::iterator it = mItems.begin(), end_it = mItems.end(); - it != end_it; - ++it) - { - LLNotificationPtr pNotif = *it; - if (pNotif && pNotif->getPayload().get("owner_id").asUUID() == ownerId) - { - notifs_to_cancel.push_back(pNotif); - } - } - - for (std::vector::iterator it = notifs_to_cancel.begin(), end_it = notifs_to_cancel.end(); - it != end_it; - ++it) - { - LLNotificationPtr pNotif = *it; - pNotif->cancel(); - updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif); - } -} - -void LLNotifications::update(const LLNotificationPtr pNotif) -{ - LLNotificationSet::iterator it=mItems.find(pNotif); - if (it != mItems.end()) - { - updateItem(LLSD().with("sigtype", "change").with("id", pNotif->id()), pNotif); - } -} - - -LLNotificationPtr LLNotifications::find(LLUUID uuid) -{ - LLNotificationPtr target = LLNotificationPtr(new LLNotification(LLNotification::Params().id(uuid))); - LLNotificationSet::iterator it=mItems.find(target); - if (it == mItems.end()) - { - LL_DEBUGS("Notifications") << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << LL_ENDL; - return LLNotificationPtr((LLNotification*)NULL); - } - else - { - return *it; - } -} - -std::string LLNotifications::getGlobalString(const std::string& key) const -{ - GlobalStringMap::const_iterator it = mGlobalStrings.find(key); - if (it != mGlobalStrings.end()) - { - return it->second; - } - else - { - // if we don't have the key as a global, return the key itself so that the error - // is self-diagnosing. - return key; - } -} - -void LLNotifications::setIgnoreAllNotifications(bool setting) -{ - mIgnoreAllNotifications = setting; -} -bool LLNotifications::getIgnoreAllNotifications() -{ - return mIgnoreAllNotifications; -} - -void LLNotifications::setIgnored(const std::string& name, bool ignored) -{ - LLNotificationTemplatePtr templatep = getTemplate(name); - templatep->mForm->setIgnored(ignored); -} - -bool LLNotifications::getIgnored(const std::string& name) -{ - LLNotificationTemplatePtr templatep = getTemplate(name); - return (mIgnoreAllNotifications) || ( (templatep->mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO) && (templatep->mForm->getIgnored()) ); -} - -bool LLNotifications::isVisibleByRules(LLNotificationPtr n) -{ - if(n->isRespondedTo()) - { - // This avoids infinite recursion in the case where the filter calls respond() - return true; - } - - VisibilityRuleList::iterator it; - - for(it = mVisibilityRules.begin(); it != mVisibilityRules.end(); it++) - { - // An empty type/tag/name string will match any notification, so only do the comparison when the string is non-empty in the rule. - LL_DEBUGS("Notifications") - << "notification \"" << n->getName() << "\" " - << "testing against " << ((*it)->mVisible?"show":"hide") << " rule, " - << "name = \"" << (*it)->mName << "\" " - << "tag = \"" << (*it)->mTag << "\" " - << "type = \"" << (*it)->mType << "\" " - << LL_ENDL; - - if(!(*it)->mType.empty()) - { - if((*it)->mType != n->getType()) - { - // Type doesn't match, so skip this rule. - continue; - } - } - - if(!(*it)->mTag.empty()) - { - // check this notification's tag(s) against it->mTag and continue if no match is found. - if(!n->matchesTag((*it)->mTag)) - { - // This rule's non-empty tag didn't match one of the notification's tags. Skip this rule. - continue; - } - } - - if(!(*it)->mName.empty()) - { - // check this notification's name against the notification's name and continue if no match is found. - if((*it)->mName != n->getName()) - { - // This rule's non-empty name didn't match the notification. Skip this rule. - continue; - } - } - - // If we got here, the rule matches. Don't evaluate subsequent rules. - if(!(*it)->mVisible) - { - // This notification is being hidden. - - if((*it)->mResponse.empty()) - { - // Response property is empty. Cancel this notification. - LL_DEBUGS("Notifications") << "cancelling notification " << n->getName() << LL_ENDL; - - cancel(n); - } - else - { - // Response property is not empty. Return the specified response. - LLSD response = n->getResponseTemplate(LLNotification::WITHOUT_DEFAULT_BUTTON); - // TODO: verify that the response template has an item with the correct name - response[(*it)->mResponse] = true; - - LL_DEBUGS("Notifications") << "responding to notification " << n->getName() << " with response = " << response << LL_ENDL; - - n->respond(response); - } - - return false; - } - - // If we got here, exit the loop and return true. - break; - } - - LL_DEBUGS("Notifications") << "allowing notification " << n->getName() << LL_ENDL; - - return true; -} - - -// --- -// END OF LLNotifications implementation -// ========================================================= - -std::ostream& operator<<(std::ostream& s, const LLNotification& notification) -{ - s << notification.summarize(); - return s; -} - -void LLPostponedNotification::lookupName(const LLUUID& id, - bool is_group) -{ - if (is_group) - { - gCacheName->getGroup(id, - boost::bind(&LLPostponedNotification::onGroupNameCache, - this, _1, _2, _3)); - } - else - { - fetchAvatarName(id); - } -} - -void LLPostponedNotification::onGroupNameCache(const LLUUID& id, - const std::string& full_name, - bool is_group) -{ - finalizeName(full_name); -} - -void LLPostponedNotification::fetchAvatarName(const LLUUID& id) -{ - if (id.notNull()) - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - - mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLPostponedNotification::onAvatarNameCache, this, _1, _2)); - } -} - -void LLPostponedNotification::onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - - std::string name = av_name.getCompleteName(); - - // from PE merge - we should figure out if this is the right thing to do - if (name.empty()) - { - LL_WARNS("Notifications") << "Empty name received for Id: " << agent_id << LL_ENDL; - name = SYSTEM_FROM; - } - - finalizeName(name); -} - -void LLPostponedNotification::finalizeName(const std::string& name) -{ - mName = name; - modifyNotificationParams(); - LLNotifications::instance().add(mParams); - cleanup(); -} +/** +* @file llnotifications.cpp +* @brief Non-UI queue manager for keeping a prioritized list of notifications +* +* $LicenseInfo:firstyear=2008&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 "llnotifications.h" +#include "llnotificationtemplate.h" +#include "llnotificationvisibilityrule.h" + +#include "llavatarnamecache.h" +#include "llinstantmessage.h" +#include "llcachename.h" +#include "llxmlnode.h" +#include "lluictrl.h" +#include "lluictrlfactory.h" +#include "lldir.h" +#include "llsdserialize.h" +#include "lltrans.h" +#include "llstring.h" +#include "llsdparam.h" +#include "llsdutil.h" + +#include +#include + + +const std::string NOTIFICATION_PERSIST_VERSION = "0.93"; + +void NotificationPriorityValues::declareValues() +{ + declare("low", NOTIFICATION_PRIORITY_LOW); + declare("normal", NOTIFICATION_PRIORITY_NORMAL); + declare("high", NOTIFICATION_PRIORITY_HIGH); + declare("critical", NOTIFICATION_PRIORITY_CRITICAL); +} + +LLNotificationForm::FormElementBase::FormElementBase() +: name("name"), + enabled("enabled", true) +{} + +LLNotificationForm::FormIgnore::FormIgnore() +: text("text"), + control("control"), + invert_control("invert_control", false), + save_option("save_option", false), + session_only("session_only", false), + checkbox_only("checkbox_only", false) +{} + +LLNotificationForm::FormButton::FormButton() +: index("index"), + text("text"), + ignore("ignore"), + is_default("default"), + width("width", 0), + type("type") +{ + // set type here so it gets serialized + type = "button"; +} + +LLNotificationForm::FormInput::FormInput() +: type("type"), + text("text"), + max_length_chars("max_length_chars"), + allow_emoji("allow_emoji"), + width("width", 0), + value("value") +{} + +LLNotificationForm::FormElement::FormElement() +: button("button"), + input("input") +{} + +LLNotificationForm::FormElements::FormElements() +: elements("") +{} + +LLNotificationForm::Params::Params() +: name("name"), + ignore("ignore"), + form_elements("") +{} + + + +bool filterIgnoredNotifications(LLNotificationPtr notification) +{ + LLNotificationFormPtr form = notification->getForm(); + // Check to see if the user wants to ignore this alert + return !notification->getForm()->getIgnored(); +} + +bool handleIgnoredNotification(const LLSD& payload) +{ + if (payload["sigtype"].asString() == "add") + { + LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); + if (!pNotif) return false; + + LLNotificationFormPtr form = pNotif->getForm(); + LLSD response; + switch(form->getIgnoreType()) + { + case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE: + case LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE_SESSION_ONLY: + response = pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON); + break; + case LLNotificationForm::IGNORE_WITH_LAST_RESPONSE: + response = LLUI::getInstance()->mSettingGroups["ignores"]->getLLSD("Default" + pNotif->getName()); + break; + case LLNotificationForm::IGNORE_SHOW_AGAIN: + break; + default: + return false; + } + pNotif->setIgnored(true); + pNotif->respond(response); + return true; // don't process this item any further + } + return false; +} + +bool defaultResponse(const LLSD& payload) +{ + if (payload["sigtype"].asString() == "add") + { + LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); + if (pNotif) + { + // supply default response + pNotif->respond(pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON)); + } + } + return false; +} + +bool visibilityRuleMached(const LLSD& payload) +{ + // This is needed because LLNotifications::isVisibleByRules may have cancelled the notification. + // Returning true here makes LLNotificationChannelBase::updateItem do an early out, which prevents things from happening in the wrong order. + return true; +} + + +namespace LLNotificationFilters +{ + // a sample filter + bool includeEverything(LLNotificationPtr p) + { + return true; + } +}; + +LLNotificationForm::LLNotificationForm() +: mIgnore(IGNORE_NO) +{ +} + +LLNotificationForm::LLNotificationForm( const LLNotificationForm& other ) +{ + mFormData = other.mFormData; + mIgnore = other.mIgnore; + mIgnoreMsg = other.mIgnoreMsg; + mIgnoreSetting = other.mIgnoreSetting; + mInvertSetting = other.mInvertSetting; +} + +LLNotificationForm::LLNotificationForm(const std::string& name, const LLNotificationForm::Params& p) +: mIgnore(IGNORE_NO), + mInvertSetting(false) // ignore settings by default mean true=show, false=ignore +{ + if (p.ignore.isProvided()) + { + // For all cases but IGNORE_CHECKBOX_ONLY this is name for use in preferences + mIgnoreMsg = p.ignore.text; + + LLUI *ui_inst = LLUI::getInstance(); + if (p.ignore.checkbox_only) + { + mIgnore = IGNORE_CHECKBOX_ONLY; + } + else if (!p.ignore.save_option) + { + mIgnore = p.ignore.session_only ? IGNORE_WITH_DEFAULT_RESPONSE_SESSION_ONLY : IGNORE_WITH_DEFAULT_RESPONSE; + } + else + { + // remember last option chosen by user and automatically respond with that in the future + mIgnore = IGNORE_WITH_LAST_RESPONSE; + ui_inst->mSettingGroups["ignores"]->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name)); + } + + bool show_notification = true; + if (p.ignore.control.isProvided()) + { + mIgnoreSetting = ui_inst->mSettingGroups["config"]->getControl(p.ignore.control()); + mInvertSetting = p.ignore.invert_control; + } + else if (mIgnore > IGNORE_NO) + { + ui_inst->mSettingGroups["ignores"]->declareBOOL(name, show_notification, "Show notification with this name", LLControlVariable::PERSIST_NONDFT); + mIgnoreSetting = ui_inst->mSettingGroups["ignores"]->getControl(name); + } + } + + LLParamSDParser parser; + parser.writeSD(mFormData, p.form_elements); + + for (LLSD::array_iterator it = mFormData.beginArray(), end_it = mFormData.endArray(); + it != end_it; + ++it) + { + // lift contents of form element up a level, since element type is already encoded in "type" param + if (it->isMap() && it->beginMap() != it->endMap()) + { + *it = it->beginMap()->second; + } + } + + LL_DEBUGS("Notifications") << name << LL_ENDL; + LL_DEBUGS("Notifications") << ll_pretty_print_sd(mFormData) << LL_ENDL; +} + +LLNotificationForm::LLNotificationForm(const LLSD& sd) + : mIgnore(IGNORE_NO) +{ + if (sd.isArray()) + { + mFormData = sd; + } + else + { + LL_WARNS("Notifications") << "Invalid form data " << sd << LL_ENDL; + mFormData = LLSD::emptyArray(); + } +} + +LLSD LLNotificationForm::asLLSD() const +{ + return mFormData; +} + +LLSD LLNotificationForm::getElement(const std::string& element_name) +{ + for (LLSD::array_const_iterator it = mFormData.beginArray(); + it != mFormData.endArray(); + ++it) + { + if ((*it)["name"].asString() == element_name) return (*it); + } + return LLSD(); +} + + +bool LLNotificationForm::hasElement(const std::string& element_name) const +{ + for (LLSD::array_const_iterator it = mFormData.beginArray(); + it != mFormData.endArray(); + ++it) + { + if ((*it)["name"].asString() == element_name) return true; + } + return false; +} + +void LLNotificationForm::getElements(LLSD& elements, S32 offset) +{ + //Finds elements that the template did not add + LLSD::array_const_iterator it = mFormData.beginArray() + offset; + + //Keeps track of only the dynamic elements + for(; it != mFormData.endArray(); ++it) + { + elements.append(*it); + } +} + +bool LLNotificationForm::getElementEnabled(const std::string& element_name) const +{ + for (LLSD::array_const_iterator it = mFormData.beginArray(); + it != mFormData.endArray(); + ++it) + { + if ((*it)["name"].asString() == element_name) + { + return (*it)["enabled"].asBoolean(); + } + } + + return false; +} + +void LLNotificationForm::setElementEnabled(const std::string& element_name, bool enabled) +{ + for (LLSD::array_iterator it = mFormData.beginArray(); + it != mFormData.endArray(); + ++it) + { + if ((*it)["name"].asString() == element_name) + { + (*it)["enabled"] = enabled; + } + } +} + + +void LLNotificationForm::addElement(const std::string& type, const std::string& name, const LLSD& value, bool enabled) +{ + LLSD element; + element["type"] = type; + element["name"] = name; + element["text"] = name; + element["value"] = value; + element["index"] = LLSD::Integer(mFormData.size()); + element["enabled"] = enabled; + mFormData.append(element); +} + +void LLNotificationForm::append(const LLSD& sub_form) +{ + if (sub_form.isArray()) + { + for (LLSD::array_const_iterator it = sub_form.beginArray(); + it != sub_form.endArray(); + ++it) + { + mFormData.append(*it); + } + } +} + +void LLNotificationForm::formatElements(const LLSD& substitutions) +{ + for (LLSD::array_iterator it = mFormData.beginArray(); + it != mFormData.endArray(); + ++it) + { + // format "text" component of each form element + if ((*it).has("text")) + { + std::string text = (*it)["text"].asString(); + LLStringUtil::format(text, substitutions); + (*it)["text"] = text; + } + if ((*it)["type"].asString() == "text" && (*it).has("value")) + { + std::string value = (*it)["value"].asString(); + LLStringUtil::format(value, substitutions); + (*it)["value"] = value; + } + } +} + +std::string LLNotificationForm::getDefaultOption() +{ + for (LLSD::array_const_iterator it = mFormData.beginArray(); + it != mFormData.endArray(); + ++it) + { + if ((*it)["default"]) return (*it)["name"].asString(); + } + return ""; +} + +LLControlVariablePtr LLNotificationForm::getIgnoreSetting() +{ + return mIgnoreSetting; +} + +bool LLNotificationForm::getIgnored() +{ + bool show = true; + if (mIgnore > LLNotificationForm::IGNORE_NO + && mIgnoreSetting) + { + show = mIgnoreSetting->getValue().asBoolean(); + if (mInvertSetting) show = !show; + } + return !show; +} + +void LLNotificationForm::setIgnored(bool ignored) +{ + if (mIgnoreSetting) + { + if (mInvertSetting) ignored = !ignored; + mIgnoreSetting->setValue(!ignored); + } +} + +LLNotificationTemplate::LLNotificationTemplate(const LLNotificationTemplate::Params& p) +: mName(p.name), + mType(p.type), + mMessage(p.value), + mFooter(p.footer.value), + mLabel(p.label), + mIcon(p.icon), + mURL(p.url.value), + mExpireSeconds(p.duration), + mExpireOption(p.expire_option), + mURLOption(p.url.option), + mURLTarget(p.url.target), + mForceUrlsExternal(p.force_urls_external), + mUnique(p.unique.isProvided()), + mCombineBehavior(p.unique.combine), + mPriority(p.priority), + mPersist(p.persist), + mDefaultFunctor(p.functor.isProvided() ? p.functor() : p.name()), + mLogToChat(p.log_to_chat), + mLogToIM(p.log_to_im), + mShowToast(p.show_toast), + mFadeToast(p.fade_toast), + mSoundName("") +{ + if (p.sound.isProvided() + && LLUI::getInstance()->mSettingGroups["config"]->controlExists(p.sound)) + { + mSoundName = p.sound; + } + + for (const LLNotificationTemplate::UniquenessContext& context : p.unique.contexts) + { + mUniqueContext.push_back(context.value); + } + + LL_DEBUGS("Notifications") << "notification \"" << mName << "\": tag count is " << p.tags.size() << LL_ENDL; + + for (const LLNotificationTemplate::Tag& tag : p.tags) + { + LL_DEBUGS("Notifications") << " tag \"" << std::string(tag.value) << "\"" << LL_ENDL; + mTags.push_back(tag.value); + } + + mForm = LLNotificationFormPtr(new LLNotificationForm(p.name, p.form_ref.form)); +} + +LLNotificationVisibilityRule::LLNotificationVisibilityRule(const LLNotificationVisibilityRule::Rule &p) +{ + if (p.show.isChosen()) + { + mType = p.show.type; + mTag = p.show.tag; + mName = p.show.name; + mVisible = true; + } + else if (p.hide.isChosen()) + { + mType = p.hide.type; + mTag = p.hide.tag; + mName = p.hide.name; + mVisible = false; + } + else if (p.respond.isChosen()) + { + mType = p.respond.type; + mTag = p.respond.tag; + mName = p.respond.name; + mVisible = false; + mResponse = p.respond.response; + } +} + +LLNotification::LLNotification(const LLSDParamAdapter& p) : + mTimestamp(p.time_stamp), + mSubstitutions(p.substitutions), + mPayload(p.payload), + mExpiresAt(p.expiry), + mTemporaryResponder(false), + mRespondedTo(false), + mPriority(p.priority), + mCancelled(false), + mIgnored(false), + mResponderObj(NULL), + mId(p.id.isProvided() ? p.id : LLUUID::generateNewID()), + mOfferFromAgent(p.offer_from_agent), + mIsDND(p.is_dnd) +{ + if (p.functor.name.isChosen()) + { + mResponseFunctorName = p.functor.name; + } + else if (p.functor.function.isChosen()) + { + mResponseFunctorName = LLUUID::generateNewID().asString(); + LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, p.functor.function()); + + mTemporaryResponder = true; + } + else if(p.functor.responder.isChosen()) + { + mResponder = p.functor.responder; + } + + if(p.responder.isProvided()) + { + mResponderObj = p.responder; + } + + init(p.name, p.form_elements); +} + + +LLSD LLNotification::asLLSD(bool excludeTemplateElements) +{ + LLParamSDParser parser; + + Params p; + p.id = mId; + p.name = mTemplatep->mName; + p.substitutions = mSubstitutions; + p.payload = mPayload; + p.time_stamp = mTimestamp; + p.expiry = mExpiresAt; + p.priority = mPriority; + + LLNotificationFormPtr templateForm = mTemplatep->mForm; + LLSD formElements = mForm->asLLSD(); + + //All form elements (dynamic or not) + if(!excludeTemplateElements) + { + p.form_elements = formElements; + } + //Only dynamic form elements (exclude template elements) + else if(templateForm->getNumElements() < formElements.size()) + { + LLSD dynamicElements; + //Offset to dynamic elements and store them + mForm->getElements(dynamicElements, templateForm->getNumElements()); + p.form_elements = dynamicElements; + } + + if(mResponder) + { + p.functor.responder_sd = mResponder->asLLSD(); + } + + if(!mResponseFunctorName.empty()) + { + p.functor.name = mResponseFunctorName; + } + + LLSD output; + parser.writeSD(output, p); + return output; +} + +void LLNotification::update() +{ + LLNotifications::instance().update(shared_from_this()); +} + +void LLNotification::updateFrom(LLNotificationPtr other) +{ + // can only update from the same notification type + if (mTemplatep != other->mTemplatep) return; + + // NOTE: do NOT change the ID, since it is the key to + // this given instance, just update all the metadata + //mId = other->mId; + + mPayload = other->mPayload; + mSubstitutions = other->mSubstitutions; + mTimestamp = other->mTimestamp; + mExpiresAt = other->mExpiresAt; + mCancelled = other->mCancelled; + mIgnored = other->mIgnored; + mPriority = other->mPriority; + mForm = other->mForm; + mResponseFunctorName = other->mResponseFunctorName; + mRespondedTo = other->mRespondedTo; + mResponse = other->mResponse; + mTemporaryResponder = other->mTemporaryResponder; + + update(); +} + +const LLNotificationFormPtr LLNotification::getForm() +{ + return mForm; +} + +void LLNotification::cancel() +{ + mCancelled = true; +} + +LLSD LLNotification::getResponseTemplate(EResponseTemplateType type) +{ + LLSD response = LLSD::emptyMap(); + for (S32 element_idx = 0; + element_idx < mForm->getNumElements(); + ++element_idx) + { + LLSD element = mForm->getElement(element_idx); + if (element.has("name")) + { + response[element["name"].asString()] = element["value"]; + } + + if ((type == WITH_DEFAULT_BUTTON) + && element["default"].asBoolean()) + { + response[element["name"].asString()] = true; + } + } + return response; +} + +//static +S32 LLNotification::getSelectedOption(const LLSD& notification, const LLSD& response) +{ + LLNotificationForm form(notification["form"]); + + for (S32 element_idx = 0; + element_idx < form.getNumElements(); + ++element_idx) + { + LLSD element = form.getElement(element_idx); + + // only look at buttons + if (element["type"].asString() == "button" + && response[element["name"].asString()].asBoolean()) + { + return element["index"].asInteger(); + } + } + + return -1; +} + +//static +std::string LLNotification::getSelectedOptionName(const LLSD& response) +{ + for (LLSD::map_const_iterator response_it = response.beginMap(); + response_it != response.endMap(); + ++response_it) + { + if (response_it->second.isBoolean() && response_it->second.asBoolean()) + { + return response_it->first; + } + } + return ""; +} + + +void LLNotification::respond(const LLSD& response) +{ + // *TODO may remove mRespondedTo and use mResponce.isDefined() in isRespondedTo() + mRespondedTo = true; + mResponse = response; + + if(mResponder) + { + mResponder->handleRespond(asLLSD(), response); + } + else if (!mResponseFunctorName.empty()) + { + // look up the functor + LLNotificationFunctorRegistry::ResponseFunctor functor = + LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName); + // and then call it + functor(asLLSD(), response); + } + else if (mCombinedNotifications.empty()) + { + // no registered responder + return; + } + + if (mTemporaryResponder) + { + LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); + mResponseFunctorName = ""; + mTemporaryResponder = false; + } + + if (mForm->getIgnoreType() > LLNotificationForm::IGNORE_NO) + { + mForm->setIgnored(mIgnored); + if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) + { + LLUI::getInstance()->mSettingGroups["ignores"]->setLLSD("Default" + getName(), response); + } + } + + for (std::vector::const_iterator it = mCombinedNotifications.begin(); it != mCombinedNotifications.end(); ++it) + { + if ((*it)) + { + (*it)->respond(response); + } + } + + update(); +} + +void LLNotification::respondWithDefault() +{ + respond(getResponseTemplate(WITH_DEFAULT_BUTTON)); +} + + +const std::string& LLNotification::getName() const +{ + return mTemplatep->mName; +} + +const std::string& LLNotification::getIcon() const +{ + return mTemplatep->mIcon; +} + + +bool LLNotification::isPersistent() const +{ + return mTemplatep->mPersist; +} + +std::string LLNotification::getType() const +{ + return (mTemplatep ? mTemplatep->mType : ""); +} + +S32 LLNotification::getURLOption() const +{ + return (mTemplatep ? mTemplatep->mURLOption : -1); +} + +S32 LLNotification::getURLOpenExternally() const +{ + return(mTemplatep? mTemplatep->mURLTarget == "_external": -1); +} + +bool LLNotification::getForceUrlsExternal() const +{ + return (mTemplatep ? mTemplatep->mForceUrlsExternal : false); +} + +bool LLNotification::hasUniquenessConstraints() const +{ + return (mTemplatep ? mTemplatep->mUnique : false); +} + +bool LLNotification::matchesTag(const std::string& tag) +{ + bool result = false; + + if(mTemplatep) + { + std::list::iterator it; + for(it = mTemplatep->mTags.begin(); it != mTemplatep->mTags.end(); it++) + { + if((*it) == tag) + { + result = true; + break; + } + } + } + + return result; +} + +void LLNotification::setIgnored(bool ignore) +{ + mIgnored = ignore; +} + +void LLNotification::setResponseFunctor(std::string const &responseFunctorName) +{ + if (mTemporaryResponder) + // get rid of the old one + LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); + mResponseFunctorName = responseFunctorName; + mTemporaryResponder = false; +} + +void LLNotification::setResponseFunctor(const LLNotificationFunctorRegistry::ResponseFunctor& cb) +{ + if(mTemporaryResponder) + { + LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); + } + + LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, cb); +} + +void LLNotification::setResponseFunctor(const LLNotificationResponderPtr& responder) +{ + mResponder = responder; +} + +bool LLNotification::isEquivalentTo(LLNotificationPtr that) const +{ + if (this->mTemplatep->mName != that->mTemplatep->mName) + { + return false; // must have the same template name or forget it + } + if (this->mTemplatep->mUnique) + { + const LLSD& these_substitutions = this->getSubstitutions(); + const LLSD& those_substitutions = that->getSubstitutions(); + const LLSD& this_payload = this->getPayload(); + const LLSD& that_payload = that->getPayload(); + + // highlander bit sez there can only be one of these + for (std::vector::const_iterator it = mTemplatep->mUniqueContext.begin(), end_it = mTemplatep->mUniqueContext.end(); + it != end_it; + ++it) + { + // if templates differ in either substitution strings or payload with the given field name + // then they are considered inequivalent + // use of get() avoids converting the LLSD value to a map as the [] operator would + if (these_substitutions.get(*it).asString() != those_substitutions.get(*it).asString() + || this_payload.get(*it).asString() != that_payload.get(*it).asString()) + { + return false; + } + } + return true; + } + + return false; +} + +void LLNotification::init(const std::string& template_name, const LLSD& form_elements) +{ + mTemplatep = LLNotifications::instance().getTemplate(template_name); + if (!mTemplatep) return; + + // add default substitutions + const LLStringUtil::format_map_t& default_args = LLTrans::getDefaultArgs(); + for (LLStringUtil::format_map_t::const_iterator iter = default_args.begin(); + iter != default_args.end(); ++iter) + { + mSubstitutions[iter->first] = iter->second; + } + mSubstitutions["_URL"] = getURL(); + mSubstitutions["_NAME"] = template_name; + // TODO: something like this so that a missing alert is sensible: + //mSubstitutions["_ARGS"] = get_all_arguments_as_text(mSubstitutions); + + mForm = LLNotificationFormPtr(new LLNotificationForm(*mTemplatep->mForm)); + mForm->append(form_elements); + + // apply substitution to form labels + mForm->formatElements(mSubstitutions); + + mIgnored = mForm->getIgnored(); + + LLDate rightnow = LLDate::now(); + if (mTemplatep->mExpireSeconds) + { + mExpiresAt = LLDate(rightnow.secondsSinceEpoch() + mTemplatep->mExpireSeconds); + } + + if (mPriority == NOTIFICATION_PRIORITY_UNSPECIFIED) + { + mPriority = mTemplatep->mPriority; + } +} + +std::string LLNotification::summarize() const +{ + std::string s = "Notification("; + s += getName(); + s += ") : "; + s += mTemplatep ? mTemplatep->mMessage : ""; + // should also include timestamp and expiration time (but probably not payload) + return s; +} + +std::string LLNotification::getMessage() const +{ + // all our callers cache this result, so it gives us more flexibility + // to do the substitution at call time rather than attempting to + // cache it in the notification + if (!mTemplatep) + return std::string(); + + std::string message = mTemplatep->mMessage; + LLStringUtil::format(message, mSubstitutions); + return message; +} + +std::string LLNotification::getFooter() const +{ + if (!mTemplatep) + return std::string(); + + std::string footer = mTemplatep->mFooter; + LLStringUtil::format(footer, mSubstitutions); + return footer; +} + +std::string LLNotification::getLabel() const +{ + std::string label = mTemplatep->mLabel; + LLStringUtil::format(label, mSubstitutions); + return (mTemplatep ? label : ""); +} + +std::string LLNotification::getURL() const +{ + if (!mTemplatep) + return std::string(); + std::string url = mTemplatep->mURL; + LLStringUtil::format(url, mSubstitutions); + return (mTemplatep ? url : ""); +} + +bool LLNotification::canLogToChat() const +{ + return mTemplatep->mLogToChat; +} + +bool LLNotification::canLogToIM() const +{ + return mTemplatep->mLogToIM; +} + +bool LLNotification::canShowToast() const +{ + return mTemplatep->mShowToast; +} + +bool LLNotification::canFadeToast() const +{ + return mTemplatep->mFadeToast; +} + +bool LLNotification::hasFormElements() const +{ + return mTemplatep->mForm->getNumElements() != 0; +} + +void LLNotification::playSound() +{ + make_ui_sound(mTemplatep->mSoundName.c_str()); +} + +LLNotification::ECombineBehavior LLNotification::getCombineBehavior() const +{ + return mTemplatep->mCombineBehavior; +} + +void LLNotification::updateForm( const LLNotificationFormPtr& form ) +{ + mForm = form; +} + +void LLNotification::repost() +{ + mRespondedTo = false; + LLNotifications::instance().update(shared_from_this()); +} + + + +// ========================================================= +// LLNotificationChannel implementation +// --- +LLBoundListener LLNotificationChannelBase::connectChangedImpl(const LLEventListener& slot) +{ + // when someone wants to connect to a channel, we first throw them + // all of the notifications that are already in the channel + // we use a special signal called "load" in case the channel wants to care + // only about new notifications + LLMutexLock lock(&mItemsMutex); + for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) + { + slot(LLSD().with("sigtype", "load").with("id", (*it)->id())); + } + // and then connect the signal so that all future notifications will also be + // forwarded. + return mChanged.connect(slot); +} + +LLBoundListener LLNotificationChannelBase::connectAtFrontChangedImpl(const LLEventListener& slot) +{ + for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) + { + slot(LLSD().with("sigtype", "load").with("id", (*it)->id())); + } + return mChanged.connect(slot, boost::signals2::at_front); +} + +LLBoundListener LLNotificationChannelBase::connectPassedFilterImpl(const LLEventListener& slot) +{ + // these two filters only fire for notifications added after the current one, because + // they don't participate in the hierarchy. + return mPassedFilter.connect(slot); +} + +LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot) +{ + return mFailedFilter.connect(slot); +} + +// external call, conforms to our standard signature +bool LLNotificationChannelBase::updateItem(const LLSD& payload) +{ + // first check to see if it's in the master list + LLNotificationPtr pNotification = LLNotifications::instance().find(payload["id"]); + if (!pNotification) + return false; // not found + + return updateItem(payload, pNotification); +} + + +//FIX QUIT NOT WORKING + + +// internal call, for use in avoiding lookup +bool LLNotificationChannelBase::updateItem(const LLSD& payload, LLNotificationPtr pNotification) +{ + std::string cmd = payload["sigtype"]; + LLNotificationSet::iterator foundItem = mItems.find(pNotification); + bool wasFound = (foundItem != mItems.end()); + bool passesFilter = mFilter ? mFilter(pNotification) : true; + + // first, we offer the result of the filter test to the simple + // signals for pass/fail. One of these is guaranteed to be called. + // If either signal returns true, the change processing is NOT performed + // (so don't return true unless you know what you're doing!) + bool abortProcessing = false; + if (passesFilter) + { + onFilterPass(pNotification); + abortProcessing = mPassedFilter(payload); + } + else + { + onFilterFail(pNotification); + abortProcessing = mFailedFilter(payload); + } + + if (abortProcessing) + { + return true; + } + + if (cmd == "load") + { + // should be no reason we'd ever get a load if we already have it + // if passes filter send a load message, else do nothing + assert(!wasFound); + if (passesFilter) + { + // not in our list, add it and say so + mItems.insert(pNotification); + onLoad(pNotification); + abortProcessing = mChanged(payload); + } + } + else if (cmd == "change") + { + // if it passes filter now and was found, we just send a change message + // if it passes filter now and wasn't found, we have to add it + // if it doesn't pass filter and wasn't found, we do nothing + // if it doesn't pass filter and was found, we need to delete it + if (passesFilter) + { + if (wasFound) + { + // it already existed, so this is a change + // since it changed in place, all we have to do is resend the signal + onChange(pNotification); + abortProcessing = mChanged(payload); + } + else + { + // not in our list, add it and say so + mItems.insert(pNotification); + onChange(pNotification); + // our payload is const, so make a copy before changing it + LLSD newpayload = payload; + newpayload["sigtype"] = "add"; + abortProcessing = mChanged(newpayload); + } + } + else + { + if (wasFound) + { + // it already existed, so this is a delete + mItems.erase(pNotification); + onChange(pNotification); + // our payload is const, so make a copy before changing it + LLSD newpayload = payload; + newpayload["sigtype"] = "delete"; + abortProcessing = mChanged(newpayload); + } + // didn't pass, not on our list, do nothing + } + } + else if (cmd == "add") + { + // should be no reason we'd ever get an add if we already have it + // if passes filter send an add message, else do nothing + assert(!wasFound); + if (passesFilter) + { + // not in our list, add it and say so + mItems.insert(pNotification); + onAdd(pNotification); + abortProcessing = mChanged(payload); + } + } + else if (cmd == "delete") + { + // if we have it in our list, pass on the delete, then delete it, else do nothing + if (wasFound) + { + onDelete(pNotification); + abortProcessing = mChanged(payload); + mItems.erase(pNotification); + } + } + return abortProcessing; +} + +LLNotificationChannel::LLNotificationChannel(const Params& p) +: LLNotificationChannelBase(p.filter()), + LLInstanceTracker(p.name.isProvided() ? p.name : LLUUID::generateNewID().asString()), + mName(p.name.isProvided() ? p.name : LLUUID::generateNewID().asString()) +{ + for (const std::string& source : p.sources) + { + connectToChannel(source); + } +} + + +LLNotificationChannel::LLNotificationChannel(const std::string& name, + const std::string& parent, + LLNotificationFilter filter) +: LLNotificationChannelBase(filter), + LLInstanceTracker(name), + mName(name) +{ + // bind to notification broadcast + connectToChannel(parent); +} + +LLNotificationChannel::~LLNotificationChannel() +{ + for (LLBoundListener &listener : mListeners) + { + listener.disconnect(); + } +} + +bool LLNotificationChannel::isEmpty() const +{ + return mItems.empty(); +} + +S32 LLNotificationChannel::size() const +{ + return mItems.size(); +} + +size_t LLNotificationChannel::size() +{ + return mItems.size(); +} + +void LLNotificationChannel::forEachNotification(NotificationProcess process) +{ + LLMutexLock lock(&mItemsMutex); + std::for_each(mItems.begin(), mItems.end(), process); +} + +std::string LLNotificationChannel::summarize() +{ + std::string s("Channel '"); + s += mName; + s += "'\n "; + LLMutexLock lock(&mItemsMutex); + for (LLNotificationChannel::Iterator it = mItems.begin(); it != mItems.end(); ++it) + { + s += (*it)->summarize(); + s += "\n "; + } + return s; +} + +void LLNotificationChannel::connectToChannel( const std::string& channel_name ) +{ + if (channel_name.empty()) + { + mListeners.push_back(LLNotifications::instance().connectChanged( + boost::bind(&LLNotificationChannelBase::updateItem, this, _1))); + } + else + { + mParents.push_back(channel_name); + LLNotificationChannelPtr p = LLNotifications::instance().getChannel(channel_name); + mListeners.push_back(p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1))); + } +} + +// --- +// END OF LLNotificationChannel implementation +// ========================================================= + + +// ============================================== =========== +// LLNotifications implementation +// --- +LLNotifications::LLNotifications() +: LLNotificationChannelBase(LLNotificationFilters::includeEverything), + mIgnoreAllNotifications(false) +{ + mListener.reset(new LLNotificationsListener(*this)); + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); + + // touch the instance tracker for notification channels, so that it will still be around in our destructor + LLInstanceTracker::instanceCount(); +} + +void LLNotifications::clear() +{ + mDefaultChannels.clear(); +} + +// The expiration channel gets all notifications that are cancelled +bool LLNotifications::expirationFilter(LLNotificationPtr pNotification) +{ + return pNotification->isCancelled() || pNotification->isRespondedTo(); +} + +bool LLNotifications::expirationHandler(const LLSD& payload) +{ + if (payload["sigtype"].asString() != "delete") + { + // anything added to this channel actually should be deleted from the master + cancel(find(payload["id"])); + return true; // don't process this item any further + } + return false; +} + +bool LLNotifications::uniqueFilter(LLNotificationPtr pNotif) +{ + if (!pNotif->hasUniquenessConstraints()) + { + return true; + } + + // checks against existing unique notifications + for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); + existing_it != mUniqueNotifications.end(); + ++existing_it) + { + LLNotificationPtr existing_notification = existing_it->second; + if (pNotif != existing_notification + && pNotif->isEquivalentTo(existing_notification)) + { + if (pNotif->getCombineBehavior() == LLNotification::CANCEL_OLD) + { + cancel(existing_notification); + return true; + } + else + { + return false; + } + } + } + + return true; +} + +bool LLNotifications::uniqueHandler(const LLSD& payload) +{ + std::string cmd = payload["sigtype"]; + + LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); + if (pNotif && pNotif->hasUniquenessConstraints()) + { + if (cmd == "add") + { + // not a duplicate according to uniqueness criteria, so we keep it + // and store it for future uniqueness checks + mUniqueNotifications.insert(std::make_pair(pNotif->getName(), pNotif)); + } + else if (cmd == "delete") + { + mUniqueNotifications.erase(pNotif->getName()); + } + } + + return false; +} + +bool LLNotifications::failedUniquenessTest(const LLSD& payload) +{ + LLNotificationPtr pNotif = LLNotifications::instance().find(payload["id"].asUUID()); + + std::string cmd = payload["sigtype"]; + + if (!pNotif || cmd != "add") + { + return false; + } + + switch(pNotif->getCombineBehavior()) + { + case LLNotification::REPLACE_WITH_NEW: + // Update the existing unique notification with the data from this particular instance... + // This guarantees that duplicate notifications will be collapsed to the one + // most recently triggered + for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); + existing_it != mUniqueNotifications.end(); + ++existing_it) + { + LLNotificationPtr existing_notification = existing_it->second; + if (pNotif != existing_notification + && pNotif->isEquivalentTo(existing_notification)) + { + // copy notification instance data over to oldest instance + // of this unique notification and update it + existing_notification->updateFrom(pNotif); + // then delete the new one + cancel(pNotif); + } + } + break; + case LLNotification::COMBINE_WITH_NEW: + // Add to the existing unique notification with the data from this particular instance... + // This guarantees that duplicate notifications will be collapsed to the one + // most recently triggered + for (LLNotificationMap::iterator existing_it = mUniqueNotifications.find(pNotif->getName()); + existing_it != mUniqueNotifications.end(); + ++existing_it) + { + LLNotificationPtr existing_notification = existing_it->second; + if (pNotif != existing_notification + && pNotif->isEquivalentTo(existing_notification)) + { + // copy the notifications from the newest instance into the oldest + existing_notification->mCombinedNotifications.push_back(pNotif); + existing_notification->mCombinedNotifications.insert(existing_notification->mCombinedNotifications.end(), + pNotif->mCombinedNotifications.begin(), pNotif->mCombinedNotifications.end()); + + // pop up again + existing_notification->update(); + } + } + break; + case LLNotification::KEEP_OLD: + break; + case LLNotification::CANCEL_OLD: + // already handled by filter logic + break; + default: + break; + } + + return false; +} + +LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelName) +{ + return LLNotificationChannelPtr(LLNotificationChannel::getInstance(channelName).get()); +} + + +// this function is called once at construction time, after the object is constructed. +void LLNotifications::initSingleton() +{ + loadTemplates(); + loadVisibilityRules(); + createDefaultChannels(); +} + +void LLNotifications::cleanupSingleton() +{ + clear(); +} + +void LLNotifications::createDefaultChannels() +{ + LL_INFOS("Notifications") << "Generating default notification channels" << LL_ENDL; + // now construct the various channels AFTER loading the notifications, + // because the history channel is going to rewrite the stored notifications file + mDefaultChannels.push_back(new LLNotificationChannel("Enabled", "", + !boost::bind(&LLNotifications::getIgnoreAllNotifications, this))); + mDefaultChannels.push_back(new LLNotificationChannel("Expiration", "Enabled", + boost::bind(&LLNotifications::expirationFilter, this, _1))); + mDefaultChannels.push_back(new LLNotificationChannel("Unexpired", "Enabled", + !boost::bind(&LLNotifications::expirationFilter, this, _1))); // use negated bind + mDefaultChannels.push_back(new LLNotificationChannel("Unique", "Unexpired", + boost::bind(&LLNotifications::uniqueFilter, this, _1))); + mDefaultChannels.push_back(new LLNotificationChannel("Ignore", "Unique", + filterIgnoredNotifications)); + mDefaultChannels.push_back(new LLNotificationChannel("VisibilityRules", "Ignore", + boost::bind(&LLNotifications::isVisibleByRules, this, _1))); + mDefaultChannels.push_back(new LLNotificationChannel("Visible", "VisibilityRules", + &LLNotificationFilters::includeEverything)); + mDefaultChannels.push_back(new LLPersistentNotificationChannel()); + + // connect action methods to these channels + getChannel("Enabled")->connectFailedFilter(&defaultResponse); + getChannel("Expiration")->connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); + // uniqueHandler slot should be added as first slot of the signal due to + // usage LLStopWhenHandled combiner in LLStandardSignal + getChannel("Unique")->connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); + getChannel("Unique")->connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); + getChannel("Ignore")->connectFailedFilter(&handleIgnoredNotification); + getChannel("VisibilityRules")->connectFailedFilter(&visibilityRuleMached); +} + + +LLNotificationTemplatePtr LLNotifications::getTemplate(const std::string& name) +{ + if (mTemplates.count(name)) + { + return mTemplates[name]; + } + else + { + return mTemplates["MissingAlert"]; + } +} + +bool LLNotifications::templateExists(const std::string& name) +{ + return (mTemplates.count(name) != 0); +} + +void LLNotifications::forceResponse(const LLNotification::Params& params, S32 option) +{ + LLNotificationPtr temp_notify(new LLNotification(params)); + LLSD response = temp_notify->getResponseTemplate(); + LLSD selected_item = temp_notify->getForm()->getElement(option); + + if (selected_item.isUndefined()) + { + LL_WARNS("Notifications") << "Invalid option" << option << " for notification " << (std::string)params.name << LL_ENDL; + return; + } + response[selected_item["name"].asString()] = true; + + temp_notify->respond(response); +} + +LLNotifications::TemplateNames LLNotifications::getTemplateNames() const +{ + TemplateNames names; + for (TemplateMap::const_iterator it = mTemplates.begin(); it != mTemplates.end(); ++it) + { + names.push_back(it->first); + } + return names; +} + +typedef std::map StringMap; +void replaceSubstitutionStrings(LLXMLNodePtr node, StringMap& replacements) +{ + // walk the list of attributes looking for replacements + for (LLXMLAttribList::iterator it=node->mAttributes.begin(); + it != node->mAttributes.end(); ++it) + { + std::string value = it->second->getValue(); + if (value[0] == '$') + { + value.erase(0, 1); // trim off the $ + std::string replacement; + StringMap::const_iterator found = replacements.find(value); + if (found != replacements.end()) + { + replacement = found->second; + LL_DEBUGS("Notifications") << "replaceSubstitutionStrings: value: \"" << value << "\" repl: \"" << replacement << "\"." << LL_ENDL; + it->second->setValue(replacement); + } + else + { + LL_WARNS("Notifications") << "replaceSubstitutionStrings FAILURE: could not find replacement \"" << value << "\"." << LL_ENDL; + } + } + } + + // now walk the list of children and call this recursively. + for (LLXMLNodePtr child = node->getFirstChild(); + child.notNull(); child = child->getNextSibling()) + { + replaceSubstitutionStrings(child, replacements); + } +} + +void replaceFormText(LLNotificationForm::Params& form, const std::string& pattern, const std::string& replace) +{ + if (form.ignore.isProvided() && form.ignore.text() == pattern) + { + form.ignore.text = replace; + } + + for (LLNotificationForm::FormElement& element : form.form_elements.elements) + { + if (element.button.isChosen() && element.button.text() == pattern) + { + element.button.text = replace; + } + } +} + +void addPathIfExists(const std::string& new_path, std::vector& paths) +{ + if (gDirUtilp->fileExists(new_path)) + { + paths.push_back(new_path); + } +} + +bool LLNotifications::loadTemplates() +{ + LL_INFOS("Notifications") << "Reading notifications template" << LL_ENDL; + // Passing findSkinnedFilenames(constraint=LLDir::ALL_SKINS) makes it + // output all relevant pathnames instead of just the ones from the most + // specific skin. + std::vector search_paths = + gDirUtilp->findSkinnedFilenames(LLDir::XUI, "notifications.xml", LLDir::ALL_SKINS); + if (search_paths.empty()) + { + LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile")); + LL_ERRS() << "Problem finding notifications.xml" << LL_ENDL; + } + + std::string base_filename = search_paths.front(); + LLXMLNodePtr root; + bool success = LLXMLNode::getLayeredXMLNode(root, search_paths); + + if (!success || root.isNull() || !root->hasName( "notifications" )) + { + LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile")); + LL_ERRS() << "Problem reading XML from UI Notifications file: " << base_filename << LL_ENDL; + return false; + } + + LLNotificationTemplate::Notifications params; + LLXUIParser parser; + parser.readXUI(root, params, base_filename); + + if(!params.validateBlock()) + { + LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile")); + LL_ERRS() << "Problem reading XUI from UI Notifications file: " << base_filename << LL_ENDL; + return false; + } + + mTemplates.clear(); + + for (const LLNotificationTemplate::GlobalString& string : params.strings) + { + mGlobalStrings[string.name] = string.value; + } + + std::map form_templates; + + for (const LLNotificationTemplate::Template& notification_template : params.templates) + { + form_templates[notification_template.name] = notification_template.form; + } + + for (LLNotificationTemplate::Params& notification : params.notifications) + { + if (notification.form_ref.form_template.isChosen()) + { + // replace form contents from template + notification.form_ref.form = form_templates[notification.form_ref.form_template.name]; + if(notification.form_ref.form_template.yes_text.isProvided()) + { + replaceFormText(notification.form_ref.form, "$yestext", notification.form_ref.form_template.yes_text); + } + if(notification.form_ref.form_template.no_text.isProvided()) + { + replaceFormText(notification.form_ref.form, "$notext", notification.form_ref.form_template.no_text); + } + if(notification.form_ref.form_template.cancel_text.isProvided()) + { + replaceFormText(notification.form_ref.form, "$canceltext", notification.form_ref.form_template.cancel_text); + } + if(notification.form_ref.form_template.help_text.isProvided()) + { + replaceFormText(notification.form_ref.form, "$helptext", notification.form_ref.form_template.help_text); + } + if(notification.form_ref.form_template.ignore_text.isProvided()) + { + replaceFormText(notification.form_ref.form, "$ignoretext", notification.form_ref.form_template.ignore_text); + } + } + mTemplates[notification.name] = LLNotificationTemplatePtr(new LLNotificationTemplate(notification)); + } + + LL_INFOS("Notifications") << "...done" << LL_ENDL; + + return true; +} + +bool LLNotifications::loadVisibilityRules() +{ + const std::string xml_filename = "notification_visibility.xml"; + // Note that here we're looking for the "en" version, the default + // language, rather than the most localized version of this file. + std::string full_filename = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, xml_filename); + + LLNotificationVisibilityRule::Rules params; + LLSimpleXUIParser parser; + parser.readXUI(full_filename, params); + + if(!params.validateBlock()) + { + LLError::LLUserWarningMsg::show(LLTrans::getString("MBMissingFile")); + LL_ERRS() << "Problem reading UI Notification Visibility Rules file: " << full_filename << LL_ENDL; + return false; + } + + mVisibilityRules.clear(); + + for (const LLNotificationVisibilityRule::Rule& rule : params.rules) + { + mVisibilityRules.push_back(LLNotificationVisibilityRulePtr(new LLNotificationVisibilityRule(rule))); + } + + return true; +} + +// Add a simple notification (from XUI) +void LLNotifications::addFromCallback(const LLSD& name) +{ + add(name.asString(), LLSD(), LLSD()); +} + +LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload) +{ + LLNotification::Params::Functor functor_p; + functor_p.name = name; + return add(LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); +} + +LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload, const std::string& functor_name) +{ + LLNotification::Params::Functor functor_p; + functor_p.name = functor_name; + return add(LLNotification::Params().name(name) + .substitutions(substitutions) + .payload(payload) + .functor(functor_p)); +} + +//virtual +LLNotificationPtr LLNotifications::add(const std::string& name, const LLSD& substitutions, const LLSD& payload, LLNotificationFunctorRegistry::ResponseFunctor functor) +{ + LLNotification::Params::Functor functor_p; + functor_p.function = functor; + return add(LLNotification::Params().name(name) + .substitutions(substitutions) + .payload(payload) + .functor(functor_p)); +} + +// generalized add function that takes a parameter block object for more complex instantiations +LLNotificationPtr LLNotifications::add(const LLNotification::Params& p) +{ + LLNotificationPtr pNotif(new LLNotification(p)); + add(pNotif); + return pNotif; +} + + +void LLNotifications::add(const LLNotificationPtr pNotif) +{ + if (pNotif == NULL) return; + + // first see if we already have it -- if so, that's a problem + LLNotificationSet::iterator it=mItems.find(pNotif); + if (it != mItems.end()) + { + LL_ERRS() << "Notification added a second time to the master notification channel." << LL_ENDL; + } + + updateItem(LLSD().with("sigtype", "add").with("id", pNotif->id()), pNotif); +} + +void LLNotifications::load(const LLNotificationPtr pNotif) +{ + if (pNotif == NULL) return; + + // first see if we already have it -- if so, that's a problem + LLNotificationSet::iterator it=mItems.find(pNotif); + if (it != mItems.end()) + { + LL_ERRS() << "Notification loaded a second time to the master notification channel." << LL_ENDL; + } + + updateItem(LLSD().with("sigtype", "load").with("id", pNotif->id()), pNotif); +} + +void LLNotifications::cancel(LLNotificationPtr pNotif) +{ + if (pNotif == NULL || pNotif->isCancelled()) return; + + LLNotificationSet::iterator it=mItems.find(pNotif); + if (it != mItems.end()) + { + pNotif->cancel(); + updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif); + } +} + +void LLNotifications::cancelByName(const std::string& name) +{ + LLMutexLock lock(&mItemsMutex); + std::vector notifs_to_cancel; + for (LLNotificationSet::iterator it=mItems.begin(), end_it = mItems.end(); + it != end_it; + ++it) + { + LLNotificationPtr pNotif = *it; + if (pNotif->getName() == name) + { + notifs_to_cancel.push_back(pNotif); + } + } + + for (std::vector::iterator it = notifs_to_cancel.begin(), end_it = notifs_to_cancel.end(); + it != end_it; + ++it) + { + LLNotificationPtr pNotif = *it; + pNotif->cancel(); + updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif); + } +} + +void LLNotifications::cancelByOwner(const LLUUID ownerId) +{ + LLMutexLock lock(&mItemsMutex); + std::vector notifs_to_cancel; + for (LLNotificationSet::iterator it = mItems.begin(), end_it = mItems.end(); + it != end_it; + ++it) + { + LLNotificationPtr pNotif = *it; + if (pNotif && pNotif->getPayload().get("owner_id").asUUID() == ownerId) + { + notifs_to_cancel.push_back(pNotif); + } + } + + for (std::vector::iterator it = notifs_to_cancel.begin(), end_it = notifs_to_cancel.end(); + it != end_it; + ++it) + { + LLNotificationPtr pNotif = *it; + pNotif->cancel(); + updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif); + } +} + +void LLNotifications::update(const LLNotificationPtr pNotif) +{ + LLNotificationSet::iterator it=mItems.find(pNotif); + if (it != mItems.end()) + { + updateItem(LLSD().with("sigtype", "change").with("id", pNotif->id()), pNotif); + } +} + + +LLNotificationPtr LLNotifications::find(LLUUID uuid) +{ + LLNotificationPtr target = LLNotificationPtr(new LLNotification(LLNotification::Params().id(uuid))); + LLNotificationSet::iterator it=mItems.find(target); + if (it == mItems.end()) + { + LL_DEBUGS("Notifications") << "Tried to dereference uuid '" << uuid << "' as a notification key but didn't find it." << LL_ENDL; + return LLNotificationPtr((LLNotification*)NULL); + } + else + { + return *it; + } +} + +std::string LLNotifications::getGlobalString(const std::string& key) const +{ + GlobalStringMap::const_iterator it = mGlobalStrings.find(key); + if (it != mGlobalStrings.end()) + { + return it->second; + } + else + { + // if we don't have the key as a global, return the key itself so that the error + // is self-diagnosing. + return key; + } +} + +void LLNotifications::setIgnoreAllNotifications(bool setting) +{ + mIgnoreAllNotifications = setting; +} +bool LLNotifications::getIgnoreAllNotifications() +{ + return mIgnoreAllNotifications; +} + +void LLNotifications::setIgnored(const std::string& name, bool ignored) +{ + LLNotificationTemplatePtr templatep = getTemplate(name); + templatep->mForm->setIgnored(ignored); +} + +bool LLNotifications::getIgnored(const std::string& name) +{ + LLNotificationTemplatePtr templatep = getTemplate(name); + return (mIgnoreAllNotifications) || ( (templatep->mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO) && (templatep->mForm->getIgnored()) ); +} + +bool LLNotifications::isVisibleByRules(LLNotificationPtr n) +{ + if(n->isRespondedTo()) + { + // This avoids infinite recursion in the case where the filter calls respond() + return true; + } + + VisibilityRuleList::iterator it; + + for(it = mVisibilityRules.begin(); it != mVisibilityRules.end(); it++) + { + // An empty type/tag/name string will match any notification, so only do the comparison when the string is non-empty in the rule. + LL_DEBUGS("Notifications") + << "notification \"" << n->getName() << "\" " + << "testing against " << ((*it)->mVisible?"show":"hide") << " rule, " + << "name = \"" << (*it)->mName << "\" " + << "tag = \"" << (*it)->mTag << "\" " + << "type = \"" << (*it)->mType << "\" " + << LL_ENDL; + + if(!(*it)->mType.empty()) + { + if((*it)->mType != n->getType()) + { + // Type doesn't match, so skip this rule. + continue; + } + } + + if(!(*it)->mTag.empty()) + { + // check this notification's tag(s) against it->mTag and continue if no match is found. + if(!n->matchesTag((*it)->mTag)) + { + // This rule's non-empty tag didn't match one of the notification's tags. Skip this rule. + continue; + } + } + + if(!(*it)->mName.empty()) + { + // check this notification's name against the notification's name and continue if no match is found. + if((*it)->mName != n->getName()) + { + // This rule's non-empty name didn't match the notification. Skip this rule. + continue; + } + } + + // If we got here, the rule matches. Don't evaluate subsequent rules. + if(!(*it)->mVisible) + { + // This notification is being hidden. + + if((*it)->mResponse.empty()) + { + // Response property is empty. Cancel this notification. + LL_DEBUGS("Notifications") << "cancelling notification " << n->getName() << LL_ENDL; + + cancel(n); + } + else + { + // Response property is not empty. Return the specified response. + LLSD response = n->getResponseTemplate(LLNotification::WITHOUT_DEFAULT_BUTTON); + // TODO: verify that the response template has an item with the correct name + response[(*it)->mResponse] = true; + + LL_DEBUGS("Notifications") << "responding to notification " << n->getName() << " with response = " << response << LL_ENDL; + + n->respond(response); + } + + return false; + } + + // If we got here, exit the loop and return true. + break; + } + + LL_DEBUGS("Notifications") << "allowing notification " << n->getName() << LL_ENDL; + + return true; +} + + +// --- +// END OF LLNotifications implementation +// ========================================================= + +std::ostream& operator<<(std::ostream& s, const LLNotification& notification) +{ + s << notification.summarize(); + return s; +} + +void LLPostponedNotification::lookupName(const LLUUID& id, + bool is_group) +{ + if (is_group) + { + gCacheName->getGroup(id, + boost::bind(&LLPostponedNotification::onGroupNameCache, + this, _1, _2, _3)); + } + else + { + fetchAvatarName(id); + } +} + +void LLPostponedNotification::onGroupNameCache(const LLUUID& id, + const std::string& full_name, + bool is_group) +{ + finalizeName(full_name); +} + +void LLPostponedNotification::fetchAvatarName(const LLUUID& id) +{ + if (id.notNull()) + { + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + + mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLPostponedNotification::onAvatarNameCache, this, _1, _2)); + } +} + +void LLPostponedNotification::onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + + std::string name = av_name.getCompleteName(); + + // from PE merge - we should figure out if this is the right thing to do + if (name.empty()) + { + LL_WARNS("Notifications") << "Empty name received for Id: " << agent_id << LL_ENDL; + name = SYSTEM_FROM; + } + + finalizeName(name); +} + +void LLPostponedNotification::finalizeName(const std::string& name) +{ + mName = name; + modifyNotificationParams(); + LLNotifications::instance().add(mParams); + cleanup(); +} diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index c3796252c7..e7159de94c 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -1,1133 +1,1133 @@ -/** -* @file llnotifications.h -* @brief Non-UI manager and support for keeping a prioritized list of notifications -* @author Q (with assistance from Richard and Coco) -* -* $LicenseInfo:firstyear=2008&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$ -*/ - -#ifndef LL_LLNOTIFICATIONS_H -#define LL_LLNOTIFICATIONS_H - -/** - * This system is intended to provide a singleton mechanism for adding - * notifications to one of an arbitrary set of event channels. - * - * Controlling JIRA: DEV-9061 - * - * Every notification has (see code for full list): - * - a textual name, which is used to look up its template in the XML files - * - a payload, which is a block of LLSD - * - a channel, which is normally extracted from the XML files but - * can be overridden. - * - a timestamp, used to order the notifications - * - expiration time -- if nonzero, specifies a time after which the - * notification will no longer be valid. - * - a callback name and a couple of status bits related to callbacks (see below) - * - * There is a management class called LLNotifications, which is an LLSingleton. - * The class maintains a collection of all of the notifications received - * or processed during this session, and also manages the persistence - * of those notifications that must be persisted. - * - * We also have Channels. A channel is a view on a collection of notifications; - * The collection is defined by a filter function that controls which - * notifications are in the channel, and its ordering is controlled by - * a comparator. - * - * There is a hierarchy of channels; notifications flow down from - * the management class (LLNotifications, which itself inherits from - * The channel base class) to the individual channels. - * Any change to notifications (add, delete, modify) is - * automatically propagated through the channel hierarchy. - * - * We provide methods for adding a new notification, for removing - * one, and for managing channels. Channels are relatively cheap to construct - * and maintain, so in general, human interfaces should use channels to - * select and manage their lists of notifications. - * - * We also maintain a collection of templates that are loaded from the - * XML file of template translations. The system supports substitution - * of named variables from the payload into the XML file. - * - * By default, only the "unknown message" template is built into the system. - * It is not an error to add a notification that's not found in the - * template system, but it is logged. - * - */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "llevents.h" -#include "llfunctorregistry.h" -#include "llinitparam.h" -#include "llinstancetracker.h" -#include "llmortician.h" -#include "llnotificationptr.h" -#include "llpointer.h" -#include "llrefcount.h" -#include "llsdparam.h" - -#include "llnotificationslistener.h" - -class LLAvatarName; -typedef enum e_notification_priority -{ - NOTIFICATION_PRIORITY_UNSPECIFIED, - NOTIFICATION_PRIORITY_LOW, - NOTIFICATION_PRIORITY_NORMAL, - NOTIFICATION_PRIORITY_HIGH, - NOTIFICATION_PRIORITY_CRITICAL -} ENotificationPriority; - -struct NotificationPriorityValues : public LLInitParam::TypeValuesHelper -{ - static void declareValues(); -}; - -class LLNotificationResponderInterface -{ -public: - LLNotificationResponderInterface(){}; - virtual ~LLNotificationResponderInterface(){}; - - virtual void handleRespond(const LLSD& notification, const LLSD& response) = 0; - - virtual LLSD asLLSD() = 0; - - virtual void fromLLSD(const LLSD& params) = 0; -}; - -typedef boost::function LLNotificationResponder; - -typedef std::shared_ptr LLNotificationResponderPtr; - -typedef LLFunctorRegistry LLNotificationFunctorRegistry; -typedef LLFunctorRegistration LLNotificationFunctorRegistration; - -// context data that can be looked up via a notification's payload by the display logic -// derive from this class to implement specific contexts -class LLNotificationContext : public LLInstanceTracker -{ -public: - - LLNotificationContext() : LLInstanceTracker(LLUUID::generateNewID()) - { - } - - virtual ~LLNotificationContext() {} - - LLSD asLLSD() const - { - return getKey(); - } - -private: - -}; - -// Contains notification form data, such as buttons and text fields along with -// manipulator functions -class LLNotificationForm -{ - LOG_CLASS(LLNotificationForm); - -public: - struct FormElementBase : public LLInitParam::Block - { - Optional name; - Optional enabled; - - FormElementBase(); - }; - - struct FormIgnore : public LLInitParam::Block - { - Optional text; - Optional save_option; - Optional control; - Optional invert_control; - Optional session_only; - Optional checkbox_only; - - FormIgnore(); - }; - - struct FormButton : public LLInitParam::Block - { - Mandatory index; - Mandatory text; - Optional ignore; - Optional is_default; - Optional width; - - Mandatory type; - - FormButton(); - }; - - struct FormInput : public LLInitParam::Block - { - Mandatory type; - Optional width; - Optional max_length_chars; - Optional allow_emoji; - Optional text; - - Optional value; - FormInput(); - }; - - struct FormElement : public LLInitParam::ChoiceBlock - { - Alternative button; - Alternative input; - - FormElement(); - }; - - struct FormElements : public LLInitParam::Block - { - Multiple elements; - FormElements(); - }; - - struct Params : public LLInitParam::Block - { - Optional name; - Optional ignore; - Optional form_elements; - - Params(); - }; - - typedef enum e_ignore_type - { - IGNORE_CHECKBOX_ONLY = -1, // ignore won't be handled, will set value/checkbox only - IGNORE_NO = 0, - IGNORE_WITH_DEFAULT_RESPONSE, - IGNORE_WITH_DEFAULT_RESPONSE_SESSION_ONLY, - IGNORE_WITH_LAST_RESPONSE, - IGNORE_SHOW_AGAIN - } EIgnoreType; - - LLNotificationForm(); - LLNotificationForm(const LLNotificationForm&); - LLNotificationForm(const LLSD& sd); - LLNotificationForm(const std::string& name, const Params& p); - - void fromLLSD(const LLSD& sd); - LLSD asLLSD() const; - - S32 getNumElements() { return mFormData.size(); } - LLSD getElement(S32 index) { return mFormData.get(index); } - LLSD getElement(const std::string& element_name); - void getElements(LLSD& elements, S32 offset = 0); - bool hasElement(const std::string& element_name) const; - bool getElementEnabled(const std::string& element_name) const; - void setElementEnabled(const std::string& element_name, bool enabled); - void addElement(const std::string& type, const std::string& name, const LLSD& value = LLSD(), bool enabled = true); - void formatElements(const LLSD& substitutions); - // appends form elements from another form serialized as LLSD - void append(const LLSD& sub_form); - std::string getDefaultOption(); - LLPointer getIgnoreSetting(); - bool getIgnored(); - void setIgnored(bool ignored); - - EIgnoreType getIgnoreType() { return mIgnore; } - std::string getIgnoreMessage() { return mIgnoreMsg; } - -private: - LLSD mFormData; - EIgnoreType mIgnore; - std::string mIgnoreMsg; - LLPointer mIgnoreSetting; - bool mInvertSetting; -}; - -typedef std::shared_ptr LLNotificationFormPtr; - - -struct LLNotificationTemplate; - -// we want to keep a map of these by name, and it's best to manage them -// with smart pointers -typedef std::shared_ptr LLNotificationTemplatePtr; - - -struct LLNotificationVisibilityRule; - -typedef std::shared_ptr LLNotificationVisibilityRulePtr; - -/** - * @class LLNotification - * @brief The object that expresses the details of a notification - * - * We make this noncopyable because - * we want to manage these through LLNotificationPtr, and only - * ever create one instance of any given notification. - * - * The enable_shared_from_this flag ensures that if we construct - * a smart pointer from a notification, we'll always get the same - * shared pointer. - */ -class LLNotification : - boost::noncopyable, - public std::enable_shared_from_this -{ -LOG_CLASS(LLNotification); -friend class LLNotifications; - -public: - - // parameter object used to instantiate a new notification - struct Params : public LLInitParam::Block - { - friend class LLNotification; - - Mandatory name; - Optional id; - Optional substitutions, - form_elements, - payload; - Optional priority; - Optional time_stamp, - expiry; - Optional context; - Optional responder; - Optional offer_from_agent; - Optional is_dnd; - - struct Functor : public LLInitParam::ChoiceBlock - { - Alternative name; - Alternative function; - Alternative responder; - Alternative responder_sd; - - Functor() - : name("responseFunctor"), - function("functor"), - responder("responder"), - responder_sd("responder_sd") - {} - }; - Optional functor; - - Params() - : name("name"), - id("id"), - priority("priority", NOTIFICATION_PRIORITY_UNSPECIFIED), - time_stamp("time"), - payload("payload"), - form_elements("form"), - substitutions("substitutions"), - expiry("expiry"), - offer_from_agent("offer_from_agent", false), - is_dnd("is_dnd", false) - { - time_stamp = LLDate::now(); - responder = NULL; - } - - Params(const std::string& _name) - : name("name"), - priority("priority", NOTIFICATION_PRIORITY_UNSPECIFIED), - time_stamp("time"), - payload("payload"), - form_elements("form"), - substitutions("substitutions"), - expiry("expiry"), - offer_from_agent("offer_from_agent", false), - is_dnd("is_dnd", false) - { - functor.name = _name; - name = _name; - time_stamp = LLDate::now(); - responder = NULL; - } - }; - - LLNotificationResponderPtr getResponderPtr() { return mResponder; } - -private: - - const LLUUID mId; - LLSD mPayload; - LLSD mSubstitutions; - LLDate mTimestamp; - LLDate mExpiresAt; - bool mCancelled; - bool mRespondedTo; // once the notification has been responded to, this becomes true - LLSD mResponse; - bool mIgnored; - ENotificationPriority mPriority; - LLNotificationFormPtr mForm; - void* mResponderObj; // TODO - refactor/remove this field - LLNotificationResponderPtr mResponder; - bool mOfferFromAgent; - bool mIsDND; - - // a reference to the template - LLNotificationTemplatePtr mTemplatep; - - /* - We want to be able to store and reload notifications so that they can survive - a shutdown/restart of the client. So we can't simply pass in callbacks; - we have to specify a callback mechanism that can be used by name rather than - by some arbitrary pointer -- and then people have to initialize callbacks - in some useful location. So we use LLNotificationFunctorRegistry to manage them. - */ - std::string mResponseFunctorName; - - /* - In cases where we want to specify an explict, non-persisted callback, - we store that in the callback registry under a dynamically generated - key, and store the key in the notification, so we can still look it up - using the same mechanism. - */ - bool mTemporaryResponder; - - // keep track of other notifications combined with COMBINE_WITH_NEW - std::vector mCombinedNotifications; - - void init(const std::string& template_name, const LLSD& form_elements); - - void cancel(); - -public: - LLNotification(const LLSDParamAdapter& p); - - void setResponseFunctor(std::string const &responseFunctorName); - - void setResponseFunctor(const LLNotificationFunctorRegistry::ResponseFunctor& cb); - - void setResponseFunctor(const LLNotificationResponderPtr& responder); - - typedef enum e_response_template_type - { - WITHOUT_DEFAULT_BUTTON, - WITH_DEFAULT_BUTTON - } EResponseTemplateType; - - // return response LLSD filled in with default form contents and (optionally) the default button selected - LLSD getResponseTemplate(EResponseTemplateType type = WITHOUT_DEFAULT_BUTTON); - - // returns index of first button with value==true - // usually this the button the user clicked on - // returns -1 if no button clicked (e.g. form has not been displayed) - static S32 getSelectedOption(const LLSD& notification, const LLSD& response); - // returns name of first button with value==true - static std::string getSelectedOptionName(const LLSD& notification); - - // after someone responds to a notification (usually by clicking a button, - // but sometimes by filling out a little form and THEN clicking a button), - // the result of the response (the name and value of the button clicked, - // plus any other data) should be packaged up as LLSD, then passed as a - // parameter to the notification's respond() method here. This will look up - // and call the appropriate responder. - // - // response is notification serialized as LLSD: - // ["name"] = notification name - // ["form"] = LLSD tree that includes form description and any prefilled form data - // ["response"] = form data filled in by user - // (including, but not limited to which button they clicked on) - // ["payload"] = transaction specific data, such as ["source_id"] (originator of notification), - // ["item_id"] (attached inventory item), etc. - // ["substitutions"] = string substitutions used to generate notification message - // from the template - // ["time"] = time at which notification was generated; - // ["expiry"] = time at which notification expires; - // ["responseFunctor"] = name of registered functor that handles responses to notification; - LLSD asLLSD(bool excludeTemplateElements = false); - - const LLNotificationFormPtr getForm(); - void updateForm(const LLNotificationFormPtr& form); - - void repost(); - - void respond(const LLSD& sd); - void respondWithDefault(); - - void* getResponder() { return mResponderObj; } - - void setResponder(void* responder) { mResponderObj = responder; } - - void setIgnored(bool ignore); - - bool isCancelled() const - { - return mCancelled; - } - - bool isRespondedTo() const - { - return mRespondedTo; - } - - bool isActive() const - { - return !isRespondedTo() - && !isCancelled() - && !isExpired(); - } - - const LLSD& getResponse() { return mResponse; } - - bool isIgnored() const - { - return mIgnored; - } - - const std::string& getName() const; - - const std::string& getIcon() const; - - bool isPersistent() const; - - const LLUUID& id() const - { - return mId; - } - - const LLSD& getPayload() const - { - return mPayload; - } - - const LLSD& getSubstitutions() const - { - return mSubstitutions; - } - - const LLDate& getDate() const - { - return mTimestamp; - } - - bool getOfferFromAgent() const - { - return mOfferFromAgent; - } - - bool isDND() const - { - return mIsDND; - } - - void setDND(const bool flag) - { - mIsDND = flag; - } - - std::string getType() const; - std::string getMessage() const; - std::string getFooter() const; - std::string getLabel() const; - std::string getURL() const; - S32 getURLOption() const; - S32 getURLOpenExternally() const; //for url responce option - bool getForceUrlsExternal() const; - bool canLogToChat() const; - bool canLogToIM() const; - bool canShowToast() const; - bool canFadeToast() const; - bool hasFormElements() const; - void playSound(); - - typedef enum e_combine_behavior - { - REPLACE_WITH_NEW, - COMBINE_WITH_NEW, - KEEP_OLD, - CANCEL_OLD - - } ECombineBehavior; - - ECombineBehavior getCombineBehavior() const; - - const LLDate getExpiration() const - { - return mExpiresAt; - } - - ENotificationPriority getPriority() const - { - return mPriority; - } - - const LLUUID getID() const - { - return mId; - } - - // comparing two notifications normally means comparing them by UUID (so we can look them - // up quickly this way) - bool operator<(const LLNotification& rhs) const - { - return mId < rhs.mId; - } - - bool operator==(const LLNotification& rhs) const - { - return mId == rhs.mId; - } - - bool operator!=(const LLNotification& rhs) const - { - return !operator==(rhs); - } - - bool isSameObjectAs(const LLNotification* rhs) const - { - return this == rhs; - } - - // this object has been updated, so tell all our clients - void update(); - - void updateFrom(LLNotificationPtr other); - - // A fuzzy equals comparator. - // true only if both notifications have the same template and - // 1) flagged as unique (there can be only one of these) OR - // 2) all required payload fields of each also exist in the other. - bool isEquivalentTo(LLNotificationPtr that) const; - - // if the current time is greater than the expiration, the notification is expired - bool isExpired() const - { - if (mExpiresAt.secondsSinceEpoch() == 0) - { - return false; - } - - LLDate rightnow = LLDate::now(); - return rightnow > mExpiresAt; - } - - std::string summarize() const; - - bool hasUniquenessConstraints() const; - - bool matchesTag(const std::string& tag); - - virtual ~LLNotification() {} -}; - -std::ostream& operator<<(std::ostream& s, const LLNotification& notification); - -namespace LLNotificationFilters -{ - // a sample filter - bool includeEverything(LLNotificationPtr p); - - typedef enum e_comparison - { - EQUAL, - LESS, - GREATER, - LESS_EQUAL, - GREATER_EQUAL - } EComparison; - - // generic filter functor that takes method or member variable reference - template - struct filterBy - { - typedef boost::function field_t; - typedef typename boost::remove_reference::type value_t; - - filterBy(field_t field, value_t value, EComparison comparison = EQUAL) - : mField(field), - mFilterValue(value), - mComparison(comparison) - { - } - - bool operator()(LLNotificationPtr p) - { - switch(mComparison) - { - case EQUAL: - return mField(p) == mFilterValue; - case LESS: - return mField(p) < mFilterValue; - case GREATER: - return mField(p) > mFilterValue; - case LESS_EQUAL: - return mField(p) <= mFilterValue; - case GREATER_EQUAL: - return mField(p) >= mFilterValue; - default: - return false; - } - } - - field_t mField; - value_t mFilterValue; - EComparison mComparison; - }; -}; - -namespace LLNotificationComparators -{ - struct orderByUUID - { - bool operator()(LLNotificationPtr lhs, LLNotificationPtr rhs) const - { - return lhs->id() < rhs->id(); - } - }; -}; - -typedef boost::function LLNotificationFilter; -typedef std::set LLNotificationSet; -typedef std::multimap LLNotificationMap; - -// ======================================================== -// Abstract base class (interface) for a channel; also used for the master container. -// This lets us arrange channels into a call hierarchy. - -// We maintain a hierarchy of notification channels; events are always started at the top -// and propagated through the hierarchy only if they pass a filter. -// Any channel can be created with a parent. A null parent (empty string) means it's -// tied to the root of the tree (the LLNotifications class itself). -// The default hierarchy looks like this: -// -// LLNotifications --+-- Expiration --+-- Mute --+-- Ignore --+-- Visible --+-- History -// +-- Alerts -// +-- Notifications -// -// In general, new channels that want to only see notifications that pass through -// all of the built-in tests should attach to the "Visible" channel -// -class LLNotificationChannelBase : - public LLEventTrackable, - public LLRefCount -{ - LOG_CLASS(LLNotificationChannelBase); -public: - LLNotificationChannelBase(LLNotificationFilter filter) - : mFilter(filter) - , mItems() - , mItemsMutex() - {} - - virtual ~LLNotificationChannelBase() - { - // explicit cleanup for easier issue detection - mChanged.disconnect_all_slots(); - mPassedFilter.disconnect_all_slots(); - mFailedFilter.disconnect_all_slots(); - LLMutexLock lock(&mItemsMutex); - mItems.clear(); - } - // you can also connect to a Channel, so you can be notified of - // changes to this channel - LLBoundListener connectChanged(const LLEventListener& slot) - { - // Call this->connectChangedImpl() to actually connect it. - return connectChangedImpl(slot); - } - LLBoundListener connectAtFrontChanged(const LLEventListener& slot) - { - return connectAtFrontChangedImpl(slot); - } - LLBoundListener connectPassedFilter(const LLEventListener& slot) - { - // see comments in connectChanged() - return connectPassedFilterImpl(slot); - } - LLBoundListener connectFailedFilter(const LLEventListener& slot) - { - // see comments in connectChanged() - return connectFailedFilterImpl(slot); - } - - // use this when items change or to add a new one - bool updateItem(const LLSD& payload); - const LLNotificationFilter& getFilter() { return mFilter; } - -protected: - LLBoundListener connectChangedImpl(const LLEventListener& slot); - LLBoundListener connectAtFrontChangedImpl(const LLEventListener& slot); - LLBoundListener connectPassedFilterImpl(const LLEventListener& slot); - LLBoundListener connectFailedFilterImpl(const LLEventListener& slot); - - LLNotificationSet mItems; - LLStandardSignal mChanged; - LLStandardSignal mPassedFilter; - LLStandardSignal mFailedFilter; - LLMutex mItemsMutex; - - // these are action methods that subclasses can override to take action - // on specific types of changes; the management of the mItems list is - // still handled by the generic handler. - virtual void onLoad(LLNotificationPtr p) {} - virtual void onAdd(LLNotificationPtr p) {} - virtual void onDelete(LLNotificationPtr p) {} - virtual void onChange(LLNotificationPtr p) {} - - virtual void onFilterPass(LLNotificationPtr p) {} - virtual void onFilterFail(LLNotificationPtr p) {} - - bool updateItem(const LLSD& payload, LLNotificationPtr pNotification); - LLNotificationFilter mFilter; -}; - -// The type of the pointers that we're going to manage in the NotificationQueue system -// Because LLNotifications is a singleton, we don't actually expect to ever -// destroy it, but if it becomes necessary to do so, the shared_ptr model -// will ensure that we don't leak resources. -class LLNotificationChannel; -typedef boost::intrusive_ptr LLNotificationChannelPtr; - -// manages a list of notifications -// Note that if this is ever copied around, we might find ourselves with multiple copies -// of a queue with notifications being added to different nonequivalent copies. So we -// make it inherit from boost::noncopyable, and then create a map of LLPointer to manage it. -// -class LLNotificationChannel : - boost::noncopyable, - public LLNotificationChannelBase, - public LLInstanceTracker -{ - LOG_CLASS(LLNotificationChannel); - -public: - // Notification Channels have a filter, which determines which notifications - // will be added to this channel. - // Channel filters cannot change. - struct Params : public LLInitParam::Block - { - Mandatory name; - Optional filter; - Multiple sources; - }; - - LLNotificationChannel(const Params& p = Params()); - LLNotificationChannel(const std::string& name, const std::string& parent, LLNotificationFilter filter); - - virtual ~LLNotificationChannel(); - typedef LLNotificationSet::iterator Iterator; - - std::string getName() const { return mName; } - typedef std::vector::const_iterator parents_iter; - boost::iterator_range getParents() const - { - return boost::iterator_range(mParents); - } - - bool isEmpty() const; - S32 size() const; - size_t size(); - - typedef boost::function NotificationProcess; - void forEachNotification(NotificationProcess process); - - std::string summarize(); - -protected: - void connectToChannel(const std::string& channel_name); - -private: - std::string mName; - std::vector mParents; - std::vector mListeners; -}; - -// An interface class to provide a clean linker seam to the LLNotifications class. -// Extend this interface as needed for your use of LLNotifications. -class LLNotificationsInterface -{ -public: - virtual LLNotificationPtr add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload, - LLNotificationFunctorRegistry::ResponseFunctor functor) = 0; -}; - -class LLNotifications : - public LLNotificationsInterface, - public LLSingleton, - public LLNotificationChannelBase -{ - LLSINGLETON(LLNotifications); - LOG_CLASS(LLNotifications); - virtual ~LLNotifications() {} - -public: - - // Needed to clear up RefCounted things prior to actual destruction - // as the singleton nature of the class makes them do "bad things" - // on at least Mac, if not all 3 platforms - // - void clear(); - - // load all notification descriptions from file - // calling more than once will overwrite existing templates - // but never delete a template - bool loadTemplates(); - - // load visibility rules from file; - // OK to call more than once because it will reload - bool loadVisibilityRules(); - - // Add a simple notification (from XUI) - void addFromCallback(const LLSD& name); - - // *NOTE: To add simple notifications, #include "llnotificationsutil.h" - // and use LLNotificationsUtil::add("MyNote") or add("MyNote", args) - LLNotificationPtr add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload); - LLNotificationPtr add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload, - const std::string& functor_name); - /* virtual */ LLNotificationPtr add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload, - LLNotificationFunctorRegistry::ResponseFunctor functor) override; - LLNotificationPtr add(const LLNotification::Params& p); - - void add(const LLNotificationPtr pNotif); - void load(const LLNotificationPtr pNotif); - void cancel(LLNotificationPtr pNotif); - void cancelByName(const std::string& name); - void cancelByOwner(const LLUUID ownerId); - void update(const LLNotificationPtr pNotif); - - LLNotificationPtr find(LLUUID uuid); - - // This is all stuff for managing the templates - // take your template out - LLNotificationTemplatePtr getTemplate(const std::string& name); - - // get the whole collection - typedef std::vector TemplateNames; - TemplateNames getTemplateNames() const; // returns a list of notification names - - typedef std::map TemplateMap; - - TemplateMap::const_iterator templatesBegin() { return mTemplates.begin(); } - TemplateMap::const_iterator templatesEnd() { return mTemplates.end(); } - - // test for existence - bool templateExists(const std::string& name); - - typedef std::list VisibilityRuleList; - - void forceResponse(const LLNotification::Params& params, S32 option); - - void createDefaultChannels(); - - LLNotificationChannelPtr getChannel(const std::string& channelName); - - std::string getGlobalString(const std::string& key) const; - - void setIgnoreAllNotifications(bool ignore); - bool getIgnoreAllNotifications(); - - void setIgnored(const std::string& name, bool ignored); - bool getIgnored(const std::string& name); - - bool isVisibleByRules(LLNotificationPtr pNotification); - -private: - /*virtual*/ void initSingleton() override; - /*virtual*/ void cleanupSingleton() override; - - void loadPersistentNotifications(); - - bool expirationFilter(LLNotificationPtr pNotification); - bool expirationHandler(const LLSD& payload); - bool uniqueFilter(LLNotificationPtr pNotification); - bool uniqueHandler(const LLSD& payload); - bool failedUniquenessTest(const LLSD& payload); - LLNotificationChannelPtr pHistoryChannel; - LLNotificationChannelPtr pExpirationChannel; - - TemplateMap mTemplates; - - VisibilityRuleList mVisibilityRules; - - std::string mFileName; - - LLNotificationMap mUniqueNotifications; - - typedef std::map GlobalStringMap; - GlobalStringMap mGlobalStrings; - - bool mIgnoreAllNotifications; - - std::unique_ptr mListener; - - std::vector mDefaultChannels; -}; - -/** - * Abstract class for postponed notifications. - * Provides possibility to add notification after specified by id avatar or group will be - * received from cache name. The object of this type automatically well be deleted - * by cleanup method after respond will be received from cache name. - * - * To add custom postponed notification to the notification system client should: - * 1 create class derived from LLPostponedNotification; - * 2 call LLPostponedNotification::add method; - */ -class LLPostponedNotification : public LLMortician -{ -public: - /** - * Performs hooking cache name callback which will add notification to notifications system. - * Type of added notification should be specified by template parameter T - * and non-private derived from LLPostponedNotification class, - * otherwise compilation error will occur. - */ - template - static void add(const LLNotification::Params& params, - const LLUUID& id, bool is_group) - { - // upcast T to the base type to restrict T derivation from LLPostponedNotification - LLPostponedNotification* thiz = new T(); - thiz->mParams = params; - - // Avoid header file dependency on llcachename.h - thiz->lookupName(id, is_group); - } - -private: - void lookupName(const LLUUID& id, bool is_group); - // only used for groups - void onGroupNameCache(const LLUUID& id, const std::string& full_name, bool is_group); - // only used for avatars - void fetchAvatarName(const LLUUID& id); - void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); - // used for both group and avatar names - void finalizeName(const std::string& name); - - void cleanup() - { - die(); - } - -protected: - LLPostponedNotification() - : mParams(), - mName(), - mAvatarNameCacheConnection() - {} - - virtual ~LLPostponedNotification() - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - } - - /** - * Abstract method provides possibility to modify notification parameters and - * will be called after cache name retrieve information about avatar or group - * and before notification will be added to the notification system. - */ - virtual void modifyNotificationParams() = 0; - - LLNotification::Params mParams; - std::string mName; - boost::signals2::connection mAvatarNameCacheConnection; -}; - -// Stores only persistent notifications. -// Class users can use connectChanged() to process persistent notifications -// (see LLPersistentNotificationStorage for example). -class LLPersistentNotificationChannel : public LLNotificationChannel -{ - LOG_CLASS(LLPersistentNotificationChannel); -public: - LLPersistentNotificationChannel() - : LLNotificationChannel("Persistent", "Visible", ¬ificationFilter) - {} - - virtual ~LLPersistentNotificationChannel() - { - mHistory.clear(); - } - - typedef std::vector history_list_t; - history_list_t::iterator beginHistory() { sortHistory(); return mHistory.begin(); } - history_list_t::iterator endHistory() { return mHistory.end(); } - -private: - struct sortByTime - { - S32 operator ()(const LLNotificationPtr& a, const LLNotificationPtr& b) - { - return a->getDate() < b->getDate(); - } - }; - - void sortHistory() - { - std::sort(mHistory.begin(), mHistory.end(), sortByTime()); - } - - // The channel gets all persistent notifications except those that have been canceled - static bool notificationFilter(LLNotificationPtr pNotification) - { - bool handle_notification = false; - - handle_notification = pNotification->isPersistent() - && !pNotification->isCancelled(); - - return handle_notification; - } - - void onAdd(LLNotificationPtr p) - { - mHistory.push_back(p); - } - - void onLoad(LLNotificationPtr p) - { - mHistory.push_back(p); - } - - std::vector mHistory; -}; - -#endif//LL_LLNOTIFICATIONS_H - +/** +* @file llnotifications.h +* @brief Non-UI manager and support for keeping a prioritized list of notifications +* @author Q (with assistance from Richard and Coco) +* +* $LicenseInfo:firstyear=2008&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$ +*/ + +#ifndef LL_LLNOTIFICATIONS_H +#define LL_LLNOTIFICATIONS_H + +/** + * This system is intended to provide a singleton mechanism for adding + * notifications to one of an arbitrary set of event channels. + * + * Controlling JIRA: DEV-9061 + * + * Every notification has (see code for full list): + * - a textual name, which is used to look up its template in the XML files + * - a payload, which is a block of LLSD + * - a channel, which is normally extracted from the XML files but + * can be overridden. + * - a timestamp, used to order the notifications + * - expiration time -- if nonzero, specifies a time after which the + * notification will no longer be valid. + * - a callback name and a couple of status bits related to callbacks (see below) + * + * There is a management class called LLNotifications, which is an LLSingleton. + * The class maintains a collection of all of the notifications received + * or processed during this session, and also manages the persistence + * of those notifications that must be persisted. + * + * We also have Channels. A channel is a view on a collection of notifications; + * The collection is defined by a filter function that controls which + * notifications are in the channel, and its ordering is controlled by + * a comparator. + * + * There is a hierarchy of channels; notifications flow down from + * the management class (LLNotifications, which itself inherits from + * The channel base class) to the individual channels. + * Any change to notifications (add, delete, modify) is + * automatically propagated through the channel hierarchy. + * + * We provide methods for adding a new notification, for removing + * one, and for managing channels. Channels are relatively cheap to construct + * and maintain, so in general, human interfaces should use channels to + * select and manage their lists of notifications. + * + * We also maintain a collection of templates that are loaded from the + * XML file of template translations. The system supports substitution + * of named variables from the payload into the XML file. + * + * By default, only the "unknown message" template is built into the system. + * It is not an error to add a notification that's not found in the + * template system, but it is logged. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "llevents.h" +#include "llfunctorregistry.h" +#include "llinitparam.h" +#include "llinstancetracker.h" +#include "llmortician.h" +#include "llnotificationptr.h" +#include "llpointer.h" +#include "llrefcount.h" +#include "llsdparam.h" + +#include "llnotificationslistener.h" + +class LLAvatarName; +typedef enum e_notification_priority +{ + NOTIFICATION_PRIORITY_UNSPECIFIED, + NOTIFICATION_PRIORITY_LOW, + NOTIFICATION_PRIORITY_NORMAL, + NOTIFICATION_PRIORITY_HIGH, + NOTIFICATION_PRIORITY_CRITICAL +} ENotificationPriority; + +struct NotificationPriorityValues : public LLInitParam::TypeValuesHelper +{ + static void declareValues(); +}; + +class LLNotificationResponderInterface +{ +public: + LLNotificationResponderInterface(){}; + virtual ~LLNotificationResponderInterface(){}; + + virtual void handleRespond(const LLSD& notification, const LLSD& response) = 0; + + virtual LLSD asLLSD() = 0; + + virtual void fromLLSD(const LLSD& params) = 0; +}; + +typedef boost::function LLNotificationResponder; + +typedef std::shared_ptr LLNotificationResponderPtr; + +typedef LLFunctorRegistry LLNotificationFunctorRegistry; +typedef LLFunctorRegistration LLNotificationFunctorRegistration; + +// context data that can be looked up via a notification's payload by the display logic +// derive from this class to implement specific contexts +class LLNotificationContext : public LLInstanceTracker +{ +public: + + LLNotificationContext() : LLInstanceTracker(LLUUID::generateNewID()) + { + } + + virtual ~LLNotificationContext() {} + + LLSD asLLSD() const + { + return getKey(); + } + +private: + +}; + +// Contains notification form data, such as buttons and text fields along with +// manipulator functions +class LLNotificationForm +{ + LOG_CLASS(LLNotificationForm); + +public: + struct FormElementBase : public LLInitParam::Block + { + Optional name; + Optional enabled; + + FormElementBase(); + }; + + struct FormIgnore : public LLInitParam::Block + { + Optional text; + Optional save_option; + Optional control; + Optional invert_control; + Optional session_only; + Optional checkbox_only; + + FormIgnore(); + }; + + struct FormButton : public LLInitParam::Block + { + Mandatory index; + Mandatory text; + Optional ignore; + Optional is_default; + Optional width; + + Mandatory type; + + FormButton(); + }; + + struct FormInput : public LLInitParam::Block + { + Mandatory type; + Optional width; + Optional max_length_chars; + Optional allow_emoji; + Optional text; + + Optional value; + FormInput(); + }; + + struct FormElement : public LLInitParam::ChoiceBlock + { + Alternative button; + Alternative input; + + FormElement(); + }; + + struct FormElements : public LLInitParam::Block + { + Multiple elements; + FormElements(); + }; + + struct Params : public LLInitParam::Block + { + Optional name; + Optional ignore; + Optional form_elements; + + Params(); + }; + + typedef enum e_ignore_type + { + IGNORE_CHECKBOX_ONLY = -1, // ignore won't be handled, will set value/checkbox only + IGNORE_NO = 0, + IGNORE_WITH_DEFAULT_RESPONSE, + IGNORE_WITH_DEFAULT_RESPONSE_SESSION_ONLY, + IGNORE_WITH_LAST_RESPONSE, + IGNORE_SHOW_AGAIN + } EIgnoreType; + + LLNotificationForm(); + LLNotificationForm(const LLNotificationForm&); + LLNotificationForm(const LLSD& sd); + LLNotificationForm(const std::string& name, const Params& p); + + void fromLLSD(const LLSD& sd); + LLSD asLLSD() const; + + S32 getNumElements() { return mFormData.size(); } + LLSD getElement(S32 index) { return mFormData.get(index); } + LLSD getElement(const std::string& element_name); + void getElements(LLSD& elements, S32 offset = 0); + bool hasElement(const std::string& element_name) const; + bool getElementEnabled(const std::string& element_name) const; + void setElementEnabled(const std::string& element_name, bool enabled); + void addElement(const std::string& type, const std::string& name, const LLSD& value = LLSD(), bool enabled = true); + void formatElements(const LLSD& substitutions); + // appends form elements from another form serialized as LLSD + void append(const LLSD& sub_form); + std::string getDefaultOption(); + LLPointer getIgnoreSetting(); + bool getIgnored(); + void setIgnored(bool ignored); + + EIgnoreType getIgnoreType() { return mIgnore; } + std::string getIgnoreMessage() { return mIgnoreMsg; } + +private: + LLSD mFormData; + EIgnoreType mIgnore; + std::string mIgnoreMsg; + LLPointer mIgnoreSetting; + bool mInvertSetting; +}; + +typedef std::shared_ptr LLNotificationFormPtr; + + +struct LLNotificationTemplate; + +// we want to keep a map of these by name, and it's best to manage them +// with smart pointers +typedef std::shared_ptr LLNotificationTemplatePtr; + + +struct LLNotificationVisibilityRule; + +typedef std::shared_ptr LLNotificationVisibilityRulePtr; + +/** + * @class LLNotification + * @brief The object that expresses the details of a notification + * + * We make this noncopyable because + * we want to manage these through LLNotificationPtr, and only + * ever create one instance of any given notification. + * + * The enable_shared_from_this flag ensures that if we construct + * a smart pointer from a notification, we'll always get the same + * shared pointer. + */ +class LLNotification : + boost::noncopyable, + public std::enable_shared_from_this +{ +LOG_CLASS(LLNotification); +friend class LLNotifications; + +public: + + // parameter object used to instantiate a new notification + struct Params : public LLInitParam::Block + { + friend class LLNotification; + + Mandatory name; + Optional id; + Optional substitutions, + form_elements, + payload; + Optional priority; + Optional time_stamp, + expiry; + Optional context; + Optional responder; + Optional offer_from_agent; + Optional is_dnd; + + struct Functor : public LLInitParam::ChoiceBlock + { + Alternative name; + Alternative function; + Alternative responder; + Alternative responder_sd; + + Functor() + : name("responseFunctor"), + function("functor"), + responder("responder"), + responder_sd("responder_sd") + {} + }; + Optional functor; + + Params() + : name("name"), + id("id"), + priority("priority", NOTIFICATION_PRIORITY_UNSPECIFIED), + time_stamp("time"), + payload("payload"), + form_elements("form"), + substitutions("substitutions"), + expiry("expiry"), + offer_from_agent("offer_from_agent", false), + is_dnd("is_dnd", false) + { + time_stamp = LLDate::now(); + responder = NULL; + } + + Params(const std::string& _name) + : name("name"), + priority("priority", NOTIFICATION_PRIORITY_UNSPECIFIED), + time_stamp("time"), + payload("payload"), + form_elements("form"), + substitutions("substitutions"), + expiry("expiry"), + offer_from_agent("offer_from_agent", false), + is_dnd("is_dnd", false) + { + functor.name = _name; + name = _name; + time_stamp = LLDate::now(); + responder = NULL; + } + }; + + LLNotificationResponderPtr getResponderPtr() { return mResponder; } + +private: + + const LLUUID mId; + LLSD mPayload; + LLSD mSubstitutions; + LLDate mTimestamp; + LLDate mExpiresAt; + bool mCancelled; + bool mRespondedTo; // once the notification has been responded to, this becomes true + LLSD mResponse; + bool mIgnored; + ENotificationPriority mPriority; + LLNotificationFormPtr mForm; + void* mResponderObj; // TODO - refactor/remove this field + LLNotificationResponderPtr mResponder; + bool mOfferFromAgent; + bool mIsDND; + + // a reference to the template + LLNotificationTemplatePtr mTemplatep; + + /* + We want to be able to store and reload notifications so that they can survive + a shutdown/restart of the client. So we can't simply pass in callbacks; + we have to specify a callback mechanism that can be used by name rather than + by some arbitrary pointer -- and then people have to initialize callbacks + in some useful location. So we use LLNotificationFunctorRegistry to manage them. + */ + std::string mResponseFunctorName; + + /* + In cases where we want to specify an explict, non-persisted callback, + we store that in the callback registry under a dynamically generated + key, and store the key in the notification, so we can still look it up + using the same mechanism. + */ + bool mTemporaryResponder; + + // keep track of other notifications combined with COMBINE_WITH_NEW + std::vector mCombinedNotifications; + + void init(const std::string& template_name, const LLSD& form_elements); + + void cancel(); + +public: + LLNotification(const LLSDParamAdapter& p); + + void setResponseFunctor(std::string const &responseFunctorName); + + void setResponseFunctor(const LLNotificationFunctorRegistry::ResponseFunctor& cb); + + void setResponseFunctor(const LLNotificationResponderPtr& responder); + + typedef enum e_response_template_type + { + WITHOUT_DEFAULT_BUTTON, + WITH_DEFAULT_BUTTON + } EResponseTemplateType; + + // return response LLSD filled in with default form contents and (optionally) the default button selected + LLSD getResponseTemplate(EResponseTemplateType type = WITHOUT_DEFAULT_BUTTON); + + // returns index of first button with value==true + // usually this the button the user clicked on + // returns -1 if no button clicked (e.g. form has not been displayed) + static S32 getSelectedOption(const LLSD& notification, const LLSD& response); + // returns name of first button with value==true + static std::string getSelectedOptionName(const LLSD& notification); + + // after someone responds to a notification (usually by clicking a button, + // but sometimes by filling out a little form and THEN clicking a button), + // the result of the response (the name and value of the button clicked, + // plus any other data) should be packaged up as LLSD, then passed as a + // parameter to the notification's respond() method here. This will look up + // and call the appropriate responder. + // + // response is notification serialized as LLSD: + // ["name"] = notification name + // ["form"] = LLSD tree that includes form description and any prefilled form data + // ["response"] = form data filled in by user + // (including, but not limited to which button they clicked on) + // ["payload"] = transaction specific data, such as ["source_id"] (originator of notification), + // ["item_id"] (attached inventory item), etc. + // ["substitutions"] = string substitutions used to generate notification message + // from the template + // ["time"] = time at which notification was generated; + // ["expiry"] = time at which notification expires; + // ["responseFunctor"] = name of registered functor that handles responses to notification; + LLSD asLLSD(bool excludeTemplateElements = false); + + const LLNotificationFormPtr getForm(); + void updateForm(const LLNotificationFormPtr& form); + + void repost(); + + void respond(const LLSD& sd); + void respondWithDefault(); + + void* getResponder() { return mResponderObj; } + + void setResponder(void* responder) { mResponderObj = responder; } + + void setIgnored(bool ignore); + + bool isCancelled() const + { + return mCancelled; + } + + bool isRespondedTo() const + { + return mRespondedTo; + } + + bool isActive() const + { + return !isRespondedTo() + && !isCancelled() + && !isExpired(); + } + + const LLSD& getResponse() { return mResponse; } + + bool isIgnored() const + { + return mIgnored; + } + + const std::string& getName() const; + + const std::string& getIcon() const; + + bool isPersistent() const; + + const LLUUID& id() const + { + return mId; + } + + const LLSD& getPayload() const + { + return mPayload; + } + + const LLSD& getSubstitutions() const + { + return mSubstitutions; + } + + const LLDate& getDate() const + { + return mTimestamp; + } + + bool getOfferFromAgent() const + { + return mOfferFromAgent; + } + + bool isDND() const + { + return mIsDND; + } + + void setDND(const bool flag) + { + mIsDND = flag; + } + + std::string getType() const; + std::string getMessage() const; + std::string getFooter() const; + std::string getLabel() const; + std::string getURL() const; + S32 getURLOption() const; + S32 getURLOpenExternally() const; //for url responce option + bool getForceUrlsExternal() const; + bool canLogToChat() const; + bool canLogToIM() const; + bool canShowToast() const; + bool canFadeToast() const; + bool hasFormElements() const; + void playSound(); + + typedef enum e_combine_behavior + { + REPLACE_WITH_NEW, + COMBINE_WITH_NEW, + KEEP_OLD, + CANCEL_OLD + + } ECombineBehavior; + + ECombineBehavior getCombineBehavior() const; + + const LLDate getExpiration() const + { + return mExpiresAt; + } + + ENotificationPriority getPriority() const + { + return mPriority; + } + + const LLUUID getID() const + { + return mId; + } + + // comparing two notifications normally means comparing them by UUID (so we can look them + // up quickly this way) + bool operator<(const LLNotification& rhs) const + { + return mId < rhs.mId; + } + + bool operator==(const LLNotification& rhs) const + { + return mId == rhs.mId; + } + + bool operator!=(const LLNotification& rhs) const + { + return !operator==(rhs); + } + + bool isSameObjectAs(const LLNotification* rhs) const + { + return this == rhs; + } + + // this object has been updated, so tell all our clients + void update(); + + void updateFrom(LLNotificationPtr other); + + // A fuzzy equals comparator. + // true only if both notifications have the same template and + // 1) flagged as unique (there can be only one of these) OR + // 2) all required payload fields of each also exist in the other. + bool isEquivalentTo(LLNotificationPtr that) const; + + // if the current time is greater than the expiration, the notification is expired + bool isExpired() const + { + if (mExpiresAt.secondsSinceEpoch() == 0) + { + return false; + } + + LLDate rightnow = LLDate::now(); + return rightnow > mExpiresAt; + } + + std::string summarize() const; + + bool hasUniquenessConstraints() const; + + bool matchesTag(const std::string& tag); + + virtual ~LLNotification() {} +}; + +std::ostream& operator<<(std::ostream& s, const LLNotification& notification); + +namespace LLNotificationFilters +{ + // a sample filter + bool includeEverything(LLNotificationPtr p); + + typedef enum e_comparison + { + EQUAL, + LESS, + GREATER, + LESS_EQUAL, + GREATER_EQUAL + } EComparison; + + // generic filter functor that takes method or member variable reference + template + struct filterBy + { + typedef boost::function field_t; + typedef typename boost::remove_reference::type value_t; + + filterBy(field_t field, value_t value, EComparison comparison = EQUAL) + : mField(field), + mFilterValue(value), + mComparison(comparison) + { + } + + bool operator()(LLNotificationPtr p) + { + switch(mComparison) + { + case EQUAL: + return mField(p) == mFilterValue; + case LESS: + return mField(p) < mFilterValue; + case GREATER: + return mField(p) > mFilterValue; + case LESS_EQUAL: + return mField(p) <= mFilterValue; + case GREATER_EQUAL: + return mField(p) >= mFilterValue; + default: + return false; + } + } + + field_t mField; + value_t mFilterValue; + EComparison mComparison; + }; +}; + +namespace LLNotificationComparators +{ + struct orderByUUID + { + bool operator()(LLNotificationPtr lhs, LLNotificationPtr rhs) const + { + return lhs->id() < rhs->id(); + } + }; +}; + +typedef boost::function LLNotificationFilter; +typedef std::set LLNotificationSet; +typedef std::multimap LLNotificationMap; + +// ======================================================== +// Abstract base class (interface) for a channel; also used for the master container. +// This lets us arrange channels into a call hierarchy. + +// We maintain a hierarchy of notification channels; events are always started at the top +// and propagated through the hierarchy only if they pass a filter. +// Any channel can be created with a parent. A null parent (empty string) means it's +// tied to the root of the tree (the LLNotifications class itself). +// The default hierarchy looks like this: +// +// LLNotifications --+-- Expiration --+-- Mute --+-- Ignore --+-- Visible --+-- History +// +-- Alerts +// +-- Notifications +// +// In general, new channels that want to only see notifications that pass through +// all of the built-in tests should attach to the "Visible" channel +// +class LLNotificationChannelBase : + public LLEventTrackable, + public LLRefCount +{ + LOG_CLASS(LLNotificationChannelBase); +public: + LLNotificationChannelBase(LLNotificationFilter filter) + : mFilter(filter) + , mItems() + , mItemsMutex() + {} + + virtual ~LLNotificationChannelBase() + { + // explicit cleanup for easier issue detection + mChanged.disconnect_all_slots(); + mPassedFilter.disconnect_all_slots(); + mFailedFilter.disconnect_all_slots(); + LLMutexLock lock(&mItemsMutex); + mItems.clear(); + } + // you can also connect to a Channel, so you can be notified of + // changes to this channel + LLBoundListener connectChanged(const LLEventListener& slot) + { + // Call this->connectChangedImpl() to actually connect it. + return connectChangedImpl(slot); + } + LLBoundListener connectAtFrontChanged(const LLEventListener& slot) + { + return connectAtFrontChangedImpl(slot); + } + LLBoundListener connectPassedFilter(const LLEventListener& slot) + { + // see comments in connectChanged() + return connectPassedFilterImpl(slot); + } + LLBoundListener connectFailedFilter(const LLEventListener& slot) + { + // see comments in connectChanged() + return connectFailedFilterImpl(slot); + } + + // use this when items change or to add a new one + bool updateItem(const LLSD& payload); + const LLNotificationFilter& getFilter() { return mFilter; } + +protected: + LLBoundListener connectChangedImpl(const LLEventListener& slot); + LLBoundListener connectAtFrontChangedImpl(const LLEventListener& slot); + LLBoundListener connectPassedFilterImpl(const LLEventListener& slot); + LLBoundListener connectFailedFilterImpl(const LLEventListener& slot); + + LLNotificationSet mItems; + LLStandardSignal mChanged; + LLStandardSignal mPassedFilter; + LLStandardSignal mFailedFilter; + LLMutex mItemsMutex; + + // these are action methods that subclasses can override to take action + // on specific types of changes; the management of the mItems list is + // still handled by the generic handler. + virtual void onLoad(LLNotificationPtr p) {} + virtual void onAdd(LLNotificationPtr p) {} + virtual void onDelete(LLNotificationPtr p) {} + virtual void onChange(LLNotificationPtr p) {} + + virtual void onFilterPass(LLNotificationPtr p) {} + virtual void onFilterFail(LLNotificationPtr p) {} + + bool updateItem(const LLSD& payload, LLNotificationPtr pNotification); + LLNotificationFilter mFilter; +}; + +// The type of the pointers that we're going to manage in the NotificationQueue system +// Because LLNotifications is a singleton, we don't actually expect to ever +// destroy it, but if it becomes necessary to do so, the shared_ptr model +// will ensure that we don't leak resources. +class LLNotificationChannel; +typedef boost::intrusive_ptr LLNotificationChannelPtr; + +// manages a list of notifications +// Note that if this is ever copied around, we might find ourselves with multiple copies +// of a queue with notifications being added to different nonequivalent copies. So we +// make it inherit from boost::noncopyable, and then create a map of LLPointer to manage it. +// +class LLNotificationChannel : + boost::noncopyable, + public LLNotificationChannelBase, + public LLInstanceTracker +{ + LOG_CLASS(LLNotificationChannel); + +public: + // Notification Channels have a filter, which determines which notifications + // will be added to this channel. + // Channel filters cannot change. + struct Params : public LLInitParam::Block + { + Mandatory name; + Optional filter; + Multiple sources; + }; + + LLNotificationChannel(const Params& p = Params()); + LLNotificationChannel(const std::string& name, const std::string& parent, LLNotificationFilter filter); + + virtual ~LLNotificationChannel(); + typedef LLNotificationSet::iterator Iterator; + + std::string getName() const { return mName; } + typedef std::vector::const_iterator parents_iter; + boost::iterator_range getParents() const + { + return boost::iterator_range(mParents); + } + + bool isEmpty() const; + S32 size() const; + size_t size(); + + typedef boost::function NotificationProcess; + void forEachNotification(NotificationProcess process); + + std::string summarize(); + +protected: + void connectToChannel(const std::string& channel_name); + +private: + std::string mName; + std::vector mParents; + std::vector mListeners; +}; + +// An interface class to provide a clean linker seam to the LLNotifications class. +// Extend this interface as needed for your use of LLNotifications. +class LLNotificationsInterface +{ +public: + virtual LLNotificationPtr add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + LLNotificationFunctorRegistry::ResponseFunctor functor) = 0; +}; + +class LLNotifications : + public LLNotificationsInterface, + public LLSingleton, + public LLNotificationChannelBase +{ + LLSINGLETON(LLNotifications); + LOG_CLASS(LLNotifications); + virtual ~LLNotifications() {} + +public: + + // Needed to clear up RefCounted things prior to actual destruction + // as the singleton nature of the class makes them do "bad things" + // on at least Mac, if not all 3 platforms + // + void clear(); + + // load all notification descriptions from file + // calling more than once will overwrite existing templates + // but never delete a template + bool loadTemplates(); + + // load visibility rules from file; + // OK to call more than once because it will reload + bool loadVisibilityRules(); + + // Add a simple notification (from XUI) + void addFromCallback(const LLSD& name); + + // *NOTE: To add simple notifications, #include "llnotificationsutil.h" + // and use LLNotificationsUtil::add("MyNote") or add("MyNote", args) + LLNotificationPtr add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload); + LLNotificationPtr add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + const std::string& functor_name); + /* virtual */ LLNotificationPtr add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + LLNotificationFunctorRegistry::ResponseFunctor functor) override; + LLNotificationPtr add(const LLNotification::Params& p); + + void add(const LLNotificationPtr pNotif); + void load(const LLNotificationPtr pNotif); + void cancel(LLNotificationPtr pNotif); + void cancelByName(const std::string& name); + void cancelByOwner(const LLUUID ownerId); + void update(const LLNotificationPtr pNotif); + + LLNotificationPtr find(LLUUID uuid); + + // This is all stuff for managing the templates + // take your template out + LLNotificationTemplatePtr getTemplate(const std::string& name); + + // get the whole collection + typedef std::vector TemplateNames; + TemplateNames getTemplateNames() const; // returns a list of notification names + + typedef std::map TemplateMap; + + TemplateMap::const_iterator templatesBegin() { return mTemplates.begin(); } + TemplateMap::const_iterator templatesEnd() { return mTemplates.end(); } + + // test for existence + bool templateExists(const std::string& name); + + typedef std::list VisibilityRuleList; + + void forceResponse(const LLNotification::Params& params, S32 option); + + void createDefaultChannels(); + + LLNotificationChannelPtr getChannel(const std::string& channelName); + + std::string getGlobalString(const std::string& key) const; + + void setIgnoreAllNotifications(bool ignore); + bool getIgnoreAllNotifications(); + + void setIgnored(const std::string& name, bool ignored); + bool getIgnored(const std::string& name); + + bool isVisibleByRules(LLNotificationPtr pNotification); + +private: + /*virtual*/ void initSingleton() override; + /*virtual*/ void cleanupSingleton() override; + + void loadPersistentNotifications(); + + bool expirationFilter(LLNotificationPtr pNotification); + bool expirationHandler(const LLSD& payload); + bool uniqueFilter(LLNotificationPtr pNotification); + bool uniqueHandler(const LLSD& payload); + bool failedUniquenessTest(const LLSD& payload); + LLNotificationChannelPtr pHistoryChannel; + LLNotificationChannelPtr pExpirationChannel; + + TemplateMap mTemplates; + + VisibilityRuleList mVisibilityRules; + + std::string mFileName; + + LLNotificationMap mUniqueNotifications; + + typedef std::map GlobalStringMap; + GlobalStringMap mGlobalStrings; + + bool mIgnoreAllNotifications; + + std::unique_ptr mListener; + + std::vector mDefaultChannels; +}; + +/** + * Abstract class for postponed notifications. + * Provides possibility to add notification after specified by id avatar or group will be + * received from cache name. The object of this type automatically well be deleted + * by cleanup method after respond will be received from cache name. + * + * To add custom postponed notification to the notification system client should: + * 1 create class derived from LLPostponedNotification; + * 2 call LLPostponedNotification::add method; + */ +class LLPostponedNotification : public LLMortician +{ +public: + /** + * Performs hooking cache name callback which will add notification to notifications system. + * Type of added notification should be specified by template parameter T + * and non-private derived from LLPostponedNotification class, + * otherwise compilation error will occur. + */ + template + static void add(const LLNotification::Params& params, + const LLUUID& id, bool is_group) + { + // upcast T to the base type to restrict T derivation from LLPostponedNotification + LLPostponedNotification* thiz = new T(); + thiz->mParams = params; + + // Avoid header file dependency on llcachename.h + thiz->lookupName(id, is_group); + } + +private: + void lookupName(const LLUUID& id, bool is_group); + // only used for groups + void onGroupNameCache(const LLUUID& id, const std::string& full_name, bool is_group); + // only used for avatars + void fetchAvatarName(const LLUUID& id); + void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); + // used for both group and avatar names + void finalizeName(const std::string& name); + + void cleanup() + { + die(); + } + +protected: + LLPostponedNotification() + : mParams(), + mName(), + mAvatarNameCacheConnection() + {} + + virtual ~LLPostponedNotification() + { + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + } + + /** + * Abstract method provides possibility to modify notification parameters and + * will be called after cache name retrieve information about avatar or group + * and before notification will be added to the notification system. + */ + virtual void modifyNotificationParams() = 0; + + LLNotification::Params mParams; + std::string mName; + boost::signals2::connection mAvatarNameCacheConnection; +}; + +// Stores only persistent notifications. +// Class users can use connectChanged() to process persistent notifications +// (see LLPersistentNotificationStorage for example). +class LLPersistentNotificationChannel : public LLNotificationChannel +{ + LOG_CLASS(LLPersistentNotificationChannel); +public: + LLPersistentNotificationChannel() + : LLNotificationChannel("Persistent", "Visible", ¬ificationFilter) + {} + + virtual ~LLPersistentNotificationChannel() + { + mHistory.clear(); + } + + typedef std::vector history_list_t; + history_list_t::iterator beginHistory() { sortHistory(); return mHistory.begin(); } + history_list_t::iterator endHistory() { return mHistory.end(); } + +private: + struct sortByTime + { + S32 operator ()(const LLNotificationPtr& a, const LLNotificationPtr& b) + { + return a->getDate() < b->getDate(); + } + }; + + void sortHistory() + { + std::sort(mHistory.begin(), mHistory.end(), sortByTime()); + } + + // The channel gets all persistent notifications except those that have been canceled + static bool notificationFilter(LLNotificationPtr pNotification) + { + bool handle_notification = false; + + handle_notification = pNotification->isPersistent() + && !pNotification->isCancelled(); + + return handle_notification; + } + + void onAdd(LLNotificationPtr p) + { + mHistory.push_back(p); + } + + void onLoad(LLNotificationPtr p) + { + mHistory.push_back(p); + } + + std::vector mHistory; +}; + +#endif//LL_LLNOTIFICATIONS_H + diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index f6758133f3..468cdb10fb 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -1,875 +1,875 @@ -/** - * @file llpanel.cpp - * @brief LLPanel base 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$ - */ - -// Opaque view with a background and a border. Can contain LLUICtrls. - -#include "linden_common.h" - -#define LLPANEL_CPP -#include "llpanel.h" - -#include "llfocusmgr.h" -#include "llfontgl.h" -#include "llrect.h" -#include "llerror.h" -#include "lldir.h" -#include "lltimer.h" - -#include "llbutton.h" -#include "llmenugl.h" -#include "llui.h" -#include "llkeyboard.h" -#include "lllineeditor.h" -#include "llcontrol.h" -#include "lltextbox.h" -#include "lluictrl.h" -#include "lluictrlfactory.h" -#include "llviewborder.h" - -static LLDefaultChildRegistry::Register r1("panel", &LLPanel::fromXML); -LLPanel::factory_stack_t LLPanel::sFactoryStack; - - -// Compiler optimization, generate extern template -template class LLPanel* LLView::getChild( - const std::string& name, bool recurse) const; - -LLPanel::LocalizedString::LocalizedString() -: name("name"), - value("value") -{} - -const LLPanel::Params& LLPanel::getDefaultParams() -{ - return LLUICtrlFactory::getDefaultParams(); -} - -LLPanel::Params::Params() -: has_border("border", false), - border(""), - background_visible("background_visible", false), - background_opaque("background_opaque", false), - bg_opaque_color("bg_opaque_color"), - bg_alpha_color("bg_alpha_color"), - bg_opaque_image_overlay("bg_opaque_image_overlay"), - bg_alpha_image_overlay("bg_alpha_image_overlay"), - bg_opaque_image("bg_opaque_image"), - bg_alpha_image("bg_alpha_image"), - min_width("min_width", 100), - min_height("min_height", 100), - strings("string"), - filename("filename"), - class_name("class"), - help_topic("help_topic"), - visible_callback("visible_callback"), - accepts_badge("accepts_badge") -{ - addSynonym(background_visible, "bg_visible"); - addSynonym(has_border, "border_visible"); - addSynonym(label, "title"); -} - - -LLPanel::LLPanel(const LLPanel::Params& p) -: LLUICtrl(p), - LLBadgeHolder(p.accepts_badge), - mBgVisible(p.background_visible), - mBgOpaque(p.background_opaque), - mBgOpaqueColor(p.bg_opaque_color()), - mBgAlphaColor(p.bg_alpha_color()), - mBgOpaqueImageOverlay(p.bg_opaque_image_overlay), - mBgAlphaImageOverlay(p.bg_alpha_image_overlay), - mBgOpaqueImage(p.bg_opaque_image()), - mBgAlphaImage(p.bg_alpha_image()), - mDefaultBtn(NULL), - mBorder(NULL), - mLabel(p.label), - mHelpTopic(p.help_topic), - mCommitCallbackRegistrar(false), - mEnableCallbackRegistrar(false), - mXMLFilename(p.filename), - mVisibleSignal(NULL) - // *NOTE: Be sure to also change LLPanel::initFromParams(). We have too - // many classes derived from LLPanel to retrofit them all to pass in params. -{ - if (p.has_border) - { - addBorder(p.border); - } -} - -LLPanel::~LLPanel() -{ - delete mVisibleSignal; -} - -// virtual -bool LLPanel::isPanel() const -{ - return true; -} - -void LLPanel::addBorder(LLViewBorder::Params p) -{ - removeBorder(); - p.rect = getLocalRect(); - - mBorder = LLUICtrlFactory::create(p); - addChild( mBorder ); -} - -void LLPanel::addBorder() -{ - LLViewBorder::Params p; - p.border_thickness(LLPANEL_BORDER_WIDTH); - addBorder(p); -} - - -void LLPanel::removeBorder() -{ - if (mBorder) - { - removeChild(mBorder); - delete mBorder; - mBorder = NULL; - } -} - - -// virtual -void LLPanel::clearCtrls() -{ - LLPanel::ctrl_list_t ctrls = getCtrlList(); - for (LLPanel::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) - { - LLUICtrl* ctrl = *ctrl_it; - ctrl->setFocus( false ); - ctrl->setEnabled( false ); - ctrl->clear(); - } -} - -void LLPanel::setCtrlsEnabled( bool b ) -{ - LLPanel::ctrl_list_t ctrls = getCtrlList(); - for (LLPanel::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) - { - LLUICtrl* ctrl = *ctrl_it; - ctrl->setEnabled( b ); - } -} - -LLPanel::ctrl_list_t LLPanel::getCtrlList() const -{ - ctrl_list_t controls; - for(child_list_t::const_iterator it = getChildList()->begin(), end_it = getChildList()->end(); it != end_it; ++it) - { - LLView* viewp = *it; - if(viewp->isCtrl()) - { - controls.push_back(static_cast(viewp)); - } - } - return controls; -} - - -void LLPanel::draw() -{ - F32 alpha = getDrawContext().mAlpha; - - // draw background - if( mBgVisible ) - { - alpha = getCurrentTransparency(); - - LLRect local_rect = getLocalRect(); - if (mBgOpaque ) - { - // opaque, in-front look - if (mBgOpaqueImage.notNull()) - { - mBgOpaqueImage->draw( local_rect, mBgOpaqueImageOverlay % alpha ); - } - else - { - // fallback to flat colors when there are no images - gl_rect_2d( local_rect, mBgOpaqueColor.get() % alpha); - } - } - else - { - // transparent, in-back look - if (mBgAlphaImage.notNull()) - { - mBgAlphaImage->draw( local_rect, mBgAlphaImageOverlay % alpha ); - } - else - { - gl_rect_2d( local_rect, mBgAlphaColor.get() % alpha ); - } - } - } - - updateDefaultBtn(); - - LLView::draw(); -} - -void LLPanel::updateDefaultBtn() -{ - if( mDefaultBtn) - { - if (gFocusMgr.childHasKeyboardFocus( this ) && mDefaultBtn->getEnabled()) - { - LLButton* buttonp = dynamic_cast(gFocusMgr.getKeyboardFocus()); - bool focus_is_child_button = buttonp && buttonp->getCommitOnReturn(); - // only enable default button when current focus is not a return-capturing button - mDefaultBtn->setBorderEnabled(!focus_is_child_button); - } - else - { - mDefaultBtn->setBorderEnabled(false); - } - } -} - -void LLPanel::refresh() -{ - // do nothing by default - // but is automatically called in setFocus(true) -} - -void LLPanel::setDefaultBtn(LLButton* btn) -{ - if (mDefaultBtn && mDefaultBtn->getEnabled()) - { - mDefaultBtn->setBorderEnabled(false); - } - mDefaultBtn = btn; - if (mDefaultBtn) - { - mDefaultBtn->setBorderEnabled(true); - } -} - -void LLPanel::setDefaultBtn(const std::string& id) -{ - LLButton *button = getChild(id); - if (button) - { - setDefaultBtn(button); - } - else - { - setDefaultBtn(NULL); - } -} - -bool LLPanel::handleKeyHere( KEY key, MASK mask ) -{ - bool handled = false; - - LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); - - // handle user hitting ESC to defocus - if (key == KEY_ESCAPE) - { - setFocus(false); - return true; - } - else if( (mask == MASK_SHIFT) && (KEY_TAB == key)) - { - //SHIFT-TAB - if (cur_focus) - { - LLUICtrl* focus_root = cur_focus->findRootMostFocusRoot(); - if (focus_root) - { - handled = focus_root->focusPrevItem(false); - } - } - } - else if( (mask == MASK_NONE ) && (KEY_TAB == key)) - { - //TAB - if (cur_focus) - { - LLUICtrl* focus_root = cur_focus->findRootMostFocusRoot(); - if (focus_root) - { - handled = focus_root->focusNextItem(false); - } - } - } - - // If RETURN was pressed and something has focus, call onCommit() - if (!handled && cur_focus && key == KEY_RETURN && mask == MASK_NONE) - { - LLButton* focused_button = dynamic_cast(cur_focus); - if (focused_button && focused_button->getCommitOnReturn()) - { - // current focus is a return-capturing button, - // let *that* button handle the return key - handled = false; - } - else if (mDefaultBtn && mDefaultBtn->getVisible() && mDefaultBtn->getEnabled()) - { - // If we have a default button, click it when return is pressed - mDefaultBtn->onCommit(); - handled = true; - } - else if (cur_focus->acceptsTextInput()) - { - // call onCommit for text input handling control - cur_focus->onCommit(); - handled = true; - } - } - - return handled; -} - -void LLPanel::onVisibilityChange ( bool new_visibility ) -{ - LLUICtrl::onVisibilityChange ( new_visibility ); - if (mVisibleSignal) - (*mVisibleSignal)(this, LLSD(new_visibility) ); // Pass bool as LLSD -} - -void LLPanel::setFocus(bool b) -{ - if( b && !hasFocus()) - { - // give ourselves focus preemptively, to avoid infinite loop - LLUICtrl::setFocus(true); - // then try to pass to first valid child - focusFirstItem(); - } - else - { - LLUICtrl::setFocus(b); - } -} - -void LLPanel::setBorderVisible(bool b) -{ - if (mBorder) - { - mBorder->setVisible( b ); - } -} - -LLTrace::BlockTimerStatHandle FTM_PANEL_CONSTRUCTION("Panel Construction"); - -LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parent, LLXMLNodePtr output_node) -{ - std::string name("panel"); - node->getAttributeString("name", name); - - std::string class_attr; - node->getAttributeString("class", class_attr); - - LLPanel* panelp = NULL; - - { LL_RECORD_BLOCK_TIME(FTM_PANEL_CONSTRUCTION); - - if(!class_attr.empty()) - { - panelp = LLRegisterPanelClass::instance().createPanelClass(class_attr); - if (!panelp) - { - LL_WARNS() << "Panel class \"" << class_attr << "\" not registered." << LL_ENDL; - } - } - - if (!panelp) - { - panelp = createFactoryPanel(name); - llassert(panelp); - - if (!panelp) - { - return NULL; // :( - } - } - - } - // factory panels may have registered their own factory maps - if (!panelp->getFactoryMap().empty()) - { - sFactoryStack.push_back(&panelp->getFactoryMap()); - } - // for local registry callbacks; define in constructor, referenced in XUI or postBuild - panelp->mCommitCallbackRegistrar.pushScope(); - panelp->mEnableCallbackRegistrar.pushScope(); - - panelp->initPanelXML(node, parent, output_node, LLUICtrlFactory::getDefaultParams()); - - panelp->mCommitCallbackRegistrar.popScope(); - panelp->mEnableCallbackRegistrar.popScope(); - - if (!panelp->getFactoryMap().empty()) - { - sFactoryStack.pop_back(); - } - - return panelp; -} - -void LLPanel::initFromParams(const LLPanel::Params& p) -{ - //setting these here since panel constructor not called with params - //and LLView::initFromParams will use them to set visible and enabled - setVisible(p.visible); - setEnabled(p.enabled); - setFocusRoot(p.focus_root); - setSoundFlags(p.sound_flags); - - // control_name, tab_stop, focus_lost_callback, initial_value, rect, enabled, visible - LLUICtrl::initFromParams(p); - - // visible callback - if (p.visible_callback.isProvided()) - { - setVisibleCallback(initCommitCallback(p.visible_callback)); - } - - for (LLInitParam::ParamIterator::const_iterator it = p.strings.begin(); - it != p.strings.end(); - ++it) - { - mUIStrings[it->name] = it->value; - } - - setLabel(p.label()); - setHelpTopic(p.help_topic); - setShape(p.rect); - parseFollowsFlags(p); - - setToolTip(p.tool_tip()); - setFromXUI(p.from_xui); - - mHoverCursor = getCursorFromString(p.hover_cursor); - - if (p.has_border) - { - addBorder(p.border); - } - // let constructors set this value if not provided - if (p.use_bounding_rect.isProvided()) - { - setUseBoundingRect(p.use_bounding_rect); - } - setDefaultTabGroup(p.default_tab_group); - setMouseOpaque(p.mouse_opaque); - - setBackgroundVisible(p.background_visible); - setBackgroundOpaque(p.background_opaque); - setBackgroundColor(p.bg_opaque_color().get()); - setTransparentColor(p.bg_alpha_color().get()); - mBgOpaqueImage = p.bg_opaque_image(); - mBgAlphaImage = p.bg_alpha_image(); - mBgOpaqueImageOverlay = p.bg_opaque_image_overlay; - mBgAlphaImageOverlay = p.bg_alpha_image_overlay; - - setAcceptsBadge(p.accepts_badge); -} - -static LLTrace::BlockTimerStatHandle FTM_PANEL_SETUP("Panel Setup"); -static LLTrace::BlockTimerStatHandle FTM_EXTERNAL_PANEL_LOAD("Load Extern Panel Reference"); -static LLTrace::BlockTimerStatHandle FTM_PANEL_POSTBUILD("Panel PostBuild"); - -bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node, const LLPanel::Params& default_params) -{ - Params params(default_params); - { - LL_RECORD_BLOCK_TIME(FTM_PANEL_SETUP); - - LLXMLNodePtr referenced_xml; - std::string xml_filename = mXMLFilename; - - // if the panel didn't provide a filename, check the node - if (xml_filename.empty()) - { - node->getAttributeString("filename", xml_filename); - setXMLFilename(xml_filename); - } - - LLXUIParser parser; - - if (!xml_filename.empty()) - { - if (output_node) - { - //if we are exporting, we want to export the current xml - //not the referenced xml - parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); - Params output_params(params); - setupParamsForExport(output_params, parent); - output_node->setName(node->getName()->mString); - parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); - return true; - } - - LLUICtrlFactory::instance().pushFileName(xml_filename); - - LL_RECORD_BLOCK_TIME(FTM_EXTERNAL_PANEL_LOAD); - if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml)) - { - LL_WARNS() << "Couldn't parse panel from: " << xml_filename << LL_ENDL; - - return false; - } - - parser.readXUI(referenced_xml, params, LLUICtrlFactory::getInstance()->getCurFileName()); - - // add children using dimensions from referenced xml for consistent layout - setShape(params.rect); - LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance()); - - LLUICtrlFactory::instance().popFileName(); - } - - // ask LLUICtrlFactory for filename, since xml_filename might be empty - parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); - - if (output_node) - { - Params output_params(params); - setupParamsForExport(output_params, parent); - output_node->setName(node->getName()->mString); - parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); - } - - params.from_xui = true; - applyXUILayout(params, parent); - { - LL_RECORD_BLOCK_TIME(FTM_PANEL_CONSTRUCTION); - initFromParams(params); - } - - // add children - LLUICtrlFactory::createChildren(this, node, child_registry_t::instance(), output_node); - - // Connect to parent after children are built, because tab containers - // do a reshape() on their child panels, which requires that the children - // be built/added. JC - if (parent) - { - S32 tab_group = params.tab_group.isProvided() ? params.tab_group() : parent->getLastTabGroup(); - parent->addChild(this, tab_group); - } - - { - LL_RECORD_BLOCK_TIME(FTM_PANEL_POSTBUILD); - postBuild(); - } - } - return true; -} - -bool LLPanel::hasString(const std::string& name) -{ - return mUIStrings.find(name) != mUIStrings.end(); -} - -std::string LLPanel::getString(const std::string& name, const LLStringUtil::format_map_t& args) const -{ - ui_string_map_t::const_iterator found_it = mUIStrings.find(name); - if (found_it != mUIStrings.end()) - { - // make a copy as format works in place - LLUIString formatted_string = LLUIString(found_it->second); - formatted_string.setArgList(args); - return formatted_string.getString(); - } - std::string err_str("Failed to find string " + name + " in panel " + getName()); //*TODO: Translate - if(LLUI::getInstance()->mSettingGroups["config"]->getBOOL("QAMode")) - { - LL_ERRS() << err_str << LL_ENDL; - } - else - { - LL_WARNS() << err_str << LL_ENDL; - } - return LLStringUtil::null; -} - -std::string LLPanel::getString(const std::string& name) const -{ - ui_string_map_t::const_iterator found_it = mUIStrings.find(name); - if (found_it != mUIStrings.end()) - { - return found_it->second; - } - std::string err_str("Failed to find string " + name + " in panel " + getName()); //*TODO: Translate - if(LLUI::getInstance()->mSettingGroups["config"]->getBOOL("QAMode")) - { - LL_ERRS() << err_str << LL_ENDL; - } - else - { - LL_WARNS() << err_str << LL_ENDL; - } - return LLStringUtil::null; -} - - -void LLPanel::childSetVisible(const std::string& id, bool visible) -{ - LLView* child = findChild(id); - if (child) - { - child->setVisible(visible); - } -} - -void LLPanel::childSetEnabled(const std::string& id, bool enabled) -{ - LLView* child = findChild(id); - if (child) - { - child->setEnabled(enabled); - } -} - -void LLPanel::childSetFocus(const std::string& id, bool focus) -{ - LLUICtrl* child = findChild(id); - if (child) - { - child->setFocus(focus); - } -} - -bool LLPanel::childHasFocus(const std::string& id) -{ - LLUICtrl* child = findChild(id); - if (child) - { - return child->hasFocus(); - } - else - { - return false; - } -} - -// *TODO: Deprecate; for backwards compatability only: -// Prefer getChild("foo")->setCommitCallback(boost:bind(...)), -// which takes a generic slot. Or use mCommitCallbackRegistrar.add() with -// a named callback and reference it in XML. -void LLPanel::childSetCommitCallback(const std::string& id, boost::function cb, void* data) -{ - LLUICtrl* child = findChild(id); - if (child) - { - child->setCommitCallback(boost::bind(cb, child, data)); - } -} - -void LLPanel::childSetColor(const std::string& id, const LLColor4& color) -{ - LLUICtrl* child = findChild(id); - if (child) - { - child->setColor(color); - } -} - -LLCtrlSelectionInterface* LLPanel::childGetSelectionInterface(const std::string& id) const -{ - LLUICtrl* child = findChild(id); - if (child) - { - return child->getSelectionInterface(); - } - return NULL; -} - -LLCtrlListInterface* LLPanel::childGetListInterface(const std::string& id) const -{ - LLUICtrl* child = findChild(id); - if (child) - { - return child->getListInterface(); - } - return NULL; -} - -LLCtrlScrollInterface* LLPanel::childGetScrollInterface(const std::string& id) const -{ - LLUICtrl* child = findChild(id); - if (child) - { - return child->getScrollInterface(); - } - return NULL; -} - -void LLPanel::childSetValue(const std::string& id, LLSD value) -{ - LLUICtrl* child = findChild(id); - if (child) - { - child->setValue(value); - } -} - -LLSD LLPanel::childGetValue(const std::string& id) const -{ - LLUICtrl* child = findChild(id); - if (child) - { - return child->getValue(); - } - // Not found => return undefined - return LLSD(); -} - -bool LLPanel::childSetTextArg(const std::string& id, const std::string& key, const LLStringExplicit& text) -{ - LLUICtrl* child = findChild(id); - if (child) - { - return child->setTextArg(key, text); - } - return false; -} - -bool LLPanel::childSetLabelArg(const std::string& id, const std::string& key, const LLStringExplicit& text) -{ - LLView* child = findChild(id); - if (child) - { - return child->setLabelArg(key, text); - } - return false; -} - -void LLPanel::childSetAction(const std::string& id, const commit_signal_t::slot_type& function) -{ - LLButton* button = findChild(id); - if (button) - { - button->setClickedCallback(function); - } -} - -void LLPanel::childSetAction(const std::string& id, boost::function function, void* value) -{ - LLButton* button = findChild(id); - if (button) - { - button->setClickedCallback(boost::bind(function, value)); - } -} - -boost::signals2::connection LLPanel::setVisibleCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mVisibleSignal) - { - mVisibleSignal = new commit_signal_t(); - } - - return mVisibleSignal->connect(cb); -} - -//----------------------------------------------------------------------------- -// buildPanel() -//----------------------------------------------------------------------------- -bool LLPanel::buildFromFile(const std::string& filename, const LLPanel::Params& default_params) -{ - LL_PROFILE_ZONE_SCOPED; - bool didPost = false; - LLXMLNodePtr root; - - if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) - { - LL_WARNS() << "Couldn't parse panel from: " << filename << LL_ENDL; - return didPost; - } - - // root must be called panel - if( !root->hasName("panel" ) ) - { - LL_WARNS() << "Root node should be named panel in : " << filename << LL_ENDL; - return didPost; - } - - LL_DEBUGS() << "Building panel " << filename << LL_ENDL; - - LLUICtrlFactory::instance().pushFileName(filename); - { - if (!getFactoryMap().empty()) - { - sFactoryStack.push_back(&getFactoryMap()); - } - - // for local registry callbacks; define in constructor, referenced in XUI or postBuild - getCommitCallbackRegistrar().pushScope(); - getEnableCallbackRegistrar().pushScope(); - - didPost = initPanelXML(root, NULL, NULL, default_params); - - getCommitCallbackRegistrar().popScope(); - getEnableCallbackRegistrar().popScope(); - - setXMLFilename(filename); - - if (!getFactoryMap().empty()) - { - sFactoryStack.pop_back(); - } - } - LLUICtrlFactory::instance().popFileName(); - return didPost; -} - -//----------------------------------------------------------------------------- -// createFactoryPanel() -//----------------------------------------------------------------------------- -LLPanel* LLPanel::createFactoryPanel(const std::string& name) -{ - std::deque::iterator itor; - for (itor = sFactoryStack.begin(); itor != sFactoryStack.end(); ++itor) - { - const LLCallbackMap::map_t* factory_map = *itor; - - // Look up this panel's name in the map. - LLCallbackMap::map_const_iter_t iter = factory_map->find( name ); - if (iter != factory_map->end()) - { - // Use the factory to create the panel, instead of using a default LLPanel. - LLPanel *ret = (LLPanel*) iter->second.mCallback( iter->second.mData ); - return ret; - } - } - LLPanel::Params panel_p; - return LLUICtrlFactory::create(panel_p); -} +/** + * @file llpanel.cpp + * @brief LLPanel base 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$ + */ + +// Opaque view with a background and a border. Can contain LLUICtrls. + +#include "linden_common.h" + +#define LLPANEL_CPP +#include "llpanel.h" + +#include "llfocusmgr.h" +#include "llfontgl.h" +#include "llrect.h" +#include "llerror.h" +#include "lldir.h" +#include "lltimer.h" + +#include "llbutton.h" +#include "llmenugl.h" +#include "llui.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llcontrol.h" +#include "lltextbox.h" +#include "lluictrl.h" +#include "lluictrlfactory.h" +#include "llviewborder.h" + +static LLDefaultChildRegistry::Register r1("panel", &LLPanel::fromXML); +LLPanel::factory_stack_t LLPanel::sFactoryStack; + + +// Compiler optimization, generate extern template +template class LLPanel* LLView::getChild( + const std::string& name, bool recurse) const; + +LLPanel::LocalizedString::LocalizedString() +: name("name"), + value("value") +{} + +const LLPanel::Params& LLPanel::getDefaultParams() +{ + return LLUICtrlFactory::getDefaultParams(); +} + +LLPanel::Params::Params() +: has_border("border", false), + border(""), + background_visible("background_visible", false), + background_opaque("background_opaque", false), + bg_opaque_color("bg_opaque_color"), + bg_alpha_color("bg_alpha_color"), + bg_opaque_image_overlay("bg_opaque_image_overlay"), + bg_alpha_image_overlay("bg_alpha_image_overlay"), + bg_opaque_image("bg_opaque_image"), + bg_alpha_image("bg_alpha_image"), + min_width("min_width", 100), + min_height("min_height", 100), + strings("string"), + filename("filename"), + class_name("class"), + help_topic("help_topic"), + visible_callback("visible_callback"), + accepts_badge("accepts_badge") +{ + addSynonym(background_visible, "bg_visible"); + addSynonym(has_border, "border_visible"); + addSynonym(label, "title"); +} + + +LLPanel::LLPanel(const LLPanel::Params& p) +: LLUICtrl(p), + LLBadgeHolder(p.accepts_badge), + mBgVisible(p.background_visible), + mBgOpaque(p.background_opaque), + mBgOpaqueColor(p.bg_opaque_color()), + mBgAlphaColor(p.bg_alpha_color()), + mBgOpaqueImageOverlay(p.bg_opaque_image_overlay), + mBgAlphaImageOverlay(p.bg_alpha_image_overlay), + mBgOpaqueImage(p.bg_opaque_image()), + mBgAlphaImage(p.bg_alpha_image()), + mDefaultBtn(NULL), + mBorder(NULL), + mLabel(p.label), + mHelpTopic(p.help_topic), + mCommitCallbackRegistrar(false), + mEnableCallbackRegistrar(false), + mXMLFilename(p.filename), + mVisibleSignal(NULL) + // *NOTE: Be sure to also change LLPanel::initFromParams(). We have too + // many classes derived from LLPanel to retrofit them all to pass in params. +{ + if (p.has_border) + { + addBorder(p.border); + } +} + +LLPanel::~LLPanel() +{ + delete mVisibleSignal; +} + +// virtual +bool LLPanel::isPanel() const +{ + return true; +} + +void LLPanel::addBorder(LLViewBorder::Params p) +{ + removeBorder(); + p.rect = getLocalRect(); + + mBorder = LLUICtrlFactory::create(p); + addChild( mBorder ); +} + +void LLPanel::addBorder() +{ + LLViewBorder::Params p; + p.border_thickness(LLPANEL_BORDER_WIDTH); + addBorder(p); +} + + +void LLPanel::removeBorder() +{ + if (mBorder) + { + removeChild(mBorder); + delete mBorder; + mBorder = NULL; + } +} + + +// virtual +void LLPanel::clearCtrls() +{ + LLPanel::ctrl_list_t ctrls = getCtrlList(); + for (LLPanel::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) + { + LLUICtrl* ctrl = *ctrl_it; + ctrl->setFocus( false ); + ctrl->setEnabled( false ); + ctrl->clear(); + } +} + +void LLPanel::setCtrlsEnabled( bool b ) +{ + LLPanel::ctrl_list_t ctrls = getCtrlList(); + for (LLPanel::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) + { + LLUICtrl* ctrl = *ctrl_it; + ctrl->setEnabled( b ); + } +} + +LLPanel::ctrl_list_t LLPanel::getCtrlList() const +{ + ctrl_list_t controls; + for(child_list_t::const_iterator it = getChildList()->begin(), end_it = getChildList()->end(); it != end_it; ++it) + { + LLView* viewp = *it; + if(viewp->isCtrl()) + { + controls.push_back(static_cast(viewp)); + } + } + return controls; +} + + +void LLPanel::draw() +{ + F32 alpha = getDrawContext().mAlpha; + + // draw background + if( mBgVisible ) + { + alpha = getCurrentTransparency(); + + LLRect local_rect = getLocalRect(); + if (mBgOpaque ) + { + // opaque, in-front look + if (mBgOpaqueImage.notNull()) + { + mBgOpaqueImage->draw( local_rect, mBgOpaqueImageOverlay % alpha ); + } + else + { + // fallback to flat colors when there are no images + gl_rect_2d( local_rect, mBgOpaqueColor.get() % alpha); + } + } + else + { + // transparent, in-back look + if (mBgAlphaImage.notNull()) + { + mBgAlphaImage->draw( local_rect, mBgAlphaImageOverlay % alpha ); + } + else + { + gl_rect_2d( local_rect, mBgAlphaColor.get() % alpha ); + } + } + } + + updateDefaultBtn(); + + LLView::draw(); +} + +void LLPanel::updateDefaultBtn() +{ + if( mDefaultBtn) + { + if (gFocusMgr.childHasKeyboardFocus( this ) && mDefaultBtn->getEnabled()) + { + LLButton* buttonp = dynamic_cast(gFocusMgr.getKeyboardFocus()); + bool focus_is_child_button = buttonp && buttonp->getCommitOnReturn(); + // only enable default button when current focus is not a return-capturing button + mDefaultBtn->setBorderEnabled(!focus_is_child_button); + } + else + { + mDefaultBtn->setBorderEnabled(false); + } + } +} + +void LLPanel::refresh() +{ + // do nothing by default + // but is automatically called in setFocus(true) +} + +void LLPanel::setDefaultBtn(LLButton* btn) +{ + if (mDefaultBtn && mDefaultBtn->getEnabled()) + { + mDefaultBtn->setBorderEnabled(false); + } + mDefaultBtn = btn; + if (mDefaultBtn) + { + mDefaultBtn->setBorderEnabled(true); + } +} + +void LLPanel::setDefaultBtn(const std::string& id) +{ + LLButton *button = getChild(id); + if (button) + { + setDefaultBtn(button); + } + else + { + setDefaultBtn(NULL); + } +} + +bool LLPanel::handleKeyHere( KEY key, MASK mask ) +{ + bool handled = false; + + LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); + + // handle user hitting ESC to defocus + if (key == KEY_ESCAPE) + { + setFocus(false); + return true; + } + else if( (mask == MASK_SHIFT) && (KEY_TAB == key)) + { + //SHIFT-TAB + if (cur_focus) + { + LLUICtrl* focus_root = cur_focus->findRootMostFocusRoot(); + if (focus_root) + { + handled = focus_root->focusPrevItem(false); + } + } + } + else if( (mask == MASK_NONE ) && (KEY_TAB == key)) + { + //TAB + if (cur_focus) + { + LLUICtrl* focus_root = cur_focus->findRootMostFocusRoot(); + if (focus_root) + { + handled = focus_root->focusNextItem(false); + } + } + } + + // If RETURN was pressed and something has focus, call onCommit() + if (!handled && cur_focus && key == KEY_RETURN && mask == MASK_NONE) + { + LLButton* focused_button = dynamic_cast(cur_focus); + if (focused_button && focused_button->getCommitOnReturn()) + { + // current focus is a return-capturing button, + // let *that* button handle the return key + handled = false; + } + else if (mDefaultBtn && mDefaultBtn->getVisible() && mDefaultBtn->getEnabled()) + { + // If we have a default button, click it when return is pressed + mDefaultBtn->onCommit(); + handled = true; + } + else if (cur_focus->acceptsTextInput()) + { + // call onCommit for text input handling control + cur_focus->onCommit(); + handled = true; + } + } + + return handled; +} + +void LLPanel::onVisibilityChange ( bool new_visibility ) +{ + LLUICtrl::onVisibilityChange ( new_visibility ); + if (mVisibleSignal) + (*mVisibleSignal)(this, LLSD(new_visibility) ); // Pass bool as LLSD +} + +void LLPanel::setFocus(bool b) +{ + if( b && !hasFocus()) + { + // give ourselves focus preemptively, to avoid infinite loop + LLUICtrl::setFocus(true); + // then try to pass to first valid child + focusFirstItem(); + } + else + { + LLUICtrl::setFocus(b); + } +} + +void LLPanel::setBorderVisible(bool b) +{ + if (mBorder) + { + mBorder->setVisible( b ); + } +} + +LLTrace::BlockTimerStatHandle FTM_PANEL_CONSTRUCTION("Panel Construction"); + +LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parent, LLXMLNodePtr output_node) +{ + std::string name("panel"); + node->getAttributeString("name", name); + + std::string class_attr; + node->getAttributeString("class", class_attr); + + LLPanel* panelp = NULL; + + { LL_RECORD_BLOCK_TIME(FTM_PANEL_CONSTRUCTION); + + if(!class_attr.empty()) + { + panelp = LLRegisterPanelClass::instance().createPanelClass(class_attr); + if (!panelp) + { + LL_WARNS() << "Panel class \"" << class_attr << "\" not registered." << LL_ENDL; + } + } + + if (!panelp) + { + panelp = createFactoryPanel(name); + llassert(panelp); + + if (!panelp) + { + return NULL; // :( + } + } + + } + // factory panels may have registered their own factory maps + if (!panelp->getFactoryMap().empty()) + { + sFactoryStack.push_back(&panelp->getFactoryMap()); + } + // for local registry callbacks; define in constructor, referenced in XUI or postBuild + panelp->mCommitCallbackRegistrar.pushScope(); + panelp->mEnableCallbackRegistrar.pushScope(); + + panelp->initPanelXML(node, parent, output_node, LLUICtrlFactory::getDefaultParams()); + + panelp->mCommitCallbackRegistrar.popScope(); + panelp->mEnableCallbackRegistrar.popScope(); + + if (!panelp->getFactoryMap().empty()) + { + sFactoryStack.pop_back(); + } + + return panelp; +} + +void LLPanel::initFromParams(const LLPanel::Params& p) +{ + //setting these here since panel constructor not called with params + //and LLView::initFromParams will use them to set visible and enabled + setVisible(p.visible); + setEnabled(p.enabled); + setFocusRoot(p.focus_root); + setSoundFlags(p.sound_flags); + + // control_name, tab_stop, focus_lost_callback, initial_value, rect, enabled, visible + LLUICtrl::initFromParams(p); + + // visible callback + if (p.visible_callback.isProvided()) + { + setVisibleCallback(initCommitCallback(p.visible_callback)); + } + + for (LLInitParam::ParamIterator::const_iterator it = p.strings.begin(); + it != p.strings.end(); + ++it) + { + mUIStrings[it->name] = it->value; + } + + setLabel(p.label()); + setHelpTopic(p.help_topic); + setShape(p.rect); + parseFollowsFlags(p); + + setToolTip(p.tool_tip()); + setFromXUI(p.from_xui); + + mHoverCursor = getCursorFromString(p.hover_cursor); + + if (p.has_border) + { + addBorder(p.border); + } + // let constructors set this value if not provided + if (p.use_bounding_rect.isProvided()) + { + setUseBoundingRect(p.use_bounding_rect); + } + setDefaultTabGroup(p.default_tab_group); + setMouseOpaque(p.mouse_opaque); + + setBackgroundVisible(p.background_visible); + setBackgroundOpaque(p.background_opaque); + setBackgroundColor(p.bg_opaque_color().get()); + setTransparentColor(p.bg_alpha_color().get()); + mBgOpaqueImage = p.bg_opaque_image(); + mBgAlphaImage = p.bg_alpha_image(); + mBgOpaqueImageOverlay = p.bg_opaque_image_overlay; + mBgAlphaImageOverlay = p.bg_alpha_image_overlay; + + setAcceptsBadge(p.accepts_badge); +} + +static LLTrace::BlockTimerStatHandle FTM_PANEL_SETUP("Panel Setup"); +static LLTrace::BlockTimerStatHandle FTM_EXTERNAL_PANEL_LOAD("Load Extern Panel Reference"); +static LLTrace::BlockTimerStatHandle FTM_PANEL_POSTBUILD("Panel PostBuild"); + +bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node, const LLPanel::Params& default_params) +{ + Params params(default_params); + { + LL_RECORD_BLOCK_TIME(FTM_PANEL_SETUP); + + LLXMLNodePtr referenced_xml; + std::string xml_filename = mXMLFilename; + + // if the panel didn't provide a filename, check the node + if (xml_filename.empty()) + { + node->getAttributeString("filename", xml_filename); + setXMLFilename(xml_filename); + } + + LLXUIParser parser; + + if (!xml_filename.empty()) + { + if (output_node) + { + //if we are exporting, we want to export the current xml + //not the referenced xml + parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + Params output_params(params); + setupParamsForExport(output_params, parent); + output_node->setName(node->getName()->mString); + parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); + return true; + } + + LLUICtrlFactory::instance().pushFileName(xml_filename); + + LL_RECORD_BLOCK_TIME(FTM_EXTERNAL_PANEL_LOAD); + if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml)) + { + LL_WARNS() << "Couldn't parse panel from: " << xml_filename << LL_ENDL; + + return false; + } + + parser.readXUI(referenced_xml, params, LLUICtrlFactory::getInstance()->getCurFileName()); + + // add children using dimensions from referenced xml for consistent layout + setShape(params.rect); + LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance()); + + LLUICtrlFactory::instance().popFileName(); + } + + // ask LLUICtrlFactory for filename, since xml_filename might be empty + parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + + if (output_node) + { + Params output_params(params); + setupParamsForExport(output_params, parent); + output_node->setName(node->getName()->mString); + parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); + } + + params.from_xui = true; + applyXUILayout(params, parent); + { + LL_RECORD_BLOCK_TIME(FTM_PANEL_CONSTRUCTION); + initFromParams(params); + } + + // add children + LLUICtrlFactory::createChildren(this, node, child_registry_t::instance(), output_node); + + // Connect to parent after children are built, because tab containers + // do a reshape() on their child panels, which requires that the children + // be built/added. JC + if (parent) + { + S32 tab_group = params.tab_group.isProvided() ? params.tab_group() : parent->getLastTabGroup(); + parent->addChild(this, tab_group); + } + + { + LL_RECORD_BLOCK_TIME(FTM_PANEL_POSTBUILD); + postBuild(); + } + } + return true; +} + +bool LLPanel::hasString(const std::string& name) +{ + return mUIStrings.find(name) != mUIStrings.end(); +} + +std::string LLPanel::getString(const std::string& name, const LLStringUtil::format_map_t& args) const +{ + ui_string_map_t::const_iterator found_it = mUIStrings.find(name); + if (found_it != mUIStrings.end()) + { + // make a copy as format works in place + LLUIString formatted_string = LLUIString(found_it->second); + formatted_string.setArgList(args); + return formatted_string.getString(); + } + std::string err_str("Failed to find string " + name + " in panel " + getName()); //*TODO: Translate + if(LLUI::getInstance()->mSettingGroups["config"]->getBOOL("QAMode")) + { + LL_ERRS() << err_str << LL_ENDL; + } + else + { + LL_WARNS() << err_str << LL_ENDL; + } + return LLStringUtil::null; +} + +std::string LLPanel::getString(const std::string& name) const +{ + ui_string_map_t::const_iterator found_it = mUIStrings.find(name); + if (found_it != mUIStrings.end()) + { + return found_it->second; + } + std::string err_str("Failed to find string " + name + " in panel " + getName()); //*TODO: Translate + if(LLUI::getInstance()->mSettingGroups["config"]->getBOOL("QAMode")) + { + LL_ERRS() << err_str << LL_ENDL; + } + else + { + LL_WARNS() << err_str << LL_ENDL; + } + return LLStringUtil::null; +} + + +void LLPanel::childSetVisible(const std::string& id, bool visible) +{ + LLView* child = findChild(id); + if (child) + { + child->setVisible(visible); + } +} + +void LLPanel::childSetEnabled(const std::string& id, bool enabled) +{ + LLView* child = findChild(id); + if (child) + { + child->setEnabled(enabled); + } +} + +void LLPanel::childSetFocus(const std::string& id, bool focus) +{ + LLUICtrl* child = findChild(id); + if (child) + { + child->setFocus(focus); + } +} + +bool LLPanel::childHasFocus(const std::string& id) +{ + LLUICtrl* child = findChild(id); + if (child) + { + return child->hasFocus(); + } + else + { + return false; + } +} + +// *TODO: Deprecate; for backwards compatability only: +// Prefer getChild("foo")->setCommitCallback(boost:bind(...)), +// which takes a generic slot. Or use mCommitCallbackRegistrar.add() with +// a named callback and reference it in XML. +void LLPanel::childSetCommitCallback(const std::string& id, boost::function cb, void* data) +{ + LLUICtrl* child = findChild(id); + if (child) + { + child->setCommitCallback(boost::bind(cb, child, data)); + } +} + +void LLPanel::childSetColor(const std::string& id, const LLColor4& color) +{ + LLUICtrl* child = findChild(id); + if (child) + { + child->setColor(color); + } +} + +LLCtrlSelectionInterface* LLPanel::childGetSelectionInterface(const std::string& id) const +{ + LLUICtrl* child = findChild(id); + if (child) + { + return child->getSelectionInterface(); + } + return NULL; +} + +LLCtrlListInterface* LLPanel::childGetListInterface(const std::string& id) const +{ + LLUICtrl* child = findChild(id); + if (child) + { + return child->getListInterface(); + } + return NULL; +} + +LLCtrlScrollInterface* LLPanel::childGetScrollInterface(const std::string& id) const +{ + LLUICtrl* child = findChild(id); + if (child) + { + return child->getScrollInterface(); + } + return NULL; +} + +void LLPanel::childSetValue(const std::string& id, LLSD value) +{ + LLUICtrl* child = findChild(id); + if (child) + { + child->setValue(value); + } +} + +LLSD LLPanel::childGetValue(const std::string& id) const +{ + LLUICtrl* child = findChild(id); + if (child) + { + return child->getValue(); + } + // Not found => return undefined + return LLSD(); +} + +bool LLPanel::childSetTextArg(const std::string& id, const std::string& key, const LLStringExplicit& text) +{ + LLUICtrl* child = findChild(id); + if (child) + { + return child->setTextArg(key, text); + } + return false; +} + +bool LLPanel::childSetLabelArg(const std::string& id, const std::string& key, const LLStringExplicit& text) +{ + LLView* child = findChild(id); + if (child) + { + return child->setLabelArg(key, text); + } + return false; +} + +void LLPanel::childSetAction(const std::string& id, const commit_signal_t::slot_type& function) +{ + LLButton* button = findChild(id); + if (button) + { + button->setClickedCallback(function); + } +} + +void LLPanel::childSetAction(const std::string& id, boost::function function, void* value) +{ + LLButton* button = findChild(id); + if (button) + { + button->setClickedCallback(boost::bind(function, value)); + } +} + +boost::signals2::connection LLPanel::setVisibleCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mVisibleSignal) + { + mVisibleSignal = new commit_signal_t(); + } + + return mVisibleSignal->connect(cb); +} + +//----------------------------------------------------------------------------- +// buildPanel() +//----------------------------------------------------------------------------- +bool LLPanel::buildFromFile(const std::string& filename, const LLPanel::Params& default_params) +{ + LL_PROFILE_ZONE_SCOPED; + bool didPost = false; + LLXMLNodePtr root; + + if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + { + LL_WARNS() << "Couldn't parse panel from: " << filename << LL_ENDL; + return didPost; + } + + // root must be called panel + if( !root->hasName("panel" ) ) + { + LL_WARNS() << "Root node should be named panel in : " << filename << LL_ENDL; + return didPost; + } + + LL_DEBUGS() << "Building panel " << filename << LL_ENDL; + + LLUICtrlFactory::instance().pushFileName(filename); + { + if (!getFactoryMap().empty()) + { + sFactoryStack.push_back(&getFactoryMap()); + } + + // for local registry callbacks; define in constructor, referenced in XUI or postBuild + getCommitCallbackRegistrar().pushScope(); + getEnableCallbackRegistrar().pushScope(); + + didPost = initPanelXML(root, NULL, NULL, default_params); + + getCommitCallbackRegistrar().popScope(); + getEnableCallbackRegistrar().popScope(); + + setXMLFilename(filename); + + if (!getFactoryMap().empty()) + { + sFactoryStack.pop_back(); + } + } + LLUICtrlFactory::instance().popFileName(); + return didPost; +} + +//----------------------------------------------------------------------------- +// createFactoryPanel() +//----------------------------------------------------------------------------- +LLPanel* LLPanel::createFactoryPanel(const std::string& name) +{ + std::deque::iterator itor; + for (itor = sFactoryStack.begin(); itor != sFactoryStack.end(); ++itor) + { + const LLCallbackMap::map_t* factory_map = *itor; + + // Look up this panel's name in the map. + LLCallbackMap::map_const_iter_t iter = factory_map->find( name ); + if (iter != factory_map->end()) + { + // Use the factory to create the panel, instead of using a default LLPanel. + LLPanel *ret = (LLPanel*) iter->second.mCallback( iter->second.mData ); + return ret; + } + } + LLPanel::Params panel_p; + return LLUICtrlFactory::create(panel_p); +} diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index 1088af0e04..2be5573faf 100644 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -1,318 +1,318 @@ -/** - * @file llpanel.h - * @author James Cook, Tom Yedwab - * @brief LLPanel base 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$ - */ - -#ifndef LL_LLPANEL_H -#define LL_LLPANEL_H - - -#include "llcallbackmap.h" -#include "lluictrl.h" -#include "llviewborder.h" -#include "lluiimage.h" -#include "lluistring.h" -#include "v4color.h" -#include "llbadgeholder.h" -#include -#include - -const S32 LLPANEL_BORDER_WIDTH = 1; -const bool BORDER_YES = true; -const bool BORDER_NO = false; - -class LLButton; -class LLUIImage; - -/* - * General purpose concrete view base class. - * Transparent or opaque, - * With or without border, - * Can contain LLUICtrls. - */ -class LLPanel : public LLUICtrl, public LLBadgeHolder -{ -public: - struct LocalizedString : public LLInitParam::Block - { - Mandatory name; - Mandatory value; - - LocalizedString(); - }; - - struct Params - : public LLInitParam::Block - { - Optional has_border; - Optional border; - - Optional background_visible, - background_opaque; - - Optional bg_opaque_color, - bg_alpha_color, - bg_opaque_image_overlay, - bg_alpha_image_overlay; - // opaque image is for "panel in foreground" look - Optional bg_opaque_image, - bg_alpha_image; - - Optional min_width, - min_height; - - Optional filename; - Optional class_name; - Optional help_topic; - - Multiple strings; - - Optional visible_callback; - - Optional accepts_badge; - - Params(); - }; - -protected: - friend class LLUICtrlFactory; - // RN: for some reason you can't just use LLUICtrlFactory::getDefaultParams as a default argument in VC8 - static const LLPanel::Params& getDefaultParams(); - - // Panels can get constructed directly - LLPanel(const LLPanel::Params& params = getDefaultParams()); - -public: - typedef std::vector ctrl_list_t; - - bool buildFromFile(const std::string &filename, const LLPanel::Params& default_params = getDefaultParams()); - - static LLPanel* createFactoryPanel(const std::string& name); - - /*virtual*/ ~LLPanel(); - - // LLView interface - /*virtual*/ bool isPanel() const; - /*virtual*/ void draw(); - /*virtual*/ bool handleKeyHere( KEY key, MASK mask ); - /*virtual*/ void onVisibilityChange ( bool new_visibility ); - - // From LLFocusableElement - /*virtual*/ void setFocus( bool b ); - - // New virtuals - virtual void refresh(); // called in setFocus() - virtual void clearCtrls(); // overridden in LLPanelObject and LLPanelVolume - - // Border controls - const LLViewBorder* getBorder() const { return mBorder; } - void addBorder( LLViewBorder::Params p); - void addBorder(); - void removeBorder(); - bool hasBorder() const { return mBorder != NULL; } - void setBorderVisible( bool b ); - - void setBackgroundColor( const LLColor4& color ) { mBgOpaqueColor = color; } - const LLColor4& getBackgroundColor() const { return mBgOpaqueColor; } - void setTransparentColor(const LLColor4& color) { mBgAlphaColor = color; } - const LLColor4& getTransparentColor() const { return mBgAlphaColor; } - void setBackgroundImage(LLUIImage* image) { mBgOpaqueImage = image; } - void setTransparentImage(LLUIImage* image) { mBgAlphaImage = image; } - LLPointer getBackgroundImage() const { return mBgOpaqueImage; } - LLPointer getTransparentImage() const { return mBgAlphaImage; } - LLColor4 getBackgroundImageOverlay() { return mBgOpaqueImageOverlay; } - LLColor4 getTransparentImageOverlay() { return mBgAlphaImageOverlay; } - void setBackgroundVisible( bool b ) { mBgVisible = b; } - bool isBackgroundVisible() const { return mBgVisible; } - void setBackgroundOpaque(bool b) { mBgOpaque = b; } - bool isBackgroundOpaque() const { return mBgOpaque; } - void setDefaultBtn(LLButton* btn = NULL); - void setDefaultBtn(const std::string& id); - void updateDefaultBtn(); - void setLabel(const LLStringExplicit& label) { mLabel = label; } - std::string getLabel() const { return mLabel; } - void setHelpTopic(const std::string& help_topic) { mHelpTopic = help_topic; } - std::string getHelpTopic() const { return mHelpTopic; } - - void setCtrlsEnabled(bool b); - ctrl_list_t getCtrlList() const; - - LLHandle getHandle() const { return getDerivedHandle(); } - - const LLCallbackMap::map_t& getFactoryMap() const { return mFactoryMap; } - - CommitCallbackRegistry::ScopedRegistrar& getCommitCallbackRegistrar() { return mCommitCallbackRegistrar; } - EnableCallbackRegistry::ScopedRegistrar& getEnableCallbackRegistrar() { return mEnableCallbackRegistrar; } - - void initFromParams(const Params& p); - bool initPanelXML( LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node, const LLPanel::Params& default_params); - - bool hasString(const std::string& name); - std::string getString(const std::string& name, const LLStringUtil::format_map_t& args) const; - std::string getString(const std::string& name) const; - - // ** Wrappers for setting child properties by name ** -TomY - // WARNING: These are deprecated, please use getChild("name")->doStuff() idiom instead - - // LLView - void childSetVisible(const std::string& name, bool visible); - - void childSetEnabled(const std::string& name, bool enabled); - void childEnable(const std::string& name) { childSetEnabled(name, true); } - void childDisable(const std::string& name) { childSetEnabled(name, false); }; - - // LLUICtrl - void childSetFocus(const std::string& id, bool focus = true); - bool childHasFocus(const std::string& id); - - // *TODO: Deprecate; for backwards compatability only: - // Prefer getChild("foo")->setCommitCallback(boost:bind(...)), - // which takes a generic slot. Or use mCommitCallbackRegistrar.add() with - // a named callback and reference it in XML. - void childSetCommitCallback(const std::string& id, boost::function cb, void* data); - void childSetColor(const std::string& id, const LLColor4& color); - - LLCtrlSelectionInterface* childGetSelectionInterface(const std::string& id) const; - LLCtrlListInterface* childGetListInterface(const std::string& id) const; - LLCtrlScrollInterface* childGetScrollInterface(const std::string& id) const; - - // This is the magic bullet for data-driven UI - void childSetValue(const std::string& id, LLSD value); - LLSD childGetValue(const std::string& id) const; - - // For setting text / label replacement params, e.g. "Hello [NAME]" - // Not implemented for all types, defaults to noop, returns false if not applicaple - bool childSetTextArg(const std::string& id, const std::string& key, const LLStringExplicit& text); - bool childSetLabelArg(const std::string& id, const std::string& key, const LLStringExplicit& text); - - // LLButton - void childSetAction(const std::string& id, boost::function function, void* value); - void childSetAction(const std::string& id, const commit_signal_t::slot_type& function); - - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); - - //call onOpen to let panel know when it's about to be shown or activated - virtual void onOpen(const LLSD& key) {} - - void setXMLFilename(std::string filename) { mXMLFilename = filename; }; - std::string getXMLFilename() { return mXMLFilename; }; - - boost::signals2::connection setVisibleCallback( const commit_signal_t::slot_type& cb ); - -protected: - // Override to set not found list - LLButton* getDefaultButton() { return mDefaultBtn; } - LLCallbackMap::map_t mFactoryMap; - CommitCallbackRegistry::ScopedRegistrar mCommitCallbackRegistrar; - EnableCallbackRegistry::ScopedRegistrar mEnableCallbackRegistrar; - - commit_signal_t* mVisibleSignal; // Called when visibility changes, passes new visibility as LLSD() - - std::string mHelpTopic; // the name of this panel's help topic to display in the Help Viewer - typedef std::deque factory_stack_t; - static factory_stack_t sFactoryStack; - - // for setting the xml filename when building panel in context dependent cases - std::string mXMLFilename; - -private: - bool mBgVisible; // any background at all? - bool mBgOpaque; // use opaque color or image - LLUIColor mBgOpaqueColor; - LLUIColor mBgAlphaColor; - LLUIColor mBgOpaqueImageOverlay; - LLUIColor mBgAlphaImageOverlay; - LLPointer mBgOpaqueImage; // "panel in front" look - LLPointer mBgAlphaImage; // "panel in back" look - LLViewBorder* mBorder; - LLButton* mDefaultBtn; - LLUIString mLabel; - - typedef std::map ui_string_map_t; - ui_string_map_t mUIStrings; - - -}; // end class LLPanel - -// Build time optimization, generate once in .cpp file -#ifndef LLPANEL_CPP -extern template class LLPanel* LLView::getChild( - const std::string& name, bool recurse) const; -#endif - -typedef boost::function LLPanelClassCreatorFunc; - -// local static instance for registering a particular panel class - -class LLRegisterPanelClass -: public LLSingleton< LLRegisterPanelClass > -{ - LLSINGLETON_EMPTY_CTOR(LLRegisterPanelClass); -public: - // register with either the provided builder, or the generic templated builder - void addPanelClass(const std::string& tag,LLPanelClassCreatorFunc func) - { - mPanelClassesNames[tag] = func; - } - - LLPanel* createPanelClass(const std::string& tag) - { - param_name_map_t::iterator iT = mPanelClassesNames.find(tag); - if(iT == mPanelClassesNames.end()) - return 0; - return iT->second(); - } - template - static T* defaultPanelClassBuilder() - { - T* pT = new T(); - return pT; - } - -private: - typedef std::map< std::string, LLPanelClassCreatorFunc> param_name_map_t; - - param_name_map_t mPanelClassesNames; -}; - - -// local static instance for registering a particular panel class -template - class LLPanelInjector -{ -public: - // register with either the provided builder, or the generic templated builder - LLPanelInjector(const std::string& tag); -}; - - -template - LLPanelInjector::LLPanelInjector(const std::string& tag) -{ - LLRegisterPanelClass::instance().addPanelClass(tag,&LLRegisterPanelClass::defaultPanelClassBuilder); -} - - -#endif +/** + * @file llpanel.h + * @author James Cook, Tom Yedwab + * @brief LLPanel base 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$ + */ + +#ifndef LL_LLPANEL_H +#define LL_LLPANEL_H + + +#include "llcallbackmap.h" +#include "lluictrl.h" +#include "llviewborder.h" +#include "lluiimage.h" +#include "lluistring.h" +#include "v4color.h" +#include "llbadgeholder.h" +#include +#include + +const S32 LLPANEL_BORDER_WIDTH = 1; +const bool BORDER_YES = true; +const bool BORDER_NO = false; + +class LLButton; +class LLUIImage; + +/* + * General purpose concrete view base class. + * Transparent or opaque, + * With or without border, + * Can contain LLUICtrls. + */ +class LLPanel : public LLUICtrl, public LLBadgeHolder +{ +public: + struct LocalizedString : public LLInitParam::Block + { + Mandatory name; + Mandatory value; + + LocalizedString(); + }; + + struct Params + : public LLInitParam::Block + { + Optional has_border; + Optional border; + + Optional background_visible, + background_opaque; + + Optional bg_opaque_color, + bg_alpha_color, + bg_opaque_image_overlay, + bg_alpha_image_overlay; + // opaque image is for "panel in foreground" look + Optional bg_opaque_image, + bg_alpha_image; + + Optional min_width, + min_height; + + Optional filename; + Optional class_name; + Optional help_topic; + + Multiple strings; + + Optional visible_callback; + + Optional accepts_badge; + + Params(); + }; + +protected: + friend class LLUICtrlFactory; + // RN: for some reason you can't just use LLUICtrlFactory::getDefaultParams as a default argument in VC8 + static const LLPanel::Params& getDefaultParams(); + + // Panels can get constructed directly + LLPanel(const LLPanel::Params& params = getDefaultParams()); + +public: + typedef std::vector ctrl_list_t; + + bool buildFromFile(const std::string &filename, const LLPanel::Params& default_params = getDefaultParams()); + + static LLPanel* createFactoryPanel(const std::string& name); + + /*virtual*/ ~LLPanel(); + + // LLView interface + /*virtual*/ bool isPanel() const; + /*virtual*/ void draw(); + /*virtual*/ bool handleKeyHere( KEY key, MASK mask ); + /*virtual*/ void onVisibilityChange ( bool new_visibility ); + + // From LLFocusableElement + /*virtual*/ void setFocus( bool b ); + + // New virtuals + virtual void refresh(); // called in setFocus() + virtual void clearCtrls(); // overridden in LLPanelObject and LLPanelVolume + + // Border controls + const LLViewBorder* getBorder() const { return mBorder; } + void addBorder( LLViewBorder::Params p); + void addBorder(); + void removeBorder(); + bool hasBorder() const { return mBorder != NULL; } + void setBorderVisible( bool b ); + + void setBackgroundColor( const LLColor4& color ) { mBgOpaqueColor = color; } + const LLColor4& getBackgroundColor() const { return mBgOpaqueColor; } + void setTransparentColor(const LLColor4& color) { mBgAlphaColor = color; } + const LLColor4& getTransparentColor() const { return mBgAlphaColor; } + void setBackgroundImage(LLUIImage* image) { mBgOpaqueImage = image; } + void setTransparentImage(LLUIImage* image) { mBgAlphaImage = image; } + LLPointer getBackgroundImage() const { return mBgOpaqueImage; } + LLPointer getTransparentImage() const { return mBgAlphaImage; } + LLColor4 getBackgroundImageOverlay() { return mBgOpaqueImageOverlay; } + LLColor4 getTransparentImageOverlay() { return mBgAlphaImageOverlay; } + void setBackgroundVisible( bool b ) { mBgVisible = b; } + bool isBackgroundVisible() const { return mBgVisible; } + void setBackgroundOpaque(bool b) { mBgOpaque = b; } + bool isBackgroundOpaque() const { return mBgOpaque; } + void setDefaultBtn(LLButton* btn = NULL); + void setDefaultBtn(const std::string& id); + void updateDefaultBtn(); + void setLabel(const LLStringExplicit& label) { mLabel = label; } + std::string getLabel() const { return mLabel; } + void setHelpTopic(const std::string& help_topic) { mHelpTopic = help_topic; } + std::string getHelpTopic() const { return mHelpTopic; } + + void setCtrlsEnabled(bool b); + ctrl_list_t getCtrlList() const; + + LLHandle getHandle() const { return getDerivedHandle(); } + + const LLCallbackMap::map_t& getFactoryMap() const { return mFactoryMap; } + + CommitCallbackRegistry::ScopedRegistrar& getCommitCallbackRegistrar() { return mCommitCallbackRegistrar; } + EnableCallbackRegistry::ScopedRegistrar& getEnableCallbackRegistrar() { return mEnableCallbackRegistrar; } + + void initFromParams(const Params& p); + bool initPanelXML( LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node, const LLPanel::Params& default_params); + + bool hasString(const std::string& name); + std::string getString(const std::string& name, const LLStringUtil::format_map_t& args) const; + std::string getString(const std::string& name) const; + + // ** Wrappers for setting child properties by name ** -TomY + // WARNING: These are deprecated, please use getChild("name")->doStuff() idiom instead + + // LLView + void childSetVisible(const std::string& name, bool visible); + + void childSetEnabled(const std::string& name, bool enabled); + void childEnable(const std::string& name) { childSetEnabled(name, true); } + void childDisable(const std::string& name) { childSetEnabled(name, false); }; + + // LLUICtrl + void childSetFocus(const std::string& id, bool focus = true); + bool childHasFocus(const std::string& id); + + // *TODO: Deprecate; for backwards compatability only: + // Prefer getChild("foo")->setCommitCallback(boost:bind(...)), + // which takes a generic slot. Or use mCommitCallbackRegistrar.add() with + // a named callback and reference it in XML. + void childSetCommitCallback(const std::string& id, boost::function cb, void* data); + void childSetColor(const std::string& id, const LLColor4& color); + + LLCtrlSelectionInterface* childGetSelectionInterface(const std::string& id) const; + LLCtrlListInterface* childGetListInterface(const std::string& id) const; + LLCtrlScrollInterface* childGetScrollInterface(const std::string& id) const; + + // This is the magic bullet for data-driven UI + void childSetValue(const std::string& id, LLSD value); + LLSD childGetValue(const std::string& id) const; + + // For setting text / label replacement params, e.g. "Hello [NAME]" + // Not implemented for all types, defaults to noop, returns false if not applicaple + bool childSetTextArg(const std::string& id, const std::string& key, const LLStringExplicit& text); + bool childSetLabelArg(const std::string& id, const std::string& key, const LLStringExplicit& text); + + // LLButton + void childSetAction(const std::string& id, boost::function function, void* value); + void childSetAction(const std::string& id, const commit_signal_t::slot_type& function); + + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); + + //call onOpen to let panel know when it's about to be shown or activated + virtual void onOpen(const LLSD& key) {} + + void setXMLFilename(std::string filename) { mXMLFilename = filename; }; + std::string getXMLFilename() { return mXMLFilename; }; + + boost::signals2::connection setVisibleCallback( const commit_signal_t::slot_type& cb ); + +protected: + // Override to set not found list + LLButton* getDefaultButton() { return mDefaultBtn; } + LLCallbackMap::map_t mFactoryMap; + CommitCallbackRegistry::ScopedRegistrar mCommitCallbackRegistrar; + EnableCallbackRegistry::ScopedRegistrar mEnableCallbackRegistrar; + + commit_signal_t* mVisibleSignal; // Called when visibility changes, passes new visibility as LLSD() + + std::string mHelpTopic; // the name of this panel's help topic to display in the Help Viewer + typedef std::deque factory_stack_t; + static factory_stack_t sFactoryStack; + + // for setting the xml filename when building panel in context dependent cases + std::string mXMLFilename; + +private: + bool mBgVisible; // any background at all? + bool mBgOpaque; // use opaque color or image + LLUIColor mBgOpaqueColor; + LLUIColor mBgAlphaColor; + LLUIColor mBgOpaqueImageOverlay; + LLUIColor mBgAlphaImageOverlay; + LLPointer mBgOpaqueImage; // "panel in front" look + LLPointer mBgAlphaImage; // "panel in back" look + LLViewBorder* mBorder; + LLButton* mDefaultBtn; + LLUIString mLabel; + + typedef std::map ui_string_map_t; + ui_string_map_t mUIStrings; + + +}; // end class LLPanel + +// Build time optimization, generate once in .cpp file +#ifndef LLPANEL_CPP +extern template class LLPanel* LLView::getChild( + const std::string& name, bool recurse) const; +#endif + +typedef boost::function LLPanelClassCreatorFunc; + +// local static instance for registering a particular panel class + +class LLRegisterPanelClass +: public LLSingleton< LLRegisterPanelClass > +{ + LLSINGLETON_EMPTY_CTOR(LLRegisterPanelClass); +public: + // register with either the provided builder, or the generic templated builder + void addPanelClass(const std::string& tag,LLPanelClassCreatorFunc func) + { + mPanelClassesNames[tag] = func; + } + + LLPanel* createPanelClass(const std::string& tag) + { + param_name_map_t::iterator iT = mPanelClassesNames.find(tag); + if(iT == mPanelClassesNames.end()) + return 0; + return iT->second(); + } + template + static T* defaultPanelClassBuilder() + { + T* pT = new T(); + return pT; + } + +private: + typedef std::map< std::string, LLPanelClassCreatorFunc> param_name_map_t; + + param_name_map_t mPanelClassesNames; +}; + + +// local static instance for registering a particular panel class +template + class LLPanelInjector +{ +public: + // register with either the provided builder, or the generic templated builder + LLPanelInjector(const std::string& tag); +}; + + +template + LLPanelInjector::LLPanelInjector(const std::string& tag) +{ + LLRegisterPanelClass::instance().addPanelClass(tag,&LLRegisterPanelClass::defaultPanelClassBuilder); +} + + +#endif diff --git a/indra/llui/llradiogroup.cpp b/indra/llui/llradiogroup.cpp index e542dac019..2aff434612 100644 --- a/indra/llui/llradiogroup.cpp +++ b/indra/llui/llradiogroup.cpp @@ -1,521 +1,521 @@ -/** - * @file llradiogroup.cpp - * @brief LLRadioGroup base 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 "llboost.h" - -#include "llradiogroup.h" -#include "indra_constants.h" - -#include "llviewborder.h" -#include "llcontrol.h" -#include "llui.h" -#include "llfocusmgr.h" -#include "lluictrlfactory.h" -#include "llsdutil.h" - -static LLDefaultChildRegistry::Register r1("radio_group"); - -/* - * An invisible view containing multiple mutually exclusive toggling - * buttons (usually radio buttons). Automatically handles the mutex - * condition by highlighting only one button at a time. - */ -class LLRadioCtrl : public LLCheckBoxCtrl -{ -public: - typedef LLRadioGroup::ItemParams Params; - /*virtual*/ ~LLRadioCtrl(); - /*virtual*/ void setValue(const LLSD& value); - - /*virtual*/ bool postBuild(); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - - LLSD getPayload() { return mPayload; } - - // Ensure label is in an attribute, not the contents - static void setupParamsForExport(Params& p, LLView* parent); - -protected: - LLRadioCtrl(const LLRadioGroup::ItemParams& p); - friend class LLUICtrlFactory; - - LLSD mPayload; // stores data that this item represents in the radio group -}; -static LLWidgetNameRegistry::StaticRegistrar register_radio_item(&typeid(LLRadioGroup::ItemParams), "radio_item"); - -LLRadioGroup::Params::Params() -: allow_deselect("allow_deselect"), - items("item") -{ - addSynonym(items, "radio_item"); - - // radio items are not tabbable until they are selected - tab_stop = false; -} - -LLRadioGroup::LLRadioGroup(const LLRadioGroup::Params& p) -: LLUICtrl(p), - mFont(p.font.isProvided() ? p.font() : LLFontGL::getFontSansSerifSmall()), - mSelectedIndex(-1), - mAllowDeselect(p.allow_deselect) -{} - -void LLRadioGroup::initFromParams(const Params& p) -{ - for (LLInitParam::ParamIterator::const_iterator it = p.items.begin(); - it != p.items.end(); - ++it) - { - LLRadioGroup::ItemParams item_params(*it); - - if (!item_params.font.isProvided()) - { - item_params.font = mFont; // apply radio group font by default - } - item_params.commit_callback.function = boost::bind(&LLRadioGroup::onClickButton, this, _1); - item_params.from_xui = p.from_xui; - if (p.from_xui) - { - applyXUILayout(item_params, this); - } - - LLRadioCtrl* item = LLUICtrlFactory::create(item_params, this); - mRadioButtons.push_back(item); - } - - // call this *after* setting up mRadioButtons so we can handle setValue() calls - LLUICtrl::initFromParams(p); -} - - -LLRadioGroup::~LLRadioGroup() -{ -} - -// virtual -bool LLRadioGroup::postBuild() -{ - if (!mRadioButtons.empty()) - { - mRadioButtons[0]->setTabStop(true); - } - return true; -} - -void LLRadioGroup::setIndexEnabled(S32 index, bool enabled) -{ - S32 count = 0; - for (button_list_t::iterator iter = mRadioButtons.begin(); - iter != mRadioButtons.end(); ++iter) - { - LLRadioCtrl* child = *iter; - if (count == index) - { - child->setEnabled(enabled); - if (index == mSelectedIndex && !enabled) - { - setSelectedIndex(-1); - } - break; - } - count++; - } - count = 0; - if (mSelectedIndex < 0) - { - // Set to highest enabled value < index, - // or lowest value above index if none lower are enabled - // or 0 if none are enabled - for (button_list_t::iterator iter = mRadioButtons.begin(); - iter != mRadioButtons.end(); ++iter) - { - LLRadioCtrl* child = *iter; - if (count >= index && mSelectedIndex >= 0) - { - break; - } - if (child->getEnabled()) - { - setSelectedIndex(count); - } - count++; - } - if (mSelectedIndex < 0) - { - setSelectedIndex(0); - } - } -} - -bool LLRadioGroup::setSelectedIndex(S32 index, bool from_event) -{ - if ((S32)mRadioButtons.size() <= index ) - { - return false; - } - - if (index < -1) - { - // less then minimum value - return false; - } - - if (index < 0 && mSelectedIndex >= 0 && !mAllowDeselect) - { - // -1 is "nothing selected" - return false; - } - - if (mSelectedIndex >= 0) - { - LLRadioCtrl* old_radio_item = mRadioButtons[mSelectedIndex]; - old_radio_item->setTabStop(false); - old_radio_item->setValue( false ); - } - else - { - mRadioButtons[0]->setTabStop(false); - } - - mSelectedIndex = index; - - if (mSelectedIndex >= 0) - { - LLRadioCtrl* radio_item = mRadioButtons[mSelectedIndex]; - radio_item->setTabStop(true); - radio_item->setValue( true ); - - if (hasFocus()) - { - radio_item->focusFirstItem(false, false); - } - } - - if (!from_event) - { - setControlValue(getValue()); - } - - return true; -} - -void LLRadioGroup::focusSelectedRadioBtn() -{ - if (mSelectedIndex >= 0) - { - LLRadioCtrl* radio_item = mRadioButtons[mSelectedIndex]; - if (radio_item->hasTabStop() && radio_item->getEnabled()) - { - radio_item->focusFirstItem(false, false); - } - } - else if (mRadioButtons[0]->hasTabStop() || hasTabStop()) - { - focusFirstItem(false, false); - } -} - -bool LLRadioGroup::handleKeyHere(KEY key, MASK mask) -{ - bool handled = false; - // do any of the tab buttons have keyboard focus? - if (mask == MASK_NONE) - { - switch(key) - { - case KEY_DOWN: - if (!setSelectedIndex((getSelectedIndex() + 1))) - { - make_ui_sound("UISndInvalidOp"); - } - else - { - onCommit(); - } - handled = true; - break; - case KEY_UP: - if (!setSelectedIndex((getSelectedIndex() - 1))) - { - make_ui_sound("UISndInvalidOp"); - } - else - { - onCommit(); - } - handled = true; - break; - case KEY_LEFT: - if (!setSelectedIndex((getSelectedIndex() - 1))) - { - make_ui_sound("UISndInvalidOp"); - } - else - { - onCommit(); - } - handled = true; - break; - case KEY_RIGHT: - if (!setSelectedIndex((getSelectedIndex() + 1))) - { - make_ui_sound("UISndInvalidOp"); - } - else - { - onCommit(); - } - handled = true; - break; - default: - break; - } - } - return handled; -} - -// Handle one button being clicked. All child buttons must have this -// function as their callback function. - -void LLRadioGroup::onClickButton(LLUICtrl* ctrl) -{ - // LL_INFOS() << "LLRadioGroup::onClickButton" << LL_ENDL; - LLRadioCtrl* clicked_radio = dynamic_cast(ctrl); - if (!clicked_radio) - return; - S32 index = 0; - for (button_list_t::iterator iter = mRadioButtons.begin(); - iter != mRadioButtons.end(); ++iter) - { - LLRadioCtrl* radio = *iter; - if (radio == clicked_radio) - { - if (index == mSelectedIndex && mAllowDeselect) - { - // don't select anything - setSelectedIndex(-1); - } - else - { - setSelectedIndex(index); - } - - // BUG: Calls click callback even if button didn't actually change - onCommit(); - - return; - } - - index++; - } - - LL_WARNS() << "LLRadioGroup::onClickButton - clicked button that isn't a child" << LL_ENDL; -} - -void LLRadioGroup::setValue( const LLSD& value ) -{ - int idx = 0; - for (button_list_t::const_iterator iter = mRadioButtons.begin(); - iter != mRadioButtons.end(); ++iter) - { - LLRadioCtrl* radio = *iter; - if (radio->getPayload().asString() == value.asString()) - { - setSelectedIndex(idx); - idx = -1; - break; - } - ++idx; - } - if (idx != -1) - { - // string not found, try integer - if (value.isInteger()) - { - setSelectedIndex((S32) value.asInteger(), true); - } - else - { - setSelectedIndex(-1, true); - } - } -} - -LLSD LLRadioGroup::getValue() const -{ - int index = getSelectedIndex(); - int idx = 0; - for (button_list_t::const_iterator iter = mRadioButtons.begin(); - iter != mRadioButtons.end(); ++iter) - { - if (idx == index) return LLSD((*iter)->getPayload()); - ++idx; - } - return LLSD(); -} - -// LLCtrlSelectionInterface functions -bool LLRadioGroup::setCurrentByID( const LLUUID& id ) -{ - return false; -} - -LLUUID LLRadioGroup::getCurrentID() const -{ - return LLUUID::null; -} - -bool LLRadioGroup::setSelectedByValue(const LLSD& value, bool selected) -{ - S32 idx = 0; - for (button_list_t::const_iterator iter = mRadioButtons.begin(); - iter != mRadioButtons.end(); ++iter) - { - if((*iter)->getPayload().asString() == value.asString()) - { - setSelectedIndex(idx); - return true; - } - idx++; - } - - return false; -} - -LLSD LLRadioGroup::getSelectedValue() -{ - return getValue(); -} - -bool LLRadioGroup::isSelected(const LLSD& value) const -{ - S32 idx = 0; - for (button_list_t::const_iterator iter = mRadioButtons.begin(); - iter != mRadioButtons.end(); ++iter) - { - if((*iter)->getPayload().asString() == value.asString()) - { - if (idx == mSelectedIndex) - { - return true; - } - } - idx++; - } - return false; -} - -bool LLRadioGroup::operateOnSelection(EOperation op) -{ - return false; -} - -bool LLRadioGroup::operateOnAll(EOperation op) -{ - return false; -} - -LLRadioGroup::ItemParams::ItemParams() -: value("value") -{ - addSynonym(value, "initial_value"); -} - -LLRadioCtrl::LLRadioCtrl(const LLRadioGroup::ItemParams& p) -: LLCheckBoxCtrl(p), - mPayload(p.value) -{ - // use name as default "Value" for backwards compatibility - if (!p.value.isProvided()) - { - mPayload = p.name(); - } -} - -bool LLRadioCtrl::postBuild() -{ - // Old-style radio_item used the text contents to indicate the label, - // but new-style radio_item uses label attribute. - std::string value = getValue().asString(); - if (!value.empty()) - { - setLabel(value); - } - return true; -} - -bool LLRadioCtrl::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // Grab focus preemptively, before button takes mousecapture - if (hasTabStop() && getEnabled()) - { - focusFirstItem(false, false); - } - else - { - // Only currently selected item in group has tab stop as result it is - // unclear how focus should behave on click, just let the group handle - // focus and LLRadioGroup::onClickButton() will set correct state later - // if needed - LLRadioGroup* parent = (LLRadioGroup*)getParent(); - if (parent) - { - parent->focusSelectedRadioBtn(); - } - } - - return LLCheckBoxCtrl::handleMouseDown(x, y, mask); -} - -LLRadioCtrl::~LLRadioCtrl() -{ -} - -void LLRadioCtrl::setValue(const LLSD& value) -{ - LLCheckBoxCtrl::setValue(value); - mButton->setTabStop(value.asBoolean()); -} - -// *TODO: Remove this function after the initial XUI XML re-export pass. -// static -void LLRadioCtrl::setupParamsForExport(Params& p, LLView* parent) -{ - std::string label = p.label; - if (label.empty()) - { - // We don't have a label attribute, so move the text contents - // stored in "value" into the label - std::string initial_value = p.LLUICtrl::Params::initial_value(); - p.label = initial_value; - p.LLUICtrl::Params::initial_value = LLSD(); - } - - LLCheckBoxCtrl::setupParamsForExport(p, parent); -} +/** + * @file llradiogroup.cpp + * @brief LLRadioGroup base 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 "llboost.h" + +#include "llradiogroup.h" +#include "indra_constants.h" + +#include "llviewborder.h" +#include "llcontrol.h" +#include "llui.h" +#include "llfocusmgr.h" +#include "lluictrlfactory.h" +#include "llsdutil.h" + +static LLDefaultChildRegistry::Register r1("radio_group"); + +/* + * An invisible view containing multiple mutually exclusive toggling + * buttons (usually radio buttons). Automatically handles the mutex + * condition by highlighting only one button at a time. + */ +class LLRadioCtrl : public LLCheckBoxCtrl +{ +public: + typedef LLRadioGroup::ItemParams Params; + /*virtual*/ ~LLRadioCtrl(); + /*virtual*/ void setValue(const LLSD& value); + + /*virtual*/ bool postBuild(); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + + LLSD getPayload() { return mPayload; } + + // Ensure label is in an attribute, not the contents + static void setupParamsForExport(Params& p, LLView* parent); + +protected: + LLRadioCtrl(const LLRadioGroup::ItemParams& p); + friend class LLUICtrlFactory; + + LLSD mPayload; // stores data that this item represents in the radio group +}; +static LLWidgetNameRegistry::StaticRegistrar register_radio_item(&typeid(LLRadioGroup::ItemParams), "radio_item"); + +LLRadioGroup::Params::Params() +: allow_deselect("allow_deselect"), + items("item") +{ + addSynonym(items, "radio_item"); + + // radio items are not tabbable until they are selected + tab_stop = false; +} + +LLRadioGroup::LLRadioGroup(const LLRadioGroup::Params& p) +: LLUICtrl(p), + mFont(p.font.isProvided() ? p.font() : LLFontGL::getFontSansSerifSmall()), + mSelectedIndex(-1), + mAllowDeselect(p.allow_deselect) +{} + +void LLRadioGroup::initFromParams(const Params& p) +{ + for (LLInitParam::ParamIterator::const_iterator it = p.items.begin(); + it != p.items.end(); + ++it) + { + LLRadioGroup::ItemParams item_params(*it); + + if (!item_params.font.isProvided()) + { + item_params.font = mFont; // apply radio group font by default + } + item_params.commit_callback.function = boost::bind(&LLRadioGroup::onClickButton, this, _1); + item_params.from_xui = p.from_xui; + if (p.from_xui) + { + applyXUILayout(item_params, this); + } + + LLRadioCtrl* item = LLUICtrlFactory::create(item_params, this); + mRadioButtons.push_back(item); + } + + // call this *after* setting up mRadioButtons so we can handle setValue() calls + LLUICtrl::initFromParams(p); +} + + +LLRadioGroup::~LLRadioGroup() +{ +} + +// virtual +bool LLRadioGroup::postBuild() +{ + if (!mRadioButtons.empty()) + { + mRadioButtons[0]->setTabStop(true); + } + return true; +} + +void LLRadioGroup::setIndexEnabled(S32 index, bool enabled) +{ + S32 count = 0; + for (button_list_t::iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* child = *iter; + if (count == index) + { + child->setEnabled(enabled); + if (index == mSelectedIndex && !enabled) + { + setSelectedIndex(-1); + } + break; + } + count++; + } + count = 0; + if (mSelectedIndex < 0) + { + // Set to highest enabled value < index, + // or lowest value above index if none lower are enabled + // or 0 if none are enabled + for (button_list_t::iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* child = *iter; + if (count >= index && mSelectedIndex >= 0) + { + break; + } + if (child->getEnabled()) + { + setSelectedIndex(count); + } + count++; + } + if (mSelectedIndex < 0) + { + setSelectedIndex(0); + } + } +} + +bool LLRadioGroup::setSelectedIndex(S32 index, bool from_event) +{ + if ((S32)mRadioButtons.size() <= index ) + { + return false; + } + + if (index < -1) + { + // less then minimum value + return false; + } + + if (index < 0 && mSelectedIndex >= 0 && !mAllowDeselect) + { + // -1 is "nothing selected" + return false; + } + + if (mSelectedIndex >= 0) + { + LLRadioCtrl* old_radio_item = mRadioButtons[mSelectedIndex]; + old_radio_item->setTabStop(false); + old_radio_item->setValue( false ); + } + else + { + mRadioButtons[0]->setTabStop(false); + } + + mSelectedIndex = index; + + if (mSelectedIndex >= 0) + { + LLRadioCtrl* radio_item = mRadioButtons[mSelectedIndex]; + radio_item->setTabStop(true); + radio_item->setValue( true ); + + if (hasFocus()) + { + radio_item->focusFirstItem(false, false); + } + } + + if (!from_event) + { + setControlValue(getValue()); + } + + return true; +} + +void LLRadioGroup::focusSelectedRadioBtn() +{ + if (mSelectedIndex >= 0) + { + LLRadioCtrl* radio_item = mRadioButtons[mSelectedIndex]; + if (radio_item->hasTabStop() && radio_item->getEnabled()) + { + radio_item->focusFirstItem(false, false); + } + } + else if (mRadioButtons[0]->hasTabStop() || hasTabStop()) + { + focusFirstItem(false, false); + } +} + +bool LLRadioGroup::handleKeyHere(KEY key, MASK mask) +{ + bool handled = false; + // do any of the tab buttons have keyboard focus? + if (mask == MASK_NONE) + { + switch(key) + { + case KEY_DOWN: + if (!setSelectedIndex((getSelectedIndex() + 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = true; + break; + case KEY_UP: + if (!setSelectedIndex((getSelectedIndex() - 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = true; + break; + case KEY_LEFT: + if (!setSelectedIndex((getSelectedIndex() - 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = true; + break; + case KEY_RIGHT: + if (!setSelectedIndex((getSelectedIndex() + 1))) + { + make_ui_sound("UISndInvalidOp"); + } + else + { + onCommit(); + } + handled = true; + break; + default: + break; + } + } + return handled; +} + +// Handle one button being clicked. All child buttons must have this +// function as their callback function. + +void LLRadioGroup::onClickButton(LLUICtrl* ctrl) +{ + // LL_INFOS() << "LLRadioGroup::onClickButton" << LL_ENDL; + LLRadioCtrl* clicked_radio = dynamic_cast(ctrl); + if (!clicked_radio) + return; + S32 index = 0; + for (button_list_t::iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* radio = *iter; + if (radio == clicked_radio) + { + if (index == mSelectedIndex && mAllowDeselect) + { + // don't select anything + setSelectedIndex(-1); + } + else + { + setSelectedIndex(index); + } + + // BUG: Calls click callback even if button didn't actually change + onCommit(); + + return; + } + + index++; + } + + LL_WARNS() << "LLRadioGroup::onClickButton - clicked button that isn't a child" << LL_ENDL; +} + +void LLRadioGroup::setValue( const LLSD& value ) +{ + int idx = 0; + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + LLRadioCtrl* radio = *iter; + if (radio->getPayload().asString() == value.asString()) + { + setSelectedIndex(idx); + idx = -1; + break; + } + ++idx; + } + if (idx != -1) + { + // string not found, try integer + if (value.isInteger()) + { + setSelectedIndex((S32) value.asInteger(), true); + } + else + { + setSelectedIndex(-1, true); + } + } +} + +LLSD LLRadioGroup::getValue() const +{ + int index = getSelectedIndex(); + int idx = 0; + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + if (idx == index) return LLSD((*iter)->getPayload()); + ++idx; + } + return LLSD(); +} + +// LLCtrlSelectionInterface functions +bool LLRadioGroup::setCurrentByID( const LLUUID& id ) +{ + return false; +} + +LLUUID LLRadioGroup::getCurrentID() const +{ + return LLUUID::null; +} + +bool LLRadioGroup::setSelectedByValue(const LLSD& value, bool selected) +{ + S32 idx = 0; + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + if((*iter)->getPayload().asString() == value.asString()) + { + setSelectedIndex(idx); + return true; + } + idx++; + } + + return false; +} + +LLSD LLRadioGroup::getSelectedValue() +{ + return getValue(); +} + +bool LLRadioGroup::isSelected(const LLSD& value) const +{ + S32 idx = 0; + for (button_list_t::const_iterator iter = mRadioButtons.begin(); + iter != mRadioButtons.end(); ++iter) + { + if((*iter)->getPayload().asString() == value.asString()) + { + if (idx == mSelectedIndex) + { + return true; + } + } + idx++; + } + return false; +} + +bool LLRadioGroup::operateOnSelection(EOperation op) +{ + return false; +} + +bool LLRadioGroup::operateOnAll(EOperation op) +{ + return false; +} + +LLRadioGroup::ItemParams::ItemParams() +: value("value") +{ + addSynonym(value, "initial_value"); +} + +LLRadioCtrl::LLRadioCtrl(const LLRadioGroup::ItemParams& p) +: LLCheckBoxCtrl(p), + mPayload(p.value) +{ + // use name as default "Value" for backwards compatibility + if (!p.value.isProvided()) + { + mPayload = p.name(); + } +} + +bool LLRadioCtrl::postBuild() +{ + // Old-style radio_item used the text contents to indicate the label, + // but new-style radio_item uses label attribute. + std::string value = getValue().asString(); + if (!value.empty()) + { + setLabel(value); + } + return true; +} + +bool LLRadioCtrl::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Grab focus preemptively, before button takes mousecapture + if (hasTabStop() && getEnabled()) + { + focusFirstItem(false, false); + } + else + { + // Only currently selected item in group has tab stop as result it is + // unclear how focus should behave on click, just let the group handle + // focus and LLRadioGroup::onClickButton() will set correct state later + // if needed + LLRadioGroup* parent = (LLRadioGroup*)getParent(); + if (parent) + { + parent->focusSelectedRadioBtn(); + } + } + + return LLCheckBoxCtrl::handleMouseDown(x, y, mask); +} + +LLRadioCtrl::~LLRadioCtrl() +{ +} + +void LLRadioCtrl::setValue(const LLSD& value) +{ + LLCheckBoxCtrl::setValue(value); + mButton->setTabStop(value.asBoolean()); +} + +// *TODO: Remove this function after the initial XUI XML re-export pass. +// static +void LLRadioCtrl::setupParamsForExport(Params& p, LLView* parent) +{ + std::string label = p.label; + if (label.empty()) + { + // We don't have a label attribute, so move the text contents + // stored in "value" into the label + std::string initial_value = p.LLUICtrl::Params::initial_value(); + p.label = initial_value; + p.LLUICtrl::Params::initial_value = LLSD(); + } + + LLCheckBoxCtrl::setupParamsForExport(p, parent); +} diff --git a/indra/llui/llradiogroup.h b/indra/llui/llradiogroup.h index d72fd83668..810830ffa4 100644 --- a/indra/llui/llradiogroup.h +++ b/indra/llui/llradiogroup.h @@ -1,114 +1,114 @@ -/** - * @file llradiogroup.h - * @brief LLRadioGroup base 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$ - */ - -#ifndef LL_LLRADIOGROUP_H -#define LL_LLRADIOGROUP_H - -#include "lluictrl.h" -#include "llcheckboxctrl.h" -#include "llctrlselectioninterface.h" - -/* - * An invisible view containing multiple mutually exclusive toggling - * buttons (usually radio buttons). Automatically handles the mutex - * condition by highlighting only one button at a time. - */ -class LLRadioGroup -: public LLUICtrl, public LLCtrlSelectionInterface -{ -public: - - struct ItemParams : public LLInitParam::Block - { - Optional value; - ItemParams(); - }; - - struct Params : public LLInitParam::Block - { - Optional allow_deselect; - Multiple > items; - Params(); - }; - -protected: - LLRadioGroup(const Params&); - friend class LLUICtrlFactory; - -public: - - /*virtual*/ void initFromParams(const Params&); - - virtual ~LLRadioGroup(); - - virtual bool postBuild(); - - virtual bool handleKeyHere(KEY key, MASK mask); - - void setIndexEnabled(S32 index, bool enabled); - // return the index value of the selected item - S32 getSelectedIndex() const { return mSelectedIndex; } - // set the index value programatically - bool setSelectedIndex(S32 index, bool from_event = false); - // foxus child by index if it can get focus - void focusSelectedRadioBtn(); - - // Accept and retrieve strings of the radio group control names - virtual void setValue(const LLSD& value ); - virtual LLSD getValue() const; - - // Update the control as needed. Userdata must be a pointer to the button. - void onClickButton(LLUICtrl* clicked_radio); - - //======================================================================== - LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; }; - - // LLCtrlSelectionInterface functions - /*virtual*/ S32 getItemCount() const { return mRadioButtons.size(); } - /*virtual*/ bool getCanSelect() const { return true; } - /*virtual*/ bool selectFirstItem() { return setSelectedIndex(0); } - /*virtual*/ bool selectNthItem( S32 index ) { return setSelectedIndex(index); } - /*virtual*/ bool selectItemRange( S32 first, S32 last ) { return setSelectedIndex(first); } - /*virtual*/ S32 getFirstSelectedIndex() const { return getSelectedIndex(); } - /*virtual*/ bool setCurrentByID( const LLUUID& id ); - /*virtual*/ LLUUID getCurrentID() const; // LLUUID::null if no items in menu - /*virtual*/ bool setSelectedByValue(const LLSD& value, bool selected); - /*virtual*/ LLSD getSelectedValue(); - /*virtual*/ bool isSelected(const LLSD& value) const; - /*virtual*/ bool operateOnSelection(EOperation op); - /*virtual*/ bool operateOnAll(EOperation op); - -private: - const LLFontGL* mFont; - S32 mSelectedIndex; - - typedef std::vector button_list_t; - button_list_t mRadioButtons; - - bool mAllowDeselect; // user can click on an already selected option to deselect it -}; - -#endif +/** + * @file llradiogroup.h + * @brief LLRadioGroup base 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$ + */ + +#ifndef LL_LLRADIOGROUP_H +#define LL_LLRADIOGROUP_H + +#include "lluictrl.h" +#include "llcheckboxctrl.h" +#include "llctrlselectioninterface.h" + +/* + * An invisible view containing multiple mutually exclusive toggling + * buttons (usually radio buttons). Automatically handles the mutex + * condition by highlighting only one button at a time. + */ +class LLRadioGroup +: public LLUICtrl, public LLCtrlSelectionInterface +{ +public: + + struct ItemParams : public LLInitParam::Block + { + Optional value; + ItemParams(); + }; + + struct Params : public LLInitParam::Block + { + Optional allow_deselect; + Multiple > items; + Params(); + }; + +protected: + LLRadioGroup(const Params&); + friend class LLUICtrlFactory; + +public: + + /*virtual*/ void initFromParams(const Params&); + + virtual ~LLRadioGroup(); + + virtual bool postBuild(); + + virtual bool handleKeyHere(KEY key, MASK mask); + + void setIndexEnabled(S32 index, bool enabled); + // return the index value of the selected item + S32 getSelectedIndex() const { return mSelectedIndex; } + // set the index value programatically + bool setSelectedIndex(S32 index, bool from_event = false); + // foxus child by index if it can get focus + void focusSelectedRadioBtn(); + + // Accept and retrieve strings of the radio group control names + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + + // Update the control as needed. Userdata must be a pointer to the button. + void onClickButton(LLUICtrl* clicked_radio); + + //======================================================================== + LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; }; + + // LLCtrlSelectionInterface functions + /*virtual*/ S32 getItemCount() const { return mRadioButtons.size(); } + /*virtual*/ bool getCanSelect() const { return true; } + /*virtual*/ bool selectFirstItem() { return setSelectedIndex(0); } + /*virtual*/ bool selectNthItem( S32 index ) { return setSelectedIndex(index); } + /*virtual*/ bool selectItemRange( S32 first, S32 last ) { return setSelectedIndex(first); } + /*virtual*/ S32 getFirstSelectedIndex() const { return getSelectedIndex(); } + /*virtual*/ bool setCurrentByID( const LLUUID& id ); + /*virtual*/ LLUUID getCurrentID() const; // LLUUID::null if no items in menu + /*virtual*/ bool setSelectedByValue(const LLSD& value, bool selected); + /*virtual*/ LLSD getSelectedValue(); + /*virtual*/ bool isSelected(const LLSD& value) const; + /*virtual*/ bool operateOnSelection(EOperation op); + /*virtual*/ bool operateOnAll(EOperation op); + +private: + const LLFontGL* mFont; + S32 mSelectedIndex; + + typedef std::vector button_list_t; + button_list_t mRadioButtons; + + bool mAllowDeselect; // user can click on an already selected option to deselect it +}; + +#endif diff --git a/indra/llui/llresizebar.cpp b/indra/llui/llresizebar.cpp index 85d886a3ed..a022fd2853 100644 --- a/indra/llui/llresizebar.cpp +++ b/indra/llui/llresizebar.cpp @@ -1,376 +1,376 @@ -/** - * @file llresizebar.cpp - * @brief LLResizeBar base 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 "llresizebar.h" - -#include "lllocalcliprect.h" -#include "llmath.h" -#include "llui.h" -#include "llmenugl.h" -#include "llfocusmgr.h" -#include "llwindow.h" - -LLResizeBar::Params::Params() -: max_size("max_size", S32_MAX), - snapping_enabled("snapping_enabled", true), - resizing_view("resizing_view"), - side("side"), - allow_double_click_snapping("allow_double_click_snapping", true) -{ - name = "resize_bar"; -} - -LLResizeBar::LLResizeBar(const LLResizeBar::Params& p) -: LLView(p), - mDragLastScreenX( 0 ), - mDragLastScreenY( 0 ), - mLastMouseScreenX( 0 ), - mLastMouseScreenY( 0 ), - mMinSize( p.min_size ), - mMaxSize( p.max_size ), - mSide( p.side ), - mSnappingEnabled(p.snapping_enabled), - mAllowDoubleClickSnapping(p.allow_double_click_snapping), - mResizingView(p.resizing_view), - mResizeListener(NULL), - mImagePanel(NULL) -{ - setFollowsNone(); - // set up some generically good follow code. - switch( mSide ) - { - case LEFT: - setFollowsLeft(); - setFollowsTop(); - setFollowsBottom(); - break; - case TOP: - setFollowsTop(); - setFollowsLeft(); - setFollowsRight(); - break; - case RIGHT: - setFollowsRight(); - setFollowsTop(); - setFollowsBottom(); - break; - case BOTTOM: - setFollowsBottom(); - setFollowsLeft(); - setFollowsRight(); - break; - default: - break; - } -} - -bool LLResizeBar::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (!canResize()) return false; - - // Route future Mouse messages here preemptively. (Release on mouse up.) - // No handler needed for focus lost since this clas has no state that depends on it. - gFocusMgr.setMouseCapture( this ); - - localPointToScreen(x, y, &mDragLastScreenX, &mDragLastScreenY); - mLastMouseScreenX = mDragLastScreenX; - mLastMouseScreenY = mDragLastScreenY; - - return true; -} - - -bool LLResizeBar::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - if( hasMouseCapture() ) - { - // Release the mouse - gFocusMgr.setMouseCapture( NULL ); - handled = true; - } - else - { - handled = true; - } - return handled; -} - - -bool LLResizeBar::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // We only handle the click if the click both started and ended within us - if( hasMouseCapture() ) - { - S32 screen_x; - S32 screen_y; - localPointToScreen(x, y, &screen_x, &screen_y); - - S32 delta_x = screen_x - mDragLastScreenX; - S32 delta_y = screen_y - mDragLastScreenY; - - LLCoordGL mouse_dir; - // use hysteresis on mouse motion to preserve user intent when mouse stops moving - mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; - mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; - mLastMouseDir = mouse_dir; - mLastMouseScreenX = screen_x; - mLastMouseScreenY = screen_y; - - // Make sure the mouse in still over the application. We don't want to make the parent - // so big that we can't see the resize handle any more. - LLRect valid_rect = getRootView()->getRect(); - - if( valid_rect.localPointInRect( screen_x, screen_y ) && mResizingView ) - { - // Resize the parent - LLRect orig_rect = mResizingView->getRect(); - LLRect scaled_rect = orig_rect; - - S32 new_width = orig_rect.getWidth(); - S32 new_height = orig_rect.getHeight(); - - switch( mSide ) - { - case LEFT: - new_width = llclamp(orig_rect.getWidth() - delta_x, mMinSize, mMaxSize); - delta_x = orig_rect.getWidth() - new_width; - scaled_rect.translate(delta_x, 0); - break; - - case TOP: - new_height = llclamp(orig_rect.getHeight() + delta_y, mMinSize, mMaxSize); - delta_y = new_height - orig_rect.getHeight(); - break; - - case RIGHT: - new_width = llclamp(orig_rect.getWidth() + delta_x, mMinSize, mMaxSize); - delta_x = new_width - orig_rect.getWidth(); - break; - - case BOTTOM: - new_height = llclamp(orig_rect.getHeight() - delta_y, mMinSize, mMaxSize); - delta_y = orig_rect.getHeight() - new_height; - scaled_rect.translate(0, delta_y); - break; - } - - notifyParent(LLSD().with("action", "resize") - .with("view_name", mResizingView->getName()) - .with("new_height", new_height) - .with("new_width", new_width)); - - scaled_rect.mTop = scaled_rect.mBottom + new_height; - scaled_rect.mRight = scaled_rect.mLeft + new_width; - mResizingView->setRect(scaled_rect); - - LLView* snap_view = NULL; - - if (mSnappingEnabled) - { - static LLUICachedControl snap_margin ("SnapMargin", 0); - switch( mSide ) - { - case LEFT: - snap_view = mResizingView->findSnapEdge(scaled_rect.mLeft, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, snap_margin); - break; - case TOP: - snap_view = mResizingView->findSnapEdge(scaled_rect.mTop, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, snap_margin); - break; - case RIGHT: - snap_view = mResizingView->findSnapEdge(scaled_rect.mRight, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, snap_margin); - break; - case BOTTOM: - snap_view = mResizingView->findSnapEdge(scaled_rect.mBottom, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, snap_margin); - break; - } - } - - // register "snap" behavior with snapped view - mResizingView->setSnappedTo(snap_view); - - // restore original rectangle so the appropriate changes are detected - mResizingView->setRect(orig_rect); - // change view shape as user operation - mResizingView->setShape(scaled_rect, true); - - // update last valid mouse cursor position based on resized view's actual size - LLRect new_rect = mResizingView->getRect(); - - switch(mSide) - { - case LEFT: - { - S32 actual_delta_x = new_rect.mLeft - orig_rect.mLeft; - if (actual_delta_x != delta_x) - { - // restore everything by left - new_rect.mBottom = orig_rect.mBottom; - new_rect.mTop = orig_rect.mTop; - new_rect.mRight = orig_rect.mRight; - mResizingView->setShape(new_rect, true); - } - mDragLastScreenX += actual_delta_x; - - break; - } - case RIGHT: - { - S32 actual_delta_x = new_rect.mRight - orig_rect.mRight; - if (actual_delta_x != delta_x) - { - // restore everything by left - new_rect.mBottom = orig_rect.mBottom; - new_rect.mTop = orig_rect.mTop; - new_rect.mLeft = orig_rect.mLeft; - mResizingView->setShape(new_rect, true); - } - mDragLastScreenX += new_rect.mRight - orig_rect.mRight; - break; - } - case TOP: - { - S32 actual_delta_y = new_rect.mTop - orig_rect.mTop; - if (actual_delta_y != delta_y) - { - // restore everything by left - new_rect.mBottom = orig_rect.mBottom; - new_rect.mLeft = orig_rect.mLeft; - new_rect.mRight = orig_rect.mRight; - mResizingView->setShape(new_rect, true); - } - mDragLastScreenY += new_rect.mTop - orig_rect.mTop; - break; - } - case BOTTOM: - { - S32 actual_delta_y = new_rect.mBottom - orig_rect.mBottom; - if (actual_delta_y != delta_y) - { - // restore everything by left - new_rect.mTop = orig_rect.mTop; - new_rect.mLeft = orig_rect.mLeft; - new_rect.mRight = orig_rect.mRight; - mResizingView->setShape(new_rect, true); - } - mDragLastScreenY += new_rect.mBottom- orig_rect.mBottom; - break; - } - default: - break; - } - } - - handled = true; - } - else - { - handled = true; - } - - if( handled && canResize() ) - { - switch( mSide ) - { - case LEFT: - case RIGHT: - getWindow()->setCursor(UI_CURSOR_SIZEWE); - break; - - case TOP: - case BOTTOM: - getWindow()->setCursor(UI_CURSOR_SIZENS); - break; - } - } - - if (mResizeListener) - { - mResizeListener(NULL); - } - - return handled; -} // end LLResizeBar::handleHover - -bool LLResizeBar::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - LLRect orig_rect = mResizingView->getRect(); - LLRect scaled_rect = orig_rect; - - if (mSnappingEnabled && mAllowDoubleClickSnapping) - { - switch( mSide ) - { - case LEFT: - mResizingView->findSnapEdge(scaled_rect.mLeft, LLCoordGL(0, 0), SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, S32_MAX); - scaled_rect.mLeft = scaled_rect.mRight - llclamp(scaled_rect.getWidth(), mMinSize, mMaxSize); - break; - case TOP: - mResizingView->findSnapEdge(scaled_rect.mTop, LLCoordGL(0, 0), SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, S32_MAX); - scaled_rect.mTop = scaled_rect.mBottom + llclamp(scaled_rect.getHeight(), mMinSize, mMaxSize); - break; - case RIGHT: - mResizingView->findSnapEdge(scaled_rect.mRight, LLCoordGL(0, 0), SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, S32_MAX); - scaled_rect.mRight = scaled_rect.mLeft + llclamp(scaled_rect.getWidth(), mMinSize, mMaxSize); - break; - case BOTTOM: - mResizingView->findSnapEdge(scaled_rect.mBottom, LLCoordGL(0, 0), SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, S32_MAX); - scaled_rect.mBottom = scaled_rect.mTop - llclamp(scaled_rect.getHeight(), mMinSize, mMaxSize); - break; - } - - mResizingView->setShape(scaled_rect, true); - } - - return true; -} - -void LLResizeBar::setImagePanel(LLPanel * panelp) -{ - const LLView::child_list_t * children = getChildList(); - if (getChildCount() == 2) - { - LLPanel * image_panelp = dynamic_cast(children->back()); - if (image_panelp) - { - removeChild(image_panelp); - delete image_panelp; - } - } - - addChild(panelp); - sendChildToBack(panelp); -} - -LLPanel * LLResizeBar::getImagePanel() const -{ - return getChildCount() > 0 ? (LLPanel *)getChildList()->back() : NULL; -} +/** + * @file llresizebar.cpp + * @brief LLResizeBar base 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 "llresizebar.h" + +#include "lllocalcliprect.h" +#include "llmath.h" +#include "llui.h" +#include "llmenugl.h" +#include "llfocusmgr.h" +#include "llwindow.h" + +LLResizeBar::Params::Params() +: max_size("max_size", S32_MAX), + snapping_enabled("snapping_enabled", true), + resizing_view("resizing_view"), + side("side"), + allow_double_click_snapping("allow_double_click_snapping", true) +{ + name = "resize_bar"; +} + +LLResizeBar::LLResizeBar(const LLResizeBar::Params& p) +: LLView(p), + mDragLastScreenX( 0 ), + mDragLastScreenY( 0 ), + mLastMouseScreenX( 0 ), + mLastMouseScreenY( 0 ), + mMinSize( p.min_size ), + mMaxSize( p.max_size ), + mSide( p.side ), + mSnappingEnabled(p.snapping_enabled), + mAllowDoubleClickSnapping(p.allow_double_click_snapping), + mResizingView(p.resizing_view), + mResizeListener(NULL), + mImagePanel(NULL) +{ + setFollowsNone(); + // set up some generically good follow code. + switch( mSide ) + { + case LEFT: + setFollowsLeft(); + setFollowsTop(); + setFollowsBottom(); + break; + case TOP: + setFollowsTop(); + setFollowsLeft(); + setFollowsRight(); + break; + case RIGHT: + setFollowsRight(); + setFollowsTop(); + setFollowsBottom(); + break; + case BOTTOM: + setFollowsBottom(); + setFollowsLeft(); + setFollowsRight(); + break; + default: + break; + } +} + +bool LLResizeBar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (!canResize()) return false; + + // Route future Mouse messages here preemptively. (Release on mouse up.) + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture( this ); + + localPointToScreen(x, y, &mDragLastScreenX, &mDragLastScreenY); + mLastMouseScreenX = mDragLastScreenX; + mLastMouseScreenY = mDragLastScreenY; + + return true; +} + + +bool LLResizeBar::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + if( hasMouseCapture() ) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL ); + handled = true; + } + else + { + handled = true; + } + return handled; +} + + +bool LLResizeBar::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // We only handle the click if the click both started and ended within us + if( hasMouseCapture() ) + { + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + + S32 delta_x = screen_x - mDragLastScreenX; + S32 delta_y = screen_y - mDragLastScreenY; + + LLCoordGL mouse_dir; + // use hysteresis on mouse motion to preserve user intent when mouse stops moving + mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; + mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; + mLastMouseDir = mouse_dir; + mLastMouseScreenX = screen_x; + mLastMouseScreenY = screen_y; + + // Make sure the mouse in still over the application. We don't want to make the parent + // so big that we can't see the resize handle any more. + LLRect valid_rect = getRootView()->getRect(); + + if( valid_rect.localPointInRect( screen_x, screen_y ) && mResizingView ) + { + // Resize the parent + LLRect orig_rect = mResizingView->getRect(); + LLRect scaled_rect = orig_rect; + + S32 new_width = orig_rect.getWidth(); + S32 new_height = orig_rect.getHeight(); + + switch( mSide ) + { + case LEFT: + new_width = llclamp(orig_rect.getWidth() - delta_x, mMinSize, mMaxSize); + delta_x = orig_rect.getWidth() - new_width; + scaled_rect.translate(delta_x, 0); + break; + + case TOP: + new_height = llclamp(orig_rect.getHeight() + delta_y, mMinSize, mMaxSize); + delta_y = new_height - orig_rect.getHeight(); + break; + + case RIGHT: + new_width = llclamp(orig_rect.getWidth() + delta_x, mMinSize, mMaxSize); + delta_x = new_width - orig_rect.getWidth(); + break; + + case BOTTOM: + new_height = llclamp(orig_rect.getHeight() - delta_y, mMinSize, mMaxSize); + delta_y = orig_rect.getHeight() - new_height; + scaled_rect.translate(0, delta_y); + break; + } + + notifyParent(LLSD().with("action", "resize") + .with("view_name", mResizingView->getName()) + .with("new_height", new_height) + .with("new_width", new_width)); + + scaled_rect.mTop = scaled_rect.mBottom + new_height; + scaled_rect.mRight = scaled_rect.mLeft + new_width; + mResizingView->setRect(scaled_rect); + + LLView* snap_view = NULL; + + if (mSnappingEnabled) + { + static LLUICachedControl snap_margin ("SnapMargin", 0); + switch( mSide ) + { + case LEFT: + snap_view = mResizingView->findSnapEdge(scaled_rect.mLeft, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, snap_margin); + break; + case TOP: + snap_view = mResizingView->findSnapEdge(scaled_rect.mTop, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, snap_margin); + break; + case RIGHT: + snap_view = mResizingView->findSnapEdge(scaled_rect.mRight, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, snap_margin); + break; + case BOTTOM: + snap_view = mResizingView->findSnapEdge(scaled_rect.mBottom, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, snap_margin); + break; + } + } + + // register "snap" behavior with snapped view + mResizingView->setSnappedTo(snap_view); + + // restore original rectangle so the appropriate changes are detected + mResizingView->setRect(orig_rect); + // change view shape as user operation + mResizingView->setShape(scaled_rect, true); + + // update last valid mouse cursor position based on resized view's actual size + LLRect new_rect = mResizingView->getRect(); + + switch(mSide) + { + case LEFT: + { + S32 actual_delta_x = new_rect.mLeft - orig_rect.mLeft; + if (actual_delta_x != delta_x) + { + // restore everything by left + new_rect.mBottom = orig_rect.mBottom; + new_rect.mTop = orig_rect.mTop; + new_rect.mRight = orig_rect.mRight; + mResizingView->setShape(new_rect, true); + } + mDragLastScreenX += actual_delta_x; + + break; + } + case RIGHT: + { + S32 actual_delta_x = new_rect.mRight - orig_rect.mRight; + if (actual_delta_x != delta_x) + { + // restore everything by left + new_rect.mBottom = orig_rect.mBottom; + new_rect.mTop = orig_rect.mTop; + new_rect.mLeft = orig_rect.mLeft; + mResizingView->setShape(new_rect, true); + } + mDragLastScreenX += new_rect.mRight - orig_rect.mRight; + break; + } + case TOP: + { + S32 actual_delta_y = new_rect.mTop - orig_rect.mTop; + if (actual_delta_y != delta_y) + { + // restore everything by left + new_rect.mBottom = orig_rect.mBottom; + new_rect.mLeft = orig_rect.mLeft; + new_rect.mRight = orig_rect.mRight; + mResizingView->setShape(new_rect, true); + } + mDragLastScreenY += new_rect.mTop - orig_rect.mTop; + break; + } + case BOTTOM: + { + S32 actual_delta_y = new_rect.mBottom - orig_rect.mBottom; + if (actual_delta_y != delta_y) + { + // restore everything by left + new_rect.mTop = orig_rect.mTop; + new_rect.mLeft = orig_rect.mLeft; + new_rect.mRight = orig_rect.mRight; + mResizingView->setShape(new_rect, true); + } + mDragLastScreenY += new_rect.mBottom- orig_rect.mBottom; + break; + } + default: + break; + } + } + + handled = true; + } + else + { + handled = true; + } + + if( handled && canResize() ) + { + switch( mSide ) + { + case LEFT: + case RIGHT: + getWindow()->setCursor(UI_CURSOR_SIZEWE); + break; + + case TOP: + case BOTTOM: + getWindow()->setCursor(UI_CURSOR_SIZENS); + break; + } + } + + if (mResizeListener) + { + mResizeListener(NULL); + } + + return handled; +} // end LLResizeBar::handleHover + +bool LLResizeBar::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + LLRect orig_rect = mResizingView->getRect(); + LLRect scaled_rect = orig_rect; + + if (mSnappingEnabled && mAllowDoubleClickSnapping) + { + switch( mSide ) + { + case LEFT: + mResizingView->findSnapEdge(scaled_rect.mLeft, LLCoordGL(0, 0), SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, S32_MAX); + scaled_rect.mLeft = scaled_rect.mRight - llclamp(scaled_rect.getWidth(), mMinSize, mMaxSize); + break; + case TOP: + mResizingView->findSnapEdge(scaled_rect.mTop, LLCoordGL(0, 0), SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, S32_MAX); + scaled_rect.mTop = scaled_rect.mBottom + llclamp(scaled_rect.getHeight(), mMinSize, mMaxSize); + break; + case RIGHT: + mResizingView->findSnapEdge(scaled_rect.mRight, LLCoordGL(0, 0), SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, S32_MAX); + scaled_rect.mRight = scaled_rect.mLeft + llclamp(scaled_rect.getWidth(), mMinSize, mMaxSize); + break; + case BOTTOM: + mResizingView->findSnapEdge(scaled_rect.mBottom, LLCoordGL(0, 0), SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, S32_MAX); + scaled_rect.mBottom = scaled_rect.mTop - llclamp(scaled_rect.getHeight(), mMinSize, mMaxSize); + break; + } + + mResizingView->setShape(scaled_rect, true); + } + + return true; +} + +void LLResizeBar::setImagePanel(LLPanel * panelp) +{ + const LLView::child_list_t * children = getChildList(); + if (getChildCount() == 2) + { + LLPanel * image_panelp = dynamic_cast(children->back()); + if (image_panelp) + { + removeChild(image_panelp); + delete image_panelp; + } + } + + addChild(panelp); + sendChildToBack(panelp); +} + +LLPanel * LLResizeBar::getImagePanel() const +{ + return getChildCount() > 0 ? (LLPanel *)getChildList()->back() : NULL; +} diff --git a/indra/llui/llresizebar.h b/indra/llui/llresizebar.h index 7a6d3fa4c0..4b0f435834 100644 --- a/indra/llui/llresizebar.h +++ b/indra/llui/llresizebar.h @@ -1,88 +1,88 @@ -/** - * @file llresizebar.h - * @brief LLResizeBar base 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$ - */ - -#ifndef LL_RESIZEBAR_H -#define LL_RESIZEBAR_H - -#include "llview.h" - -class LLResizeBar : public LLView -{ -public: - enum Side { LEFT, TOP, RIGHT, BOTTOM }; - - struct Params : public LLInitParam::Block - { - Mandatory resizing_view; - Mandatory side; - - Optional min_size; - Optional max_size; - Optional snapping_enabled; - Optional allow_double_click_snapping; - - Params(); - }; - -protected: - LLResizeBar(const LLResizeBar::Params& p); - friend class LLUICtrlFactory; - -public: - - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); - - void setResizeLimits( S32 min_size, S32 max_size ) { mMinSize = min_size; mMaxSize = max_size; } - void setEnableSnapping(bool enable) { mSnappingEnabled = enable; } - void setAllowDoubleClickSnapping(bool allow) { mAllowDoubleClickSnapping = allow; } - bool canResize() { return getEnabled() && mMaxSize > mMinSize; } - void setResizeListener(boost::function listener) {mResizeListener = listener;} - void setImagePanel(LLPanel * panelp); - LLPanel * getImagePanel() const; - -private: - S32 mDragLastScreenX; - S32 mDragLastScreenY; - S32 mLastMouseScreenX; - S32 mLastMouseScreenY; - LLCoordGL mLastMouseDir; - S32 mMinSize; - S32 mMaxSize; - const Side mSide; - bool mSnappingEnabled, - mAllowDoubleClickSnapping; - LLView* mResizingView; - boost::function mResizeListener; - LLPointer mDragHandleImage; - LLPanel * mImagePanel; -}; - -#endif // LL_RESIZEBAR_H - - +/** + * @file llresizebar.h + * @brief LLResizeBar base 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$ + */ + +#ifndef LL_RESIZEBAR_H +#define LL_RESIZEBAR_H + +#include "llview.h" + +class LLResizeBar : public LLView +{ +public: + enum Side { LEFT, TOP, RIGHT, BOTTOM }; + + struct Params : public LLInitParam::Block + { + Mandatory resizing_view; + Mandatory side; + + Optional min_size; + Optional max_size; + Optional snapping_enabled; + Optional allow_double_click_snapping; + + Params(); + }; + +protected: + LLResizeBar(const LLResizeBar::Params& p); + friend class LLUICtrlFactory; + +public: + + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); + + void setResizeLimits( S32 min_size, S32 max_size ) { mMinSize = min_size; mMaxSize = max_size; } + void setEnableSnapping(bool enable) { mSnappingEnabled = enable; } + void setAllowDoubleClickSnapping(bool allow) { mAllowDoubleClickSnapping = allow; } + bool canResize() { return getEnabled() && mMaxSize > mMinSize; } + void setResizeListener(boost::function listener) {mResizeListener = listener;} + void setImagePanel(LLPanel * panelp); + LLPanel * getImagePanel() const; + +private: + S32 mDragLastScreenX; + S32 mDragLastScreenY; + S32 mLastMouseScreenX; + S32 mLastMouseScreenY; + LLCoordGL mLastMouseDir; + S32 mMinSize; + S32 mMaxSize; + const Side mSide; + bool mSnappingEnabled, + mAllowDoubleClickSnapping; + LLView* mResizingView; + boost::function mResizeListener; + LLPointer mDragHandleImage; + LLPanel * mImagePanel; +}; + +#endif // LL_RESIZEBAR_H + + diff --git a/indra/llui/llresizehandle.cpp b/indra/llui/llresizehandle.cpp index 55eeae6963..0b7bb55360 100644 --- a/indra/llui/llresizehandle.cpp +++ b/indra/llui/llresizehandle.cpp @@ -1,385 +1,385 @@ -/** - * @file llresizehandle.cpp - * @brief LLResizeHandle base 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 "llresizehandle.h" - -#include "llfocusmgr.h" -#include "llmath.h" -#include "llui.h" -#include "llmenugl.h" -#include "llcontrol.h" -#include "llfloater.h" -#include "llwindow.h" - -const S32 RESIZE_BORDER_WIDTH = 3; - -LLResizeHandle::Params::Params() -: corner("corner"), - min_width("min_width"), - min_height("min_height") -{ - name = "resize_handle"; -} - -LLResizeHandle::LLResizeHandle(const LLResizeHandle::Params& p) -: LLView(p), - mDragLastScreenX( 0 ), - mDragLastScreenY( 0 ), - mLastMouseScreenX( 0 ), - mLastMouseScreenY( 0 ), - mImage( NULL ), - mMinWidth( p.min_width ), - mMinHeight( p.min_height ), - mCorner( p.corner ) -{ - if( RIGHT_BOTTOM == mCorner) - { - mImage = LLUI::getUIImage("Resize_Corner"); - } - switch( p.corner ) - { - case LEFT_TOP: setFollows( FOLLOWS_LEFT | FOLLOWS_TOP ); break; - case LEFT_BOTTOM: setFollows( FOLLOWS_LEFT | FOLLOWS_BOTTOM ); break; - case RIGHT_TOP: setFollows( FOLLOWS_RIGHT | FOLLOWS_TOP ); break; - case RIGHT_BOTTOM: setFollows( FOLLOWS_RIGHT | FOLLOWS_BOTTOM ); break; - } -} - -LLResizeHandle::~LLResizeHandle() -{ - gFocusMgr.removeKeyboardFocusWithoutCallback(this); -} - - -bool LLResizeHandle::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - if( pointInHandle(x, y) ) - { - handled = true; - // Route future Mouse messages here preemptively. (Release on mouse up.) - // No handler needed for focus lost since this clas has no state that depends on it. - gFocusMgr.setMouseCapture( this ); - - localPointToScreen(x, y, &mDragLastScreenX, &mDragLastScreenY); - mLastMouseScreenX = mDragLastScreenX; - mLastMouseScreenY = mDragLastScreenY; - } - - return handled; -} - - -bool LLResizeHandle::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - if( hasMouseCapture() ) - { - // Release the mouse - gFocusMgr.setMouseCapture( NULL ); - handled = true; - } - else if( pointInHandle(x, y) ) - { - handled = true; - } - - return handled; -} - - -bool LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // We only handle the click if the click both started and ended within us - if( hasMouseCapture() ) - { - // Make sure the mouse in still over the application. We don't want to make the parent - // so big that we can't see the resize handle any more. - - S32 screen_x; - S32 screen_y; - localPointToScreen(x, y, &screen_x, &screen_y); - const LLRect valid_rect = getRootView()->getRect(); - screen_x = llclamp( screen_x, valid_rect.mLeft, valid_rect.mRight ); - screen_y = llclamp( screen_y, valid_rect.mBottom, valid_rect.mTop ); - - LLView* resizing_view = getParent(); - if( resizing_view ) - { - // undock floater when user resize it - LLFloater* floater_parent = dynamic_cast(getParent()); - if (floater_parent && floater_parent->isDocked()) - { - floater_parent->setDocked(false, false); - } - - // Resize the parent - LLRect orig_rect = resizing_view->getRect(); - LLRect scaled_rect = orig_rect; - S32 delta_x = screen_x - mDragLastScreenX; - S32 delta_y = screen_y - mDragLastScreenY; - LLCoordGL mouse_dir; - // use hysteresis on mouse motion to preserve user intent when mouse stops moving - mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; - mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; - mLastMouseScreenX = screen_x; - mLastMouseScreenY = screen_y; - mLastMouseDir = mouse_dir; - - S32 x_multiple = 1; - S32 y_multiple = 1; - switch( mCorner ) - { - case LEFT_TOP: - x_multiple = -1; - y_multiple = 1; - break; - case LEFT_BOTTOM: - x_multiple = -1; - y_multiple = -1; - break; - case RIGHT_TOP: - x_multiple = 1; - y_multiple = 1; - break; - case RIGHT_BOTTOM: - x_multiple = 1; - y_multiple = -1; - break; - } - - S32 new_width = orig_rect.getWidth() + x_multiple * delta_x; - if( new_width < mMinWidth ) - { - new_width = mMinWidth; - delta_x = x_multiple * (mMinWidth - orig_rect.getWidth()); - } - - S32 new_height = orig_rect.getHeight() + y_multiple * delta_y; - if( new_height < mMinHeight ) - { - new_height = mMinHeight; - delta_y = y_multiple * (mMinHeight - orig_rect.getHeight()); - } - - switch( mCorner ) - { - case LEFT_TOP: - scaled_rect.translate(delta_x, 0); - break; - case LEFT_BOTTOM: - scaled_rect.translate(delta_x, delta_y); - break; - case RIGHT_TOP: - break; - case RIGHT_BOTTOM: - scaled_rect.translate(0, delta_y); - break; - } - - // temporarily set new parent rect - scaled_rect.mRight = scaled_rect.mLeft + new_width; - scaled_rect.mTop = scaled_rect.mBottom + new_height; - resizing_view->setRect(scaled_rect); - - LLView* snap_view = NULL; - LLView* test_view = NULL; - - static LLUICachedControl snap_margin ("SnapMargin", 0); - // now do snapping - switch(mCorner) - { - case LEFT_TOP: - snap_view = resizing_view->findSnapEdge(scaled_rect.mLeft, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, snap_margin); - test_view = resizing_view->findSnapEdge(scaled_rect.mTop, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, snap_margin); - if (!snap_view) - { - snap_view = test_view; - } - break; - case LEFT_BOTTOM: - snap_view = resizing_view->findSnapEdge(scaled_rect.mLeft, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, snap_margin); - test_view = resizing_view->findSnapEdge(scaled_rect.mBottom, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, snap_margin); - if (!snap_view) - { - snap_view = test_view; - } - break; - case RIGHT_TOP: - snap_view = resizing_view->findSnapEdge(scaled_rect.mRight, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, snap_margin); - test_view = resizing_view->findSnapEdge(scaled_rect.mTop, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, snap_margin); - if (!snap_view) - { - snap_view = test_view; - } - break; - case RIGHT_BOTTOM: - snap_view = resizing_view->findSnapEdge(scaled_rect.mRight, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, snap_margin); - test_view = resizing_view->findSnapEdge(scaled_rect.mBottom, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, snap_margin); - if (!snap_view) - { - snap_view = test_view; - } - break; - } - - // register "snap" behavior with snapped view - resizing_view->setSnappedTo(snap_view); - - // reset parent rect - resizing_view->setRect(orig_rect); - - // translate and scale to new shape - resizing_view->setShape(scaled_rect, true); - - // update last valid mouse cursor position based on resized view's actual size - LLRect new_rect = resizing_view->getRect(); - S32 actual_delta_x = 0; - S32 actual_delta_y = 0; - switch(mCorner) - { - case LEFT_TOP: - actual_delta_x = new_rect.mLeft - orig_rect.mLeft; - actual_delta_y = new_rect.mTop - orig_rect.mTop; - if (actual_delta_x != delta_x - || actual_delta_y != delta_y) - { - new_rect.mRight = orig_rect.mRight; - new_rect.mBottom = orig_rect.mBottom; - resizing_view->setShape(new_rect, true); - } - - mDragLastScreenX += actual_delta_x; - mDragLastScreenY += actual_delta_y; - break; - case LEFT_BOTTOM: - actual_delta_x = new_rect.mLeft - orig_rect.mLeft; - actual_delta_y = new_rect.mBottom - orig_rect.mBottom; - if (actual_delta_x != delta_x - || actual_delta_y != delta_y) - { - new_rect.mRight = orig_rect.mRight; - new_rect.mTop = orig_rect.mTop; - resizing_view->setShape(new_rect, true); - } - - mDragLastScreenX += actual_delta_x; - mDragLastScreenY += actual_delta_y; - break; - case RIGHT_TOP: - actual_delta_x = new_rect.mRight - orig_rect.mRight; - actual_delta_y = new_rect.mTop - orig_rect.mTop; - if (actual_delta_x != delta_x - || actual_delta_y != delta_y) - { - new_rect.mLeft = orig_rect.mLeft; - new_rect.mBottom = orig_rect.mBottom; - resizing_view->setShape(new_rect, true); - } - - mDragLastScreenX += actual_delta_x; - mDragLastScreenY += actual_delta_y; - break; - case RIGHT_BOTTOM: - actual_delta_x = new_rect.mRight - orig_rect.mRight; - actual_delta_y = new_rect.mBottom - orig_rect.mBottom; - if (actual_delta_x != delta_x - || actual_delta_y != delta_y) - { - new_rect.mLeft = orig_rect.mLeft; - new_rect.mTop = orig_rect.mTop; - resizing_view->setShape(new_rect, true); - } - - mDragLastScreenX += actual_delta_x; - mDragLastScreenY += actual_delta_y; - break; - default: - break; - } - } - - handled = true; - } - else // don't have mouse capture - { - if( pointInHandle( x, y ) ) - { - handled = true; - } - } - - if( handled ) - { - switch( mCorner ) - { - case RIGHT_BOTTOM: - case LEFT_TOP: - getWindow()->setCursor(UI_CURSOR_SIZENWSE); - break; - case LEFT_BOTTOM: - case RIGHT_TOP: - getWindow()->setCursor(UI_CURSOR_SIZENESW); - break; - } - } - - return handled; -} // end handleHover - - -// assumes GL state is set for 2D -void LLResizeHandle::draw() -{ - if( mImage.notNull() && getVisible() && (RIGHT_BOTTOM == mCorner) ) - { - mImage->draw(0, 0); - } -} - - -bool LLResizeHandle::pointInHandle( S32 x, S32 y ) -{ - if( pointInView(x, y) ) - { - const S32 TOP_BORDER = (getRect().getHeight() - RESIZE_BORDER_WIDTH); - const S32 RIGHT_BORDER = (getRect().getWidth() - RESIZE_BORDER_WIDTH); - - switch( mCorner ) - { - case LEFT_TOP: return (x <= RESIZE_BORDER_WIDTH) || (y >= TOP_BORDER); - case LEFT_BOTTOM: return (x <= RESIZE_BORDER_WIDTH) || (y <= RESIZE_BORDER_WIDTH); - case RIGHT_TOP: return (x >= RIGHT_BORDER) || (y >= TOP_BORDER); - case RIGHT_BOTTOM: return true; - } - } - return false; -} +/** + * @file llresizehandle.cpp + * @brief LLResizeHandle base 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 "llresizehandle.h" + +#include "llfocusmgr.h" +#include "llmath.h" +#include "llui.h" +#include "llmenugl.h" +#include "llcontrol.h" +#include "llfloater.h" +#include "llwindow.h" + +const S32 RESIZE_BORDER_WIDTH = 3; + +LLResizeHandle::Params::Params() +: corner("corner"), + min_width("min_width"), + min_height("min_height") +{ + name = "resize_handle"; +} + +LLResizeHandle::LLResizeHandle(const LLResizeHandle::Params& p) +: LLView(p), + mDragLastScreenX( 0 ), + mDragLastScreenY( 0 ), + mLastMouseScreenX( 0 ), + mLastMouseScreenY( 0 ), + mImage( NULL ), + mMinWidth( p.min_width ), + mMinHeight( p.min_height ), + mCorner( p.corner ) +{ + if( RIGHT_BOTTOM == mCorner) + { + mImage = LLUI::getUIImage("Resize_Corner"); + } + switch( p.corner ) + { + case LEFT_TOP: setFollows( FOLLOWS_LEFT | FOLLOWS_TOP ); break; + case LEFT_BOTTOM: setFollows( FOLLOWS_LEFT | FOLLOWS_BOTTOM ); break; + case RIGHT_TOP: setFollows( FOLLOWS_RIGHT | FOLLOWS_TOP ); break; + case RIGHT_BOTTOM: setFollows( FOLLOWS_RIGHT | FOLLOWS_BOTTOM ); break; + } +} + +LLResizeHandle::~LLResizeHandle() +{ + gFocusMgr.removeKeyboardFocusWithoutCallback(this); +} + + +bool LLResizeHandle::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + if( pointInHandle(x, y) ) + { + handled = true; + // Route future Mouse messages here preemptively. (Release on mouse up.) + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture( this ); + + localPointToScreen(x, y, &mDragLastScreenX, &mDragLastScreenY); + mLastMouseScreenX = mDragLastScreenX; + mLastMouseScreenY = mDragLastScreenY; + } + + return handled; +} + + +bool LLResizeHandle::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + if( hasMouseCapture() ) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL ); + handled = true; + } + else if( pointInHandle(x, y) ) + { + handled = true; + } + + return handled; +} + + +bool LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // We only handle the click if the click both started and ended within us + if( hasMouseCapture() ) + { + // Make sure the mouse in still over the application. We don't want to make the parent + // so big that we can't see the resize handle any more. + + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + const LLRect valid_rect = getRootView()->getRect(); + screen_x = llclamp( screen_x, valid_rect.mLeft, valid_rect.mRight ); + screen_y = llclamp( screen_y, valid_rect.mBottom, valid_rect.mTop ); + + LLView* resizing_view = getParent(); + if( resizing_view ) + { + // undock floater when user resize it + LLFloater* floater_parent = dynamic_cast(getParent()); + if (floater_parent && floater_parent->isDocked()) + { + floater_parent->setDocked(false, false); + } + + // Resize the parent + LLRect orig_rect = resizing_view->getRect(); + LLRect scaled_rect = orig_rect; + S32 delta_x = screen_x - mDragLastScreenX; + S32 delta_y = screen_y - mDragLastScreenY; + LLCoordGL mouse_dir; + // use hysteresis on mouse motion to preserve user intent when mouse stops moving + mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; + mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; + mLastMouseScreenX = screen_x; + mLastMouseScreenY = screen_y; + mLastMouseDir = mouse_dir; + + S32 x_multiple = 1; + S32 y_multiple = 1; + switch( mCorner ) + { + case LEFT_TOP: + x_multiple = -1; + y_multiple = 1; + break; + case LEFT_BOTTOM: + x_multiple = -1; + y_multiple = -1; + break; + case RIGHT_TOP: + x_multiple = 1; + y_multiple = 1; + break; + case RIGHT_BOTTOM: + x_multiple = 1; + y_multiple = -1; + break; + } + + S32 new_width = orig_rect.getWidth() + x_multiple * delta_x; + if( new_width < mMinWidth ) + { + new_width = mMinWidth; + delta_x = x_multiple * (mMinWidth - orig_rect.getWidth()); + } + + S32 new_height = orig_rect.getHeight() + y_multiple * delta_y; + if( new_height < mMinHeight ) + { + new_height = mMinHeight; + delta_y = y_multiple * (mMinHeight - orig_rect.getHeight()); + } + + switch( mCorner ) + { + case LEFT_TOP: + scaled_rect.translate(delta_x, 0); + break; + case LEFT_BOTTOM: + scaled_rect.translate(delta_x, delta_y); + break; + case RIGHT_TOP: + break; + case RIGHT_BOTTOM: + scaled_rect.translate(0, delta_y); + break; + } + + // temporarily set new parent rect + scaled_rect.mRight = scaled_rect.mLeft + new_width; + scaled_rect.mTop = scaled_rect.mBottom + new_height; + resizing_view->setRect(scaled_rect); + + LLView* snap_view = NULL; + LLView* test_view = NULL; + + static LLUICachedControl snap_margin ("SnapMargin", 0); + // now do snapping + switch(mCorner) + { + case LEFT_TOP: + snap_view = resizing_view->findSnapEdge(scaled_rect.mLeft, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, snap_margin); + test_view = resizing_view->findSnapEdge(scaled_rect.mTop, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, snap_margin); + if (!snap_view) + { + snap_view = test_view; + } + break; + case LEFT_BOTTOM: + snap_view = resizing_view->findSnapEdge(scaled_rect.mLeft, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, snap_margin); + test_view = resizing_view->findSnapEdge(scaled_rect.mBottom, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, snap_margin); + if (!snap_view) + { + snap_view = test_view; + } + break; + case RIGHT_TOP: + snap_view = resizing_view->findSnapEdge(scaled_rect.mRight, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, snap_margin); + test_view = resizing_view->findSnapEdge(scaled_rect.mTop, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, snap_margin); + if (!snap_view) + { + snap_view = test_view; + } + break; + case RIGHT_BOTTOM: + snap_view = resizing_view->findSnapEdge(scaled_rect.mRight, mouse_dir, SNAP_RIGHT, SNAP_PARENT_AND_SIBLINGS, snap_margin); + test_view = resizing_view->findSnapEdge(scaled_rect.mBottom, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, snap_margin); + if (!snap_view) + { + snap_view = test_view; + } + break; + } + + // register "snap" behavior with snapped view + resizing_view->setSnappedTo(snap_view); + + // reset parent rect + resizing_view->setRect(orig_rect); + + // translate and scale to new shape + resizing_view->setShape(scaled_rect, true); + + // update last valid mouse cursor position based on resized view's actual size + LLRect new_rect = resizing_view->getRect(); + S32 actual_delta_x = 0; + S32 actual_delta_y = 0; + switch(mCorner) + { + case LEFT_TOP: + actual_delta_x = new_rect.mLeft - orig_rect.mLeft; + actual_delta_y = new_rect.mTop - orig_rect.mTop; + if (actual_delta_x != delta_x + || actual_delta_y != delta_y) + { + new_rect.mRight = orig_rect.mRight; + new_rect.mBottom = orig_rect.mBottom; + resizing_view->setShape(new_rect, true); + } + + mDragLastScreenX += actual_delta_x; + mDragLastScreenY += actual_delta_y; + break; + case LEFT_BOTTOM: + actual_delta_x = new_rect.mLeft - orig_rect.mLeft; + actual_delta_y = new_rect.mBottom - orig_rect.mBottom; + if (actual_delta_x != delta_x + || actual_delta_y != delta_y) + { + new_rect.mRight = orig_rect.mRight; + new_rect.mTop = orig_rect.mTop; + resizing_view->setShape(new_rect, true); + } + + mDragLastScreenX += actual_delta_x; + mDragLastScreenY += actual_delta_y; + break; + case RIGHT_TOP: + actual_delta_x = new_rect.mRight - orig_rect.mRight; + actual_delta_y = new_rect.mTop - orig_rect.mTop; + if (actual_delta_x != delta_x + || actual_delta_y != delta_y) + { + new_rect.mLeft = orig_rect.mLeft; + new_rect.mBottom = orig_rect.mBottom; + resizing_view->setShape(new_rect, true); + } + + mDragLastScreenX += actual_delta_x; + mDragLastScreenY += actual_delta_y; + break; + case RIGHT_BOTTOM: + actual_delta_x = new_rect.mRight - orig_rect.mRight; + actual_delta_y = new_rect.mBottom - orig_rect.mBottom; + if (actual_delta_x != delta_x + || actual_delta_y != delta_y) + { + new_rect.mLeft = orig_rect.mLeft; + new_rect.mTop = orig_rect.mTop; + resizing_view->setShape(new_rect, true); + } + + mDragLastScreenX += actual_delta_x; + mDragLastScreenY += actual_delta_y; + break; + default: + break; + } + } + + handled = true; + } + else // don't have mouse capture + { + if( pointInHandle( x, y ) ) + { + handled = true; + } + } + + if( handled ) + { + switch( mCorner ) + { + case RIGHT_BOTTOM: + case LEFT_TOP: + getWindow()->setCursor(UI_CURSOR_SIZENWSE); + break; + case LEFT_BOTTOM: + case RIGHT_TOP: + getWindow()->setCursor(UI_CURSOR_SIZENESW); + break; + } + } + + return handled; +} // end handleHover + + +// assumes GL state is set for 2D +void LLResizeHandle::draw() +{ + if( mImage.notNull() && getVisible() && (RIGHT_BOTTOM == mCorner) ) + { + mImage->draw(0, 0); + } +} + + +bool LLResizeHandle::pointInHandle( S32 x, S32 y ) +{ + if( pointInView(x, y) ) + { + const S32 TOP_BORDER = (getRect().getHeight() - RESIZE_BORDER_WIDTH); + const S32 RIGHT_BORDER = (getRect().getWidth() - RESIZE_BORDER_WIDTH); + + switch( mCorner ) + { + case LEFT_TOP: return (x <= RESIZE_BORDER_WIDTH) || (y >= TOP_BORDER); + case LEFT_BOTTOM: return (x <= RESIZE_BORDER_WIDTH) || (y <= RESIZE_BORDER_WIDTH); + case RIGHT_TOP: return (x >= RIGHT_BORDER) || (y >= TOP_BORDER); + case RIGHT_BOTTOM: return true; + } + } + return false; +} diff --git a/indra/llui/llresizehandle.h b/indra/llui/llresizehandle.h index 6f4199a782..9cc4123544 100644 --- a/indra/llui/llresizehandle.h +++ b/indra/llui/llresizehandle.h @@ -1,79 +1,79 @@ -/** - * @file llresizehandle.h - * @brief LLResizeHandle base 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$ - */ - -#ifndef LL_RESIZEHANDLE_H -#define LL_RESIZEHANDLE_H - -#include "stdtypes.h" -#include "llview.h" -#include "llcoord.h" - - -class LLResizeHandle : public LLView -{ -public: - enum ECorner { LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM }; - - struct Params : public LLInitParam::Block - { - Mandatory corner; - Optional min_width; - Optional min_height; - Params(); - }; - - ~LLResizeHandle(); -protected: - LLResizeHandle(const LLResizeHandle::Params&); - friend class LLUICtrlFactory; -public: - virtual void draw(); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - - void setResizeLimits( S32 min_width, S32 min_height ) { mMinWidth = min_width; mMinHeight = min_height; } - -private: - bool pointInHandle( S32 x, S32 y ); - - S32 mDragLastScreenX; - S32 mDragLastScreenY; - S32 mLastMouseScreenX; - S32 mLastMouseScreenY; - LLCoordGL mLastMouseDir; - LLPointer mImage; - S32 mMinWidth; - S32 mMinHeight; - const ECorner mCorner; -}; - -const S32 RESIZE_HANDLE_HEIGHT = 11; -const S32 RESIZE_HANDLE_WIDTH = 11; - -#endif // LL_RESIZEHANDLE_H - - +/** + * @file llresizehandle.h + * @brief LLResizeHandle base 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$ + */ + +#ifndef LL_RESIZEHANDLE_H +#define LL_RESIZEHANDLE_H + +#include "stdtypes.h" +#include "llview.h" +#include "llcoord.h" + + +class LLResizeHandle : public LLView +{ +public: + enum ECorner { LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM }; + + struct Params : public LLInitParam::Block + { + Mandatory corner; + Optional min_width; + Optional min_height; + Params(); + }; + + ~LLResizeHandle(); +protected: + LLResizeHandle(const LLResizeHandle::Params&); + friend class LLUICtrlFactory; +public: + virtual void draw(); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + + void setResizeLimits( S32 min_width, S32 min_height ) { mMinWidth = min_width; mMinHeight = min_height; } + +private: + bool pointInHandle( S32 x, S32 y ); + + S32 mDragLastScreenX; + S32 mDragLastScreenY; + S32 mLastMouseScreenX; + S32 mLastMouseScreenY; + LLCoordGL mLastMouseDir; + LLPointer mImage; + S32 mMinWidth; + S32 mMinHeight; + const ECorner mCorner; +}; + +const S32 RESIZE_HANDLE_HEIGHT = 11; +const S32 RESIZE_HANDLE_WIDTH = 11; + +#endif // LL_RESIZEHANDLE_H + + diff --git a/indra/llui/llresmgr.cpp b/indra/llui/llresmgr.cpp index 342842e328..d07aa800d1 100644 --- a/indra/llui/llresmgr.cpp +++ b/indra/llui/llresmgr.cpp @@ -1,267 +1,267 @@ -/** - * @file llresmgr.cpp - * @brief Localized resource manager - * - * $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$ - */ - -// NOTE: this is a MINIMAL implementation. The interface will remain, but the implementation will -// (when the time is right) become dynamic and probably use external files. - -#include "linden_common.h" - -#include "llresmgr.h" -#include "llfontgl.h" -#include "llerror.h" -#include "llstring.h" - - -LLResMgr::LLResMgr() -{ - // Set default - setLocale( LLLOCALE_USA ); -} - - -void LLResMgr::setLocale( LLLOCALE_ID locale_id ) -{ - mLocale = locale_id; -} - -char LLResMgr::getDecimalPoint() const -{ - char decimal = localeconv()->decimal_point[0]; - - return decimal; -} - -char LLResMgr::getThousandsSeparator() const -{ - char separator = localeconv()->thousands_sep[0]; - - return separator; -} - -char LLResMgr::getMonetaryDecimalPoint() const -{ - char decimal = localeconv()->mon_decimal_point[0]; - - return decimal; -} - -char LLResMgr::getMonetaryThousandsSeparator() const -{ - char separator = localeconv()->mon_thousands_sep[0]; - - return separator; -} - - -// Sets output to a string of integers with monetary separators inserted according to the locale. -std::string LLResMgr::getMonetaryString( S32 input ) const -{ - std::string output; - - LLLocale locale(LLLocale::USER_LOCALE); - struct lconv *conv = localeconv(); - - char* negative_sign = conv->negative_sign; - char separator = getMonetaryThousandsSeparator(); - char* grouping = conv->mon_grouping; - - // Note on mon_grouping: - // Specifies a string that defines the size of each group of digits in formatted monetary quantities. - // The operand for the mon_grouping keyword consists of a sequence of semicolon-separated integers. - // Each integer specifies the number of digits in a group. The initial integer defines the size of - // the group immediately to the left of the decimal delimiter. The following integers define succeeding - // groups to the left of the previous group. If the last integer is not -1, the size of the previous - // group (if any) is repeatedly used for the remainder of the digits. If the last integer is -1, no - // further grouping is performed. - - - // Note: we assume here that the currency symbol goes on the left. (Hey, it's Lindens! We can just decide.) - bool negative = (input < 0 ); - bool negative_before = negative && (conv->n_sign_posn != 2); - bool negative_after = negative && (conv->n_sign_posn == 2); - - std::string digits = llformat("%u", abs(input)); - if( !grouping || !grouping[0] ) - { - if( negative_before ) - { - output.append( negative_sign ); - } - output.append( digits ); - if( negative_after ) - { - output.append( negative_sign ); - } - return output; - } - - S32 groupings[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - S32 cur_group; - for( cur_group = 0; grouping[ cur_group ]; cur_group++ ) - { - if( grouping[ cur_group ] != ';' ) - { - groupings[cur_group] = grouping[ cur_group ]; - } - cur_group++; - - if( groupings[cur_group] < 0 ) - { - break; - } - } - S32 group_count = cur_group; - - char reversed_output[20] = ""; /* Flawfinder: ignore */ - char forward_output[20] = ""; /* Flawfinder: ignore */ - S32 output_pos = 0; - - cur_group = 0; - S32 pos = digits.size()-1; - S32 count_within_group = 0; - while( (pos >= 0) && (groupings[cur_group] >= 0) ) - { - count_within_group++; - if( count_within_group > groupings[cur_group] ) - { - count_within_group = 1; - reversed_output[ output_pos++ ] = separator; - - if( (cur_group + 1) >= group_count ) - { - break; - } - else - if( groupings[cur_group + 1] > 0 ) - { - cur_group++; - } - } - reversed_output[ output_pos++ ] = digits[pos--]; - } - - while( pos >= 0 ) - { - reversed_output[ output_pos++ ] = digits[pos--]; - } - - - reversed_output[ output_pos ] = '\0'; - forward_output[ output_pos ] = '\0'; - - for( S32 i = 0; i < output_pos; i++ ) - { - forward_output[ output_pos - 1 - i ] = reversed_output[ i ]; - } - - if( negative_before ) - { - output.append( negative_sign ); - } - output.append( forward_output ); - if( negative_after ) - { - output.append( negative_sign ); - } - return output; -} - -void LLResMgr::getIntegerString( std::string& output, S32 input ) const -{ - // handle special case of input value being zero - if (input == 0) - { - output = "0"; - return; - } - - // *NOTE: this method does not handle negative input integers correctly - S32 fraction = 0; - std::string fraction_string; - S32 remaining_count = input; - while(remaining_count > 0) - { - fraction = (remaining_count) % 1000; - - if (!output.empty()) - { - if (fraction == remaining_count) - { - fraction_string = llformat_to_utf8("%d%c", fraction, getThousandsSeparator()); - } - else - { - fraction_string = llformat_to_utf8("%3.3d%c", fraction, getThousandsSeparator()); - } - output = fraction_string + output; - } - else - { - if (fraction == remaining_count) - { - fraction_string = llformat("%d", fraction); - } - else - { - fraction_string = llformat("%3.3d", fraction); - } - output = fraction_string; - } - remaining_count /= 1000; - } -} - -#if LL_WINDOWS -const std::string LLLocale::USER_LOCALE("English_United States.1252");// = LLStringUtil::null; -const std::string LLLocale::SYSTEM_LOCALE("English_United States.1252"); -#elif LL_DARWIN -const std::string LLLocale::USER_LOCALE("en_US.iso8859-1");// = LLStringUtil::null; -const std::string LLLocale::SYSTEM_LOCALE("en_US.iso8859-1"); -#else // LL_LINUX likes this -const std::string LLLocale::USER_LOCALE("en_US.utf8"); -const std::string LLLocale::SYSTEM_LOCALE("C"); -#endif - - -LLLocale::LLLocale(const std::string& locale_string) -{ - mPrevLocaleString = setlocale( LC_ALL, NULL ); - char* new_locale_string = setlocale( LC_ALL, locale_string.c_str()); - if ( new_locale_string == NULL) - { - LL_WARNS_ONCE("LLLocale") << "Failed to set locale " << locale_string << LL_ENDL; - setlocale(LC_ALL, SYSTEM_LOCALE.c_str()); - } - //else - //{ - // LL_INFOS() << "Set locale to " << new_locale_string << LL_ENDL; - //} -} - -LLLocale::~LLLocale() -{ - setlocale( LC_ALL, mPrevLocaleString.c_str() ); -} +/** + * @file llresmgr.cpp + * @brief Localized resource manager + * + * $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$ + */ + +// NOTE: this is a MINIMAL implementation. The interface will remain, but the implementation will +// (when the time is right) become dynamic and probably use external files. + +#include "linden_common.h" + +#include "llresmgr.h" +#include "llfontgl.h" +#include "llerror.h" +#include "llstring.h" + + +LLResMgr::LLResMgr() +{ + // Set default + setLocale( LLLOCALE_USA ); +} + + +void LLResMgr::setLocale( LLLOCALE_ID locale_id ) +{ + mLocale = locale_id; +} + +char LLResMgr::getDecimalPoint() const +{ + char decimal = localeconv()->decimal_point[0]; + + return decimal; +} + +char LLResMgr::getThousandsSeparator() const +{ + char separator = localeconv()->thousands_sep[0]; + + return separator; +} + +char LLResMgr::getMonetaryDecimalPoint() const +{ + char decimal = localeconv()->mon_decimal_point[0]; + + return decimal; +} + +char LLResMgr::getMonetaryThousandsSeparator() const +{ + char separator = localeconv()->mon_thousands_sep[0]; + + return separator; +} + + +// Sets output to a string of integers with monetary separators inserted according to the locale. +std::string LLResMgr::getMonetaryString( S32 input ) const +{ + std::string output; + + LLLocale locale(LLLocale::USER_LOCALE); + struct lconv *conv = localeconv(); + + char* negative_sign = conv->negative_sign; + char separator = getMonetaryThousandsSeparator(); + char* grouping = conv->mon_grouping; + + // Note on mon_grouping: + // Specifies a string that defines the size of each group of digits in formatted monetary quantities. + // The operand for the mon_grouping keyword consists of a sequence of semicolon-separated integers. + // Each integer specifies the number of digits in a group. The initial integer defines the size of + // the group immediately to the left of the decimal delimiter. The following integers define succeeding + // groups to the left of the previous group. If the last integer is not -1, the size of the previous + // group (if any) is repeatedly used for the remainder of the digits. If the last integer is -1, no + // further grouping is performed. + + + // Note: we assume here that the currency symbol goes on the left. (Hey, it's Lindens! We can just decide.) + bool negative = (input < 0 ); + bool negative_before = negative && (conv->n_sign_posn != 2); + bool negative_after = negative && (conv->n_sign_posn == 2); + + std::string digits = llformat("%u", abs(input)); + if( !grouping || !grouping[0] ) + { + if( negative_before ) + { + output.append( negative_sign ); + } + output.append( digits ); + if( negative_after ) + { + output.append( negative_sign ); + } + return output; + } + + S32 groupings[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + S32 cur_group; + for( cur_group = 0; grouping[ cur_group ]; cur_group++ ) + { + if( grouping[ cur_group ] != ';' ) + { + groupings[cur_group] = grouping[ cur_group ]; + } + cur_group++; + + if( groupings[cur_group] < 0 ) + { + break; + } + } + S32 group_count = cur_group; + + char reversed_output[20] = ""; /* Flawfinder: ignore */ + char forward_output[20] = ""; /* Flawfinder: ignore */ + S32 output_pos = 0; + + cur_group = 0; + S32 pos = digits.size()-1; + S32 count_within_group = 0; + while( (pos >= 0) && (groupings[cur_group] >= 0) ) + { + count_within_group++; + if( count_within_group > groupings[cur_group] ) + { + count_within_group = 1; + reversed_output[ output_pos++ ] = separator; + + if( (cur_group + 1) >= group_count ) + { + break; + } + else + if( groupings[cur_group + 1] > 0 ) + { + cur_group++; + } + } + reversed_output[ output_pos++ ] = digits[pos--]; + } + + while( pos >= 0 ) + { + reversed_output[ output_pos++ ] = digits[pos--]; + } + + + reversed_output[ output_pos ] = '\0'; + forward_output[ output_pos ] = '\0'; + + for( S32 i = 0; i < output_pos; i++ ) + { + forward_output[ output_pos - 1 - i ] = reversed_output[ i ]; + } + + if( negative_before ) + { + output.append( negative_sign ); + } + output.append( forward_output ); + if( negative_after ) + { + output.append( negative_sign ); + } + return output; +} + +void LLResMgr::getIntegerString( std::string& output, S32 input ) const +{ + // handle special case of input value being zero + if (input == 0) + { + output = "0"; + return; + } + + // *NOTE: this method does not handle negative input integers correctly + S32 fraction = 0; + std::string fraction_string; + S32 remaining_count = input; + while(remaining_count > 0) + { + fraction = (remaining_count) % 1000; + + if (!output.empty()) + { + if (fraction == remaining_count) + { + fraction_string = llformat_to_utf8("%d%c", fraction, getThousandsSeparator()); + } + else + { + fraction_string = llformat_to_utf8("%3.3d%c", fraction, getThousandsSeparator()); + } + output = fraction_string + output; + } + else + { + if (fraction == remaining_count) + { + fraction_string = llformat("%d", fraction); + } + else + { + fraction_string = llformat("%3.3d", fraction); + } + output = fraction_string; + } + remaining_count /= 1000; + } +} + +#if LL_WINDOWS +const std::string LLLocale::USER_LOCALE("English_United States.1252");// = LLStringUtil::null; +const std::string LLLocale::SYSTEM_LOCALE("English_United States.1252"); +#elif LL_DARWIN +const std::string LLLocale::USER_LOCALE("en_US.iso8859-1");// = LLStringUtil::null; +const std::string LLLocale::SYSTEM_LOCALE("en_US.iso8859-1"); +#else // LL_LINUX likes this +const std::string LLLocale::USER_LOCALE("en_US.utf8"); +const std::string LLLocale::SYSTEM_LOCALE("C"); +#endif + + +LLLocale::LLLocale(const std::string& locale_string) +{ + mPrevLocaleString = setlocale( LC_ALL, NULL ); + char* new_locale_string = setlocale( LC_ALL, locale_string.c_str()); + if ( new_locale_string == NULL) + { + LL_WARNS_ONCE("LLLocale") << "Failed to set locale " << locale_string << LL_ENDL; + setlocale(LC_ALL, SYSTEM_LOCALE.c_str()); + } + //else + //{ + // LL_INFOS() << "Set locale to " << new_locale_string << LL_ENDL; + //} +} + +LLLocale::~LLLocale() +{ + setlocale( LC_ALL, mPrevLocaleString.c_str() ); +} diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp index ab7d0bc38f..9c73b1ba3f 100644 --- a/indra/llui/llscrollbar.cpp +++ b/indra/llui/llscrollbar.cpp @@ -1,666 +1,666 @@ -/** - * @file llscrollbar.cpp - * @brief Scrollbar UI widget - * - * $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 "llscrollbar.h" - -#include "llmath.h" -#include "lltimer.h" -#include "v3color.h" - -#include "llbutton.h" -#include "llcriticaldamp.h" -#include "llkeyboard.h" -#include "llui.h" -#include "llfocusmgr.h" -#include "llwindow.h" -#include "llcontrol.h" -#include "llrender.h" -#include "lluictrlfactory.h" - -static LLDefaultChildRegistry::Register register_scrollbar("scroll_bar"); - -LLScrollbar::Params::Params() -: orientation ("orientation", HORIZONTAL), - doc_size ("doc_size", 0), - doc_pos ("doc_pos", 0), - page_size ("page_size", 0), - step_size ("step_size", 1), - thumb_image_vertical("thumb_image_vertical"), - thumb_image_horizontal("thumb_image_horizontal"), - track_image_vertical("track_image_vertical"), - track_image_horizontal("track_image_horizontal"), - track_color("track_color"), - thumb_color("thumb_color"), - thickness("thickness"), - up_button("up_button"), - down_button("down_button"), - left_button("left_button"), - right_button("right_button"), - bg_visible("bg_visible", false), - bg_color("bg_color", LLColor4::black) -{} - -LLScrollbar::LLScrollbar(const Params & p) -: LLUICtrl(p), - mChangeCallback( p.change_callback() ), - mOrientation( p.orientation ), - mDocSize( p.doc_size ), - mDocPos( p.doc_pos ), - mPageSize( p.page_size ), - mStepSize( p.step_size ), - mDocChanged(false), - mDragStartX( 0 ), - mDragStartY( 0 ), - mHoverGlowStrength(0.15f), - mCurGlowStrength(0.f), - mTrackColor( p.track_color() ), - mThumbColor ( p.thumb_color() ), - mThumbImageV(p.thumb_image_vertical), - mThumbImageH(p.thumb_image_horizontal), - mTrackImageV(p.track_image_vertical), - mTrackImageH(p.track_image_horizontal), - mThickness(p.thickness.isProvided() ? p.thickness : LLUI::getInstance()->mSettingGroups["config"]->getS32("UIScrollbarSize")), - mBGVisible(p.bg_visible), - mBGColor(p.bg_color) -{ - updateThumbRect(); - - // Page up and page down buttons - LLRect line_up_rect; - LLRect line_down_rect; - - if( VERTICAL == mOrientation ) - { - line_up_rect.setLeftTopAndSize( 0, getRect().getHeight(), mThickness, mThickness ); - line_down_rect.setOriginAndSize( 0, 0, mThickness, mThickness ); - } - else // HORIZONTAL - { - line_up_rect.setOriginAndSize( 0, 0, mThickness, mThickness ); - line_down_rect.setOriginAndSize( getRect().getWidth() - mThickness, 0, mThickness, mThickness ); - } - - LLButton::Params up_btn(mOrientation == VERTICAL ? p.up_button : p.left_button); - up_btn.name(std::string("Line Up")); - up_btn.rect(line_up_rect); - up_btn.click_callback.function(boost::bind(&LLScrollbar::onLineUpBtnPressed, this, _2)); - up_btn.mouse_held_callback.function(boost::bind(&LLScrollbar::onLineUpBtnPressed, this, _2)); - up_btn.tab_stop(false); - up_btn.follows.flags = (mOrientation == VERTICAL ? (FOLLOWS_RIGHT | FOLLOWS_TOP) : (FOLLOWS_LEFT | FOLLOWS_BOTTOM)); - - addChild(LLUICtrlFactory::create(up_btn)); - - LLButton::Params down_btn(mOrientation == VERTICAL ? p.down_button : p.right_button); - down_btn.name(std::string("Line Down")); - down_btn.rect(line_down_rect); - down_btn.follows.flags(FOLLOWS_RIGHT|FOLLOWS_BOTTOM); - down_btn.click_callback.function(boost::bind(&LLScrollbar::onLineDownBtnPressed, this, _2)); - down_btn.mouse_held_callback.function(boost::bind(&LLScrollbar::onLineDownBtnPressed, this, _2)); - down_btn.tab_stop(false); - - addChild(LLUICtrlFactory::create(down_btn)); -} - - -LLScrollbar::~LLScrollbar() -{ - // Children buttons killed by parent class -} - -void LLScrollbar::setDocParams( S32 size, S32 pos ) -{ - mDocSize = size; - setDocPos(pos); - mDocChanged = true; - - updateThumbRect(); -} - -// returns true if document position really changed -bool LLScrollbar::setDocPos(S32 pos, bool update_thumb) -{ - pos = llclamp(pos, 0, getDocPosMax()); - if (pos != mDocPos) - { - mDocPos = pos; - mDocChanged = true; - - if( mChangeCallback ) - { - mChangeCallback( mDocPos, this ); - } - - if( update_thumb ) - { - updateThumbRect(); - } - return true; - } - return false; -} - -void LLScrollbar::setDocSize(S32 size) -{ - if (size != mDocSize) - { - mDocSize = size; - setDocPos(mDocPos); - mDocChanged = true; - - updateThumbRect(); - } -} - -void LLScrollbar::setPageSize( S32 page_size ) -{ - if (page_size != mPageSize) - { - mPageSize = page_size; - setDocPos(mDocPos); - mDocChanged = true; - - updateThumbRect(); - } -} - -bool LLScrollbar::isAtBeginning() const -{ - return mDocPos == 0; -} - -bool LLScrollbar::isAtEnd() const -{ - return mDocPos == getDocPosMax(); -} - - -void LLScrollbar::updateThumbRect() -{ -// llassert( 0 <= mDocSize ); -// llassert( 0 <= mDocPos && mDocPos <= getDocPosMax() ); - - const S32 THUMB_MIN_LENGTH = 16; - - S32 window_length = (mOrientation == LLScrollbar::HORIZONTAL) ? getRect().getWidth() : getRect().getHeight(); - S32 thumb_bg_length = llmax(0, window_length - 2 * mThickness); - S32 visible_lines = llmin( mDocSize, mPageSize ); - S32 thumb_length = mDocSize ? llmin(llmax( visible_lines * thumb_bg_length / mDocSize, THUMB_MIN_LENGTH), thumb_bg_length) : thumb_bg_length; - - S32 variable_lines = mDocSize - visible_lines; - - if( mOrientation == LLScrollbar::VERTICAL ) - { - S32 thumb_start_max = thumb_bg_length + mThickness; - S32 thumb_start_min = mThickness + THUMB_MIN_LENGTH; - S32 thumb_start = variable_lines ? llmin( llmax(thumb_start_max - (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_max; - - mThumbRect.mLeft = 0; - mThumbRect.mTop = thumb_start; - mThumbRect.mRight = mThickness; - mThumbRect.mBottom = thumb_start - thumb_length; - } - else - { - // Horizontal - S32 thumb_start_max = thumb_bg_length + mThickness - thumb_length; - S32 thumb_start_min = mThickness; - S32 thumb_start = variable_lines ? llmin(llmax( thumb_start_min + (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_min; - - mThumbRect.mLeft = thumb_start; - mThumbRect.mTop = mThickness; - mThumbRect.mRight = thumb_start + thumb_length; - mThumbRect.mBottom = 0; - } -} - -bool LLScrollbar::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // Check children first - bool handled_by_child = LLView::childrenHandleMouseDown(x, y, mask) != NULL; - if( !handled_by_child ) - { - if( mThumbRect.pointInRect(x,y) ) - { - // Start dragging the thumb - // No handler needed for focus lost since this clas has no state that depends on it. - gFocusMgr.setMouseCapture( this ); - mDragStartX = x; - mDragStartY = y; - mOrigRect.mTop = mThumbRect.mTop; - mOrigRect.mBottom = mThumbRect.mBottom; - mOrigRect.mLeft = mThumbRect.mLeft; - mOrigRect.mRight = mThumbRect.mRight; - mLastDelta = 0; - } - else - { - if( - ( (LLScrollbar::VERTICAL == mOrientation) && (mThumbRect.mTop < y) ) || - ( (LLScrollbar::HORIZONTAL == mOrientation) && (x < mThumbRect.mLeft) ) - ) - { - // Page up - pageUp(0); - } - else - if( - ( (LLScrollbar::VERTICAL == mOrientation) && (y < mThumbRect.mBottom) ) || - ( (LLScrollbar::HORIZONTAL == mOrientation) && (mThumbRect.mRight < x) ) - ) - { - // Page down - pageDown(0); - } - } - } - - return true; -} - - -bool LLScrollbar::handleHover(S32 x, S32 y, MASK mask) -{ - // Note: we don't bother sending the event to the children (the arrow buttons) - // because they'll capture the mouse whenever they need hover events. - - bool handled = false; - if( hasMouseCapture() ) - { - S32 height = getRect().getHeight(); - S32 width = getRect().getWidth(); - - if( VERTICAL == mOrientation ) - { -// S32 old_pos = mThumbRect.mTop; - - S32 delta_pixels = y - mDragStartY; - if( mOrigRect.mBottom + delta_pixels < mThickness ) - { - delta_pixels = mThickness - mOrigRect.mBottom - 1; - } - else - if( mOrigRect.mTop + delta_pixels > height - mThickness ) - { - delta_pixels = height - mThickness - mOrigRect.mTop + 1; - } - - mThumbRect.mTop = mOrigRect.mTop + delta_pixels; - mThumbRect.mBottom = mOrigRect.mBottom + delta_pixels; - - S32 thumb_length = mThumbRect.getHeight(); - S32 thumb_track_length = height - 2 * mThickness; - - - if( delta_pixels != mLastDelta || mDocChanged) - { - // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page). - S32 usable_track_length = thumb_track_length - thumb_length; - if( 0 < usable_track_length ) - { - S32 variable_lines = getDocPosMax(); - S32 pos = mThumbRect.mTop; - F32 ratio = F32(pos - mThickness - thumb_length) / usable_track_length; - - S32 new_pos = llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines ); - // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly - // out of sync (less than a line's worth) to make the thumb feel responsive. - changeLine( new_pos - mDocPos, false ); - } - } - - mLastDelta = delta_pixels; - - } - else - { - // Horizontal -// S32 old_pos = mThumbRect.mLeft; - - S32 delta_pixels = x - mDragStartX; - - if( mOrigRect.mLeft + delta_pixels < mThickness ) - { - delta_pixels = mThickness - mOrigRect.mLeft - 1; - } - else - if( mOrigRect.mRight + delta_pixels > width - mThickness ) - { - delta_pixels = width - mThickness - mOrigRect.mRight + 1; - } - - mThumbRect.mLeft = mOrigRect.mLeft + delta_pixels; - mThumbRect.mRight = mOrigRect.mRight + delta_pixels; - - S32 thumb_length = mThumbRect.getWidth(); - S32 thumb_track_length = width - 2 * mThickness; - - if( delta_pixels != mLastDelta || mDocChanged) - { - // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page). - S32 usable_track_length = thumb_track_length - thumb_length; - if( 0 < usable_track_length ) - { - S32 variable_lines = getDocPosMax(); - S32 pos = mThumbRect.mLeft; - F32 ratio = F32(pos - mThickness) / usable_track_length; - - S32 new_pos = llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines); - - // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly - // out of sync (less than a line's worth) to make the thumb feel responsive. - changeLine( new_pos - mDocPos, false ); - } - } - - mLastDelta = delta_pixels; - } - - getWindow()->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; - handled = true; - } - else - { - handled = childrenHandleHover( x, y, mask ) != NULL; - } - - // Opaque - if( !handled ) - { - getWindow()->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; - handled = true; - } - - mDocChanged = false; - return handled; -} // end handleHover - - -bool LLScrollbar::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - bool handled = changeLine( clicks * mStepSize, true ); - return handled; -} - -bool LLScrollbar::handleScrollHWheel(S32 x, S32 y, S32 clicks) -{ - bool handled = false; - if (LLScrollbar::HORIZONTAL == mOrientation) - { - handled = changeLine(clicks * mStepSize, true); - } - return handled; -} - -bool LLScrollbar::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string &tooltip_msg) -{ - // enable this to get drag and drop to control scrollbars - //if (!drop) - //{ - // //TODO: refactor this - // S32 variable_lines = getDocPosMax(); - // S32 pos = (VERTICAL == mOrientation) ? y : x; - // S32 thumb_length = (VERTICAL == mOrientation) ? mThumbRect.getHeight() : mThumbRect.getWidth(); - // S32 thumb_track_length = (VERTICAL == mOrientation) ? (getRect().getHeight() - 2 * SCROLLBAR_SIZE) : (getRect().getWidth() - 2 * SCROLLBAR_SIZE); - // S32 usable_track_length = thumb_track_length - thumb_length; - // F32 ratio = (VERTICAL == mOrientation) ? F32(pos - SCROLLBAR_SIZE - thumb_length) / usable_track_length - // : F32(pos - SCROLLBAR_SIZE) / usable_track_length; - // S32 new_pos = (VERTICAL == mOrientation) ? llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines ) - // : llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines ); - // changeLine( new_pos - mDocPos, true ); - //} - //return true; - return false; -} - -bool LLScrollbar::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - if( hasMouseCapture() ) - { - gFocusMgr.setMouseCapture( NULL ); - handled = true; - } - else - { - // Opaque, so don't just check children - handled = LLView::handleMouseUp( x, y, mask ); - } - - return handled; -} - -bool LLScrollbar::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - // just treat a double click as a second click - return handleMouseDown(x, y, mask); -} - - -void LLScrollbar::reshape(S32 width, S32 height, bool called_from_parent) -{ - if (width == getRect().getWidth() && height == getRect().getHeight()) return; - LLView::reshape( width, height, called_from_parent ); - LLButton* up_button = getChild("Line Up"); - LLButton* down_button = getChild("Line Down"); - - if (mOrientation == VERTICAL) - { - up_button->reshape(up_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness)); - down_button->reshape(down_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness)); - up_button->setOrigin(0, getRect().getHeight() - up_button->getRect().getHeight()); - down_button->setOrigin(0, 0); - } - else - { - up_button->reshape(llmin(getRect().getWidth() / 2, mThickness), up_button->getRect().getHeight()); - down_button->reshape(llmin(getRect().getWidth() / 2, mThickness), down_button->getRect().getHeight()); - up_button->setOrigin(0, 0); - down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), 0); - } - updateThumbRect(); -} - - -void LLScrollbar::draw() -{ - if (!getRect().isValid()) return; - - if(mBGVisible) - { - gl_rect_2d(getLocalRect(), mBGColor.get(), true); - } - - S32 local_mouse_x; - S32 local_mouse_y; - LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); - bool other_captor = gFocusMgr.getMouseCapture() && gFocusMgr.getMouseCapture() != this; - bool hovered = getEnabled() && !other_captor && (hasMouseCapture() || mThumbRect.pointInRect(local_mouse_x, local_mouse_y)); - if (hovered) - { - mCurGlowStrength = lerp(mCurGlowStrength, mHoverGlowStrength, LLSmoothInterpolation::getInterpolant(0.05f)); - } - else - { - mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLSmoothInterpolation::getInterpolant(0.05f)); - } - - // Draw background and thumb. - if ( ( mOrientation == VERTICAL&&(mThumbImageV.isNull() || mThumbImageH.isNull()) ) - || (mOrientation == HORIZONTAL&&(mTrackImageH.isNull() || mTrackImageV.isNull()) )) - { - gl_rect_2d(mOrientation == HORIZONTAL ? mThickness : 0, - mOrientation == VERTICAL ? getRect().getHeight() - 2 * mThickness : getRect().getHeight(), - mOrientation == HORIZONTAL ? getRect().getWidth() - 2 * mThickness : getRect().getWidth(), - mOrientation == VERTICAL ? mThickness : 0, mTrackColor.get(), true); - - gl_rect_2d(mThumbRect, mThumbColor.get(), true); - - } - else - { - // Thumb - LLRect outline_rect = mThumbRect; - outline_rect.stretch(2); - // Background - - if(mOrientation == HORIZONTAL) - { - mTrackImageH->drawSolid(mThickness //S32 x - , 0 //S32 y - , getRect().getWidth() - 2 * mThickness //S32 width - , getRect().getHeight() //S32 height - , mTrackColor.get()); //const LLColor4& color - - if (gFocusMgr.getKeyboardFocus() == this) - { - mTrackImageH->draw(outline_rect, gFocusMgr.getFocusColor()); - } - - mThumbImageH->draw(mThumbRect, mThumbColor.get()); - if (mCurGlowStrength > 0.01f) - { - gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); - mThumbImageH->drawSolid(mThumbRect, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength)); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - } - - } - else if(mOrientation == VERTICAL) - { - mTrackImageV->drawSolid( 0 //S32 x - , mThickness //S32 y - , getRect().getWidth() //S32 width - , getRect().getHeight() - 2 * mThickness //S32 height - , mTrackColor.get()); //const LLColor4& color - if (gFocusMgr.getKeyboardFocus() == this) - { - mTrackImageV->draw(outline_rect, gFocusMgr.getFocusColor()); - } - - mThumbImageV->draw(mThumbRect, mThumbColor.get()); - if (mCurGlowStrength > 0.01f) - { - gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); - mThumbImageV->drawSolid(mThumbRect, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength)); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - } - } - } - - // Draw children - LLView::draw(); -} // end draw - - -bool LLScrollbar::changeLine( S32 delta, bool update_thumb ) -{ - return setDocPos(mDocPos + delta, update_thumb); -} - -void LLScrollbar::setValue(const LLSD& value) -{ - setDocPos((S32) value.asInteger()); -} - - -bool LLScrollbar::handleKeyHere(KEY key, MASK mask) -{ - if (getDocPosMax() == 0 && !getVisible()) - { - return false; - } - - bool handled = false; - - switch( key ) - { - case KEY_HOME: - setDocPos( 0 ); - handled = true; - break; - - case KEY_END: - setDocPos( getDocPosMax() ); - handled = true; - break; - - case KEY_DOWN: - setDocPos( getDocPos() + mStepSize ); - handled = true; - break; - - case KEY_UP: - setDocPos( getDocPos() - mStepSize ); - handled = true; - break; - - case KEY_PAGE_DOWN: - pageDown(1); - break; - - case KEY_PAGE_UP: - pageUp(1); - break; - } - - return handled; -} - -void LLScrollbar::pageUp(S32 overlap) -{ - if (mDocSize > mPageSize) - { - changeLine( -(mPageSize - overlap), true ); - } -} - -void LLScrollbar::pageDown(S32 overlap) -{ - if (mDocSize > mPageSize) - { - changeLine( mPageSize - overlap, true ); - } -} - -void LLScrollbar::onLineUpBtnPressed( const LLSD& data ) -{ - changeLine( -mStepSize, true ); -} - -void LLScrollbar::onLineDownBtnPressed( const LLSD& data ) -{ - changeLine( mStepSize, true ); -} - -void LLScrollbar::setThickness(S32 thickness) -{ - mThickness = thickness < 0 ? LLUI::getInstance()->mSettingGroups["config"]->getS32("UIScrollbarSize") : thickness; -} +/** + * @file llscrollbar.cpp + * @brief Scrollbar UI widget + * + * $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 "llscrollbar.h" + +#include "llmath.h" +#include "lltimer.h" +#include "v3color.h" + +#include "llbutton.h" +#include "llcriticaldamp.h" +#include "llkeyboard.h" +#include "llui.h" +#include "llfocusmgr.h" +#include "llwindow.h" +#include "llcontrol.h" +#include "llrender.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register register_scrollbar("scroll_bar"); + +LLScrollbar::Params::Params() +: orientation ("orientation", HORIZONTAL), + doc_size ("doc_size", 0), + doc_pos ("doc_pos", 0), + page_size ("page_size", 0), + step_size ("step_size", 1), + thumb_image_vertical("thumb_image_vertical"), + thumb_image_horizontal("thumb_image_horizontal"), + track_image_vertical("track_image_vertical"), + track_image_horizontal("track_image_horizontal"), + track_color("track_color"), + thumb_color("thumb_color"), + thickness("thickness"), + up_button("up_button"), + down_button("down_button"), + left_button("left_button"), + right_button("right_button"), + bg_visible("bg_visible", false), + bg_color("bg_color", LLColor4::black) +{} + +LLScrollbar::LLScrollbar(const Params & p) +: LLUICtrl(p), + mChangeCallback( p.change_callback() ), + mOrientation( p.orientation ), + mDocSize( p.doc_size ), + mDocPos( p.doc_pos ), + mPageSize( p.page_size ), + mStepSize( p.step_size ), + mDocChanged(false), + mDragStartX( 0 ), + mDragStartY( 0 ), + mHoverGlowStrength(0.15f), + mCurGlowStrength(0.f), + mTrackColor( p.track_color() ), + mThumbColor ( p.thumb_color() ), + mThumbImageV(p.thumb_image_vertical), + mThumbImageH(p.thumb_image_horizontal), + mTrackImageV(p.track_image_vertical), + mTrackImageH(p.track_image_horizontal), + mThickness(p.thickness.isProvided() ? p.thickness : LLUI::getInstance()->mSettingGroups["config"]->getS32("UIScrollbarSize")), + mBGVisible(p.bg_visible), + mBGColor(p.bg_color) +{ + updateThumbRect(); + + // Page up and page down buttons + LLRect line_up_rect; + LLRect line_down_rect; + + if( VERTICAL == mOrientation ) + { + line_up_rect.setLeftTopAndSize( 0, getRect().getHeight(), mThickness, mThickness ); + line_down_rect.setOriginAndSize( 0, 0, mThickness, mThickness ); + } + else // HORIZONTAL + { + line_up_rect.setOriginAndSize( 0, 0, mThickness, mThickness ); + line_down_rect.setOriginAndSize( getRect().getWidth() - mThickness, 0, mThickness, mThickness ); + } + + LLButton::Params up_btn(mOrientation == VERTICAL ? p.up_button : p.left_button); + up_btn.name(std::string("Line Up")); + up_btn.rect(line_up_rect); + up_btn.click_callback.function(boost::bind(&LLScrollbar::onLineUpBtnPressed, this, _2)); + up_btn.mouse_held_callback.function(boost::bind(&LLScrollbar::onLineUpBtnPressed, this, _2)); + up_btn.tab_stop(false); + up_btn.follows.flags = (mOrientation == VERTICAL ? (FOLLOWS_RIGHT | FOLLOWS_TOP) : (FOLLOWS_LEFT | FOLLOWS_BOTTOM)); + + addChild(LLUICtrlFactory::create(up_btn)); + + LLButton::Params down_btn(mOrientation == VERTICAL ? p.down_button : p.right_button); + down_btn.name(std::string("Line Down")); + down_btn.rect(line_down_rect); + down_btn.follows.flags(FOLLOWS_RIGHT|FOLLOWS_BOTTOM); + down_btn.click_callback.function(boost::bind(&LLScrollbar::onLineDownBtnPressed, this, _2)); + down_btn.mouse_held_callback.function(boost::bind(&LLScrollbar::onLineDownBtnPressed, this, _2)); + down_btn.tab_stop(false); + + addChild(LLUICtrlFactory::create(down_btn)); +} + + +LLScrollbar::~LLScrollbar() +{ + // Children buttons killed by parent class +} + +void LLScrollbar::setDocParams( S32 size, S32 pos ) +{ + mDocSize = size; + setDocPos(pos); + mDocChanged = true; + + updateThumbRect(); +} + +// returns true if document position really changed +bool LLScrollbar::setDocPos(S32 pos, bool update_thumb) +{ + pos = llclamp(pos, 0, getDocPosMax()); + if (pos != mDocPos) + { + mDocPos = pos; + mDocChanged = true; + + if( mChangeCallback ) + { + mChangeCallback( mDocPos, this ); + } + + if( update_thumb ) + { + updateThumbRect(); + } + return true; + } + return false; +} + +void LLScrollbar::setDocSize(S32 size) +{ + if (size != mDocSize) + { + mDocSize = size; + setDocPos(mDocPos); + mDocChanged = true; + + updateThumbRect(); + } +} + +void LLScrollbar::setPageSize( S32 page_size ) +{ + if (page_size != mPageSize) + { + mPageSize = page_size; + setDocPos(mDocPos); + mDocChanged = true; + + updateThumbRect(); + } +} + +bool LLScrollbar::isAtBeginning() const +{ + return mDocPos == 0; +} + +bool LLScrollbar::isAtEnd() const +{ + return mDocPos == getDocPosMax(); +} + + +void LLScrollbar::updateThumbRect() +{ +// llassert( 0 <= mDocSize ); +// llassert( 0 <= mDocPos && mDocPos <= getDocPosMax() ); + + const S32 THUMB_MIN_LENGTH = 16; + + S32 window_length = (mOrientation == LLScrollbar::HORIZONTAL) ? getRect().getWidth() : getRect().getHeight(); + S32 thumb_bg_length = llmax(0, window_length - 2 * mThickness); + S32 visible_lines = llmin( mDocSize, mPageSize ); + S32 thumb_length = mDocSize ? llmin(llmax( visible_lines * thumb_bg_length / mDocSize, THUMB_MIN_LENGTH), thumb_bg_length) : thumb_bg_length; + + S32 variable_lines = mDocSize - visible_lines; + + if( mOrientation == LLScrollbar::VERTICAL ) + { + S32 thumb_start_max = thumb_bg_length + mThickness; + S32 thumb_start_min = mThickness + THUMB_MIN_LENGTH; + S32 thumb_start = variable_lines ? llmin( llmax(thumb_start_max - (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_max; + + mThumbRect.mLeft = 0; + mThumbRect.mTop = thumb_start; + mThumbRect.mRight = mThickness; + mThumbRect.mBottom = thumb_start - thumb_length; + } + else + { + // Horizontal + S32 thumb_start_max = thumb_bg_length + mThickness - thumb_length; + S32 thumb_start_min = mThickness; + S32 thumb_start = variable_lines ? llmin(llmax( thumb_start_min + (mDocPos * (thumb_bg_length - thumb_length)) / variable_lines, thumb_start_min), thumb_start_max ) : thumb_start_min; + + mThumbRect.mLeft = thumb_start; + mThumbRect.mTop = mThickness; + mThumbRect.mRight = thumb_start + thumb_length; + mThumbRect.mBottom = 0; + } +} + +bool LLScrollbar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Check children first + bool handled_by_child = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + if( !handled_by_child ) + { + if( mThumbRect.pointInRect(x,y) ) + { + // Start dragging the thumb + // No handler needed for focus lost since this clas has no state that depends on it. + gFocusMgr.setMouseCapture( this ); + mDragStartX = x; + mDragStartY = y; + mOrigRect.mTop = mThumbRect.mTop; + mOrigRect.mBottom = mThumbRect.mBottom; + mOrigRect.mLeft = mThumbRect.mLeft; + mOrigRect.mRight = mThumbRect.mRight; + mLastDelta = 0; + } + else + { + if( + ( (LLScrollbar::VERTICAL == mOrientation) && (mThumbRect.mTop < y) ) || + ( (LLScrollbar::HORIZONTAL == mOrientation) && (x < mThumbRect.mLeft) ) + ) + { + // Page up + pageUp(0); + } + else + if( + ( (LLScrollbar::VERTICAL == mOrientation) && (y < mThumbRect.mBottom) ) || + ( (LLScrollbar::HORIZONTAL == mOrientation) && (mThumbRect.mRight < x) ) + ) + { + // Page down + pageDown(0); + } + } + } + + return true; +} + + +bool LLScrollbar::handleHover(S32 x, S32 y, MASK mask) +{ + // Note: we don't bother sending the event to the children (the arrow buttons) + // because they'll capture the mouse whenever they need hover events. + + bool handled = false; + if( hasMouseCapture() ) + { + S32 height = getRect().getHeight(); + S32 width = getRect().getWidth(); + + if( VERTICAL == mOrientation ) + { +// S32 old_pos = mThumbRect.mTop; + + S32 delta_pixels = y - mDragStartY; + if( mOrigRect.mBottom + delta_pixels < mThickness ) + { + delta_pixels = mThickness - mOrigRect.mBottom - 1; + } + else + if( mOrigRect.mTop + delta_pixels > height - mThickness ) + { + delta_pixels = height - mThickness - mOrigRect.mTop + 1; + } + + mThumbRect.mTop = mOrigRect.mTop + delta_pixels; + mThumbRect.mBottom = mOrigRect.mBottom + delta_pixels; + + S32 thumb_length = mThumbRect.getHeight(); + S32 thumb_track_length = height - 2 * mThickness; + + + if( delta_pixels != mLastDelta || mDocChanged) + { + // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page). + S32 usable_track_length = thumb_track_length - thumb_length; + if( 0 < usable_track_length ) + { + S32 variable_lines = getDocPosMax(); + S32 pos = mThumbRect.mTop; + F32 ratio = F32(pos - mThickness - thumb_length) / usable_track_length; + + S32 new_pos = llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines ); + // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly + // out of sync (less than a line's worth) to make the thumb feel responsive. + changeLine( new_pos - mDocPos, false ); + } + } + + mLastDelta = delta_pixels; + + } + else + { + // Horizontal +// S32 old_pos = mThumbRect.mLeft; + + S32 delta_pixels = x - mDragStartX; + + if( mOrigRect.mLeft + delta_pixels < mThickness ) + { + delta_pixels = mThickness - mOrigRect.mLeft - 1; + } + else + if( mOrigRect.mRight + delta_pixels > width - mThickness ) + { + delta_pixels = width - mThickness - mOrigRect.mRight + 1; + } + + mThumbRect.mLeft = mOrigRect.mLeft + delta_pixels; + mThumbRect.mRight = mOrigRect.mRight + delta_pixels; + + S32 thumb_length = mThumbRect.getWidth(); + S32 thumb_track_length = width - 2 * mThickness; + + if( delta_pixels != mLastDelta || mDocChanged) + { + // Note: delta_pixels increases as you go up. mDocPos increases down (line 0 is at the top of the page). + S32 usable_track_length = thumb_track_length - thumb_length; + if( 0 < usable_track_length ) + { + S32 variable_lines = getDocPosMax(); + S32 pos = mThumbRect.mLeft; + F32 ratio = F32(pos - mThickness) / usable_track_length; + + S32 new_pos = llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines); + + // Note: we do not call updateThumbRect() here. Instead we let the thumb and the document go slightly + // out of sync (less than a line's worth) to make the thumb feel responsive. + changeLine( new_pos - mDocPos, false ); + } + } + + mLastDelta = delta_pixels; + } + + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; + handled = true; + } + else + { + handled = childrenHandleHover( x, y, mask ) != NULL; + } + + // Opaque + if( !handled ) + { + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; + handled = true; + } + + mDocChanged = false; + return handled; +} // end handleHover + + +bool LLScrollbar::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + bool handled = changeLine( clicks * mStepSize, true ); + return handled; +} + +bool LLScrollbar::handleScrollHWheel(S32 x, S32 y, S32 clicks) +{ + bool handled = false; + if (LLScrollbar::HORIZONTAL == mOrientation) + { + handled = changeLine(clicks * mStepSize, true); + } + return handled; +} + +bool LLScrollbar::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string &tooltip_msg) +{ + // enable this to get drag and drop to control scrollbars + //if (!drop) + //{ + // //TODO: refactor this + // S32 variable_lines = getDocPosMax(); + // S32 pos = (VERTICAL == mOrientation) ? y : x; + // S32 thumb_length = (VERTICAL == mOrientation) ? mThumbRect.getHeight() : mThumbRect.getWidth(); + // S32 thumb_track_length = (VERTICAL == mOrientation) ? (getRect().getHeight() - 2 * SCROLLBAR_SIZE) : (getRect().getWidth() - 2 * SCROLLBAR_SIZE); + // S32 usable_track_length = thumb_track_length - thumb_length; + // F32 ratio = (VERTICAL == mOrientation) ? F32(pos - SCROLLBAR_SIZE - thumb_length) / usable_track_length + // : F32(pos - SCROLLBAR_SIZE) / usable_track_length; + // S32 new_pos = (VERTICAL == mOrientation) ? llclamp( S32(variable_lines - ratio * variable_lines + 0.5f), 0, variable_lines ) + // : llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines ); + // changeLine( new_pos - mDocPos, true ); + //} + //return true; + return false; +} + +bool LLScrollbar::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + if( hasMouseCapture() ) + { + gFocusMgr.setMouseCapture( NULL ); + handled = true; + } + else + { + // Opaque, so don't just check children + handled = LLView::handleMouseUp( x, y, mask ); + } + + return handled; +} + +bool LLScrollbar::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + // just treat a double click as a second click + return handleMouseDown(x, y, mask); +} + + +void LLScrollbar::reshape(S32 width, S32 height, bool called_from_parent) +{ + if (width == getRect().getWidth() && height == getRect().getHeight()) return; + LLView::reshape( width, height, called_from_parent ); + LLButton* up_button = getChild("Line Up"); + LLButton* down_button = getChild("Line Down"); + + if (mOrientation == VERTICAL) + { + up_button->reshape(up_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness)); + down_button->reshape(down_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, mThickness)); + up_button->setOrigin(0, getRect().getHeight() - up_button->getRect().getHeight()); + down_button->setOrigin(0, 0); + } + else + { + up_button->reshape(llmin(getRect().getWidth() / 2, mThickness), up_button->getRect().getHeight()); + down_button->reshape(llmin(getRect().getWidth() / 2, mThickness), down_button->getRect().getHeight()); + up_button->setOrigin(0, 0); + down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), 0); + } + updateThumbRect(); +} + + +void LLScrollbar::draw() +{ + if (!getRect().isValid()) return; + + if(mBGVisible) + { + gl_rect_2d(getLocalRect(), mBGColor.get(), true); + } + + S32 local_mouse_x; + S32 local_mouse_y; + LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); + bool other_captor = gFocusMgr.getMouseCapture() && gFocusMgr.getMouseCapture() != this; + bool hovered = getEnabled() && !other_captor && (hasMouseCapture() || mThumbRect.pointInRect(local_mouse_x, local_mouse_y)); + if (hovered) + { + mCurGlowStrength = lerp(mCurGlowStrength, mHoverGlowStrength, LLSmoothInterpolation::getInterpolant(0.05f)); + } + else + { + mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLSmoothInterpolation::getInterpolant(0.05f)); + } + + // Draw background and thumb. + if ( ( mOrientation == VERTICAL&&(mThumbImageV.isNull() || mThumbImageH.isNull()) ) + || (mOrientation == HORIZONTAL&&(mTrackImageH.isNull() || mTrackImageV.isNull()) )) + { + gl_rect_2d(mOrientation == HORIZONTAL ? mThickness : 0, + mOrientation == VERTICAL ? getRect().getHeight() - 2 * mThickness : getRect().getHeight(), + mOrientation == HORIZONTAL ? getRect().getWidth() - 2 * mThickness : getRect().getWidth(), + mOrientation == VERTICAL ? mThickness : 0, mTrackColor.get(), true); + + gl_rect_2d(mThumbRect, mThumbColor.get(), true); + + } + else + { + // Thumb + LLRect outline_rect = mThumbRect; + outline_rect.stretch(2); + // Background + + if(mOrientation == HORIZONTAL) + { + mTrackImageH->drawSolid(mThickness //S32 x + , 0 //S32 y + , getRect().getWidth() - 2 * mThickness //S32 width + , getRect().getHeight() //S32 height + , mTrackColor.get()); //const LLColor4& color + + if (gFocusMgr.getKeyboardFocus() == this) + { + mTrackImageH->draw(outline_rect, gFocusMgr.getFocusColor()); + } + + mThumbImageH->draw(mThumbRect, mThumbColor.get()); + if (mCurGlowStrength > 0.01f) + { + gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); + mThumbImageH->drawSolid(mThumbRect, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength)); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } + + } + else if(mOrientation == VERTICAL) + { + mTrackImageV->drawSolid( 0 //S32 x + , mThickness //S32 y + , getRect().getWidth() //S32 width + , getRect().getHeight() - 2 * mThickness //S32 height + , mTrackColor.get()); //const LLColor4& color + if (gFocusMgr.getKeyboardFocus() == this) + { + mTrackImageV->draw(outline_rect, gFocusMgr.getFocusColor()); + } + + mThumbImageV->draw(mThumbRect, mThumbColor.get()); + if (mCurGlowStrength > 0.01f) + { + gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); + mThumbImageV->drawSolid(mThumbRect, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength)); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } + } + } + + // Draw children + LLView::draw(); +} // end draw + + +bool LLScrollbar::changeLine( S32 delta, bool update_thumb ) +{ + return setDocPos(mDocPos + delta, update_thumb); +} + +void LLScrollbar::setValue(const LLSD& value) +{ + setDocPos((S32) value.asInteger()); +} + + +bool LLScrollbar::handleKeyHere(KEY key, MASK mask) +{ + if (getDocPosMax() == 0 && !getVisible()) + { + return false; + } + + bool handled = false; + + switch( key ) + { + case KEY_HOME: + setDocPos( 0 ); + handled = true; + break; + + case KEY_END: + setDocPos( getDocPosMax() ); + handled = true; + break; + + case KEY_DOWN: + setDocPos( getDocPos() + mStepSize ); + handled = true; + break; + + case KEY_UP: + setDocPos( getDocPos() - mStepSize ); + handled = true; + break; + + case KEY_PAGE_DOWN: + pageDown(1); + break; + + case KEY_PAGE_UP: + pageUp(1); + break; + } + + return handled; +} + +void LLScrollbar::pageUp(S32 overlap) +{ + if (mDocSize > mPageSize) + { + changeLine( -(mPageSize - overlap), true ); + } +} + +void LLScrollbar::pageDown(S32 overlap) +{ + if (mDocSize > mPageSize) + { + changeLine( mPageSize - overlap, true ); + } +} + +void LLScrollbar::onLineUpBtnPressed( const LLSD& data ) +{ + changeLine( -mStepSize, true ); +} + +void LLScrollbar::onLineDownBtnPressed( const LLSD& data ) +{ + changeLine( mStepSize, true ); +} + +void LLScrollbar::setThickness(S32 thickness) +{ + mThickness = thickness < 0 ? LLUI::getInstance()->mSettingGroups["config"]->getS32("UIScrollbarSize") : thickness; +} diff --git a/indra/llui/llscrollbar.h b/indra/llui/llscrollbar.h index b93c988b37..7b935aa51b 100644 --- a/indra/llui/llscrollbar.h +++ b/indra/llui/llscrollbar.h @@ -1,167 +1,167 @@ -/** - * @file llscrollbar.h - * @brief Scrollbar UI widget - * - * $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$ - */ - -#ifndef LL_SCROLLBAR_H -#define LL_SCROLLBAR_H - -#include "stdtypes.h" -#include "lluictrl.h" -#include "v4color.h" -#include "llbutton.h" - -// -// Classes -// -class LLScrollbar -: public LLUICtrl -{ -public: - - typedef boost::function callback_t; - struct Params - : public LLInitParam::Block - { - Mandatory orientation; - Mandatory doc_size; - Mandatory doc_pos; - Mandatory page_size; - - Optional change_callback; - Optional step_size; - Optional thickness; - - Optional thumb_image_vertical, - thumb_image_horizontal, - track_image_horizontal, - track_image_vertical; - - Optional bg_visible; - - Optional track_color, - thumb_color, - bg_color; - - Optional up_button; - Optional down_button; - Optional left_button; - Optional right_button; - - Params(); - }; - -protected: - LLScrollbar (const Params & p); - friend class LLUICtrlFactory; - -public: - virtual ~LLScrollbar(); - - virtual void setValue(const LLSD& value); - - // Overrides from LLView - virtual bool handleKeyHere(KEY key, MASK mask); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks); - virtual bool handleScrollHWheel(S32 x, S32 y, S32 clicks); - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string &tooltip_msg); - - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - - virtual void draw(); - - // How long the "document" is. - void setDocSize( S32 size ); - S32 getDocSize() const { return mDocSize; } - - // How many "lines" the "document" has scrolled. - // 0 <= DocPos <= DocSize - DocVisibile - bool setDocPos( S32 pos, bool update_thumb = true ); - S32 getDocPos() const { return mDocPos; } - - bool isAtBeginning() const; - bool isAtEnd() const; - - // Setting both at once. - void setDocParams( S32 size, S32 pos ); - - // How many "lines" of the "document" is can appear on a page. - void setPageSize( S32 page_size ); - S32 getPageSize() const { return mPageSize; } - - // The farthest the document can be scrolled (top of the last page). - S32 getDocPosMax() const { return llmax( 0, mDocSize - mPageSize); } - - void pageUp(S32 overlap); - void pageDown(S32 overlap); - - void onLineUpBtnPressed(const LLSD& data); - void onLineDownBtnPressed(const LLSD& data); - - S32 getThickness() const { return mThickness; } - void setThickness(S32 thickness); - -private: - void updateThumbRect(); - bool changeLine(S32 delta, bool update_thumb ); - - callback_t mChangeCallback; - - const EOrientation mOrientation; - S32 mDocSize; // Size of the document that the scrollbar is modeling. Units depend on the user. 0 <= mDocSize. - S32 mDocPos; // Position within the doc that the scrollbar is modeling, in "lines" (user size) - S32 mPageSize; // Maximum number of lines that can be seen at one time. - S32 mStepSize; - bool mDocChanged; - - LLRect mThumbRect; - S32 mDragStartX; - S32 mDragStartY; - F32 mHoverGlowStrength; - F32 mCurGlowStrength; - - LLRect mOrigRect; - S32 mLastDelta; - - LLUIColor mTrackColor; - LLUIColor mThumbColor; - LLUIColor mBGColor; - - bool mBGVisible; - - LLUIImagePtr mThumbImageV; - LLUIImagePtr mThumbImageH; - LLUIImagePtr mTrackImageV; - LLUIImagePtr mTrackImageH; - - S32 mThickness; -}; - - -#endif // LL_SCROLLBAR_H +/** + * @file llscrollbar.h + * @brief Scrollbar UI widget + * + * $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$ + */ + +#ifndef LL_SCROLLBAR_H +#define LL_SCROLLBAR_H + +#include "stdtypes.h" +#include "lluictrl.h" +#include "v4color.h" +#include "llbutton.h" + +// +// Classes +// +class LLScrollbar +: public LLUICtrl +{ +public: + + typedef boost::function callback_t; + struct Params + : public LLInitParam::Block + { + Mandatory orientation; + Mandatory doc_size; + Mandatory doc_pos; + Mandatory page_size; + + Optional change_callback; + Optional step_size; + Optional thickness; + + Optional thumb_image_vertical, + thumb_image_horizontal, + track_image_horizontal, + track_image_vertical; + + Optional bg_visible; + + Optional track_color, + thumb_color, + bg_color; + + Optional up_button; + Optional down_button; + Optional left_button; + Optional right_button; + + Params(); + }; + +protected: + LLScrollbar (const Params & p); + friend class LLUICtrlFactory; + +public: + virtual ~LLScrollbar(); + + virtual void setValue(const LLSD& value); + + // Overrides from LLView + virtual bool handleKeyHere(KEY key, MASK mask); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks); + virtual bool handleScrollHWheel(S32 x, S32 y, S32 clicks); + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string &tooltip_msg); + + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + + virtual void draw(); + + // How long the "document" is. + void setDocSize( S32 size ); + S32 getDocSize() const { return mDocSize; } + + // How many "lines" the "document" has scrolled. + // 0 <= DocPos <= DocSize - DocVisibile + bool setDocPos( S32 pos, bool update_thumb = true ); + S32 getDocPos() const { return mDocPos; } + + bool isAtBeginning() const; + bool isAtEnd() const; + + // Setting both at once. + void setDocParams( S32 size, S32 pos ); + + // How many "lines" of the "document" is can appear on a page. + void setPageSize( S32 page_size ); + S32 getPageSize() const { return mPageSize; } + + // The farthest the document can be scrolled (top of the last page). + S32 getDocPosMax() const { return llmax( 0, mDocSize - mPageSize); } + + void pageUp(S32 overlap); + void pageDown(S32 overlap); + + void onLineUpBtnPressed(const LLSD& data); + void onLineDownBtnPressed(const LLSD& data); + + S32 getThickness() const { return mThickness; } + void setThickness(S32 thickness); + +private: + void updateThumbRect(); + bool changeLine(S32 delta, bool update_thumb ); + + callback_t mChangeCallback; + + const EOrientation mOrientation; + S32 mDocSize; // Size of the document that the scrollbar is modeling. Units depend on the user. 0 <= mDocSize. + S32 mDocPos; // Position within the doc that the scrollbar is modeling, in "lines" (user size) + S32 mPageSize; // Maximum number of lines that can be seen at one time. + S32 mStepSize; + bool mDocChanged; + + LLRect mThumbRect; + S32 mDragStartX; + S32 mDragStartY; + F32 mHoverGlowStrength; + F32 mCurGlowStrength; + + LLRect mOrigRect; + S32 mLastDelta; + + LLUIColor mTrackColor; + LLUIColor mThumbColor; + LLUIColor mBGColor; + + bool mBGVisible; + + LLUIImagePtr mThumbImageV; + LLUIImagePtr mThumbImageH; + LLUIImagePtr mTrackImageV; + LLUIImagePtr mTrackImageH; + + S32 mThickness; +}; + + +#endif // LL_SCROLLBAR_H diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index 1e99d408cc..df99c4f636 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -1,802 +1,802 @@ -/** - * @file llscrollcontainer.cpp - * @brief LLScrollContainer base 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 "llscrollcontainer.h" - -#include "llrender.h" -#include "llcontainerview.h" -#include "lllocalcliprect.h" -// #include "llfolderview.h" -#include "llscrollingpanellist.h" -#include "llscrollbar.h" -#include "llui.h" -#include "llkeyboard.h" -#include "llviewborder.h" -#include "llfocusmgr.h" -#include "llframetimer.h" -#include "lluictrlfactory.h" -#include "llpanel.h" -#include "llfontgl.h" - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -static const S32 VERTICAL_MULTIPLE = 16; -static const F32 AUTO_SCROLL_RATE_ACCEL = 120.f; - -///---------------------------------------------------------------------------- -/// Class LLScrollContainer -///---------------------------------------------------------------------------- - -static LLDefaultChildRegistry::Register r("scroll_container"); - -#include "llscrollingpanellist.h" -#include "llcontainerview.h" -#include "llpanel.h" - -static ScrollContainerRegistry::Register r1("scrolling_panel_list"); -static ScrollContainerRegistry::Register r2("container_view"); -static ScrollContainerRegistry::Register r3("panel", &LLPanel::fromXML); - -LLScrollContainer::Params::Params() -: is_opaque("opaque"), - bg_color("color"), - border_visible("border_visible"), - hide_scrollbar("hide_scrollbar"), - ignore_arrow_keys("ignore_arrow_keys"), - min_auto_scroll_rate("min_auto_scroll_rate", 100), - max_auto_scroll_rate("max_auto_scroll_rate", 1000), - max_auto_scroll_zone("max_auto_scroll_zone", 16), - reserve_scroll_corner("reserve_scroll_corner", false), - size("size", -1) -{} - - -// Default constructor -LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p) -: LLUICtrl(p), - mAutoScrolling( false ), - mAutoScrollRate( 0.f ), - mBackgroundColor(p.bg_color()), - mIsOpaque(p.is_opaque), - mHideScrollbar(p.hide_scrollbar), - mIgnoreArrowKeys(p.ignore_arrow_keys), - mReserveScrollCorner(p.reserve_scroll_corner), - mMinAutoScrollRate(p.min_auto_scroll_rate), - mMaxAutoScrollRate(p.max_auto_scroll_rate), - mMaxAutoScrollZone(p.max_auto_scroll_zone), - mScrolledView(NULL), - mSize(p.size) -{ - static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); - S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); - - LLRect border_rect( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - LLViewBorder::Params params; - params.name("scroll border"); - params.rect(border_rect); - params.visible(p.border_visible); - params.bevel_style(LLViewBorder::BEVEL_IN); - mBorder = LLUICtrlFactory::create (params); - LLView::addChild( mBorder ); - - mInnerRect = getLocalRect(); - mInnerRect.stretch( -getBorderWidth() ); - - LLRect vertical_scroll_rect = mInnerRect; - vertical_scroll_rect.mLeft = vertical_scroll_rect.mRight - scrollbar_size; - LLScrollbar::Params sbparams; - sbparams.name("scrollable vertical"); - sbparams.rect(vertical_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.visible(false); - sbparams.change_callback(p.scroll_callback); - mScrollbar[VERTICAL] = LLUICtrlFactory::create (sbparams); - LLView::addChild( mScrollbar[VERTICAL] ); - - LLRect horizontal_scroll_rect; - horizontal_scroll_rect.mTop = scrollbar_size; - horizontal_scroll_rect.mRight = mInnerRect.getWidth(); - sbparams.name("scrollable horizontal"); - sbparams.rect(horizontal_scroll_rect); - sbparams.orientation(LLScrollbar::HORIZONTAL); - sbparams.doc_size(mInnerRect.getWidth()); - sbparams.doc_pos(0); - sbparams.page_size(mInnerRect.getWidth()); - sbparams.step_size(VERTICAL_MULTIPLE); - sbparams.visible(false); - sbparams.follows.flags(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_BOTTOM); - sbparams.change_callback(p.scroll_callback); - mScrollbar[HORIZONTAL] = LLUICtrlFactory::create (sbparams); - LLView::addChild( mScrollbar[HORIZONTAL] ); -} - -// Destroys the object -LLScrollContainer::~LLScrollContainer( void ) -{ - // mScrolledView and mScrollbar are child views, so the LLView - // destructor takes care of memory deallocation. - for( S32 i = 0; i < ORIENTATION_COUNT; i++ ) - { - mScrollbar[i] = NULL; - } - mScrolledView = NULL; -} - -// internal scrollbar handlers -// virtual -void LLScrollContainer::scrollHorizontal( S32 new_pos ) -{ - if( mScrolledView ) - { - LLRect doc_rect = mScrolledView->getRect(); - S32 old_pos = -(doc_rect.mLeft - mInnerRect.mLeft); - mScrolledView->translate( -(new_pos - old_pos), 0 ); - } -} - -// virtual -void LLScrollContainer::scrollVertical( S32 new_pos ) -{ - if( mScrolledView ) - { - LLRect doc_rect = mScrolledView->getRect(); - S32 old_pos = doc_rect.mTop - mInnerRect.mTop; - mScrolledView->translate( 0, new_pos - old_pos ); - } -} - -// LLView functionality -void LLScrollContainer::reshape(S32 width, S32 height, - bool called_from_parent) -{ - LLUICtrl::reshape( width, height, called_from_parent ); - - mInnerRect = getLocalRect(); - mInnerRect.stretch( -getBorderWidth() ); - - if (mScrolledView) - { - const LLRect& scrolled_rect = mScrolledView->getRect(); - - S32 visible_width = 0; - S32 visible_height = 0; - bool show_v_scrollbar = false; - bool show_h_scrollbar = false; - calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); - - mScrollbar[VERTICAL]->setDocSize( scrolled_rect.getHeight() ); - mScrollbar[VERTICAL]->setPageSize( visible_height ); - - mScrollbar[HORIZONTAL]->setDocSize( scrolled_rect.getWidth() ); - mScrollbar[HORIZONTAL]->setPageSize( visible_width ); - updateScroll(); - } -} - -// virtual -bool LLScrollContainer::handleKeyHere(KEY key, MASK mask) -{ - if (mIgnoreArrowKeys) - { - switch(key) - { - case KEY_LEFT: - case KEY_RIGHT: - case KEY_UP: - case KEY_DOWN: - case KEY_PAGE_UP: - case KEY_PAGE_DOWN: - case KEY_HOME: - case KEY_END: - return false; - default: - break; - } - } - - // allow scrolled view to handle keystrokes in case it delegated keyboard focus - // to the scroll container. - // NOTE: this should not recurse indefinitely as handleKeyHere - // should not propagate to parent controls, so mScrolledView should *not* - // call LLScrollContainer::handleKeyHere in turn - if (mScrolledView && mScrolledView->handleKeyHere(key, mask)) - { - return true; - } - for( S32 i = 0; i < ORIENTATION_COUNT; i++ ) - { - if( mScrollbar[i]->handleKeyHere(key, mask) ) - { - updateScroll(); - return true; - } - } - - return false; -} - -bool LLScrollContainer::handleUnicodeCharHere(llwchar uni_char) -{ - if (mScrolledView && mScrolledView->handleUnicodeCharHere(uni_char)) - { - return true; - } - return false; -} - -bool LLScrollContainer::handleScrollWheel( S32 x, S32 y, S32 clicks ) -{ - // Give event to my child views - they may have scroll bars - // (Bad UI design, but technically possible.) - if (LLUICtrl::handleScrollWheel(x,y,clicks)) - return true; - - // When the vertical scrollbar is visible, scroll wheel - // only affects vertical scrolling. It's confusing to have - // scroll wheel perform both vertical and horizontal in a - // single container. - LLScrollbar* vertical = mScrollbar[VERTICAL]; - if (vertical->getVisible() - && vertical->getEnabled()) - { - // Pretend the mouse is over the scrollbar - if (vertical->handleScrollWheel( 0, 0, clicks ) ) - { - updateScroll(); - } - // Always eat the event - return true; - } - - LLScrollbar* horizontal = mScrollbar[HORIZONTAL]; - // Test enablement and visibility for consistency with - // LLView::childrenHandleScrollWheel(). - if (horizontal->getVisible() - && horizontal->getEnabled() - && horizontal->handleScrollWheel( 0, 0, clicks ) ) - { - updateScroll(); - return true; - } - return false; -} - -bool LLScrollContainer::handleScrollHWheel(S32 x, S32 y, S32 clicks) -{ - if (LLUICtrl::handleScrollHWheel(x,y,clicks)) - { - return true; - } - - LLScrollbar* horizontal = mScrollbar[HORIZONTAL]; - if (horizontal->getVisible() - && horizontal->getEnabled() - && horizontal->handleScrollHWheel( 0, 0, clicks ) ) - { - updateScroll(); - return true; - } - - return false; -} - -bool LLScrollContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - // Scroll folder view if needed. Never accepts a drag or drop. - *accept = ACCEPT_NO; - bool handled = autoScroll(x, y); - - if( !handled ) - { - handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, - cargo_data, accept, tooltip_msg) != NULL; - } - - return true; -} - -bool LLScrollContainer::canAutoScroll(S32 x, S32 y) -{ - if (mAutoScrolling) - { - return true; // already scrolling - } - return autoScroll(x, y, false); -} - -bool LLScrollContainer::autoScroll(S32 x, S32 y) -{ - return autoScroll(x, y, true); -} - -bool LLScrollContainer::autoScroll(S32 x, S32 y, bool do_scroll) -{ - static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); - S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); - - bool scrolling = false; - if( mScrollbar[HORIZONTAL]->getVisible() || mScrollbar[VERTICAL]->getVisible() ) - { - LLRect screen_local_extents; - screenRectToLocal(getRootView()->getLocalRect(), &screen_local_extents); - - LLRect inner_rect_local( 0, mInnerRect.getHeight(), mInnerRect.getWidth(), 0 ); - // Note: Will also include scrollers as scroll zones, so opposite - // scroll zones might have different size due to visible scrollers - if( mScrollbar[HORIZONTAL]->getVisible() ) - { - inner_rect_local.mBottom += scrollbar_size; - } - if( mScrollbar[VERTICAL]->getVisible() ) - { - inner_rect_local.mRight -= scrollbar_size; - } - - // clip rect against root view - inner_rect_local.intersectWith(screen_local_extents); - - S32 auto_scroll_speed = ll_round(mAutoScrollRate * LLFrameTimer::getFrameDeltaTimeF32()); - // autoscroll region should take up no more than one third of visible scroller area - S32 auto_scroll_region_width = llmin(inner_rect_local.getWidth() / 3, (S32)mMaxAutoScrollZone); - S32 auto_scroll_region_height = llmin(inner_rect_local.getHeight() / 3, (S32)mMaxAutoScrollZone); - - if( mScrollbar[HORIZONTAL]->getVisible() ) - { - LLRect left_scroll_rect = screen_local_extents; - left_scroll_rect.mRight = inner_rect_local.mLeft + auto_scroll_region_width; - if( left_scroll_rect.pointInRect( x, y ) && (mScrollbar[HORIZONTAL]->getDocPos() > 0) ) - { - if (do_scroll) - { - mScrollbar[HORIZONTAL]->setDocPos(mScrollbar[HORIZONTAL]->getDocPos() - auto_scroll_speed); - mAutoScrolling = true; - } - scrolling = true; - } - - LLRect right_scroll_rect = screen_local_extents; - right_scroll_rect.mLeft = inner_rect_local.mRight - auto_scroll_region_width; - if( right_scroll_rect.pointInRect( x, y ) && (mScrollbar[HORIZONTAL]->getDocPos() < mScrollbar[HORIZONTAL]->getDocPosMax()) ) - { - if (do_scroll) - { - mScrollbar[HORIZONTAL]->setDocPos(mScrollbar[HORIZONTAL]->getDocPos() + auto_scroll_speed); - mAutoScrolling = true; - } - scrolling = true; - } - } - if( mScrollbar[VERTICAL]->getVisible() ) - { - LLRect bottom_scroll_rect = screen_local_extents; - bottom_scroll_rect.mTop = inner_rect_local.mBottom + auto_scroll_region_height; - if( bottom_scroll_rect.pointInRect( x, y ) && (mScrollbar[VERTICAL]->getDocPos() < mScrollbar[VERTICAL]->getDocPosMax()) ) - { - if (do_scroll) - { - mScrollbar[VERTICAL]->setDocPos(mScrollbar[VERTICAL]->getDocPos() + auto_scroll_speed); - mAutoScrolling = true; - } - scrolling = true; - } - - LLRect top_scroll_rect = screen_local_extents; - top_scroll_rect.mBottom = inner_rect_local.mTop - auto_scroll_region_height; - if( top_scroll_rect.pointInRect( x, y ) && (mScrollbar[VERTICAL]->getDocPos() > 0) ) - { - if (do_scroll) - { - mScrollbar[VERTICAL]->setDocPos(mScrollbar[VERTICAL]->getDocPos() - auto_scroll_speed); - mAutoScrolling = true; - } - scrolling = true; - } - } - } - return scrolling; -} - -void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height, bool* show_h_scrollbar, bool* show_v_scrollbar ) const -{ - const LLRect& doc_rect = getScrolledViewRect(); - static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); - S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); - - S32 doc_width = doc_rect.getWidth(); - S32 doc_height = doc_rect.getHeight(); - - S32 border_width = getBorderWidth(); - *visible_width = getRect().getWidth() - 2 * border_width; - *visible_height = getRect().getHeight() - 2 * border_width; - - *show_v_scrollbar = false; - *show_h_scrollbar = false; - - if (!mHideScrollbar) - { - // Note: 1 pixel change can happen on final animation and should not trigger - // the display of sliders. - if ((doc_height - *visible_height) > 1) - { - *show_v_scrollbar = true; - *visible_width -= scrollbar_size; - } - if ((doc_width - *visible_width) > 1) - { - *show_h_scrollbar = true; - *visible_height -= scrollbar_size; - // Note: Do *not* recompute *show_v_scrollbar here because with - // The view inside the scroll container should not be extended - // to container's full height to ensure the correct computation - // of *show_v_scrollbar after subtracting horizontal scrollbar_size. - - if( !*show_v_scrollbar && ((doc_height - *visible_height) > 1) ) - { - *show_v_scrollbar = true; - *visible_width -= scrollbar_size; - } - } - } -} - - -void LLScrollContainer::draw() -{ - static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); - S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); - - if (mAutoScrolling) - { - // add acceleration to autoscroll - mAutoScrollRate = llmin(mAutoScrollRate + (LLFrameTimer::getFrameDeltaTimeF32() * AUTO_SCROLL_RATE_ACCEL), mMaxAutoScrollRate); - } - else - { - // reset to minimum for next time - mAutoScrollRate = mMinAutoScrollRate; - } - // clear this flag to be set on next call to autoScroll - mAutoScrolling = false; - - // auto-focus when scrollbar active - // this allows us to capture user intent (i.e. stop automatically scrolling the view/etc) - if (!hasFocus() - && (mScrollbar[VERTICAL]->hasMouseCapture() || mScrollbar[HORIZONTAL]->hasMouseCapture())) - { - focusFirstItem(); - } - - if (getRect().isValid()) - { - // Draw background - if( mIsOpaque ) - { - F32 alpha = getCurrentTransparency(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gl_rect_2d(mInnerRect, mBackgroundColor.get() % alpha); - } - - // Draw mScrolledViews and update scroll bars. - // get a scissor region ready, and draw the scrolling view. The - // scissor region ensures that we don't draw outside of the bounds - // of the rectangle. - if( mScrolledView ) - { - updateScroll(); - - // Draw the scrolled area. - { - S32 visible_width = 0; - S32 visible_height = 0; - bool show_v_scrollbar = false; - bool show_h_scrollbar = false; - calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); - - LLLocalClipRect clip(LLRect(mInnerRect.mLeft, - mInnerRect.mBottom + (show_h_scrollbar ? scrollbar_size : 0) + visible_height, - mInnerRect.mRight - (show_v_scrollbar ? scrollbar_size: 0), - mInnerRect.mBottom + (show_h_scrollbar ? scrollbar_size : 0) - )); - drawChild(mScrolledView); - } - } - - // Highlight border if a child of this container has keyboard focus - if( mBorder->getVisible() ) - { - mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus(this) ); - } - - // Draw all children except mScrolledView - // Note: scrollbars have been adjusted by above drawing code - for (child_list_const_reverse_iter_t child_iter = getChildList()->rbegin(); - child_iter != getChildList()->rend(); ++child_iter) - { - LLView *viewp = *child_iter; - if( sDebugRects ) - { - sDepth++; - } - if( (viewp != mScrolledView) && viewp->getVisible() ) - { - drawChild(viewp); - } - if( sDebugRects ) - { - sDepth--; - } - } - } -} // end draw - -bool LLScrollContainer::addChild(LLView* view, S32 tab_group) -{ - if (!mScrolledView) - { - // Use the first panel or container as the scrollable view (bit of a hack) - mScrolledView = view; - } - - bool ret_val = LLView::addChild(view, tab_group); - - //bring the scrollbars to the front - sendChildToFront( mScrollbar[HORIZONTAL] ); - sendChildToFront( mScrollbar[VERTICAL] ); - - return ret_val; -} - -void LLScrollContainer::updateScroll() -{ - if (!getVisible() || !mScrolledView) - { - return; - } - static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); - S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); - - LLRect doc_rect = mScrolledView->getRect(); - S32 doc_width = doc_rect.getWidth(); - S32 doc_height = doc_rect.getHeight(); - S32 visible_width = 0; - S32 visible_height = 0; - bool show_v_scrollbar = false; - bool show_h_scrollbar = false; - calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); - - S32 border_width = getBorderWidth(); - if( show_v_scrollbar ) - { - if( doc_rect.mTop < getRect().getHeight() - border_width ) - { - mScrolledView->translate( 0, getRect().getHeight() - border_width - doc_rect.mTop ); - } - - scrollVertical( mScrollbar[VERTICAL]->getDocPos() ); - mScrollbar[VERTICAL]->setVisible( true ); - - S32 v_scrollbar_height = visible_height; - if( !show_h_scrollbar && mReserveScrollCorner ) - { - v_scrollbar_height -= scrollbar_size; - } - mScrollbar[VERTICAL]->reshape( scrollbar_size, v_scrollbar_height, true ); - - // Make room for the horizontal scrollbar (or not) - S32 v_scrollbar_offset = 0; - if( show_h_scrollbar || mReserveScrollCorner ) - { - v_scrollbar_offset = scrollbar_size; - } - LLRect r = mScrollbar[VERTICAL]->getRect(); - r.translate( 0, mInnerRect.mBottom - r.mBottom + v_scrollbar_offset ); - mScrollbar[VERTICAL]->setRect( r ); - } - else - { - mScrolledView->translate( 0, getRect().getHeight() - border_width - doc_rect.mTop ); - - mScrollbar[VERTICAL]->setVisible( false ); - mScrollbar[VERTICAL]->setDocPos( 0 ); - } - - if( show_h_scrollbar ) - { - if( doc_rect.mLeft > border_width ) - { - mScrolledView->translate( border_width - doc_rect.mLeft, 0 ); - mScrollbar[HORIZONTAL]->setDocPos( 0 ); - } - else - { - scrollHorizontal( mScrollbar[HORIZONTAL]->getDocPos() ); - } - - mScrollbar[HORIZONTAL]->setVisible( true ); - S32 h_scrollbar_width = visible_width; - if( !show_v_scrollbar && mReserveScrollCorner ) - { - h_scrollbar_width -= scrollbar_size; - } - mScrollbar[HORIZONTAL]->reshape( h_scrollbar_width, scrollbar_size, true ); - } - else - { - mScrolledView->translate( border_width - doc_rect.mLeft, 0 ); - - mScrollbar[HORIZONTAL]->setVisible( false ); - mScrollbar[HORIZONTAL]->setDocPos( 0 ); - } - - mScrollbar[HORIZONTAL]->setDocSize( doc_width ); - mScrollbar[HORIZONTAL]->setPageSize( visible_width ); - - mScrollbar[VERTICAL]->setDocSize( doc_height ); - mScrollbar[VERTICAL]->setPageSize( visible_height ); -} // end updateScroll - -void LLScrollContainer::setBorderVisible(bool b) -{ - mBorder->setVisible( b ); - // Recompute inner rect, as border visibility changes it - mInnerRect = getLocalRect(); - mInnerRect.stretch( -getBorderWidth() ); -} - -LLRect LLScrollContainer::getVisibleContentRect() -{ - updateScroll(); - LLRect visible_rect = getContentWindowRect(); - LLRect contents_rect = mScrolledView->getRect(); - visible_rect.translate(-contents_rect.mLeft, -contents_rect.mBottom); - return visible_rect; -} - -LLRect LLScrollContainer::getContentWindowRect() -{ - updateScroll(); - LLRect scroller_view_rect; - S32 visible_width = 0; - S32 visible_height = 0; - bool show_h_scrollbar = false; - bool show_v_scrollbar = false; - calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); - S32 border_width = getBorderWidth(); - scroller_view_rect.setOriginAndSize(border_width, - show_h_scrollbar ? mScrollbar[HORIZONTAL]->getRect().mTop : border_width, - visible_width, - visible_height); - return scroller_view_rect; -} - -// rect is in document coordinates, constraint is in display coordinates relative to content window rect -void LLScrollContainer::scrollToShowRect(const LLRect& rect, const LLRect& constraint) -{ - if (!mScrolledView) - { - LL_WARNS() << "LLScrollContainer::scrollToShowRect with no view!" << LL_ENDL; - return; - } - - LLRect content_window_rect = getContentWindowRect(); - // get document rect - LLRect scrolled_rect = mScrolledView->getRect(); - - // shrink target rect to fit within constraint region, biasing towards top left - LLRect rect_to_constrain = rect; - rect_to_constrain.mBottom = llmax(rect_to_constrain.mBottom, rect_to_constrain.mTop - constraint.getHeight()); - rect_to_constrain.mRight = llmin(rect_to_constrain.mRight, rect_to_constrain.mLeft + constraint.getWidth()); - - // calculate allowable positions for scroller window in document coordinates - LLRect allowable_scroll_rect(rect_to_constrain.mRight - constraint.mRight, - rect_to_constrain.mBottom - constraint.mBottom, - rect_to_constrain.mLeft - constraint.mLeft, - rect_to_constrain.mTop - constraint.mTop); - - // translate from allowable region for lower left corner to upper left corner - allowable_scroll_rect.translate(0, content_window_rect.getHeight()); - - S32 vert_pos = llclamp(mScrollbar[VERTICAL]->getDocPos(), - mScrollbar[VERTICAL]->getDocSize() - allowable_scroll_rect.mTop, // min vertical scroll - mScrollbar[VERTICAL]->getDocSize() - allowable_scroll_rect.mBottom); // max vertical scroll - - mScrollbar[VERTICAL]->setDocSize( scrolled_rect.getHeight() ); - mScrollbar[VERTICAL]->setPageSize( content_window_rect.getHeight() ); - mScrollbar[VERTICAL]->setDocPos( vert_pos ); - - S32 horizontal_pos = llclamp(mScrollbar[HORIZONTAL]->getDocPos(), - allowable_scroll_rect.mLeft, - allowable_scroll_rect.mRight); - - mScrollbar[HORIZONTAL]->setDocSize( scrolled_rect.getWidth() ); - mScrollbar[HORIZONTAL]->setPageSize( content_window_rect.getWidth() ); - mScrollbar[HORIZONTAL]->setDocPos( horizontal_pos ); - - // propagate scroll to document - updateScroll(); - - // In case we are in accordion tab notify parent to show selected rectangle - LLRect screen_rc; - localRectToScreen(rect_to_constrain, &screen_rc); - notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue())); -} - -void LLScrollContainer::pageUp(S32 overlap) -{ - mScrollbar[VERTICAL]->pageUp(overlap); - updateScroll(); -} - -void LLScrollContainer::pageDown(S32 overlap) -{ - mScrollbar[VERTICAL]->pageDown(overlap); - updateScroll(); -} - -void LLScrollContainer::goToTop() -{ - mScrollbar[VERTICAL]->setDocPos(0); - updateScroll(); -} - -void LLScrollContainer::goToBottom() -{ - mScrollbar[VERTICAL]->setDocPos(mScrollbar[VERTICAL]->getDocSize()); - updateScroll(); -} - -S32 LLScrollContainer::getBorderWidth() const -{ - if (mBorder->getVisible()) - { - return mBorder->getBorderWidth(); - } - - return 0; -} - -void LLScrollContainer::setSize(S32 size) -{ - mSize = size; - mScrollbar[VERTICAL]->setThickness(size); - mScrollbar[HORIZONTAL]->setThickness(size); -} +/** + * @file llscrollcontainer.cpp + * @brief LLScrollContainer base 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 "llscrollcontainer.h" + +#include "llrender.h" +#include "llcontainerview.h" +#include "lllocalcliprect.h" +// #include "llfolderview.h" +#include "llscrollingpanellist.h" +#include "llscrollbar.h" +#include "llui.h" +#include "llkeyboard.h" +#include "llviewborder.h" +#include "llfocusmgr.h" +#include "llframetimer.h" +#include "lluictrlfactory.h" +#include "llpanel.h" +#include "llfontgl.h" + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +static const S32 VERTICAL_MULTIPLE = 16; +static const F32 AUTO_SCROLL_RATE_ACCEL = 120.f; + +///---------------------------------------------------------------------------- +/// Class LLScrollContainer +///---------------------------------------------------------------------------- + +static LLDefaultChildRegistry::Register r("scroll_container"); + +#include "llscrollingpanellist.h" +#include "llcontainerview.h" +#include "llpanel.h" + +static ScrollContainerRegistry::Register r1("scrolling_panel_list"); +static ScrollContainerRegistry::Register r2("container_view"); +static ScrollContainerRegistry::Register r3("panel", &LLPanel::fromXML); + +LLScrollContainer::Params::Params() +: is_opaque("opaque"), + bg_color("color"), + border_visible("border_visible"), + hide_scrollbar("hide_scrollbar"), + ignore_arrow_keys("ignore_arrow_keys"), + min_auto_scroll_rate("min_auto_scroll_rate", 100), + max_auto_scroll_rate("max_auto_scroll_rate", 1000), + max_auto_scroll_zone("max_auto_scroll_zone", 16), + reserve_scroll_corner("reserve_scroll_corner", false), + size("size", -1) +{} + + +// Default constructor +LLScrollContainer::LLScrollContainer(const LLScrollContainer::Params& p) +: LLUICtrl(p), + mAutoScrolling( false ), + mAutoScrollRate( 0.f ), + mBackgroundColor(p.bg_color()), + mIsOpaque(p.is_opaque), + mHideScrollbar(p.hide_scrollbar), + mIgnoreArrowKeys(p.ignore_arrow_keys), + mReserveScrollCorner(p.reserve_scroll_corner), + mMinAutoScrollRate(p.min_auto_scroll_rate), + mMaxAutoScrollRate(p.max_auto_scroll_rate), + mMaxAutoScrollZone(p.max_auto_scroll_zone), + mScrolledView(NULL), + mSize(p.size) +{ + static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); + S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); + + LLRect border_rect( 0, getRect().getHeight(), getRect().getWidth(), 0 ); + LLViewBorder::Params params; + params.name("scroll border"); + params.rect(border_rect); + params.visible(p.border_visible); + params.bevel_style(LLViewBorder::BEVEL_IN); + mBorder = LLUICtrlFactory::create (params); + LLView::addChild( mBorder ); + + mInnerRect = getLocalRect(); + mInnerRect.stretch( -getBorderWidth() ); + + LLRect vertical_scroll_rect = mInnerRect; + vertical_scroll_rect.mLeft = vertical_scroll_rect.mRight - scrollbar_size; + LLScrollbar::Params sbparams; + sbparams.name("scrollable vertical"); + sbparams.rect(vertical_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.visible(false); + sbparams.change_callback(p.scroll_callback); + mScrollbar[VERTICAL] = LLUICtrlFactory::create (sbparams); + LLView::addChild( mScrollbar[VERTICAL] ); + + LLRect horizontal_scroll_rect; + horizontal_scroll_rect.mTop = scrollbar_size; + horizontal_scroll_rect.mRight = mInnerRect.getWidth(); + sbparams.name("scrollable horizontal"); + sbparams.rect(horizontal_scroll_rect); + sbparams.orientation(LLScrollbar::HORIZONTAL); + sbparams.doc_size(mInnerRect.getWidth()); + sbparams.doc_pos(0); + sbparams.page_size(mInnerRect.getWidth()); + sbparams.step_size(VERTICAL_MULTIPLE); + sbparams.visible(false); + sbparams.follows.flags(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_BOTTOM); + sbparams.change_callback(p.scroll_callback); + mScrollbar[HORIZONTAL] = LLUICtrlFactory::create (sbparams); + LLView::addChild( mScrollbar[HORIZONTAL] ); +} + +// Destroys the object +LLScrollContainer::~LLScrollContainer( void ) +{ + // mScrolledView and mScrollbar are child views, so the LLView + // destructor takes care of memory deallocation. + for( S32 i = 0; i < ORIENTATION_COUNT; i++ ) + { + mScrollbar[i] = NULL; + } + mScrolledView = NULL; +} + +// internal scrollbar handlers +// virtual +void LLScrollContainer::scrollHorizontal( S32 new_pos ) +{ + if( mScrolledView ) + { + LLRect doc_rect = mScrolledView->getRect(); + S32 old_pos = -(doc_rect.mLeft - mInnerRect.mLeft); + mScrolledView->translate( -(new_pos - old_pos), 0 ); + } +} + +// virtual +void LLScrollContainer::scrollVertical( S32 new_pos ) +{ + if( mScrolledView ) + { + LLRect doc_rect = mScrolledView->getRect(); + S32 old_pos = doc_rect.mTop - mInnerRect.mTop; + mScrolledView->translate( 0, new_pos - old_pos ); + } +} + +// LLView functionality +void LLScrollContainer::reshape(S32 width, S32 height, + bool called_from_parent) +{ + LLUICtrl::reshape( width, height, called_from_parent ); + + mInnerRect = getLocalRect(); + mInnerRect.stretch( -getBorderWidth() ); + + if (mScrolledView) + { + const LLRect& scrolled_rect = mScrolledView->getRect(); + + S32 visible_width = 0; + S32 visible_height = 0; + bool show_v_scrollbar = false; + bool show_h_scrollbar = false; + calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + mScrollbar[VERTICAL]->setDocSize( scrolled_rect.getHeight() ); + mScrollbar[VERTICAL]->setPageSize( visible_height ); + + mScrollbar[HORIZONTAL]->setDocSize( scrolled_rect.getWidth() ); + mScrollbar[HORIZONTAL]->setPageSize( visible_width ); + updateScroll(); + } +} + +// virtual +bool LLScrollContainer::handleKeyHere(KEY key, MASK mask) +{ + if (mIgnoreArrowKeys) + { + switch(key) + { + case KEY_LEFT: + case KEY_RIGHT: + case KEY_UP: + case KEY_DOWN: + case KEY_PAGE_UP: + case KEY_PAGE_DOWN: + case KEY_HOME: + case KEY_END: + return false; + default: + break; + } + } + + // allow scrolled view to handle keystrokes in case it delegated keyboard focus + // to the scroll container. + // NOTE: this should not recurse indefinitely as handleKeyHere + // should not propagate to parent controls, so mScrolledView should *not* + // call LLScrollContainer::handleKeyHere in turn + if (mScrolledView && mScrolledView->handleKeyHere(key, mask)) + { + return true; + } + for( S32 i = 0; i < ORIENTATION_COUNT; i++ ) + { + if( mScrollbar[i]->handleKeyHere(key, mask) ) + { + updateScroll(); + return true; + } + } + + return false; +} + +bool LLScrollContainer::handleUnicodeCharHere(llwchar uni_char) +{ + if (mScrolledView && mScrolledView->handleUnicodeCharHere(uni_char)) + { + return true; + } + return false; +} + +bool LLScrollContainer::handleScrollWheel( S32 x, S32 y, S32 clicks ) +{ + // Give event to my child views - they may have scroll bars + // (Bad UI design, but technically possible.) + if (LLUICtrl::handleScrollWheel(x,y,clicks)) + return true; + + // When the vertical scrollbar is visible, scroll wheel + // only affects vertical scrolling. It's confusing to have + // scroll wheel perform both vertical and horizontal in a + // single container. + LLScrollbar* vertical = mScrollbar[VERTICAL]; + if (vertical->getVisible() + && vertical->getEnabled()) + { + // Pretend the mouse is over the scrollbar + if (vertical->handleScrollWheel( 0, 0, clicks ) ) + { + updateScroll(); + } + // Always eat the event + return true; + } + + LLScrollbar* horizontal = mScrollbar[HORIZONTAL]; + // Test enablement and visibility for consistency with + // LLView::childrenHandleScrollWheel(). + if (horizontal->getVisible() + && horizontal->getEnabled() + && horizontal->handleScrollWheel( 0, 0, clicks ) ) + { + updateScroll(); + return true; + } + return false; +} + +bool LLScrollContainer::handleScrollHWheel(S32 x, S32 y, S32 clicks) +{ + if (LLUICtrl::handleScrollHWheel(x,y,clicks)) + { + return true; + } + + LLScrollbar* horizontal = mScrollbar[HORIZONTAL]; + if (horizontal->getVisible() + && horizontal->getEnabled() + && horizontal->handleScrollHWheel( 0, 0, clicks ) ) + { + updateScroll(); + return true; + } + + return false; +} + +bool LLScrollContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // Scroll folder view if needed. Never accepts a drag or drop. + *accept = ACCEPT_NO; + bool handled = autoScroll(x, y); + + if( !handled ) + { + handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, + cargo_data, accept, tooltip_msg) != NULL; + } + + return true; +} + +bool LLScrollContainer::canAutoScroll(S32 x, S32 y) +{ + if (mAutoScrolling) + { + return true; // already scrolling + } + return autoScroll(x, y, false); +} + +bool LLScrollContainer::autoScroll(S32 x, S32 y) +{ + return autoScroll(x, y, true); +} + +bool LLScrollContainer::autoScroll(S32 x, S32 y, bool do_scroll) +{ + static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); + S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); + + bool scrolling = false; + if( mScrollbar[HORIZONTAL]->getVisible() || mScrollbar[VERTICAL]->getVisible() ) + { + LLRect screen_local_extents; + screenRectToLocal(getRootView()->getLocalRect(), &screen_local_extents); + + LLRect inner_rect_local( 0, mInnerRect.getHeight(), mInnerRect.getWidth(), 0 ); + // Note: Will also include scrollers as scroll zones, so opposite + // scroll zones might have different size due to visible scrollers + if( mScrollbar[HORIZONTAL]->getVisible() ) + { + inner_rect_local.mBottom += scrollbar_size; + } + if( mScrollbar[VERTICAL]->getVisible() ) + { + inner_rect_local.mRight -= scrollbar_size; + } + + // clip rect against root view + inner_rect_local.intersectWith(screen_local_extents); + + S32 auto_scroll_speed = ll_round(mAutoScrollRate * LLFrameTimer::getFrameDeltaTimeF32()); + // autoscroll region should take up no more than one third of visible scroller area + S32 auto_scroll_region_width = llmin(inner_rect_local.getWidth() / 3, (S32)mMaxAutoScrollZone); + S32 auto_scroll_region_height = llmin(inner_rect_local.getHeight() / 3, (S32)mMaxAutoScrollZone); + + if( mScrollbar[HORIZONTAL]->getVisible() ) + { + LLRect left_scroll_rect = screen_local_extents; + left_scroll_rect.mRight = inner_rect_local.mLeft + auto_scroll_region_width; + if( left_scroll_rect.pointInRect( x, y ) && (mScrollbar[HORIZONTAL]->getDocPos() > 0) ) + { + if (do_scroll) + { + mScrollbar[HORIZONTAL]->setDocPos(mScrollbar[HORIZONTAL]->getDocPos() - auto_scroll_speed); + mAutoScrolling = true; + } + scrolling = true; + } + + LLRect right_scroll_rect = screen_local_extents; + right_scroll_rect.mLeft = inner_rect_local.mRight - auto_scroll_region_width; + if( right_scroll_rect.pointInRect( x, y ) && (mScrollbar[HORIZONTAL]->getDocPos() < mScrollbar[HORIZONTAL]->getDocPosMax()) ) + { + if (do_scroll) + { + mScrollbar[HORIZONTAL]->setDocPos(mScrollbar[HORIZONTAL]->getDocPos() + auto_scroll_speed); + mAutoScrolling = true; + } + scrolling = true; + } + } + if( mScrollbar[VERTICAL]->getVisible() ) + { + LLRect bottom_scroll_rect = screen_local_extents; + bottom_scroll_rect.mTop = inner_rect_local.mBottom + auto_scroll_region_height; + if( bottom_scroll_rect.pointInRect( x, y ) && (mScrollbar[VERTICAL]->getDocPos() < mScrollbar[VERTICAL]->getDocPosMax()) ) + { + if (do_scroll) + { + mScrollbar[VERTICAL]->setDocPos(mScrollbar[VERTICAL]->getDocPos() + auto_scroll_speed); + mAutoScrolling = true; + } + scrolling = true; + } + + LLRect top_scroll_rect = screen_local_extents; + top_scroll_rect.mBottom = inner_rect_local.mTop - auto_scroll_region_height; + if( top_scroll_rect.pointInRect( x, y ) && (mScrollbar[VERTICAL]->getDocPos() > 0) ) + { + if (do_scroll) + { + mScrollbar[VERTICAL]->setDocPos(mScrollbar[VERTICAL]->getDocPos() - auto_scroll_speed); + mAutoScrolling = true; + } + scrolling = true; + } + } + } + return scrolling; +} + +void LLScrollContainer::calcVisibleSize( S32 *visible_width, S32 *visible_height, bool* show_h_scrollbar, bool* show_v_scrollbar ) const +{ + const LLRect& doc_rect = getScrolledViewRect(); + static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); + S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); + + S32 doc_width = doc_rect.getWidth(); + S32 doc_height = doc_rect.getHeight(); + + S32 border_width = getBorderWidth(); + *visible_width = getRect().getWidth() - 2 * border_width; + *visible_height = getRect().getHeight() - 2 * border_width; + + *show_v_scrollbar = false; + *show_h_scrollbar = false; + + if (!mHideScrollbar) + { + // Note: 1 pixel change can happen on final animation and should not trigger + // the display of sliders. + if ((doc_height - *visible_height) > 1) + { + *show_v_scrollbar = true; + *visible_width -= scrollbar_size; + } + if ((doc_width - *visible_width) > 1) + { + *show_h_scrollbar = true; + *visible_height -= scrollbar_size; + // Note: Do *not* recompute *show_v_scrollbar here because with + // The view inside the scroll container should not be extended + // to container's full height to ensure the correct computation + // of *show_v_scrollbar after subtracting horizontal scrollbar_size. + + if( !*show_v_scrollbar && ((doc_height - *visible_height) > 1) ) + { + *show_v_scrollbar = true; + *visible_width -= scrollbar_size; + } + } + } +} + + +void LLScrollContainer::draw() +{ + static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); + S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); + + if (mAutoScrolling) + { + // add acceleration to autoscroll + mAutoScrollRate = llmin(mAutoScrollRate + (LLFrameTimer::getFrameDeltaTimeF32() * AUTO_SCROLL_RATE_ACCEL), mMaxAutoScrollRate); + } + else + { + // reset to minimum for next time + mAutoScrollRate = mMinAutoScrollRate; + } + // clear this flag to be set on next call to autoScroll + mAutoScrolling = false; + + // auto-focus when scrollbar active + // this allows us to capture user intent (i.e. stop automatically scrolling the view/etc) + if (!hasFocus() + && (mScrollbar[VERTICAL]->hasMouseCapture() || mScrollbar[HORIZONTAL]->hasMouseCapture())) + { + focusFirstItem(); + } + + if (getRect().isValid()) + { + // Draw background + if( mIsOpaque ) + { + F32 alpha = getCurrentTransparency(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gl_rect_2d(mInnerRect, mBackgroundColor.get() % alpha); + } + + // Draw mScrolledViews and update scroll bars. + // get a scissor region ready, and draw the scrolling view. The + // scissor region ensures that we don't draw outside of the bounds + // of the rectangle. + if( mScrolledView ) + { + updateScroll(); + + // Draw the scrolled area. + { + S32 visible_width = 0; + S32 visible_height = 0; + bool show_v_scrollbar = false; + bool show_h_scrollbar = false; + calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + LLLocalClipRect clip(LLRect(mInnerRect.mLeft, + mInnerRect.mBottom + (show_h_scrollbar ? scrollbar_size : 0) + visible_height, + mInnerRect.mRight - (show_v_scrollbar ? scrollbar_size: 0), + mInnerRect.mBottom + (show_h_scrollbar ? scrollbar_size : 0) + )); + drawChild(mScrolledView); + } + } + + // Highlight border if a child of this container has keyboard focus + if( mBorder->getVisible() ) + { + mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus(this) ); + } + + // Draw all children except mScrolledView + // Note: scrollbars have been adjusted by above drawing code + for (child_list_const_reverse_iter_t child_iter = getChildList()->rbegin(); + child_iter != getChildList()->rend(); ++child_iter) + { + LLView *viewp = *child_iter; + if( sDebugRects ) + { + sDepth++; + } + if( (viewp != mScrolledView) && viewp->getVisible() ) + { + drawChild(viewp); + } + if( sDebugRects ) + { + sDepth--; + } + } + } +} // end draw + +bool LLScrollContainer::addChild(LLView* view, S32 tab_group) +{ + if (!mScrolledView) + { + // Use the first panel or container as the scrollable view (bit of a hack) + mScrolledView = view; + } + + bool ret_val = LLView::addChild(view, tab_group); + + //bring the scrollbars to the front + sendChildToFront( mScrollbar[HORIZONTAL] ); + sendChildToFront( mScrollbar[VERTICAL] ); + + return ret_val; +} + +void LLScrollContainer::updateScroll() +{ + if (!getVisible() || !mScrolledView) + { + return; + } + static LLUICachedControl scrollbar_size_control ("UIScrollbarSize", 0); + S32 scrollbar_size = (mSize == -1 ? scrollbar_size_control : mSize); + + LLRect doc_rect = mScrolledView->getRect(); + S32 doc_width = doc_rect.getWidth(); + S32 doc_height = doc_rect.getHeight(); + S32 visible_width = 0; + S32 visible_height = 0; + bool show_v_scrollbar = false; + bool show_h_scrollbar = false; + calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + + S32 border_width = getBorderWidth(); + if( show_v_scrollbar ) + { + if( doc_rect.mTop < getRect().getHeight() - border_width ) + { + mScrolledView->translate( 0, getRect().getHeight() - border_width - doc_rect.mTop ); + } + + scrollVertical( mScrollbar[VERTICAL]->getDocPos() ); + mScrollbar[VERTICAL]->setVisible( true ); + + S32 v_scrollbar_height = visible_height; + if( !show_h_scrollbar && mReserveScrollCorner ) + { + v_scrollbar_height -= scrollbar_size; + } + mScrollbar[VERTICAL]->reshape( scrollbar_size, v_scrollbar_height, true ); + + // Make room for the horizontal scrollbar (or not) + S32 v_scrollbar_offset = 0; + if( show_h_scrollbar || mReserveScrollCorner ) + { + v_scrollbar_offset = scrollbar_size; + } + LLRect r = mScrollbar[VERTICAL]->getRect(); + r.translate( 0, mInnerRect.mBottom - r.mBottom + v_scrollbar_offset ); + mScrollbar[VERTICAL]->setRect( r ); + } + else + { + mScrolledView->translate( 0, getRect().getHeight() - border_width - doc_rect.mTop ); + + mScrollbar[VERTICAL]->setVisible( false ); + mScrollbar[VERTICAL]->setDocPos( 0 ); + } + + if( show_h_scrollbar ) + { + if( doc_rect.mLeft > border_width ) + { + mScrolledView->translate( border_width - doc_rect.mLeft, 0 ); + mScrollbar[HORIZONTAL]->setDocPos( 0 ); + } + else + { + scrollHorizontal( mScrollbar[HORIZONTAL]->getDocPos() ); + } + + mScrollbar[HORIZONTAL]->setVisible( true ); + S32 h_scrollbar_width = visible_width; + if( !show_v_scrollbar && mReserveScrollCorner ) + { + h_scrollbar_width -= scrollbar_size; + } + mScrollbar[HORIZONTAL]->reshape( h_scrollbar_width, scrollbar_size, true ); + } + else + { + mScrolledView->translate( border_width - doc_rect.mLeft, 0 ); + + mScrollbar[HORIZONTAL]->setVisible( false ); + mScrollbar[HORIZONTAL]->setDocPos( 0 ); + } + + mScrollbar[HORIZONTAL]->setDocSize( doc_width ); + mScrollbar[HORIZONTAL]->setPageSize( visible_width ); + + mScrollbar[VERTICAL]->setDocSize( doc_height ); + mScrollbar[VERTICAL]->setPageSize( visible_height ); +} // end updateScroll + +void LLScrollContainer::setBorderVisible(bool b) +{ + mBorder->setVisible( b ); + // Recompute inner rect, as border visibility changes it + mInnerRect = getLocalRect(); + mInnerRect.stretch( -getBorderWidth() ); +} + +LLRect LLScrollContainer::getVisibleContentRect() +{ + updateScroll(); + LLRect visible_rect = getContentWindowRect(); + LLRect contents_rect = mScrolledView->getRect(); + visible_rect.translate(-contents_rect.mLeft, -contents_rect.mBottom); + return visible_rect; +} + +LLRect LLScrollContainer::getContentWindowRect() +{ + updateScroll(); + LLRect scroller_view_rect; + S32 visible_width = 0; + S32 visible_height = 0; + bool show_h_scrollbar = false; + bool show_v_scrollbar = false; + calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + S32 border_width = getBorderWidth(); + scroller_view_rect.setOriginAndSize(border_width, + show_h_scrollbar ? mScrollbar[HORIZONTAL]->getRect().mTop : border_width, + visible_width, + visible_height); + return scroller_view_rect; +} + +// rect is in document coordinates, constraint is in display coordinates relative to content window rect +void LLScrollContainer::scrollToShowRect(const LLRect& rect, const LLRect& constraint) +{ + if (!mScrolledView) + { + LL_WARNS() << "LLScrollContainer::scrollToShowRect with no view!" << LL_ENDL; + return; + } + + LLRect content_window_rect = getContentWindowRect(); + // get document rect + LLRect scrolled_rect = mScrolledView->getRect(); + + // shrink target rect to fit within constraint region, biasing towards top left + LLRect rect_to_constrain = rect; + rect_to_constrain.mBottom = llmax(rect_to_constrain.mBottom, rect_to_constrain.mTop - constraint.getHeight()); + rect_to_constrain.mRight = llmin(rect_to_constrain.mRight, rect_to_constrain.mLeft + constraint.getWidth()); + + // calculate allowable positions for scroller window in document coordinates + LLRect allowable_scroll_rect(rect_to_constrain.mRight - constraint.mRight, + rect_to_constrain.mBottom - constraint.mBottom, + rect_to_constrain.mLeft - constraint.mLeft, + rect_to_constrain.mTop - constraint.mTop); + + // translate from allowable region for lower left corner to upper left corner + allowable_scroll_rect.translate(0, content_window_rect.getHeight()); + + S32 vert_pos = llclamp(mScrollbar[VERTICAL]->getDocPos(), + mScrollbar[VERTICAL]->getDocSize() - allowable_scroll_rect.mTop, // min vertical scroll + mScrollbar[VERTICAL]->getDocSize() - allowable_scroll_rect.mBottom); // max vertical scroll + + mScrollbar[VERTICAL]->setDocSize( scrolled_rect.getHeight() ); + mScrollbar[VERTICAL]->setPageSize( content_window_rect.getHeight() ); + mScrollbar[VERTICAL]->setDocPos( vert_pos ); + + S32 horizontal_pos = llclamp(mScrollbar[HORIZONTAL]->getDocPos(), + allowable_scroll_rect.mLeft, + allowable_scroll_rect.mRight); + + mScrollbar[HORIZONTAL]->setDocSize( scrolled_rect.getWidth() ); + mScrollbar[HORIZONTAL]->setPageSize( content_window_rect.getWidth() ); + mScrollbar[HORIZONTAL]->setDocPos( horizontal_pos ); + + // propagate scroll to document + updateScroll(); + + // In case we are in accordion tab notify parent to show selected rectangle + LLRect screen_rc; + localRectToScreen(rect_to_constrain, &screen_rc); + notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue())); +} + +void LLScrollContainer::pageUp(S32 overlap) +{ + mScrollbar[VERTICAL]->pageUp(overlap); + updateScroll(); +} + +void LLScrollContainer::pageDown(S32 overlap) +{ + mScrollbar[VERTICAL]->pageDown(overlap); + updateScroll(); +} + +void LLScrollContainer::goToTop() +{ + mScrollbar[VERTICAL]->setDocPos(0); + updateScroll(); +} + +void LLScrollContainer::goToBottom() +{ + mScrollbar[VERTICAL]->setDocPos(mScrollbar[VERTICAL]->getDocSize()); + updateScroll(); +} + +S32 LLScrollContainer::getBorderWidth() const +{ + if (mBorder->getVisible()) + { + return mBorder->getBorderWidth(); + } + + return 0; +} + +void LLScrollContainer::setSize(S32 size) +{ + mSize = size; + mScrollbar[VERTICAL]->setThickness(size); + mScrollbar[HORIZONTAL]->setThickness(size); +} diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h index 7e9532822b..859750d71c 100644 --- a/indra/llui/llscrollcontainer.h +++ b/indra/llui/llscrollcontainer.h @@ -1,157 +1,157 @@ -/** - * @file llscrollcontainer.h - * @brief LLScrollContainer class header file. - * - * $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$ - */ - -#ifndef LL_LLSCROLLCONTAINER_H -#define LL_LLSCROLLCONTAINER_H - -#include "lluictrl.h" -#ifndef LL_V4COLOR_H -#include "v4color.h" -#endif -#include "llcoord.h" -#include "llscrollbar.h" - - -class LLViewBorder; -class LLUICtrlFactory; - -/***************************************************************************** - * - * A decorator view class meant to encapsulate a clipped region which is - * scrollable. It automatically takes care of pixel perfect scrolling - * and cliipping, as well as turning the scrollbars on or off based on - * the width and height of the view you're scrolling. - * - *****************************************************************************/ - -struct ScrollContainerRegistry : public LLChildRegistry -{ - LLSINGLETON_EMPTY_CTOR(ScrollContainerRegistry); -}; - -class LLScrollContainer : public LLUICtrl -{ -public: - // Note: vertical comes before horizontal because vertical - // scrollbars have priority for mouse and keyboard events. - - struct Params : public LLInitParam::Block - { - Optional is_opaque, - reserve_scroll_corner, - border_visible, - hide_scrollbar, - ignore_arrow_keys; - Optional min_auto_scroll_rate, - max_auto_scroll_rate; - Optional max_auto_scroll_zone; - Optional bg_color; - Optional scroll_callback; - Optional size; - - Params(); - }; - - // my valid children are stored in this registry - typedef ScrollContainerRegistry child_registry_t; - -protected: - LLScrollContainer(const Params&); - friend class LLUICtrlFactory; -public: - virtual ~LLScrollContainer( void ); - - virtual void setValue(const LLSD& value) { mInnerRect.setValue(value); } - - void setBorderVisible( bool b ); - - void scrollToShowRect( const LLRect& rect, const LLRect& constraint); - void scrollToShowRect( const LLRect& rect) { scrollToShowRect(rect, LLRect(0, mInnerRect.getHeight(), mInnerRect.getWidth(), 0)); } - - void setReserveScrollCorner( bool b ) { mReserveScrollCorner = b; } - LLRect getVisibleContentRect(); - LLRect getContentWindowRect(); - virtual const LLRect getScrolledViewRect() const { return mScrolledView ? mScrolledView->getRect() : LLRect::null; } - void pageUp(S32 overlap = 0); - void pageDown(S32 overlap = 0); - void goToTop(); - void goToBottom(); - bool isAtTop() const { return mScrollbar[VERTICAL]->isAtBeginning(); } - bool isAtBottom() const { return mScrollbar[VERTICAL]->isAtEnd(); } - S32 getDocPosVertical() const { return mScrollbar[VERTICAL]->getDocPos(); } - S32 getDocPosHorizontal() const { return mScrollbar[HORIZONTAL]->getDocPos(); } - S32 getBorderWidth() const; - - // LLView functionality - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - virtual bool handleKeyHere(KEY key, MASK mask); - virtual bool handleUnicodeCharHere(llwchar uni_char); - virtual bool handleScrollWheel( S32 x, S32 y, S32 clicks ); - virtual bool handleScrollHWheel( S32 x, S32 y, S32 clicks ); - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - - virtual void draw(); - virtual bool addChild(LLView* view, S32 tab_group = 0); - - bool canAutoScroll(S32 x, S32 y); - bool autoScroll(S32 x, S32 y); - - S32 getSize() const { return mSize; } - void setSize(S32 thickness); - -protected: - LLView* mScrolledView; - -private: - // internal scrollbar handlers - virtual void scrollHorizontal( S32 new_pos ); - virtual void scrollVertical( S32 new_pos ); - void updateScroll(); - bool autoScroll(S32 x, S32 y, bool do_scroll); - void calcVisibleSize( S32 *visible_width, S32 *visible_height, bool* show_h_scrollbar, bool* show_v_scrollbar ) const; - - LLScrollbar* mScrollbar[ORIENTATION_COUNT]; - S32 mSize; - bool mIsOpaque; - LLUIColor mBackgroundColor; - LLRect mInnerRect; - LLViewBorder* mBorder; - bool mReserveScrollCorner; - bool mAutoScrolling; - F32 mAutoScrollRate; - F32 mMinAutoScrollRate; - F32 mMaxAutoScrollRate; - U32 mMaxAutoScrollZone; - bool mHideScrollbar; - bool mIgnoreArrowKeys; -}; - - -#endif // LL_LLSCROLLCONTAINER_H +/** + * @file llscrollcontainer.h + * @brief LLScrollContainer class header file. + * + * $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$ + */ + +#ifndef LL_LLSCROLLCONTAINER_H +#define LL_LLSCROLLCONTAINER_H + +#include "lluictrl.h" +#ifndef LL_V4COLOR_H +#include "v4color.h" +#endif +#include "llcoord.h" +#include "llscrollbar.h" + + +class LLViewBorder; +class LLUICtrlFactory; + +/***************************************************************************** + * + * A decorator view class meant to encapsulate a clipped region which is + * scrollable. It automatically takes care of pixel perfect scrolling + * and cliipping, as well as turning the scrollbars on or off based on + * the width and height of the view you're scrolling. + * + *****************************************************************************/ + +struct ScrollContainerRegistry : public LLChildRegistry +{ + LLSINGLETON_EMPTY_CTOR(ScrollContainerRegistry); +}; + +class LLScrollContainer : public LLUICtrl +{ +public: + // Note: vertical comes before horizontal because vertical + // scrollbars have priority for mouse and keyboard events. + + struct Params : public LLInitParam::Block + { + Optional is_opaque, + reserve_scroll_corner, + border_visible, + hide_scrollbar, + ignore_arrow_keys; + Optional min_auto_scroll_rate, + max_auto_scroll_rate; + Optional max_auto_scroll_zone; + Optional bg_color; + Optional scroll_callback; + Optional size; + + Params(); + }; + + // my valid children are stored in this registry + typedef ScrollContainerRegistry child_registry_t; + +protected: + LLScrollContainer(const Params&); + friend class LLUICtrlFactory; +public: + virtual ~LLScrollContainer( void ); + + virtual void setValue(const LLSD& value) { mInnerRect.setValue(value); } + + void setBorderVisible( bool b ); + + void scrollToShowRect( const LLRect& rect, const LLRect& constraint); + void scrollToShowRect( const LLRect& rect) { scrollToShowRect(rect, LLRect(0, mInnerRect.getHeight(), mInnerRect.getWidth(), 0)); } + + void setReserveScrollCorner( bool b ) { mReserveScrollCorner = b; } + LLRect getVisibleContentRect(); + LLRect getContentWindowRect(); + virtual const LLRect getScrolledViewRect() const { return mScrolledView ? mScrolledView->getRect() : LLRect::null; } + void pageUp(S32 overlap = 0); + void pageDown(S32 overlap = 0); + void goToTop(); + void goToBottom(); + bool isAtTop() const { return mScrollbar[VERTICAL]->isAtBeginning(); } + bool isAtBottom() const { return mScrollbar[VERTICAL]->isAtEnd(); } + S32 getDocPosVertical() const { return mScrollbar[VERTICAL]->getDocPos(); } + S32 getDocPosHorizontal() const { return mScrollbar[HORIZONTAL]->getDocPos(); } + S32 getBorderWidth() const; + + // LLView functionality + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + virtual bool handleKeyHere(KEY key, MASK mask); + virtual bool handleUnicodeCharHere(llwchar uni_char); + virtual bool handleScrollWheel( S32 x, S32 y, S32 clicks ); + virtual bool handleScrollHWheel( S32 x, S32 y, S32 clicks ); + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + + virtual void draw(); + virtual bool addChild(LLView* view, S32 tab_group = 0); + + bool canAutoScroll(S32 x, S32 y); + bool autoScroll(S32 x, S32 y); + + S32 getSize() const { return mSize; } + void setSize(S32 thickness); + +protected: + LLView* mScrolledView; + +private: + // internal scrollbar handlers + virtual void scrollHorizontal( S32 new_pos ); + virtual void scrollVertical( S32 new_pos ); + void updateScroll(); + bool autoScroll(S32 x, S32 y, bool do_scroll); + void calcVisibleSize( S32 *visible_width, S32 *visible_height, bool* show_h_scrollbar, bool* show_v_scrollbar ) const; + + LLScrollbar* mScrollbar[ORIENTATION_COUNT]; + S32 mSize; + bool mIsOpaque; + LLUIColor mBackgroundColor; + LLRect mInnerRect; + LLViewBorder* mBorder; + bool mReserveScrollCorner; + bool mAutoScrolling; + F32 mAutoScrollRate; + F32 mMinAutoScrollRate; + F32 mMaxAutoScrollRate; + U32 mMaxAutoScrollZone; + bool mHideScrollbar; + bool mIgnoreArrowKeys; +}; + + +#endif // LL_LLSCROLLCONTAINER_H diff --git a/indra/llui/llscrollingpanellist.cpp b/indra/llui/llscrollingpanellist.cpp index 5c6b528afc..b158d7b1b7 100644 --- a/indra/llui/llscrollingpanellist.cpp +++ b/indra/llui/llscrollingpanellist.cpp @@ -1,252 +1,252 @@ -/** - * @file llscrollingpanellist.cpp - * @brief - * - * $LicenseInfo:firstyear=2006&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 "llstl.h" - -#include "llscrollingpanellist.h" - -static LLDefaultChildRegistry::Register r("scrolling_panel_list"); - - -///////////////////////////////////////////////////////////////////// -// LLScrollingPanelList - -// This could probably be integrated with LLScrollContainer -SJB - -LLScrollingPanelList::Params::Params() - : is_horizontal("is_horizontal") - , padding("padding") - , spacing("spacing") -{ -} - -LLScrollingPanelList::LLScrollingPanelList(const Params& p) - : LLUICtrl(p) - , mIsHorizontal(p.is_horizontal) - , mPadding(p.padding.isProvided() ? p.padding : DEFAULT_PADDING) - , mSpacing(p.spacing.isProvided() ? p.spacing : DEFAULT_SPACING) -{ -} - -void LLScrollingPanelList::clearPanels() -{ - deleteAllChildren(); - mPanelList.clear(); - rearrange(); -} - -S32 LLScrollingPanelList::addPanel(LLScrollingPanel* panel, bool back) -{ - if (back) - { - addChild(panel); - mPanelList.push_back(panel); - } - else - { - addChildInBack(panel); - mPanelList.push_front(panel); - } - - rearrange(); - - return mIsHorizontal ? getRect().getWidth() : getRect().getHeight(); -} - -void LLScrollingPanelList::removePanel(LLScrollingPanel* panel) -{ - U32 index = 0; - LLScrollingPanelList::panel_list_t::const_iterator iter; - - if (!mPanelList.empty()) - { - for (iter = mPanelList.begin(); iter != mPanelList.end(); ++iter, ++index) - { - if (*iter == panel) - { - break; - } - } - if (iter != mPanelList.end()) - { - removePanel(index); - } - } -} - -void LLScrollingPanelList::removePanel( U32 panel_index ) -{ - if ( mPanelList.empty() || panel_index >= mPanelList.size() ) - { - LL_WARNS() << "Panel index " << panel_index << " is out of range!" << LL_ENDL; - return; - } - else - { - removeChild( mPanelList.at(panel_index) ); - mPanelList.erase( mPanelList.begin() + panel_index ); - } - - rearrange(); -} - -void LLScrollingPanelList::updatePanels(bool allow_modify) -{ - for (std::deque::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) - { - LLScrollingPanel *childp = *iter; - childp->updatePanel(allow_modify); - } -} - -void LLScrollingPanelList::rearrange() -{ - // Resize this view - S32 new_width, new_height; - if (!mPanelList.empty()) - { - new_width = new_height = mPadding * 2; - for (std::deque::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) - { - LLScrollingPanel* childp = *iter; - const LLRect& rect = childp->getRect(); - if (mIsHorizontal) - { - new_width += rect.getWidth() + mSpacing; - new_height = llmax(new_height, rect.getHeight()); - } - else - { - new_height += rect.getHeight() + mSpacing; - new_width = llmax(new_width, rect.getWidth()); - } - } - - if (mIsHorizontal) - { - new_width -= mSpacing; - } - else - { - new_height -= mSpacing; - } - } - else - { - new_width = new_height = 1; - } - - LLRect rc = getRect(); - if (mIsHorizontal || !followsRight()) - { - rc.mRight = rc.mLeft + new_width; - } - if (!mIsHorizontal || !followsBottom()) - { - rc.mBottom = rc.mTop - new_height; - } - - if (rc.mRight != getRect().mRight || rc.mBottom != getRect().mBottom) - { - setRect(rc); - notifySizeChanged(); - } - - // Reposition each of the child views - S32 pos = mIsHorizontal ? mPadding : rc.getHeight() - mPadding; - for (std::deque::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) - { - LLScrollingPanel* childp = *iter; - const LLRect& rect = childp->getRect(); - if (mIsHorizontal) - { - childp->translate(pos - rect.mLeft, rc.getHeight() - mPadding - rect.mTop); - pos += rect.getWidth() + mSpacing; - } - else - { - childp->translate(mPadding - rect.mLeft, pos - rect.mTop); - pos -= rect.getHeight() + mSpacing; - } - } -} - -void LLScrollingPanelList::updatePanelVisiblilty() -{ - // Determine visibility of children. - - LLRect parent_screen_rect; - getParent()->localPointToScreen( - mPadding, mPadding, - &parent_screen_rect.mLeft, &parent_screen_rect.mBottom ); - getParent()->localPointToScreen( - getParent()->getRect().getWidth() - mPadding, - getParent()->getRect().getHeight() - mPadding, - &parent_screen_rect.mRight, &parent_screen_rect.mTop ); - - for (std::deque::iterator iter = mPanelList.begin(); - iter != mPanelList.end(); ++iter) - { - LLScrollingPanel *childp = *iter; - const LLRect& local_rect = childp->getRect(); - LLRect screen_rect; - childp->localPointToScreen( - 0, 0, - &screen_rect.mLeft, &screen_rect.mBottom ); - childp->localPointToScreen( - local_rect.getWidth(), local_rect.getHeight(), - &screen_rect.mRight, &screen_rect.mTop ); - - bool intersects = - ( (screen_rect.mRight > parent_screen_rect.mLeft) && (screen_rect.mLeft < parent_screen_rect.mRight) ) && - ( (screen_rect.mTop > parent_screen_rect.mBottom) && (screen_rect.mBottom < parent_screen_rect.mTop) ); - - childp->setVisible( intersects ); - } -} - - -void LLScrollingPanelList::draw() -{ - updatePanelVisiblilty(); - - LLUICtrl::draw(); -} - -void LLScrollingPanelList::notifySizeChanged() -{ - LLSD info; - info["action"] = "size_changes"; - info["height"] = getRect().getHeight(); - info["width"] = getRect().getWidth(); - notifyParent(info); -} - -// EOF +/** + * @file llscrollingpanellist.cpp + * @brief + * + * $LicenseInfo:firstyear=2006&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 "llstl.h" + +#include "llscrollingpanellist.h" + +static LLDefaultChildRegistry::Register r("scrolling_panel_list"); + + +///////////////////////////////////////////////////////////////////// +// LLScrollingPanelList + +// This could probably be integrated with LLScrollContainer -SJB + +LLScrollingPanelList::Params::Params() + : is_horizontal("is_horizontal") + , padding("padding") + , spacing("spacing") +{ +} + +LLScrollingPanelList::LLScrollingPanelList(const Params& p) + : LLUICtrl(p) + , mIsHorizontal(p.is_horizontal) + , mPadding(p.padding.isProvided() ? p.padding : DEFAULT_PADDING) + , mSpacing(p.spacing.isProvided() ? p.spacing : DEFAULT_SPACING) +{ +} + +void LLScrollingPanelList::clearPanels() +{ + deleteAllChildren(); + mPanelList.clear(); + rearrange(); +} + +S32 LLScrollingPanelList::addPanel(LLScrollingPanel* panel, bool back) +{ + if (back) + { + addChild(panel); + mPanelList.push_back(panel); + } + else + { + addChildInBack(panel); + mPanelList.push_front(panel); + } + + rearrange(); + + return mIsHorizontal ? getRect().getWidth() : getRect().getHeight(); +} + +void LLScrollingPanelList::removePanel(LLScrollingPanel* panel) +{ + U32 index = 0; + LLScrollingPanelList::panel_list_t::const_iterator iter; + + if (!mPanelList.empty()) + { + for (iter = mPanelList.begin(); iter != mPanelList.end(); ++iter, ++index) + { + if (*iter == panel) + { + break; + } + } + if (iter != mPanelList.end()) + { + removePanel(index); + } + } +} + +void LLScrollingPanelList::removePanel( U32 panel_index ) +{ + if ( mPanelList.empty() || panel_index >= mPanelList.size() ) + { + LL_WARNS() << "Panel index " << panel_index << " is out of range!" << LL_ENDL; + return; + } + else + { + removeChild( mPanelList.at(panel_index) ); + mPanelList.erase( mPanelList.begin() + panel_index ); + } + + rearrange(); +} + +void LLScrollingPanelList::updatePanels(bool allow_modify) +{ + for (std::deque::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + childp->updatePanel(allow_modify); + } +} + +void LLScrollingPanelList::rearrange() +{ + // Resize this view + S32 new_width, new_height; + if (!mPanelList.empty()) + { + new_width = new_height = mPadding * 2; + for (std::deque::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel* childp = *iter; + const LLRect& rect = childp->getRect(); + if (mIsHorizontal) + { + new_width += rect.getWidth() + mSpacing; + new_height = llmax(new_height, rect.getHeight()); + } + else + { + new_height += rect.getHeight() + mSpacing; + new_width = llmax(new_width, rect.getWidth()); + } + } + + if (mIsHorizontal) + { + new_width -= mSpacing; + } + else + { + new_height -= mSpacing; + } + } + else + { + new_width = new_height = 1; + } + + LLRect rc = getRect(); + if (mIsHorizontal || !followsRight()) + { + rc.mRight = rc.mLeft + new_width; + } + if (!mIsHorizontal || !followsBottom()) + { + rc.mBottom = rc.mTop - new_height; + } + + if (rc.mRight != getRect().mRight || rc.mBottom != getRect().mBottom) + { + setRect(rc); + notifySizeChanged(); + } + + // Reposition each of the child views + S32 pos = mIsHorizontal ? mPadding : rc.getHeight() - mPadding; + for (std::deque::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel* childp = *iter; + const LLRect& rect = childp->getRect(); + if (mIsHorizontal) + { + childp->translate(pos - rect.mLeft, rc.getHeight() - mPadding - rect.mTop); + pos += rect.getWidth() + mSpacing; + } + else + { + childp->translate(mPadding - rect.mLeft, pos - rect.mTop); + pos -= rect.getHeight() + mSpacing; + } + } +} + +void LLScrollingPanelList::updatePanelVisiblilty() +{ + // Determine visibility of children. + + LLRect parent_screen_rect; + getParent()->localPointToScreen( + mPadding, mPadding, + &parent_screen_rect.mLeft, &parent_screen_rect.mBottom ); + getParent()->localPointToScreen( + getParent()->getRect().getWidth() - mPadding, + getParent()->getRect().getHeight() - mPadding, + &parent_screen_rect.mRight, &parent_screen_rect.mTop ); + + for (std::deque::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + const LLRect& local_rect = childp->getRect(); + LLRect screen_rect; + childp->localPointToScreen( + 0, 0, + &screen_rect.mLeft, &screen_rect.mBottom ); + childp->localPointToScreen( + local_rect.getWidth(), local_rect.getHeight(), + &screen_rect.mRight, &screen_rect.mTop ); + + bool intersects = + ( (screen_rect.mRight > parent_screen_rect.mLeft) && (screen_rect.mLeft < parent_screen_rect.mRight) ) && + ( (screen_rect.mTop > parent_screen_rect.mBottom) && (screen_rect.mBottom < parent_screen_rect.mTop) ); + + childp->setVisible( intersects ); + } +} + + +void LLScrollingPanelList::draw() +{ + updatePanelVisiblilty(); + + LLUICtrl::draw(); +} + +void LLScrollingPanelList::notifySizeChanged() +{ + LLSD info; + info["action"] = "size_changes"; + info["height"] = getRect().getHeight(); + info["width"] = getRect().getWidth(); + notifyParent(info); +} + +// EOF diff --git a/indra/llui/llscrollingpanellist.h b/indra/llui/llscrollingpanellist.h index 8e92e3d6aa..309ce4592d 100644 --- a/indra/llui/llscrollingpanellist.h +++ b/indra/llui/llscrollingpanellist.h @@ -1,102 +1,102 @@ -/** - * @file llscrollingpanellist.h - * - * $LicenseInfo:firstyear=2006&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$ - */ - -#ifndef LL_LLSCROLLINGPANELLIST_H -#define LL_LLSCROLLINGPANELLIST_H - -#include - -#include "llui.h" -#include "lluictrlfactory.h" -#include "llview.h" -#include "llpanel.h" - -/* - * Pure virtual class represents a scrolling panel. - */ -class LLScrollingPanel : public LLPanel -{ -public: - LLScrollingPanel(const LLPanel::Params& params) : LLPanel(params) {} - virtual void updatePanel(bool allow_modify) = 0; -}; - - -/* - * A set of panels that are displayed in a sequence inside a scroll container. - */ -class LLScrollingPanelList : public LLUICtrl -{ -public: - struct Params : public LLInitParam::Block - { - Optional is_horizontal; - Optional padding; - Optional spacing; - - Params(); - }; - - LLScrollingPanelList(const Params& p); - - static const S32 DEFAULT_SPACING = 6; - static const S32 DEFAULT_PADDING = 2; - - typedef std::deque panel_list_t; - - virtual void setValue(const LLSD& value) {}; - - virtual void draw(); - - void clearPanels(); - S32 addPanel(LLScrollingPanel* panel, bool back = false); - void removePanel(LLScrollingPanel* panel); - void removePanel(U32 panel_index); - void updatePanels(bool allow_modify); - void rearrange(); - - const panel_list_t& getPanelList() const { return mPanelList; } - bool getIsHorizontal() const { return mIsHorizontal; } - void setPadding(S32 padding) { mPadding = padding; rearrange(); } - void setSpacing(S32 spacing) { mSpacing = spacing; rearrange(); } - S32 getPadding() const { return mPadding; } - S32 getSpacing() const { return mSpacing; } - -private: - void updatePanelVisiblilty(); - - /** - * Notify parent about size change, makes sense when used inside accordion - */ - void notifySizeChanged(); - - bool mIsHorizontal; - S32 mPadding; - S32 mSpacing; - - panel_list_t mPanelList; -}; - -#endif //LL_LLSCROLLINGPANELLIST_H +/** + * @file llscrollingpanellist.h + * + * $LicenseInfo:firstyear=2006&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$ + */ + +#ifndef LL_LLSCROLLINGPANELLIST_H +#define LL_LLSCROLLINGPANELLIST_H + +#include + +#include "llui.h" +#include "lluictrlfactory.h" +#include "llview.h" +#include "llpanel.h" + +/* + * Pure virtual class represents a scrolling panel. + */ +class LLScrollingPanel : public LLPanel +{ +public: + LLScrollingPanel(const LLPanel::Params& params) : LLPanel(params) {} + virtual void updatePanel(bool allow_modify) = 0; +}; + + +/* + * A set of panels that are displayed in a sequence inside a scroll container. + */ +class LLScrollingPanelList : public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block + { + Optional is_horizontal; + Optional padding; + Optional spacing; + + Params(); + }; + + LLScrollingPanelList(const Params& p); + + static const S32 DEFAULT_SPACING = 6; + static const S32 DEFAULT_PADDING = 2; + + typedef std::deque panel_list_t; + + virtual void setValue(const LLSD& value) {}; + + virtual void draw(); + + void clearPanels(); + S32 addPanel(LLScrollingPanel* panel, bool back = false); + void removePanel(LLScrollingPanel* panel); + void removePanel(U32 panel_index); + void updatePanels(bool allow_modify); + void rearrange(); + + const panel_list_t& getPanelList() const { return mPanelList; } + bool getIsHorizontal() const { return mIsHorizontal; } + void setPadding(S32 padding) { mPadding = padding; rearrange(); } + void setSpacing(S32 spacing) { mSpacing = spacing; rearrange(); } + S32 getPadding() const { return mPadding; } + S32 getSpacing() const { return mSpacing; } + +private: + void updatePanelVisiblilty(); + + /** + * Notify parent about size change, makes sense when used inside accordion + */ + void notifySizeChanged(); + + bool mIsHorizontal; + S32 mPadding; + S32 mSpacing; + + panel_list_t mPanelList; +}; + +#endif //LL_LLSCROLLINGPANELLIST_H diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp index d58696a5c9..403879646d 100644 --- a/indra/llui/llscrolllistcell.cpp +++ b/indra/llui/llscrolllistcell.cpp @@ -1,671 +1,671 @@ -/** - * @file llscrolllistcell.cpp - * @brief Scroll lists are composed of rows (items), each of which - * contains columns (cells). - * - * $LicenseInfo:firstyear=2007&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 "llscrolllistcell.h" - -#include "llcheckboxctrl.h" -#include "llui.h" // LLUIImage -#include "lluictrlfactory.h" - -//static -LLScrollListCell* LLScrollListCell::create(const LLScrollListCell::Params& cell_p) -{ - LLScrollListCell* cell = NULL; - - if (cell_p.type() == "icon") - { - cell = new LLScrollListIcon(cell_p); - } - else if (cell_p.type() == "checkbox") - { - cell = new LLScrollListCheck(cell_p); - } - else if (cell_p.type() == "date") - { - cell = new LLScrollListDate(cell_p); - } - else if (cell_p.type() == "icontext") - { - cell = new LLScrollListIconText(cell_p); - } - else if (cell_p.type() == "bar") - { - cell = new LLScrollListBar(cell_p); - } - else // default is "text" - { - cell = new LLScrollListText(cell_p); - } - - if (cell_p.value.isProvided()) - { - cell->setValue(cell_p.value); - } - - return cell; -} - - -LLScrollListCell::LLScrollListCell(const LLScrollListCell::Params& p) -: mWidth(p.width), - mToolTip(p.tool_tip) -{} - -// virtual -const LLSD LLScrollListCell::getValue() const -{ - return LLStringUtil::null; -} - - -// virtual -const LLSD LLScrollListCell::getAltValue() const -{ - return LLStringUtil::null; -} - - -// -// LLScrollListIcon -// -LLScrollListIcon::LLScrollListIcon(const LLScrollListCell::Params& p) -: LLScrollListCell(p), - mIcon(LLUI::getUIImage(p.value().asString())), - mColor(p.color), - mAlignment(p.font_halign) -{} - -LLScrollListIcon::~LLScrollListIcon() -{ -} - -/*virtual*/ -S32 LLScrollListIcon::getHeight() const -{ return mIcon ? mIcon->getHeight() : 0; } - -/*virtual*/ -const LLSD LLScrollListIcon::getValue() const -{ return mIcon.isNull() ? LLStringUtil::null : mIcon->getName(); } - -void LLScrollListIcon::setValue(const LLSD& value) -{ - if (value.isUUID()) - { - // don't use default image specified by LLUUID::null, use no image in that case - LLUUID image_id = value.asUUID(); - mIcon = image_id.notNull() ? LLUI::getUIImageByID(image_id) : LLUIImagePtr(NULL); - } - else - { - std::string value_string = value.asString(); - if (LLUUID::validate(value_string)) - { - setValue(LLUUID(value_string)); - } - else if (!value_string.empty()) - { - mIcon = LLUI::getUIImage(value.asString()); - } - else - { - mIcon = NULL; - } - } -} - - -void LLScrollListIcon::setColor(const LLColor4& color) -{ - mColor = color; -} - -S32 LLScrollListIcon::getWidth() const -{ - // if no specified fix width, use width of icon - if (LLScrollListCell::getWidth() == 0 && mIcon.notNull()) - { - return mIcon->getWidth(); - } - return LLScrollListCell::getWidth(); -} - - -void LLScrollListIcon::draw(const LLColor4& color, const LLColor4& highlight_color) const -{ - if (mIcon) - { - switch(mAlignment) - { - case LLFontGL::LEFT: - mIcon->draw(0, 0, mColor); - break; - case LLFontGL::RIGHT: - mIcon->draw(getWidth() - mIcon->getWidth(), 0, mColor); - break; - case LLFontGL::HCENTER: - mIcon->draw((getWidth() - mIcon->getWidth()) / 2, 0, mColor); - break; - default: - break; - } - } -} - -// -// LLScrollListBar -// -LLScrollListBar::LLScrollListBar(const LLScrollListCell::Params& p) - : LLScrollListCell(p), - mRatio(0), - mColor(p.color), - mBottom(1), - mLeftPad(1), - mRightPad(1) -{} - -LLScrollListBar::~LLScrollListBar() -{ -} - -/*virtual*/ -S32 LLScrollListBar::getHeight() const -{ - return LLScrollListCell::getHeight(); -} - -/*virtual*/ -const LLSD LLScrollListBar::getValue() const -{ - return LLStringUtil::null; -} - -void LLScrollListBar::setValue(const LLSD& value) -{ - if (value.has("ratio")) - { - mRatio = value["ratio"].asReal(); - } - if (value.has("bottom")) - { - mBottom = value["bottom"].asInteger(); - } - if (value.has("left_pad")) - { - mLeftPad = value["left_pad"].asInteger(); - } - if (value.has("right_pad")) - { - mRightPad = value["right_pad"].asInteger(); - } -} - -void LLScrollListBar::setColor(const LLColor4& color) -{ - mColor = color; -} - -S32 LLScrollListBar::getWidth() const -{ - return LLScrollListCell::getWidth(); -} - - -void LLScrollListBar::draw(const LLColor4& color, const LLColor4& highlight_color) const -{ - S32 bar_width = getWidth() - mLeftPad - mRightPad; - S32 left = bar_width - bar_width * mRatio; - left = llclamp(left, mLeftPad, getWidth() - mRightPad - 1); - - gl_rect_2d(left, mBottom, getWidth() - mRightPad, mBottom - 1, mColor); -} - -// -// LLScrollListText -// -U32 LLScrollListText::sCount = 0; - -LLScrollListText::LLScrollListText(const LLScrollListCell::Params& p) -: LLScrollListCell(p), - mText(p.label.isProvided() ? p.label() : p.value().asString()), - mAltText(p.alt_value().asString()), - mFont(p.font), - mColor(p.color), - mUseColor(p.color.isProvided()), - mFontAlignment(p.font_halign), - mVisible(p.visible), - mHighlightCount( 0 ), - mHighlightOffset( 0 ) -{ - sCount++; - - mTextWidth = getWidth(); - - // initialize rounded rect image - if (!mRoundedRectImage) - { - mRoundedRectImage = LLUI::getUIImage("Rounded_Square"); - } -} - -//virtual -void LLScrollListText::highlightText(S32 offset, S32 num_chars) -{ - mHighlightOffset = offset; - mHighlightCount = llmax(0, num_chars); -} - -//virtual -bool LLScrollListText::isText() const -{ - return true; -} - -// virtual -const std::string &LLScrollListText::getToolTip() const -{ - // If base class has a tooltip, return that - if (! LLScrollListCell::getToolTip().empty()) - return LLScrollListCell::getToolTip(); - - // ...otherwise, return the value itself as the tooltip - return mText.getString(); -} - -// virtual -bool LLScrollListText::needsToolTip() const -{ - // If base class has a tooltip, return that - if (LLScrollListCell::needsToolTip()) - return LLScrollListCell::needsToolTip(); - - // ...otherwise, show tooltips for truncated text - return mFont->getWidth(mText.getString()) > getWidth(); -} - -//virtual -bool LLScrollListText::getVisible() const -{ - return mVisible; -} - -//virtual -S32 LLScrollListText::getHeight() const -{ - return mFont->getLineHeight(); -} - - -LLScrollListText::~LLScrollListText() -{ - sCount--; -} - -S32 LLScrollListText::getContentWidth() const -{ - return mFont->getWidth(mText.getString()); -} - - -void LLScrollListText::setColor(const LLColor4& color) -{ - mColor = color; - mUseColor = true; -} - -void LLScrollListText::setText(const LLStringExplicit& text) -{ - mText = text; -} - -void LLScrollListText::setFontStyle(const U8 font_style) -{ - LLFontDescriptor new_desc(mFont->getFontDesc()); - new_desc.setStyle(font_style); - mFont = LLFontGL::getFont(new_desc); -} - -//virtual -void LLScrollListText::setValue(const LLSD& text) -{ - setText(text.asString()); -} - -//virtual -void LLScrollListText::setAltValue(const LLSD& text) -{ - mAltText = text.asString(); -} - -//virtual -const LLSD LLScrollListText::getValue() const -{ - return LLSD(mText.getString()); -} - -//virtual -const LLSD LLScrollListText::getAltValue() const -{ - return LLSD(mAltText.getString()); -} - - -void LLScrollListText::draw(const LLColor4& color, const LLColor4& highlight_color) const -{ - LLColor4 display_color; - if (mUseColor) - { - display_color = mColor; - } - else - { - display_color = color; - } - - if (mHighlightCount > 0) - { - // Highlight text - S32 left = 0; - switch(mFontAlignment) - { - case LLFontGL::LEFT: - left = mFont->getWidth(mText.getString(), 1, mHighlightOffset); - break; - case LLFontGL::RIGHT: - left = getWidth() - mFont->getWidth(mText.getString(), mHighlightOffset, S32_MAX); - break; - case LLFontGL::HCENTER: - left = (getWidth() - mFont->getWidth(mText.getString())) / 2; - break; - } - LLRect highlight_rect(left - 2, - mFont->getLineHeight() + 1, - left + mFont->getWidth(mText.getString(), mHighlightOffset, mHighlightCount) + 1, - 1); - mRoundedRectImage->draw(highlight_rect, highlight_color); - } - - // Try to draw the entire string - F32 right_x; - U32 string_chars = mText.length(); - F32 start_x = 0.f; - switch(mFontAlignment) - { - case LLFontGL::LEFT: - start_x = 1.f; - break; - case LLFontGL::RIGHT: - start_x = (F32)getWidth(); - break; - case LLFontGL::HCENTER: - start_x = (F32)getWidth() * 0.5f; - break; - } - mFont->render(mText.getWString(), 0, - start_x, 0.f, - display_color, - mFontAlignment, - LLFontGL::BOTTOM, - 0, - LLFontGL::NO_SHADOW, - string_chars, - getTextWidth(), - &right_x, - true); -} - -// -// LLScrollListCheck -// -LLScrollListCheck::LLScrollListCheck(const LLScrollListCell::Params& p) -: LLScrollListCell(p) -{ - LLCheckBoxCtrl::Params checkbox_p; - checkbox_p.name("checkbox"); - checkbox_p.rect = LLRect(0, p.width, p.width, 0); - checkbox_p.enabled(p.enabled); - checkbox_p.initial_value(p.value()); - - mCheckBox = LLUICtrlFactory::create(checkbox_p); - - LLRect rect(mCheckBox->getRect()); - if (p.width) - { - rect.mRight = rect.mLeft + p.width; - mCheckBox->setRect(rect); - setWidth(p.width); - } - else - { - setWidth(rect.getWidth()); //check_box->getWidth(); - } - - mCheckBox->setColor(p.color); -} - - -LLScrollListCheck::~LLScrollListCheck() -{ - delete mCheckBox; - mCheckBox = NULL; -} - -void LLScrollListCheck::draw(const LLColor4& color, const LLColor4& highlight_color) const -{ - mCheckBox->draw(); -} - -bool LLScrollListCheck::handleClick() -{ - if (mCheckBox->getEnabled()) - { - mCheckBox->toggle(); - } - // don't change selection when clicking on embedded checkbox - return true; -} - -/*virtual*/ -const LLSD LLScrollListCheck::getValue() const -{ - return mCheckBox->getValue(); -} - -/*virtual*/ -void LLScrollListCheck::setValue(const LLSD& value) -{ - mCheckBox->setValue(value); -} - -/*virtual*/ -void LLScrollListCheck::onCommit() -{ - mCheckBox->onCommit(); -} - -/*virtual*/ -void LLScrollListCheck::setEnabled(bool enable) -{ - mCheckBox->setEnabled(enable); -} - -// -// LLScrollListDate -// - -LLScrollListDate::LLScrollListDate( const LLScrollListCell::Params& p) -: LLScrollListText(p), - mDate(p.value().asDate()) -{} - -void LLScrollListDate::setValue(const LLSD& value) -{ - mDate = value.asDate(); - LLScrollListText::setValue(mDate.asRFC1123()); -} - -const LLSD LLScrollListDate::getValue() const -{ - return mDate; -} - -// -// LLScrollListIconText -// -LLScrollListIconText::LLScrollListIconText(const LLScrollListCell::Params& p) - : LLScrollListText(p), - mIcon(p.value().isUUID() ? LLUI::getUIImageByID(p.value().asUUID()) : LLUI::getUIImage(p.value().asString())), - mPad(4) -{ - mTextWidth = getWidth() - mPad /*padding*/ - mFont->getLineHeight(); -} - -LLScrollListIconText::~LLScrollListIconText() -{ -} - -const LLSD LLScrollListIconText::getValue() const -{ - if (mIcon.isNull()) - { - return LLStringUtil::null; - } - return mIcon->getName(); -} - -void LLScrollListIconText::setValue(const LLSD& value) -{ - if (value.isUUID()) - { - // don't use default image specified by LLUUID::null, use no image in that case - LLUUID image_id = value.asUUID(); - mIcon = image_id.notNull() ? LLUI::getUIImageByID(image_id) : LLUIImagePtr(NULL); - } - else - { - std::string value_string = value.asString(); - if (LLUUID::validate(value_string)) - { - setValue(LLUUID(value_string)); - } - else if (!value_string.empty()) - { - mIcon = LLUI::getUIImage(value.asString()); - } - else - { - mIcon = NULL; - } - } -} - -void LLScrollListIconText::setWidth(S32 width) -{ - LLScrollListCell::setWidth(width); - // Assume that iamge height and width is identical to font height and width - mTextWidth = width - mPad /*padding*/ - mFont->getLineHeight(); -} - - -void LLScrollListIconText::draw(const LLColor4& color, const LLColor4& highlight_color) const -{ - LLColor4 display_color; - if (mUseColor) - { - display_color = mColor; - } - else - { - display_color = color; - } - - S32 icon_height = mFont->getLineHeight(); - S32 icon_space = mIcon ? (icon_height + mPad) : 0; - - if (mHighlightCount > 0) - { - S32 left = 0; - switch (mFontAlignment) - { - case LLFontGL::LEFT: - left = mFont->getWidth(mText.getString(), icon_space + 1, mHighlightOffset); - break; - case LLFontGL::RIGHT: - left = getWidth() - mFont->getWidth(mText.getString(), mHighlightOffset, S32_MAX) - icon_space; - break; - case LLFontGL::HCENTER: - left = (getWidth() - mFont->getWidth(mText.getString()) - icon_space) / 2; - break; - } - LLRect highlight_rect(left - 2, - mFont->getLineHeight() + 1, - left + mFont->getWidth(mText.getString(), mHighlightOffset, mHighlightCount) + 1, - 1); - mRoundedRectImage->draw(highlight_rect, highlight_color); - } - - // Try to draw the entire string - F32 right_x; - U32 string_chars = mText.length(); - F32 start_text_x = 0.f; - S32 start_icon_x = 0; - switch (mFontAlignment) - { - case LLFontGL::LEFT: - start_text_x = icon_space + 1; - start_icon_x = 1; - break; - case LLFontGL::RIGHT: - start_text_x = (F32)getWidth(); - start_icon_x = getWidth() - mFont->getWidth(mText.getString()) - icon_space; - break; - case LLFontGL::HCENTER: - F32 center = (F32)getWidth()* 0.5f; - start_text_x = center + ((F32)icon_space * 0.5f); - start_icon_x = center - (((F32)icon_space + mFont->getWidth(mText.getString())) * 0.5f); - break; - } - mFont->render(mText.getWString(), 0, - start_text_x, 0.f, - display_color, - mFontAlignment, - LLFontGL::BOTTOM, - 0, - LLFontGL::NO_SHADOW, - string_chars, - getTextWidth(), - &right_x, - true); - - if (mIcon) - { - mIcon->draw(start_icon_x, 0, icon_height, icon_height, mColor); - } -} - - +/** + * @file llscrolllistcell.cpp + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2007&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 "llscrolllistcell.h" + +#include "llcheckboxctrl.h" +#include "llui.h" // LLUIImage +#include "lluictrlfactory.h" + +//static +LLScrollListCell* LLScrollListCell::create(const LLScrollListCell::Params& cell_p) +{ + LLScrollListCell* cell = NULL; + + if (cell_p.type() == "icon") + { + cell = new LLScrollListIcon(cell_p); + } + else if (cell_p.type() == "checkbox") + { + cell = new LLScrollListCheck(cell_p); + } + else if (cell_p.type() == "date") + { + cell = new LLScrollListDate(cell_p); + } + else if (cell_p.type() == "icontext") + { + cell = new LLScrollListIconText(cell_p); + } + else if (cell_p.type() == "bar") + { + cell = new LLScrollListBar(cell_p); + } + else // default is "text" + { + cell = new LLScrollListText(cell_p); + } + + if (cell_p.value.isProvided()) + { + cell->setValue(cell_p.value); + } + + return cell; +} + + +LLScrollListCell::LLScrollListCell(const LLScrollListCell::Params& p) +: mWidth(p.width), + mToolTip(p.tool_tip) +{} + +// virtual +const LLSD LLScrollListCell::getValue() const +{ + return LLStringUtil::null; +} + + +// virtual +const LLSD LLScrollListCell::getAltValue() const +{ + return LLStringUtil::null; +} + + +// +// LLScrollListIcon +// +LLScrollListIcon::LLScrollListIcon(const LLScrollListCell::Params& p) +: LLScrollListCell(p), + mIcon(LLUI::getUIImage(p.value().asString())), + mColor(p.color), + mAlignment(p.font_halign) +{} + +LLScrollListIcon::~LLScrollListIcon() +{ +} + +/*virtual*/ +S32 LLScrollListIcon::getHeight() const +{ return mIcon ? mIcon->getHeight() : 0; } + +/*virtual*/ +const LLSD LLScrollListIcon::getValue() const +{ return mIcon.isNull() ? LLStringUtil::null : mIcon->getName(); } + +void LLScrollListIcon::setValue(const LLSD& value) +{ + if (value.isUUID()) + { + // don't use default image specified by LLUUID::null, use no image in that case + LLUUID image_id = value.asUUID(); + mIcon = image_id.notNull() ? LLUI::getUIImageByID(image_id) : LLUIImagePtr(NULL); + } + else + { + std::string value_string = value.asString(); + if (LLUUID::validate(value_string)) + { + setValue(LLUUID(value_string)); + } + else if (!value_string.empty()) + { + mIcon = LLUI::getUIImage(value.asString()); + } + else + { + mIcon = NULL; + } + } +} + + +void LLScrollListIcon::setColor(const LLColor4& color) +{ + mColor = color; +} + +S32 LLScrollListIcon::getWidth() const +{ + // if no specified fix width, use width of icon + if (LLScrollListCell::getWidth() == 0 && mIcon.notNull()) + { + return mIcon->getWidth(); + } + return LLScrollListCell::getWidth(); +} + + +void LLScrollListIcon::draw(const LLColor4& color, const LLColor4& highlight_color) const +{ + if (mIcon) + { + switch(mAlignment) + { + case LLFontGL::LEFT: + mIcon->draw(0, 0, mColor); + break; + case LLFontGL::RIGHT: + mIcon->draw(getWidth() - mIcon->getWidth(), 0, mColor); + break; + case LLFontGL::HCENTER: + mIcon->draw((getWidth() - mIcon->getWidth()) / 2, 0, mColor); + break; + default: + break; + } + } +} + +// +// LLScrollListBar +// +LLScrollListBar::LLScrollListBar(const LLScrollListCell::Params& p) + : LLScrollListCell(p), + mRatio(0), + mColor(p.color), + mBottom(1), + mLeftPad(1), + mRightPad(1) +{} + +LLScrollListBar::~LLScrollListBar() +{ +} + +/*virtual*/ +S32 LLScrollListBar::getHeight() const +{ + return LLScrollListCell::getHeight(); +} + +/*virtual*/ +const LLSD LLScrollListBar::getValue() const +{ + return LLStringUtil::null; +} + +void LLScrollListBar::setValue(const LLSD& value) +{ + if (value.has("ratio")) + { + mRatio = value["ratio"].asReal(); + } + if (value.has("bottom")) + { + mBottom = value["bottom"].asInteger(); + } + if (value.has("left_pad")) + { + mLeftPad = value["left_pad"].asInteger(); + } + if (value.has("right_pad")) + { + mRightPad = value["right_pad"].asInteger(); + } +} + +void LLScrollListBar::setColor(const LLColor4& color) +{ + mColor = color; +} + +S32 LLScrollListBar::getWidth() const +{ + return LLScrollListCell::getWidth(); +} + + +void LLScrollListBar::draw(const LLColor4& color, const LLColor4& highlight_color) const +{ + S32 bar_width = getWidth() - mLeftPad - mRightPad; + S32 left = bar_width - bar_width * mRatio; + left = llclamp(left, mLeftPad, getWidth() - mRightPad - 1); + + gl_rect_2d(left, mBottom, getWidth() - mRightPad, mBottom - 1, mColor); +} + +// +// LLScrollListText +// +U32 LLScrollListText::sCount = 0; + +LLScrollListText::LLScrollListText(const LLScrollListCell::Params& p) +: LLScrollListCell(p), + mText(p.label.isProvided() ? p.label() : p.value().asString()), + mAltText(p.alt_value().asString()), + mFont(p.font), + mColor(p.color), + mUseColor(p.color.isProvided()), + mFontAlignment(p.font_halign), + mVisible(p.visible), + mHighlightCount( 0 ), + mHighlightOffset( 0 ) +{ + sCount++; + + mTextWidth = getWidth(); + + // initialize rounded rect image + if (!mRoundedRectImage) + { + mRoundedRectImage = LLUI::getUIImage("Rounded_Square"); + } +} + +//virtual +void LLScrollListText::highlightText(S32 offset, S32 num_chars) +{ + mHighlightOffset = offset; + mHighlightCount = llmax(0, num_chars); +} + +//virtual +bool LLScrollListText::isText() const +{ + return true; +} + +// virtual +const std::string &LLScrollListText::getToolTip() const +{ + // If base class has a tooltip, return that + if (! LLScrollListCell::getToolTip().empty()) + return LLScrollListCell::getToolTip(); + + // ...otherwise, return the value itself as the tooltip + return mText.getString(); +} + +// virtual +bool LLScrollListText::needsToolTip() const +{ + // If base class has a tooltip, return that + if (LLScrollListCell::needsToolTip()) + return LLScrollListCell::needsToolTip(); + + // ...otherwise, show tooltips for truncated text + return mFont->getWidth(mText.getString()) > getWidth(); +} + +//virtual +bool LLScrollListText::getVisible() const +{ + return mVisible; +} + +//virtual +S32 LLScrollListText::getHeight() const +{ + return mFont->getLineHeight(); +} + + +LLScrollListText::~LLScrollListText() +{ + sCount--; +} + +S32 LLScrollListText::getContentWidth() const +{ + return mFont->getWidth(mText.getString()); +} + + +void LLScrollListText::setColor(const LLColor4& color) +{ + mColor = color; + mUseColor = true; +} + +void LLScrollListText::setText(const LLStringExplicit& text) +{ + mText = text; +} + +void LLScrollListText::setFontStyle(const U8 font_style) +{ + LLFontDescriptor new_desc(mFont->getFontDesc()); + new_desc.setStyle(font_style); + mFont = LLFontGL::getFont(new_desc); +} + +//virtual +void LLScrollListText::setValue(const LLSD& text) +{ + setText(text.asString()); +} + +//virtual +void LLScrollListText::setAltValue(const LLSD& text) +{ + mAltText = text.asString(); +} + +//virtual +const LLSD LLScrollListText::getValue() const +{ + return LLSD(mText.getString()); +} + +//virtual +const LLSD LLScrollListText::getAltValue() const +{ + return LLSD(mAltText.getString()); +} + + +void LLScrollListText::draw(const LLColor4& color, const LLColor4& highlight_color) const +{ + LLColor4 display_color; + if (mUseColor) + { + display_color = mColor; + } + else + { + display_color = color; + } + + if (mHighlightCount > 0) + { + // Highlight text + S32 left = 0; + switch(mFontAlignment) + { + case LLFontGL::LEFT: + left = mFont->getWidth(mText.getString(), 1, mHighlightOffset); + break; + case LLFontGL::RIGHT: + left = getWidth() - mFont->getWidth(mText.getString(), mHighlightOffset, S32_MAX); + break; + case LLFontGL::HCENTER: + left = (getWidth() - mFont->getWidth(mText.getString())) / 2; + break; + } + LLRect highlight_rect(left - 2, + mFont->getLineHeight() + 1, + left + mFont->getWidth(mText.getString(), mHighlightOffset, mHighlightCount) + 1, + 1); + mRoundedRectImage->draw(highlight_rect, highlight_color); + } + + // Try to draw the entire string + F32 right_x; + U32 string_chars = mText.length(); + F32 start_x = 0.f; + switch(mFontAlignment) + { + case LLFontGL::LEFT: + start_x = 1.f; + break; + case LLFontGL::RIGHT: + start_x = (F32)getWidth(); + break; + case LLFontGL::HCENTER: + start_x = (F32)getWidth() * 0.5f; + break; + } + mFont->render(mText.getWString(), 0, + start_x, 0.f, + display_color, + mFontAlignment, + LLFontGL::BOTTOM, + 0, + LLFontGL::NO_SHADOW, + string_chars, + getTextWidth(), + &right_x, + true); +} + +// +// LLScrollListCheck +// +LLScrollListCheck::LLScrollListCheck(const LLScrollListCell::Params& p) +: LLScrollListCell(p) +{ + LLCheckBoxCtrl::Params checkbox_p; + checkbox_p.name("checkbox"); + checkbox_p.rect = LLRect(0, p.width, p.width, 0); + checkbox_p.enabled(p.enabled); + checkbox_p.initial_value(p.value()); + + mCheckBox = LLUICtrlFactory::create(checkbox_p); + + LLRect rect(mCheckBox->getRect()); + if (p.width) + { + rect.mRight = rect.mLeft + p.width; + mCheckBox->setRect(rect); + setWidth(p.width); + } + else + { + setWidth(rect.getWidth()); //check_box->getWidth(); + } + + mCheckBox->setColor(p.color); +} + + +LLScrollListCheck::~LLScrollListCheck() +{ + delete mCheckBox; + mCheckBox = NULL; +} + +void LLScrollListCheck::draw(const LLColor4& color, const LLColor4& highlight_color) const +{ + mCheckBox->draw(); +} + +bool LLScrollListCheck::handleClick() +{ + if (mCheckBox->getEnabled()) + { + mCheckBox->toggle(); + } + // don't change selection when clicking on embedded checkbox + return true; +} + +/*virtual*/ +const LLSD LLScrollListCheck::getValue() const +{ + return mCheckBox->getValue(); +} + +/*virtual*/ +void LLScrollListCheck::setValue(const LLSD& value) +{ + mCheckBox->setValue(value); +} + +/*virtual*/ +void LLScrollListCheck::onCommit() +{ + mCheckBox->onCommit(); +} + +/*virtual*/ +void LLScrollListCheck::setEnabled(bool enable) +{ + mCheckBox->setEnabled(enable); +} + +// +// LLScrollListDate +// + +LLScrollListDate::LLScrollListDate( const LLScrollListCell::Params& p) +: LLScrollListText(p), + mDate(p.value().asDate()) +{} + +void LLScrollListDate::setValue(const LLSD& value) +{ + mDate = value.asDate(); + LLScrollListText::setValue(mDate.asRFC1123()); +} + +const LLSD LLScrollListDate::getValue() const +{ + return mDate; +} + +// +// LLScrollListIconText +// +LLScrollListIconText::LLScrollListIconText(const LLScrollListCell::Params& p) + : LLScrollListText(p), + mIcon(p.value().isUUID() ? LLUI::getUIImageByID(p.value().asUUID()) : LLUI::getUIImage(p.value().asString())), + mPad(4) +{ + mTextWidth = getWidth() - mPad /*padding*/ - mFont->getLineHeight(); +} + +LLScrollListIconText::~LLScrollListIconText() +{ +} + +const LLSD LLScrollListIconText::getValue() const +{ + if (mIcon.isNull()) + { + return LLStringUtil::null; + } + return mIcon->getName(); +} + +void LLScrollListIconText::setValue(const LLSD& value) +{ + if (value.isUUID()) + { + // don't use default image specified by LLUUID::null, use no image in that case + LLUUID image_id = value.asUUID(); + mIcon = image_id.notNull() ? LLUI::getUIImageByID(image_id) : LLUIImagePtr(NULL); + } + else + { + std::string value_string = value.asString(); + if (LLUUID::validate(value_string)) + { + setValue(LLUUID(value_string)); + } + else if (!value_string.empty()) + { + mIcon = LLUI::getUIImage(value.asString()); + } + else + { + mIcon = NULL; + } + } +} + +void LLScrollListIconText::setWidth(S32 width) +{ + LLScrollListCell::setWidth(width); + // Assume that iamge height and width is identical to font height and width + mTextWidth = width - mPad /*padding*/ - mFont->getLineHeight(); +} + + +void LLScrollListIconText::draw(const LLColor4& color, const LLColor4& highlight_color) const +{ + LLColor4 display_color; + if (mUseColor) + { + display_color = mColor; + } + else + { + display_color = color; + } + + S32 icon_height = mFont->getLineHeight(); + S32 icon_space = mIcon ? (icon_height + mPad) : 0; + + if (mHighlightCount > 0) + { + S32 left = 0; + switch (mFontAlignment) + { + case LLFontGL::LEFT: + left = mFont->getWidth(mText.getString(), icon_space + 1, mHighlightOffset); + break; + case LLFontGL::RIGHT: + left = getWidth() - mFont->getWidth(mText.getString(), mHighlightOffset, S32_MAX) - icon_space; + break; + case LLFontGL::HCENTER: + left = (getWidth() - mFont->getWidth(mText.getString()) - icon_space) / 2; + break; + } + LLRect highlight_rect(left - 2, + mFont->getLineHeight() + 1, + left + mFont->getWidth(mText.getString(), mHighlightOffset, mHighlightCount) + 1, + 1); + mRoundedRectImage->draw(highlight_rect, highlight_color); + } + + // Try to draw the entire string + F32 right_x; + U32 string_chars = mText.length(); + F32 start_text_x = 0.f; + S32 start_icon_x = 0; + switch (mFontAlignment) + { + case LLFontGL::LEFT: + start_text_x = icon_space + 1; + start_icon_x = 1; + break; + case LLFontGL::RIGHT: + start_text_x = (F32)getWidth(); + start_icon_x = getWidth() - mFont->getWidth(mText.getString()) - icon_space; + break; + case LLFontGL::HCENTER: + F32 center = (F32)getWidth()* 0.5f; + start_text_x = center + ((F32)icon_space * 0.5f); + start_icon_x = center - (((F32)icon_space + mFont->getWidth(mText.getString())) * 0.5f); + break; + } + mFont->render(mText.getWString(), 0, + start_text_x, 0.f, + display_color, + mFontAlignment, + LLFontGL::BOTTOM, + 0, + LLFontGL::NO_SHADOW, + string_chars, + getTextWidth(), + &right_x, + true); + + if (mIcon) + { + mIcon->draw(start_icon_x, 0, icon_height, icon_height, mColor); + } +} + + diff --git a/indra/llui/llscrolllistcell.h b/indra/llui/llscrolllistcell.h index c6c43537c6..c5d785ae52 100644 --- a/indra/llui/llscrolllistcell.h +++ b/indra/llui/llscrolllistcell.h @@ -1,280 +1,280 @@ -/** - * @file llscrolllistcell.h - * @brief Scroll lists are composed of rows (items), each of which - * contains columns (cells). - * - * $LicenseInfo:firstyear=2007&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$ - */ - -#ifndef LLSCROLLLISTCELL_H -#define LLSCROLLLISTCELL_H - -#include "llfontgl.h" // HAlign -#include "llpointer.h" // LLPointer<> -#include "lluistring.h" -#include "v4color.h" -#include "llui.h" -#include "llgltexture.h" - -class LLCheckBoxCtrl; -class LLSD; -class LLUIImage; - -/* - * Represents a cell in a scrollable table. - * - * Sub-classes must return height and other properties - * though width accessors are implemented by the base class. - * It is therefore important for sub-class constructors to call - * setWidth() with realistic values. - */ -class LLScrollListCell -{ -public: - struct Params : public LLInitParam::Block - { - Optional type, - column; - - Optional width; - Optional enabled, - visible; - - Optional userdata; - Optional value; // state of checkbox, icon id/name, date - Optional alt_value; - Optional label; // description or text - Optional tool_tip; - - Optional font; - Optional font_color; - Optional font_halign; - - Optional color; - - Params() - : type("type", "text"), - column("column"), - width("width"), - enabled("enabled", true), - visible("visible", true), - value("value"), - alt_value("alt_value", ""), - label("label"), - tool_tip("tool_tip", ""), - font("font", LLFontGL::getFontEmojiSmall()), - font_color("font_color", LLColor4::black), - color("color", LLColor4::white), - font_halign("halign", LLFontGL::LEFT) - { - addSynonym(column, "name"); - addSynonym(font_color, "font-color"); - } - }; - - static LLScrollListCell* create(const Params&); - - LLScrollListCell(const LLScrollListCell::Params&); - virtual ~LLScrollListCell() {}; - - virtual void draw(const LLColor4& color, const LLColor4& highlight_color) const {}; // truncate to given width, if possible - virtual S32 getWidth() const {return mWidth;} - virtual S32 getContentWidth() const { return 0; } - virtual S32 getHeight() const { return 0; } - virtual const LLSD getValue() const; - virtual const LLSD getAltValue() const; - virtual void setValue(const LLSD& value) { } - virtual void setAltValue(const LLSD& value) { } - virtual const std::string &getToolTip() const { return mToolTip; } - virtual void setToolTip(const std::string &str) { mToolTip = str; } - virtual bool getVisible() const { return true; } - virtual void setWidth(S32 width) { mWidth = width; } - virtual void highlightText(S32 offset, S32 num_chars) {} - virtual bool isText() const { return false; } - virtual bool needsToolTip() const { return ! mToolTip.empty(); } - virtual void setColor(const LLColor4&) {} - virtual void onCommit() {}; - - virtual bool handleClick() { return false; } - virtual void setEnabled(bool enable) { } - -private: - S32 mWidth; - std::string mToolTip; -}; - -class LLScrollListSpacer : public LLScrollListCell -{ -public: - LLScrollListSpacer(const LLScrollListCell::Params& p) : LLScrollListCell(p) {} - /*virtual*/ ~LLScrollListSpacer() {}; - /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const {} -}; - -/* - * Cell displaying a text label. - */ -class LLScrollListText : public LLScrollListCell -{ -public: - LLScrollListText(const LLScrollListCell::Params&); - /*virtual*/ ~LLScrollListText(); - - /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; - /*virtual*/ S32 getContentWidth() const; - /*virtual*/ S32 getHeight() const; - /*virtual*/ void setValue(const LLSD& value); - /*virtual*/ void setAltValue(const LLSD& value); - /*virtual*/ const LLSD getValue() const; - /*virtual*/ const LLSD getAltValue() const; - /*virtual*/ bool getVisible() const; - /*virtual*/ void highlightText(S32 offset, S32 num_chars); - - /*virtual*/ void setColor(const LLColor4&); - /*virtual*/ bool isText() const; - /*virtual*/ const std::string & getToolTip() const; - /*virtual*/ bool needsToolTip() const; - - S32 getTextWidth() const { return mTextWidth;} - void setTextWidth(S32 value) { mTextWidth = value;} - virtual void setWidth(S32 width) { LLScrollListCell::setWidth(width); mTextWidth = width; } - - void setText(const LLStringExplicit& text); - void setFontStyle(const U8 font_style); - void setAlignment(LLFontGL::HAlign align) { mFontAlignment = align; } - -protected: - LLUIString mText; - LLUIString mAltText; - S32 mTextWidth; - const LLFontGL* mFont; - LLColor4 mColor; - LLColor4 mHighlightColor; - U8 mUseColor; - LLFontGL::HAlign mFontAlignment; - bool mVisible; - S32 mHighlightCount; - S32 mHighlightOffset; - - LLPointer mRoundedRectImage; - - static U32 sCount; -}; - -/* - * Cell displaying an image. AT the moment, this is specifically UI image - */ -class LLScrollListIcon : public LLScrollListCell -{ -public: - LLScrollListIcon(const LLScrollListCell::Params& p); - /*virtual*/ ~LLScrollListIcon(); - /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; - /*virtual*/ S32 getWidth() const; - /*virtual*/ S32 getHeight() const; - /*virtual*/ const LLSD getValue() const; - /*virtual*/ void setColor(const LLColor4&); - /*virtual*/ void setValue(const LLSD& value); - -private: - LLPointer mIcon; - LLColor4 mColor; - LLFontGL::HAlign mAlignment; -}; - - -class LLScrollListBar : public LLScrollListCell -{ -public: - LLScrollListBar(const LLScrollListCell::Params& p); - /*virtual*/ ~LLScrollListBar(); - /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; - /*virtual*/ S32 getWidth() const; - /*virtual*/ S32 getHeight() const; - /*virtual*/ const LLSD getValue() const; - /*virtual*/ void setColor(const LLColor4&); - /*virtual*/ void setValue(const LLSD& value); - -private: - LLColor4 mColor; - F32 mRatio; - S32 mBottom; - S32 mRightPad; - S32 mLeftPad; -}; -/* - * An interactive cell containing a check box. - */ -class LLScrollListCheck : public LLScrollListCell -{ -public: - LLScrollListCheck( const LLScrollListCell::Params&); - /*virtual*/ ~LLScrollListCheck(); - /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; - /*virtual*/ S32 getHeight() const { return 0; } - /*virtual*/ const LLSD getValue() const; - /*virtual*/ void setValue(const LLSD& value); - /*virtual*/ void onCommit(); - - /*virtual*/ bool handleClick(); - /*virtual*/ void setEnabled(bool enable); - - LLCheckBoxCtrl* getCheckBox() { return mCheckBox; } - -private: - LLCheckBoxCtrl* mCheckBox; -}; - -class LLScrollListDate : public LLScrollListText -{ -public: - LLScrollListDate( const LLScrollListCell::Params& p ); - virtual void setValue(const LLSD& value); - virtual const LLSD getValue() const; - -private: - LLDate mDate; -}; - -/* -* Cell displaying icon and text. -*/ - -class LLScrollListIconText : public LLScrollListText -{ -public: - LLScrollListIconText(const LLScrollListCell::Params& p); - /*virtual*/ ~LLScrollListIconText(); - /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; - /*virtual*/ const LLSD getValue() const; - /*virtual*/ void setValue(const LLSD& value); - - - S32 getIconWidth() const; - /*virtual*/ void setWidth(S32 width);/* { LLScrollListCell::setWidth(width); mTextWidth = width - ; }*/ - -private: - LLPointer mIcon; - S32 mPad; -}; - -#endif +/** + * @file llscrolllistcell.h + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2007&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$ + */ + +#ifndef LLSCROLLLISTCELL_H +#define LLSCROLLLISTCELL_H + +#include "llfontgl.h" // HAlign +#include "llpointer.h" // LLPointer<> +#include "lluistring.h" +#include "v4color.h" +#include "llui.h" +#include "llgltexture.h" + +class LLCheckBoxCtrl; +class LLSD; +class LLUIImage; + +/* + * Represents a cell in a scrollable table. + * + * Sub-classes must return height and other properties + * though width accessors are implemented by the base class. + * It is therefore important for sub-class constructors to call + * setWidth() with realistic values. + */ +class LLScrollListCell +{ +public: + struct Params : public LLInitParam::Block + { + Optional type, + column; + + Optional width; + Optional enabled, + visible; + + Optional userdata; + Optional value; // state of checkbox, icon id/name, date + Optional alt_value; + Optional label; // description or text + Optional tool_tip; + + Optional font; + Optional font_color; + Optional font_halign; + + Optional color; + + Params() + : type("type", "text"), + column("column"), + width("width"), + enabled("enabled", true), + visible("visible", true), + value("value"), + alt_value("alt_value", ""), + label("label"), + tool_tip("tool_tip", ""), + font("font", LLFontGL::getFontEmojiSmall()), + font_color("font_color", LLColor4::black), + color("color", LLColor4::white), + font_halign("halign", LLFontGL::LEFT) + { + addSynonym(column, "name"); + addSynonym(font_color, "font-color"); + } + }; + + static LLScrollListCell* create(const Params&); + + LLScrollListCell(const LLScrollListCell::Params&); + virtual ~LLScrollListCell() {}; + + virtual void draw(const LLColor4& color, const LLColor4& highlight_color) const {}; // truncate to given width, if possible + virtual S32 getWidth() const {return mWidth;} + virtual S32 getContentWidth() const { return 0; } + virtual S32 getHeight() const { return 0; } + virtual const LLSD getValue() const; + virtual const LLSD getAltValue() const; + virtual void setValue(const LLSD& value) { } + virtual void setAltValue(const LLSD& value) { } + virtual const std::string &getToolTip() const { return mToolTip; } + virtual void setToolTip(const std::string &str) { mToolTip = str; } + virtual bool getVisible() const { return true; } + virtual void setWidth(S32 width) { mWidth = width; } + virtual void highlightText(S32 offset, S32 num_chars) {} + virtual bool isText() const { return false; } + virtual bool needsToolTip() const { return ! mToolTip.empty(); } + virtual void setColor(const LLColor4&) {} + virtual void onCommit() {}; + + virtual bool handleClick() { return false; } + virtual void setEnabled(bool enable) { } + +private: + S32 mWidth; + std::string mToolTip; +}; + +class LLScrollListSpacer : public LLScrollListCell +{ +public: + LLScrollListSpacer(const LLScrollListCell::Params& p) : LLScrollListCell(p) {} + /*virtual*/ ~LLScrollListSpacer() {}; + /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const {} +}; + +/* + * Cell displaying a text label. + */ +class LLScrollListText : public LLScrollListCell +{ +public: + LLScrollListText(const LLScrollListCell::Params&); + /*virtual*/ ~LLScrollListText(); + + /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; + /*virtual*/ S32 getContentWidth() const; + /*virtual*/ S32 getHeight() const; + /*virtual*/ void setValue(const LLSD& value); + /*virtual*/ void setAltValue(const LLSD& value); + /*virtual*/ const LLSD getValue() const; + /*virtual*/ const LLSD getAltValue() const; + /*virtual*/ bool getVisible() const; + /*virtual*/ void highlightText(S32 offset, S32 num_chars); + + /*virtual*/ void setColor(const LLColor4&); + /*virtual*/ bool isText() const; + /*virtual*/ const std::string & getToolTip() const; + /*virtual*/ bool needsToolTip() const; + + S32 getTextWidth() const { return mTextWidth;} + void setTextWidth(S32 value) { mTextWidth = value;} + virtual void setWidth(S32 width) { LLScrollListCell::setWidth(width); mTextWidth = width; } + + void setText(const LLStringExplicit& text); + void setFontStyle(const U8 font_style); + void setAlignment(LLFontGL::HAlign align) { mFontAlignment = align; } + +protected: + LLUIString mText; + LLUIString mAltText; + S32 mTextWidth; + const LLFontGL* mFont; + LLColor4 mColor; + LLColor4 mHighlightColor; + U8 mUseColor; + LLFontGL::HAlign mFontAlignment; + bool mVisible; + S32 mHighlightCount; + S32 mHighlightOffset; + + LLPointer mRoundedRectImage; + + static U32 sCount; +}; + +/* + * Cell displaying an image. AT the moment, this is specifically UI image + */ +class LLScrollListIcon : public LLScrollListCell +{ +public: + LLScrollListIcon(const LLScrollListCell::Params& p); + /*virtual*/ ~LLScrollListIcon(); + /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; + /*virtual*/ S32 getWidth() const; + /*virtual*/ S32 getHeight() const; + /*virtual*/ const LLSD getValue() const; + /*virtual*/ void setColor(const LLColor4&); + /*virtual*/ void setValue(const LLSD& value); + +private: + LLPointer mIcon; + LLColor4 mColor; + LLFontGL::HAlign mAlignment; +}; + + +class LLScrollListBar : public LLScrollListCell +{ +public: + LLScrollListBar(const LLScrollListCell::Params& p); + /*virtual*/ ~LLScrollListBar(); + /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; + /*virtual*/ S32 getWidth() const; + /*virtual*/ S32 getHeight() const; + /*virtual*/ const LLSD getValue() const; + /*virtual*/ void setColor(const LLColor4&); + /*virtual*/ void setValue(const LLSD& value); + +private: + LLColor4 mColor; + F32 mRatio; + S32 mBottom; + S32 mRightPad; + S32 mLeftPad; +}; +/* + * An interactive cell containing a check box. + */ +class LLScrollListCheck : public LLScrollListCell +{ +public: + LLScrollListCheck( const LLScrollListCell::Params&); + /*virtual*/ ~LLScrollListCheck(); + /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; + /*virtual*/ S32 getHeight() const { return 0; } + /*virtual*/ const LLSD getValue() const; + /*virtual*/ void setValue(const LLSD& value); + /*virtual*/ void onCommit(); + + /*virtual*/ bool handleClick(); + /*virtual*/ void setEnabled(bool enable); + + LLCheckBoxCtrl* getCheckBox() { return mCheckBox; } + +private: + LLCheckBoxCtrl* mCheckBox; +}; + +class LLScrollListDate : public LLScrollListText +{ +public: + LLScrollListDate( const LLScrollListCell::Params& p ); + virtual void setValue(const LLSD& value); + virtual const LLSD getValue() const; + +private: + LLDate mDate; +}; + +/* +* Cell displaying icon and text. +*/ + +class LLScrollListIconText : public LLScrollListText +{ +public: + LLScrollListIconText(const LLScrollListCell::Params& p); + /*virtual*/ ~LLScrollListIconText(); + /*virtual*/ void draw(const LLColor4& color, const LLColor4& highlight_color) const; + /*virtual*/ const LLSD getValue() const; + /*virtual*/ void setValue(const LLSD& value); + + + S32 getIconWidth() const; + /*virtual*/ void setWidth(S32 width);/* { LLScrollListCell::setWidth(width); mTextWidth = width - ; }*/ + +private: + LLPointer mIcon; + S32 mPad; +}; + +#endif diff --git a/indra/llui/llscrolllistcolumn.cpp b/indra/llui/llscrolllistcolumn.cpp index c6be401676..a4510d1fc2 100644 --- a/indra/llui/llscrolllistcolumn.cpp +++ b/indra/llui/llscrolllistcolumn.cpp @@ -1,340 +1,340 @@ -/** - * @file llscrollcolumnheader.cpp - * @brief Scroll lists are composed of rows (items), each of which - * contains columns (cells). - * - * $LicenseInfo:firstyear=2007&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 "llscrolllistcolumn.h" - -#include "llbutton.h" -#include "llresizebar.h" -#include "llscrolllistcell.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "lluictrlfactory.h" - -const S32 MIN_COLUMN_WIDTH = 20; - -// defaults for LLScrollColumnHeader param block pulled from widgets/scroll_column_header.xml -static LLWidgetNameRegistry::StaticRegistrar sRegisterColumnHeaderParams(&typeid(LLScrollColumnHeader::Params), "scroll_column_header"); - -//--------------------------------------------------------------------------- -// LLScrollColumnHeader -//--------------------------------------------------------------------------- -LLScrollColumnHeader::Params::Params() -: column("column") -{} - - -LLScrollColumnHeader::LLScrollColumnHeader(const LLScrollColumnHeader::Params& p) -: LLButton(p), // use combobox params to steal images - mColumn(p.column), - mHasResizableElement(false) -{ - setClickedCallback(boost::bind(&LLScrollColumnHeader::onClick, this, _2)); - - // resize handles on left and right - const S32 RESIZE_BAR_THICKNESS = 3; - LLResizeBar::Params resize_bar_p; - resize_bar_p.resizing_view(this); - resize_bar_p.rect(LLRect(getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0)); - resize_bar_p.min_size(MIN_COLUMN_WIDTH); - resize_bar_p.side(LLResizeBar::RIGHT); - resize_bar_p.enabled(false); - mResizeBar = LLUICtrlFactory::create(resize_bar_p); - addChild(mResizeBar); -} - -LLScrollColumnHeader::~LLScrollColumnHeader() -{} - -void LLScrollColumnHeader::draw() -{ - std::string sort_column = mColumn->mParentCtrl->getSortColumnName(); - bool draw_arrow = !mColumn->mLabel.empty() - && mColumn->mParentCtrl->isSorted() - // check for indirect sorting column as well as column's sorting name - && (sort_column == mColumn->mSortingColumn || sort_column == mColumn->mName); - - bool is_ascending = mColumn->mParentCtrl->getSortAscending(); - if (draw_arrow) - { - setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, LLColor4::white); - } - else - { - setImageOverlay(LLUUID::null); - } - - // Draw children - LLButton::draw(); -} - -bool LLScrollColumnHeader::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if (canResize() && mResizeBar->getRect().pointInRect(x, y)) - { - // reshape column to max content width - mColumn->mParentCtrl->calcMaxContentWidth(); - LLRect column_rect = getRect(); - column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth; - setShape(column_rect, true); - } - else - { - onClick(LLSD()); - } - return true; -} - -void LLScrollColumnHeader::onClick(const LLSD& data) -{ - if (mColumn) - { - LLScrollListCtrl::onClickColumn(mColumn); - } -} - -LLView* LLScrollColumnHeader::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding) -{ - // this logic assumes dragging on right - llassert(snap_edge == SNAP_RIGHT); - - // use higher snap threshold for column headers - threshold = llmin(threshold, 10); - - LLRect snap_rect = getSnapRect(); - - mColumn->mParentCtrl->calcMaxContentWidth(); - - S32 snap_delta = mColumn->mMaxContentWidth - snap_rect.getWidth(); - - // x coord growing means column growing, so same signs mean we're going in right direction - if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 ) - { - new_edge_val = snap_rect.mRight + snap_delta; - } - else - { - LLScrollListColumn* next_column = mColumn->mParentCtrl->getColumn(mColumn->mIndex + 1); - while (next_column) - { - if (next_column->mHeader) - { - snap_delta = (next_column->mHeader->getSnapRect().mRight - next_column->mMaxContentWidth) - snap_rect.mRight; - if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 ) - { - new_edge_val = snap_rect.mRight + snap_delta; - } - break; - } - next_column = mColumn->mParentCtrl->getColumn(next_column->mIndex + 1); - } - } - - return this; -} - -void LLScrollColumnHeader::handleReshape(const LLRect& new_rect, bool by_user) -{ - S32 new_width = new_rect.getWidth(); - S32 delta_width = new_width - (getRect().getWidth() /*+ mColumn->mParentCtrl->getColumnPadding()*/); - - if (delta_width != 0) - { - S32 remaining_width = -delta_width; - S32 col; - for (col = mColumn->mIndex + 1; col < mColumn->mParentCtrl->getNumColumns(); col++) - { - LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col); - if (!columnp) continue; - - if (columnp->mHeader && columnp->mHeader->canResize()) - { - // how many pixels in width can this column afford to give up? - S32 resize_buffer_amt = llmax(0, columnp->getWidth() - MIN_COLUMN_WIDTH); - - // user shrinking column, need to add width to other columns - if (delta_width < 0) - { - if (columnp->getWidth() > 0) - { - // statically sized column, give all remaining width to this column - columnp->setWidth(columnp->getWidth() + remaining_width); - if (columnp->mRelWidth > 0.f) - { - columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth(); - } - // all padding went to this widget, we're done - break; - } - } - else - { - // user growing column, need to take width from other columns - remaining_width += resize_buffer_amt; - - if (columnp->getWidth() > 0) - { - columnp->setWidth(columnp->getWidth() - llmin(columnp->getWidth() - MIN_COLUMN_WIDTH, delta_width)); - if (columnp->mRelWidth > 0.f) - { - columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth(); - } - } - - if (remaining_width >= 0) - { - // width sucked up from neighboring columns, done - break; - } - } - } - } - - // clamp resize amount to maximum that can be absorbed by other columns - if (delta_width > 0) - { - delta_width += llmin(remaining_width, 0); - } - - // propagate constrained delta_width to new width for this column - new_width = getRect().getWidth() + delta_width - mColumn->mParentCtrl->getColumnPadding(); - - // use requested width - mColumn->setWidth(new_width); - - // update proportional spacing - if (mColumn->mRelWidth > 0.f) - { - mColumn->mRelWidth = (F32)new_width / (F32)mColumn->mParentCtrl->getItemListRect().getWidth(); - } - - // tell scroll list to layout columns again - // do immediate update to get proper feedback to resize handle - // which needs to know how far the resize actually went - const bool force_update = true; - mColumn->mParentCtrl->updateColumns(force_update); - } -} - -void LLScrollColumnHeader::setHasResizableElement(bool resizable) -{ - if (mHasResizableElement != resizable) - { - mColumn->mParentCtrl->dirtyColumns(); - mHasResizableElement = resizable; - } -} - -void LLScrollColumnHeader::updateResizeBars() -{ - S32 num_resizable_columns = 0; - S32 col; - for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++) - { - LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col); - if (columnp && columnp->mHeader && columnp->mHeader->canResize()) - { - num_resizable_columns++; - } - } - - S32 num_resizers_enabled = 0; - - // now enable/disable resize handles on resizable columns if we have at least two - for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++) - { - LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col); - if (!columnp || !columnp->mHeader) continue; - bool enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize(); - columnp->mHeader->enableResizeBar(enable); - if (enable) - { - num_resizers_enabled++; - } - } -} - -void LLScrollColumnHeader::enableResizeBar(bool enable) -{ - mResizeBar->setEnabled(enable); -} - -bool LLScrollColumnHeader::canResize() -{ - return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth); -} - -void LLScrollListColumn::SortNames::declareValues() -{ - declare("ascending", LLScrollListColumn::ASCENDING); - declare("descending", LLScrollListColumn::DESCENDING); -} - -// -// LLScrollListColumn -// -//static -const LLScrollListColumn::Params& LLScrollListColumn::getDefaultParams() -{ - return LLUICtrlFactory::getDefaultParams(); -} - - -LLScrollListColumn::LLScrollListColumn(const Params& p, LLScrollListCtrl* parent) -: mWidth(0), - mIndex (-1), - mParentCtrl(parent), - mName(p.name), - mLabel(p.header.label), - mHeader(NULL), - mMaxContentWidth(0), - mDynamicWidth(p.width.dynamic_width), - mRelWidth(p.width.relative_width), - mFontAlignment(p.halign), - mSortingColumn(p.sort_column) -{ - if (p.sort_ascending.isProvided()) - { - mSortDirection = p.sort_ascending() ? ASCENDING : DESCENDING; - } - else - { - mSortDirection = p.sort_direction; - } - - setWidth(p.width.pixel_width); -} - -void LLScrollListColumn::setWidth(S32 width) -{ - if (!mDynamicWidth && mRelWidth <= 0.f) - { - mParentCtrl->updateStaticColumnWidth(this, width); - } - mWidth = width; -} +/** + * @file llscrollcolumnheader.cpp + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2007&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 "llscrolllistcolumn.h" + +#include "llbutton.h" +#include "llresizebar.h" +#include "llscrolllistcell.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "lluictrlfactory.h" + +const S32 MIN_COLUMN_WIDTH = 20; + +// defaults for LLScrollColumnHeader param block pulled from widgets/scroll_column_header.xml +static LLWidgetNameRegistry::StaticRegistrar sRegisterColumnHeaderParams(&typeid(LLScrollColumnHeader::Params), "scroll_column_header"); + +//--------------------------------------------------------------------------- +// LLScrollColumnHeader +//--------------------------------------------------------------------------- +LLScrollColumnHeader::Params::Params() +: column("column") +{} + + +LLScrollColumnHeader::LLScrollColumnHeader(const LLScrollColumnHeader::Params& p) +: LLButton(p), // use combobox params to steal images + mColumn(p.column), + mHasResizableElement(false) +{ + setClickedCallback(boost::bind(&LLScrollColumnHeader::onClick, this, _2)); + + // resize handles on left and right + const S32 RESIZE_BAR_THICKNESS = 3; + LLResizeBar::Params resize_bar_p; + resize_bar_p.resizing_view(this); + resize_bar_p.rect(LLRect(getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0)); + resize_bar_p.min_size(MIN_COLUMN_WIDTH); + resize_bar_p.side(LLResizeBar::RIGHT); + resize_bar_p.enabled(false); + mResizeBar = LLUICtrlFactory::create(resize_bar_p); + addChild(mResizeBar); +} + +LLScrollColumnHeader::~LLScrollColumnHeader() +{} + +void LLScrollColumnHeader::draw() +{ + std::string sort_column = mColumn->mParentCtrl->getSortColumnName(); + bool draw_arrow = !mColumn->mLabel.empty() + && mColumn->mParentCtrl->isSorted() + // check for indirect sorting column as well as column's sorting name + && (sort_column == mColumn->mSortingColumn || sort_column == mColumn->mName); + + bool is_ascending = mColumn->mParentCtrl->getSortAscending(); + if (draw_arrow) + { + setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, LLColor4::white); + } + else + { + setImageOverlay(LLUUID::null); + } + + // Draw children + LLButton::draw(); +} + +bool LLScrollColumnHeader::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (canResize() && mResizeBar->getRect().pointInRect(x, y)) + { + // reshape column to max content width + mColumn->mParentCtrl->calcMaxContentWidth(); + LLRect column_rect = getRect(); + column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth; + setShape(column_rect, true); + } + else + { + onClick(LLSD()); + } + return true; +} + +void LLScrollColumnHeader::onClick(const LLSD& data) +{ + if (mColumn) + { + LLScrollListCtrl::onClickColumn(mColumn); + } +} + +LLView* LLScrollColumnHeader::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding) +{ + // this logic assumes dragging on right + llassert(snap_edge == SNAP_RIGHT); + + // use higher snap threshold for column headers + threshold = llmin(threshold, 10); + + LLRect snap_rect = getSnapRect(); + + mColumn->mParentCtrl->calcMaxContentWidth(); + + S32 snap_delta = mColumn->mMaxContentWidth - snap_rect.getWidth(); + + // x coord growing means column growing, so same signs mean we're going in right direction + if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 ) + { + new_edge_val = snap_rect.mRight + snap_delta; + } + else + { + LLScrollListColumn* next_column = mColumn->mParentCtrl->getColumn(mColumn->mIndex + 1); + while (next_column) + { + if (next_column->mHeader) + { + snap_delta = (next_column->mHeader->getSnapRect().mRight - next_column->mMaxContentWidth) - snap_rect.mRight; + if (llabs(snap_delta) <= threshold && mouse_dir.mX * snap_delta > 0 ) + { + new_edge_val = snap_rect.mRight + snap_delta; + } + break; + } + next_column = mColumn->mParentCtrl->getColumn(next_column->mIndex + 1); + } + } + + return this; +} + +void LLScrollColumnHeader::handleReshape(const LLRect& new_rect, bool by_user) +{ + S32 new_width = new_rect.getWidth(); + S32 delta_width = new_width - (getRect().getWidth() /*+ mColumn->mParentCtrl->getColumnPadding()*/); + + if (delta_width != 0) + { + S32 remaining_width = -delta_width; + S32 col; + for (col = mColumn->mIndex + 1; col < mColumn->mParentCtrl->getNumColumns(); col++) + { + LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col); + if (!columnp) continue; + + if (columnp->mHeader && columnp->mHeader->canResize()) + { + // how many pixels in width can this column afford to give up? + S32 resize_buffer_amt = llmax(0, columnp->getWidth() - MIN_COLUMN_WIDTH); + + // user shrinking column, need to add width to other columns + if (delta_width < 0) + { + if (columnp->getWidth() > 0) + { + // statically sized column, give all remaining width to this column + columnp->setWidth(columnp->getWidth() + remaining_width); + if (columnp->mRelWidth > 0.f) + { + columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth(); + } + // all padding went to this widget, we're done + break; + } + } + else + { + // user growing column, need to take width from other columns + remaining_width += resize_buffer_amt; + + if (columnp->getWidth() > 0) + { + columnp->setWidth(columnp->getWidth() - llmin(columnp->getWidth() - MIN_COLUMN_WIDTH, delta_width)); + if (columnp->mRelWidth > 0.f) + { + columnp->mRelWidth = (F32)columnp->getWidth() / (F32)mColumn->mParentCtrl->getItemListRect().getWidth(); + } + } + + if (remaining_width >= 0) + { + // width sucked up from neighboring columns, done + break; + } + } + } + } + + // clamp resize amount to maximum that can be absorbed by other columns + if (delta_width > 0) + { + delta_width += llmin(remaining_width, 0); + } + + // propagate constrained delta_width to new width for this column + new_width = getRect().getWidth() + delta_width - mColumn->mParentCtrl->getColumnPadding(); + + // use requested width + mColumn->setWidth(new_width); + + // update proportional spacing + if (mColumn->mRelWidth > 0.f) + { + mColumn->mRelWidth = (F32)new_width / (F32)mColumn->mParentCtrl->getItemListRect().getWidth(); + } + + // tell scroll list to layout columns again + // do immediate update to get proper feedback to resize handle + // which needs to know how far the resize actually went + const bool force_update = true; + mColumn->mParentCtrl->updateColumns(force_update); + } +} + +void LLScrollColumnHeader::setHasResizableElement(bool resizable) +{ + if (mHasResizableElement != resizable) + { + mColumn->mParentCtrl->dirtyColumns(); + mHasResizableElement = resizable; + } +} + +void LLScrollColumnHeader::updateResizeBars() +{ + S32 num_resizable_columns = 0; + S32 col; + for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++) + { + LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col); + if (columnp && columnp->mHeader && columnp->mHeader->canResize()) + { + num_resizable_columns++; + } + } + + S32 num_resizers_enabled = 0; + + // now enable/disable resize handles on resizable columns if we have at least two + for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++) + { + LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col); + if (!columnp || !columnp->mHeader) continue; + bool enable = num_resizable_columns >= 2 && num_resizers_enabled < (num_resizable_columns - 1) && columnp->mHeader->canResize(); + columnp->mHeader->enableResizeBar(enable); + if (enable) + { + num_resizers_enabled++; + } + } +} + +void LLScrollColumnHeader::enableResizeBar(bool enable) +{ + mResizeBar->setEnabled(enable); +} + +bool LLScrollColumnHeader::canResize() +{ + return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth); +} + +void LLScrollListColumn::SortNames::declareValues() +{ + declare("ascending", LLScrollListColumn::ASCENDING); + declare("descending", LLScrollListColumn::DESCENDING); +} + +// +// LLScrollListColumn +// +//static +const LLScrollListColumn::Params& LLScrollListColumn::getDefaultParams() +{ + return LLUICtrlFactory::getDefaultParams(); +} + + +LLScrollListColumn::LLScrollListColumn(const Params& p, LLScrollListCtrl* parent) +: mWidth(0), + mIndex (-1), + mParentCtrl(parent), + mName(p.name), + mLabel(p.header.label), + mHeader(NULL), + mMaxContentWidth(0), + mDynamicWidth(p.width.dynamic_width), + mRelWidth(p.width.relative_width), + mFontAlignment(p.halign), + mSortingColumn(p.sort_column) +{ + if (p.sort_ascending.isProvided()) + { + mSortDirection = p.sort_ascending() ? ASCENDING : DESCENDING; + } + else + { + mSortDirection = p.sort_direction; + } + + setWidth(p.width.pixel_width); +} + +void LLScrollListColumn::setWidth(S32 width) +{ + if (!mDynamicWidth && mRelWidth <= 0.f) + { + mParentCtrl->updateStaticColumnWidth(this, width); + } + mWidth = width; +} diff --git a/indra/llui/llscrolllistcolumn.h b/indra/llui/llscrolllistcolumn.h index 042e9e5f02..72dfcdafe0 100644 --- a/indra/llui/llscrolllistcolumn.h +++ b/indra/llui/llscrolllistcolumn.h @@ -1,172 +1,172 @@ -/** - * @file llscrollcolumnheader.h - * @brief Scroll lists are composed of rows (items), each of which - * contains columns (cells). - * - * $LicenseInfo:firstyear=2007&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$ - */ - -#ifndef LLSCROLLLISTCOLUMN_H -#define LLSCROLLLISTCOLUMN_H - -#include "llrect.h" -#include "lluistring.h" -#include "llbutton.h" -#include "llinitparam.h" - -class LLScrollListColumn; -class LLResizeBar; -class LLScrollListCtrl; - -class LLScrollColumnHeader : public LLButton -{ -public: - struct Params : public LLInitParam::Block - { - Mandatory column; - - Params(); - }; - LLScrollColumnHeader(const Params&); - ~LLScrollColumnHeader(); - - /*virtual*/ void draw(); - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - - /*virtual*/ LLView* findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding); - /*virtual*/ void handleReshape(const LLRect& new_rect, bool by_user = false); - - LLScrollListColumn* getColumn() { return mColumn; } - void setHasResizableElement(bool resizable); - void updateResizeBars(); - bool canResize(); - void enableResizeBar(bool enable); - - void onClick(const LLSD& data); - -private: - LLScrollListColumn* mColumn; - LLResizeBar* mResizeBar; - bool mHasResizableElement; -}; - -/* - * A simple data class describing a column within a scroll list. - */ -class LLScrollListColumn -{ -public: - typedef enum e_sort_direction - { - DESCENDING, - ASCENDING - } ESortDirection; - - struct SortNames - : public LLInitParam::TypeValuesHelper - { - static void declareValues(); - }; - - struct Params : public LLInitParam::Block - { - Optional name, - tool_tip; - Optional sort_column; - Optional sort_direction; - Optional sort_ascending; - - struct Width : public LLInitParam::ChoiceBlock - { - Alternative dynamic_width; - Alternative pixel_width; - Alternative relative_width; - - Width() - : dynamic_width("dynamic_width", false), - pixel_width("width"), - relative_width("relative_width", -1.f) - { - addSynonym(relative_width, "relwidth"); - } - }; - Optional width; - - // either an image or label is used in column header - struct Header : public LLInitParam::ChoiceBlock
- { - Alternative label; - Alternative image; - - Header() - : label("label"), - image("image") - {} - }; - Optional
header; - - Optional halign; - - Params() - : name("name"), - tool_tip("tool_tip"), - sort_column("sort_column"), - sort_direction("sort_direction"), - sort_ascending("sort_ascending", true), - halign("halign", LLFontGL::LEFT) - { - // default choice to "dynamic_width" - changeDefault(width.dynamic_width, true); - - addSynonym(sort_column, "sort"); - } - }; - - static const Params& getDefaultParams(); - - //NOTE: this is default constructible so we can store it in a map. - LLScrollListColumn(const Params& p = getDefaultParams(), LLScrollListCtrl* = NULL); - - void setWidth(S32 width); - S32 getWidth() const { return mWidth; } - -public: - // Public data is fine so long as this remains a simple struct-like data class. - // If it ever gets any smarter than that, these should all become private - // with protected or public accessor methods added as needed. -MG - std::string mName; - std::string mSortingColumn; - ESortDirection mSortDirection; - LLUIString mLabel; - F32 mRelWidth; - bool mDynamicWidth; - S32 mMaxContentWidth; - S32 mIndex; - LLScrollListCtrl* mParentCtrl; - LLScrollColumnHeader* mHeader; - LLFontGL::HAlign mFontAlignment; - -private: - S32 mWidth; -}; - -#endif +/** + * @file llscrollcolumnheader.h + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2007&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$ + */ + +#ifndef LLSCROLLLISTCOLUMN_H +#define LLSCROLLLISTCOLUMN_H + +#include "llrect.h" +#include "lluistring.h" +#include "llbutton.h" +#include "llinitparam.h" + +class LLScrollListColumn; +class LLResizeBar; +class LLScrollListCtrl; + +class LLScrollColumnHeader : public LLButton +{ +public: + struct Params : public LLInitParam::Block + { + Mandatory column; + + Params(); + }; + LLScrollColumnHeader(const Params&); + ~LLScrollColumnHeader(); + + /*virtual*/ void draw(); + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + + /*virtual*/ LLView* findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding); + /*virtual*/ void handleReshape(const LLRect& new_rect, bool by_user = false); + + LLScrollListColumn* getColumn() { return mColumn; } + void setHasResizableElement(bool resizable); + void updateResizeBars(); + bool canResize(); + void enableResizeBar(bool enable); + + void onClick(const LLSD& data); + +private: + LLScrollListColumn* mColumn; + LLResizeBar* mResizeBar; + bool mHasResizableElement; +}; + +/* + * A simple data class describing a column within a scroll list. + */ +class LLScrollListColumn +{ +public: + typedef enum e_sort_direction + { + DESCENDING, + ASCENDING + } ESortDirection; + + struct SortNames + : public LLInitParam::TypeValuesHelper + { + static void declareValues(); + }; + + struct Params : public LLInitParam::Block + { + Optional name, + tool_tip; + Optional sort_column; + Optional sort_direction; + Optional sort_ascending; + + struct Width : public LLInitParam::ChoiceBlock + { + Alternative dynamic_width; + Alternative pixel_width; + Alternative relative_width; + + Width() + : dynamic_width("dynamic_width", false), + pixel_width("width"), + relative_width("relative_width", -1.f) + { + addSynonym(relative_width, "relwidth"); + } + }; + Optional width; + + // either an image or label is used in column header + struct Header : public LLInitParam::ChoiceBlock
+ { + Alternative label; + Alternative image; + + Header() + : label("label"), + image("image") + {} + }; + Optional
header; + + Optional halign; + + Params() + : name("name"), + tool_tip("tool_tip"), + sort_column("sort_column"), + sort_direction("sort_direction"), + sort_ascending("sort_ascending", true), + halign("halign", LLFontGL::LEFT) + { + // default choice to "dynamic_width" + changeDefault(width.dynamic_width, true); + + addSynonym(sort_column, "sort"); + } + }; + + static const Params& getDefaultParams(); + + //NOTE: this is default constructible so we can store it in a map. + LLScrollListColumn(const Params& p = getDefaultParams(), LLScrollListCtrl* = NULL); + + void setWidth(S32 width); + S32 getWidth() const { return mWidth; } + +public: + // Public data is fine so long as this remains a simple struct-like data class. + // If it ever gets any smarter than that, these should all become private + // with protected or public accessor methods added as needed. -MG + std::string mName; + std::string mSortingColumn; + ESortDirection mSortDirection; + LLUIString mLabel; + F32 mRelWidth; + bool mDynamicWidth; + S32 mMaxContentWidth; + S32 mIndex; + LLScrollListCtrl* mParentCtrl; + LLScrollColumnHeader* mHeader; + LLFontGL::HAlign mFontAlignment; + +private: + S32 mWidth; +}; + +#endif diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 1868ac9639..39e575173d 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -1,3448 +1,3448 @@ - /** - * @file llscrolllistctrl.cpp - * @brief Scroll lists are composed of rows (items), each of which - * contains columns (cells). - * - * $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 "llscrolllistctrl.h" - -#include - -#include "llstl.h" -#include "llboost.h" -//#include "indra_constants.h" - -#include "llavatarnamecache.h" -#include "llcheckboxctrl.h" -#include "llclipboard.h" -#include "llfocusmgr.h" -#include "llgl.h" // LLGLSUIDefault() -#include "lllocalcliprect.h" -//#include "llrender.h" -#include "llresmgr.h" -#include "llscrollbar.h" -#include "llscrolllistcell.h" -#include "llstring.h" -#include "llui.h" -#include "lluictrlfactory.h" -#include "llwindow.h" -#include "llcontrol.h" -#include "llkeyboard.h" -#include "llviewborder.h" -#include "lltextbox.h" -#include "llsdparam.h" -#include "llcachename.h" -#include "llmenugl.h" -#include "llurlaction.h" -#include "lltooltip.h" - -#include - -static LLDefaultChildRegistry::Register r("scroll_list"); - -// local structures & classes. -struct SortScrollListItem -{ - SortScrollListItem(const std::vector >& sort_orders,const LLScrollListCtrl::sort_signal_t* sort_signal, bool alternate_sort) - : mSortOrders(sort_orders) - , mSortSignal(sort_signal) - , mAltSort(alternate_sort) - {} - - bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2) - { - // sort over all columns in order specified by mSortOrders - S32 sort_result = 0; - for (sort_order_t::const_reverse_iterator it = mSortOrders.rbegin(); - it != mSortOrders.rend(); ++it) - { - S32 col_idx = it->first; - bool sort_ascending = it->second; - - S32 order = sort_ascending ? 1 : -1; // ascending or descending sort for this column? - - const LLScrollListCell *cell1 = i1->getColumn(col_idx); - const LLScrollListCell *cell2 = i2->getColumn(col_idx); - if (cell1 && cell2) - { - if(mSortSignal) - { - sort_result = order * (*mSortSignal)(col_idx,i1, i2); - } - else - { - if (mAltSort && !cell1->getAltValue().asString().empty() && !cell2->getAltValue().asString().empty()) - { - sort_result = order * LLStringUtil::compareDict(cell1->getAltValue().asString(), cell2->getAltValue().asString()); - } - else - { - sort_result = order * LLStringUtil::compareDict(cell1->getValue().asString(), cell2->getValue().asString()); - } - } - if (sort_result != 0) - { - break; // we have a sort order! - } - } - } - - return sort_result < 0; - } - - - typedef std::vector > sort_order_t; - const LLScrollListCtrl::sort_signal_t* mSortSignal; - const sort_order_t& mSortOrders; - const bool mAltSort; -}; - -//--------------------------------------------------------------------------- -// LLScrollListCtrl -//--------------------------------------------------------------------------- - -void LLScrollListCtrl::SelectionTypeNames::declareValues() -{ - declare("row", LLScrollListCtrl::ROW); - declare("cell", LLScrollListCtrl::CELL); - declare("header", LLScrollListCtrl::HEADER); -} - -LLScrollListCtrl::Contents::Contents() -: columns("column"), - rows("row") -{ - addSynonym(columns, "columns"); - addSynonym(rows, "rows"); -} - -LLScrollListCtrl::Params::Params() -: multi_select("multi_select", false), - has_border("draw_border"), - draw_heading("draw_heading"), - search_column("search_column", 0), - selection_type("selection_type", ROW), - sort_column("sort_column", -1), - sort_ascending("sort_ascending", true), - can_sort("can_sort", true), - mouse_wheel_opaque("mouse_wheel_opaque", false), - commit_on_keyboard_movement("commit_on_keyboard_movement", true), - commit_on_selection_change("commit_on_selection_change", false), - heading_height("heading_height"), - page_lines("page_lines", 0), - background_visible("background_visible"), - draw_stripes("draw_stripes"), - column_padding("column_padding"), - row_padding("row_padding", 2), - fg_unselected_color("fg_unselected_color"), - fg_selected_color("fg_selected_color"), - bg_selected_color("bg_selected_color"), - fg_disable_color("fg_disable_color"), - bg_writeable_color("bg_writeable_color"), - bg_readonly_color("bg_readonly_color"), - bg_stripe_color("bg_stripe_color"), - hovered_color("hovered_color"), - highlighted_color("highlighted_color"), - contents(""), - scroll_bar_bg_visible("scroll_bar_bg_visible"), - scroll_bar_bg_color("scroll_bar_bg_color"), - border("border") -{} - -LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) -: LLUICtrl(p), - mLineHeight(0), - mScrollLines(0), - mMouseWheelOpaque(p.mouse_wheel_opaque), - mPageLines(p.page_lines), - mMaxSelectable(0), - mAllowKeyboardMovement(true), - mCommitOnKeyboardMovement(p.commit_on_keyboard_movement), - mCommitOnSelectionChange(p.commit_on_selection_change), - mSelectionChanged(false), - mSelectionType(p.selection_type), - mNeedsScroll(false), - mCanSelect(true), - mCanSort(p.can_sort), - mColumnsDirty(false), - mMaxItemCount(INT_MAX), - mBorderThickness( 2 ), - mOnDoubleClickCallback( NULL ), - mOnMaximumSelectCallback( NULL ), - mOnSortChangedCallback( NULL ), - mHighlightedItem(-1), - mBorder(NULL), - mSortCallback(NULL), - mCommentTextView(NULL), - mNumDynamicWidthColumns(0), - mTotalStaticColumnWidth(0), - mTotalColumnPadding(0), - mSorted(false), - mDirty(false), - mOriginalSelection(-1), - mLastSelected(NULL), - mHeadingHeight(p.heading_height), - mAllowMultipleSelection(p.multi_select), - mDisplayColumnHeaders(p.draw_heading), - mBackgroundVisible(p.background_visible), - mDrawStripes(p.draw_stripes), - mBgWriteableColor(p.bg_writeable_color()), - mBgReadOnlyColor(p.bg_readonly_color()), - mBgSelectedColor(p.bg_selected_color()), - mBgStripeColor(p.bg_stripe_color()), - mFgSelectedColor(p.fg_selected_color()), - mFgUnselectedColor(p.fg_unselected_color()), - mFgDisabledColor(p.fg_disable_color()), - mHighlightedColor(p.highlighted_color()), - mHoveredColor(p.hovered_color()), - mSearchColumn(p.search_column), - mColumnPadding(p.column_padding), - mRowPadding(p.row_padding), - mAlternateSort(false), - mContextMenuType(MENU_NONE), - mIsFriendSignal(NULL) -{ - mItemListRect.setOriginAndSize( - mBorderThickness, - mBorderThickness, - getRect().getWidth() - 2 * mBorderThickness, - getRect().getHeight() - 2 * mBorderThickness ); - - updateLineHeight(); - - // Init the scrollbar - static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); - - LLRect scroll_rect; - scroll_rect.setOriginAndSize( - getRect().getWidth() - mBorderThickness - scrollbar_size, - mItemListRect.mBottom, - scrollbar_size, - mItemListRect.getHeight()); - - LLScrollbar::Params sbparams; - sbparams.name("Scrollbar"); - sbparams.rect(scroll_rect); - sbparams.orientation(LLScrollbar::VERTICAL); - sbparams.doc_size(getItemCount()); - sbparams.doc_pos(mScrollLines); - sbparams.page_size( getLinesPerPage() ); - sbparams.change_callback(boost::bind(&LLScrollListCtrl::onScrollChange, this, _1, _2)); - sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM); - sbparams.visible(false); - sbparams.bg_visible(p.scroll_bar_bg_visible); - sbparams.bg_color(p.scroll_bar_bg_color); - mScrollbar = LLUICtrlFactory::create (sbparams); - addChild(mScrollbar); - - // Border - if (p.has_border) - { - LLRect border_rect = getLocalRect(); - LLViewBorder::Params params = p.border; - params.rect(border_rect); - mBorder = LLUICtrlFactory::create (params); - addChild(mBorder); - } - - // set border *after* rect is fully initialized - if (mBorder) - { - mBorder->setRect(getLocalRect()); - mBorder->reshape(getRect().getWidth(), getRect().getHeight()); - } - - if (p.sort_column >= 0) - { - sortByColumnIndex(p.sort_column, p.sort_ascending); - } - - - for (LLInitParam::ParamIterator::const_iterator row_it = p.contents.columns.begin(); - row_it != p.contents.columns.end(); - ++row_it) - { - addColumn(*row_it); - } - - for (LLInitParam::ParamIterator::const_iterator row_it = p.contents.rows.begin(); - row_it != p.contents.rows.end(); - ++row_it) - { - addRow(*row_it); - } - - LLTextBox::Params text_p; - text_p.name("comment_text"); - text_p.border_visible(false); - text_p.rect(mItemListRect); - text_p.follows.flags(FOLLOWS_ALL); - // word wrap was added accroding to the EXT-6841 - text_p.wrap(true); - addChild(LLUICtrlFactory::create(text_p)); -} - -S32 LLScrollListCtrl::getSearchColumn() -{ - // search for proper search column - if (mSearchColumn < 0) - { - LLScrollListItem* itemp = getFirstData(); - if (itemp) - { - for(S32 column = 0; column < getNumColumns(); column++) - { - LLScrollListCell* cell = itemp->getColumn(column); - if (cell && cell->isText()) - { - mSearchColumn = column; - break; - } - } - } - } - return llclamp(mSearchColumn, 0, getNumColumns()); -} -/*virtual*/ -bool LLScrollListCtrl::preProcessChildNode(LLXMLNodePtr child) -{ - if (child->hasName("column") || child->hasName("row")) - { - return true; // skip - } - else - { - return false; - } -} - -LLScrollListCtrl::~LLScrollListCtrl() -{ - delete mSortCallback; - - std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); - mItemList.clear(); - clearColumns(); //clears columns and deletes headers - delete mIsFriendSignal; - - auto menu = mPopupMenuHandle.get(); - if (menu) - { - menu->die(); - mPopupMenuHandle.markDead(); - } -} - - -bool LLScrollListCtrl::setMaxItemCount(S32 max_count) -{ - if (max_count >= getItemCount()) - { - mMaxItemCount = max_count; - } - return (max_count == mMaxItemCount); -} - -S32 LLScrollListCtrl::isEmpty() const -{ - return mItemList.empty(); -} - -S32 LLScrollListCtrl::getItemCount() const -{ - return mItemList.size(); -} - -bool LLScrollListCtrl::hasSelectedItem() const -{ - item_list::iterator iter; - for (iter = mItemList.begin(); iter < mItemList.end(); ) - { - LLScrollListItem* itemp = *iter; - if (itemp && itemp->getSelected()) - { - return true; - } - iter++; - } - return false; -} - -// virtual LLScrolListInterface function (was deleteAllItems) -void LLScrollListCtrl::clearRows() -{ - std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); - mItemList.clear(); - //mItemCount = 0; - - // Scroll the bar back up to the top. - mScrollbar->setDocParams(0, 0); - - mScrollLines = 0; - mLastSelected = NULL; - updateLayout(); - mDirty = false; -} - - -LLScrollListItem* LLScrollListCtrl::getFirstSelected() const -{ - item_list::const_iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - if (item->getSelected()) - { - return item; - } - } - return NULL; -} - -std::vector LLScrollListCtrl::getAllSelected() const -{ - std::vector ret; - item_list::const_iterator iter; - for(iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - if (item->getSelected()) - { - ret.push_back(item); - } - } - return ret; -} - -S32 LLScrollListCtrl::getNumSelected() const -{ - S32 numSelected = 0; - - for(item_list::const_iterator iter = mItemList.begin(); iter != mItemList.end(); ++iter) - { - LLScrollListItem* item = *iter; - if (item->getSelected()) - { - ++numSelected; - } - } - - return numSelected; -} - -S32 LLScrollListCtrl::getFirstSelectedIndex() const -{ - S32 CurSelectedIndex = 0; - - // make sure sort is up to date before returning an index - updateSort(); - - item_list::const_iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - if (item->getSelected()) - { - return CurSelectedIndex; - } - CurSelectedIndex++; - } - - return -1; -} - -LLScrollListItem* LLScrollListCtrl::getFirstData() const -{ - if (mItemList.size() == 0) - { - return NULL; - } - return mItemList[0]; -} - -LLScrollListItem* LLScrollListCtrl::getLastData() const -{ - if (mItemList.size() == 0) - { - return NULL; - } - return mItemList[mItemList.size() - 1]; -} - -std::vector LLScrollListCtrl::getAllData() const -{ - std::vector ret; - item_list::const_iterator iter; - for(iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - ret.push_back(item); - } - return ret; -} - -// returns first matching item -LLScrollListItem* LLScrollListCtrl::getItem(const LLSD& sd) const -{ - std::string string_val = sd.asString(); - - item_list::const_iterator iter; - for(iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - // assumes string representation is good enough for comparison - if (item->getValue().asString() == string_val) - { - return item; - } - } - return NULL; -} - - -void LLScrollListCtrl::reshape( S32 width, S32 height, bool called_from_parent ) -{ - LLUICtrl::reshape( width, height, called_from_parent ); - - updateLayout(); -} - -void LLScrollListCtrl::updateLayout() -{ - static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); - // reserve room for column headers, if needed - S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0); - mItemListRect.setOriginAndSize( - mBorderThickness, - mBorderThickness, - getRect().getWidth() - 2 * mBorderThickness, - getRect().getHeight() - (2 * mBorderThickness ) - heading_size ); - - if (mCommentTextView == NULL) - { - mCommentTextView = getChildView("comment_text"); - } - - mCommentTextView->setShape(mItemListRect); - - // how many lines of content in a single "page" - S32 page_lines = getLinesPerPage(); - - bool scrollbar_visible = mLineHeight * getItemCount() > mItemListRect.getHeight(); - if (scrollbar_visible) - { - // provide space on the right for scrollbar - mItemListRect.mRight = getRect().getWidth() - mBorderThickness - scrollbar_size; - } - - mScrollbar->setOrigin(getRect().getWidth() - mBorderThickness - scrollbar_size, mItemListRect.mBottom); - mScrollbar->reshape(scrollbar_size, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0)); - mScrollbar->setPageSize(page_lines); - mScrollbar->setDocSize( getItemCount() ); - mScrollbar->setVisible(scrollbar_visible); - - dirtyColumns(); -} - -// Attempt to size the control to show all items. -// Do not make larger than width or height. -void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height) -{ - S32 height = llmin( getRequiredRect().getHeight(), max_height ); - if(mPageLines) - height = llmin( mPageLines * mLineHeight + 2*mBorderThickness + (mDisplayColumnHeaders ? mHeadingHeight : 0), height ); - - S32 width = getRect().getWidth(); - - reshape( width, height ); -} - - -LLRect LLScrollListCtrl::getRequiredRect() -{ - S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0); - S32 height = (mLineHeight * getItemCount()) - + (2 * mBorderThickness ) - + heading_size; - S32 width = getRect().getWidth(); - - return LLRect(0, height, width, 0); -} - - -bool LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, bool requires_column ) -{ - bool not_too_big = getItemCount() < mMaxItemCount; - if (not_too_big) - { - switch( pos ) - { - case ADD_TOP: - mItemList.push_front(item); - setNeedsSort(); - break; - - case ADD_DEFAULT: - case ADD_BOTTOM: - mItemList.push_back(item); - setNeedsSort(); - break; - - default: - llassert(0); - mItemList.push_back(item); - setNeedsSort(); - break; - } - - // create new column on demand - if (mColumns.empty() && requires_column) - { - LLScrollListColumn::Params col_params; - col_params.name = "default_column"; - col_params.header.label = ""; - col_params.width.dynamic_width = true; - addColumn(col_params); - } - - S32 num_cols = item->getNumColumns(); - S32 i = 0; - for (LLScrollListCell* cell = item->getColumn(i); i < num_cols; cell = item->getColumn(++i)) - { - if (i >= (S32)mColumnsIndexed.size()) break; - - cell->setWidth(mColumnsIndexed[i]->getWidth()); - } - - updateLineHeightInsert(item); - - updateLayout(); - } - - return not_too_big; -} - -// NOTE: This is *very* expensive for large lists, especially when we are dirtying the list every frame -// while receiving a long list of names. -// *TODO: Use bookkeeping to make this an incramental cost with item additions -S32 LLScrollListCtrl::calcMaxContentWidth() -{ - const S32 HEADING_TEXT_PADDING = 25; - const S32 COLUMN_TEXT_PADDING = 10; - - S32 max_item_width = 0; - - ordered_columns_t::iterator column_itor; - for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor) - { - LLScrollListColumn* column = *column_itor; - if (!column) continue; - - if (mColumnWidthsDirty) - { - // update max content width for this column, by looking at all items - column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0; - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex); - if (!cellp) continue; - - column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth); - } - } - max_item_width += column->mMaxContentWidth; - } - mColumnWidthsDirty = false; - - return max_item_width; -} - -bool LLScrollListCtrl::updateColumnWidths() -{ - bool width_changed = false; - ordered_columns_t::iterator column_itor; - for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor) - { - LLScrollListColumn* column = *column_itor; - if (!column) continue; - - // update column width - S32 new_width = 0; - if (column->mRelWidth >= 0) - { - new_width = (S32)ll_round(column->mRelWidth*mItemListRect.getWidth()); - } - else if (column->mDynamicWidth) - { - new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns; - } - else - { - new_width = column->getWidth(); - } - - if (column->getWidth() != new_width) - { - column->setWidth(new_width); - width_changed = true; - } - } - return width_changed; -} - -// Line height is the max height of all the cells in all the items. -void LLScrollListCtrl::updateLineHeight() -{ - mLineHeight = 0; - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem *itemp = *iter; - S32 num_cols = itemp->getNumColumns(); - S32 i = 0; - for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i)) - { - mLineHeight = llmax( mLineHeight, cell->getHeight() + mRowPadding ); - } - } -} - -// when the only change to line height is from an insert, we needn't scan the entire list -void LLScrollListCtrl::updateLineHeightInsert(LLScrollListItem* itemp) -{ - S32 num_cols = itemp->getNumColumns(); - S32 i = 0; - for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i)) - { - mLineHeight = llmax( mLineHeight, cell->getHeight() + mRowPadding ); - } -} - - -void LLScrollListCtrl::updateColumns(bool force_update) -{ - if (!mColumnsDirty && !force_update) - return; - - mColumnsDirty = false; - - bool columns_changed_width = updateColumnWidths(); - - // update column headers - std::vector::iterator column_ordered_it; - S32 left = mItemListRect.mLeft; - LLScrollColumnHeader* last_header = NULL; - for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it) - { - LLScrollListColumn* column = *column_ordered_it; - if (!column || column->getWidth() < 0) - { - // skip hidden columns - continue; - } - - if (column->mHeader) - { - column->mHeader->updateResizeBars(); - - last_header = column->mHeader; - S32 top = mItemListRect.mTop; - S32 right = left + column->getWidth(); - - if (column->mIndex != (S32)mColumnsIndexed.size()-1) - { - right += mColumnPadding; - } - right = llmax(left, llmin(mItemListRect.getWidth(), right)); - S32 header_width = right - left; - - last_header->reshape(header_width, mHeadingHeight); - last_header->translate( - left - last_header->getRect().mLeft, - top - last_header->getRect().mBottom); - last_header->setVisible(mDisplayColumnHeaders && header_width > 0); - left = right; - } - } - - bool header_changed_width = false; - // expand last column header we encountered to full list width - if (last_header) - { - S32 old_width = last_header->getColumn()->getWidth(); - S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft); - last_header->reshape(new_width, last_header->getRect().getHeight()); - last_header->setVisible(mDisplayColumnHeaders && new_width > 0); - if (old_width != new_width) - { - last_header->getColumn()->setWidth(new_width); - header_changed_width = true; - } - } - - // propagate column widths to individual cells - if (columns_changed_width || force_update) - { - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem *itemp = *iter; - S32 num_cols = itemp->getNumColumns(); - S32 i = 0; - for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i)) - { - if (i >= (S32)mColumnsIndexed.size()) break; - - cell->setWidth(mColumnsIndexed[i]->getWidth()); - } - } - } - else if (header_changed_width) - { - item_list::iterator iter; - S32 index = last_header->getColumn()->mIndex; // Not always identical to last column! - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem *itemp = *iter; - LLScrollListCell* cell = itemp->getColumn(index); - if (cell) - { - cell->setWidth(last_header->getColumn()->getWidth()); - } - } - } -} - -void LLScrollListCtrl::setHeadingHeight(S32 heading_height) -{ - mHeadingHeight = heading_height; - - updateLayout(); - -} -void LLScrollListCtrl::setPageLines(S32 new_page_lines) -{ - mPageLines = new_page_lines; - - updateLayout(); -} - -bool LLScrollListCtrl::selectFirstItem() -{ - bool success = false; - - // our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration - bool first_item = true; - - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem *itemp = *iter; - if( first_item && itemp->getEnabled() ) - { - if (!itemp->getSelected()) - { - switch (mSelectionType) - { - case CELL: - selectItem(itemp, 0); - break; - case HEADER: - case ROW: - selectItem(itemp, -1); - } - } - success = true; - mOriginalSelection = 0; - } - else - { - deselectItem(itemp); - } - first_item = false; - } - if (mCommitOnSelectionChange) - { - commitIfChanged(); - } - return success; -} - -// Deselects all other items -// virtual -bool LLScrollListCtrl::selectNthItem( S32 target_index ) -{ - return selectItemRange(target_index, target_index); -} - -// virtual -bool LLScrollListCtrl::selectItemRange( S32 first_index, S32 last_index ) -{ - if (mItemList.empty()) - { - return false; - } - - // make sure sort is up to date - updateSort(); - - S32 listlen = (S32)mItemList.size(); - first_index = llclamp(first_index, 0, listlen-1); - - if (last_index < 0) - last_index = listlen-1; - else - last_index = llclamp(last_index, first_index, listlen-1); - - bool success = false; - S32 index = 0; - for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); ) - { - LLScrollListItem *itemp = *iter; - if(!itemp) - { - iter = mItemList.erase(iter); - continue ; - } - - if( index >= first_index && index <= last_index ) - { - if( itemp->getEnabled() ) - { - // TODO: support range selection for cells - selectItem(itemp, -1, false); - success = true; - } - } - else - { - deselectItem(itemp); - } - index++; - iter++ ; - } - - if (mCommitOnSelectionChange) - { - commitIfChanged(); - } - - mSearchString.clear(); - - return success; -} - - -void LLScrollListCtrl::swapWithNext(S32 index) -{ - if (index >= ((S32)mItemList.size() - 1)) - { - // At end of list, doesn't do anything - return; - } - updateSort(); - LLScrollListItem *cur_itemp = mItemList[index]; - mItemList[index] = mItemList[index + 1]; - mItemList[index + 1] = cur_itemp; -} - - -void LLScrollListCtrl::swapWithPrevious(S32 index) -{ - if (index <= 0) - { - // At beginning of list, don't do anything - } - - updateSort(); - LLScrollListItem *cur_itemp = mItemList[index]; - mItemList[index] = mItemList[index - 1]; - mItemList[index - 1] = cur_itemp; -} - - -void LLScrollListCtrl::deleteSingleItem(S32 target_index) -{ - if (target_index < 0 || target_index >= (S32)mItemList.size()) - { - return; - } - - updateSort(); - - LLScrollListItem *itemp; - itemp = mItemList[target_index]; - if (itemp == mLastSelected) - { - mLastSelected = NULL; - } - delete itemp; - mItemList.erase(mItemList.begin() + target_index); - dirtyColumns(); -} - -//FIXME: refactor item deletion -void LLScrollListCtrl::deleteItems(const LLSD& sd) -{ - item_list::iterator iter; - for (iter = mItemList.begin(); iter < mItemList.end(); ) - { - LLScrollListItem* itemp = *iter; - if (itemp->getValue().asString() == sd.asString()) - { - if (itemp == mLastSelected) - { - mLastSelected = NULL; - } - delete itemp; - iter = mItemList.erase(iter); - } - else - { - iter++; - } - } - - dirtyColumns(); -} - -void LLScrollListCtrl::deleteSelectedItems() -{ - item_list::iterator iter; - for (iter = mItemList.begin(); iter < mItemList.end(); ) - { - LLScrollListItem* itemp = *iter; - if (itemp->getSelected()) - { - delete itemp; - iter = mItemList.erase(iter); - } - else - { - iter++; - } - } - mLastSelected = NULL; - dirtyColumns(); -} - -void LLScrollListCtrl::clearHighlightedItems() -{ - for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); ++iter) - { - (*iter)->setHighlighted(false); - } -} - -void LLScrollListCtrl::mouseOverHighlightNthItem(S32 target_index) -{ - if (mHighlightedItem != target_index) - { - if (mHighlightedItem >= 0 && mHighlightedItem < mItemList.size()) - { - mItemList[mHighlightedItem]->setHoverCell(-1); - } - mHighlightedItem = target_index; - } -} - -S32 LLScrollListCtrl::selectMultiple( uuid_vec_t ids ) -{ - item_list::iterator iter; - S32 count = 0; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - uuid_vec_t::iterator iditr; - for(iditr = ids.begin(); iditr != ids.end(); ++iditr) - { - if (item->getEnabled() && (item->getUUID() == (*iditr))) - { - // TODO: support multiple selection for cells - selectItem(item, -1, false); - ++count; - break; - } - } - if(ids.end() != iditr) ids.erase(iditr); - } - - if (mCommitOnSelectionChange) - { - commitIfChanged(); - } - return count; -} - -S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const -{ - updateSort(); - - S32 index = 0; - item_list::const_iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem *itemp = *iter; - if (target_item == itemp) - { - return index; - } - index++; - } - return -1; -} - -S32 LLScrollListCtrl::getItemIndex( const LLUUID& target_id ) const -{ - updateSort(); - - S32 index = 0; - item_list::const_iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem *itemp = *iter; - if (target_id == itemp->getUUID()) - { - return index; - } - index++; - } - return -1; -} - -void LLScrollListCtrl::selectPrevItem( bool extend_selection) -{ - LLScrollListItem* prev_item = NULL; - - if (!getFirstSelected()) - { - // select last item - selectNthItem(getItemCount() - 1); - } - else - { - updateSort(); - - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* cur_item = *iter; - - if (cur_item->getSelected()) - { - if (prev_item) - { - selectItem(prev_item, cur_item->getSelectedCell(), !extend_selection); - } - else - { - reportInvalidInput(); - } - break; - } - - // don't allow navigation to disabled elements - prev_item = cur_item->getEnabled() ? cur_item : prev_item; - } - } - - if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement)) - { - commitIfChanged(); - } - - mSearchString.clear(); -} - - -void LLScrollListCtrl::selectNextItem( bool extend_selection) -{ - LLScrollListItem* next_item = NULL; - - if (!getFirstSelected()) - { - selectFirstItem(); - } - else - { - updateSort(); - - item_list::reverse_iterator iter; - for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++) - { - LLScrollListItem* cur_item = *iter; - - if (cur_item->getSelected()) - { - if (next_item) - { - selectItem(next_item, cur_item->getSelectedCell(), !extend_selection); - } - else - { - reportInvalidInput(); - } - break; - } - - // don't allow navigation to disabled items - next_item = cur_item->getEnabled() ? cur_item : next_item; - } - } - - if (mCommitOnKeyboardMovement) - { - onCommit(); - } - - mSearchString.clear(); -} - - - -void LLScrollListCtrl::deselectAllItems(bool no_commit_on_change) -{ - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - deselectItem(item); - } - - if (mCommitOnSelectionChange && !no_commit_on_change) - { - commitIfChanged(); - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Use this to add comment text such as "Searching", which ignores column settings of list - -void LLScrollListCtrl::setCommentText(const std::string& comment_text) -{ - getChild("comment_text")->setValue(comment_text); -} - -LLScrollListItem* LLScrollListCtrl::addSeparator(EAddPosition pos) -{ - LLScrollListItem::Params separator_params; - separator_params.enabled(false); - LLScrollListCell::Params column_params; - column_params.type = "icon"; - column_params.value = "menu_separator"; - column_params.color = LLColor4(0.f, 0.f, 0.f, 0.7f); - column_params.font_halign = LLFontGL::HCENTER; - separator_params.columns.add(column_params); - return addRow( separator_params, pos ); -} - -// Selects first enabled item of the given name. -// Returns false if item not found. -// Calls getItemByLabel in order to combine functionality -bool LLScrollListCtrl::selectItemByLabel(const std::string& label, bool case_sensitive, S32 column/* = 0*/) -{ - deselectAllItems(true); // ensure that no stale items are selected, even if we don't find a match - LLScrollListItem* item = getItemByLabel(label, case_sensitive, column); - - bool found = NULL != item; - if (found) - { - selectItem(item, -1); - } - - if (mCommitOnSelectionChange) - { - commitIfChanged(); - } - - return found; -} - -LLScrollListItem* LLScrollListCtrl::getItemByLabel(const std::string& label, bool case_sensitive, S32 column) -{ - if (label.empty()) //RN: assume no empty items - { - return NULL; - } - - std::string target_text = label; - if (!case_sensitive) - { - LLStringUtil::toLower(target_text); - } - - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - std::string item_text = item->getColumn(column)->getValue().asString(); // Only select enabled items with matching names - if (!case_sensitive) - { - LLStringUtil::toLower(item_text); - } - if(item_text == target_text) - { - return item; - } - } - return NULL; -} - - -bool LLScrollListCtrl::selectItemByPrefix(const std::string& target, bool case_sensitive, S32 column) -{ - return selectItemByPrefix(utf8str_to_wstring(target), case_sensitive, column); -} - -// Selects first enabled item that has a name where the name's first part matched the target string. -// Returns false if item not found. -bool LLScrollListCtrl::selectItemByPrefix(const LLWString& target, bool case_sensitive, S32 column) -{ - bool found = false; - - LLWString target_trimmed( target ); - S32 target_len = target_trimmed.size(); - - if( 0 == target_len ) - { - // Is "" a valid choice? - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - // Only select enabled items with matching names - LLScrollListCell* cellp = item->getColumn(column == -1 ? getSearchColumn() : column); - bool select = cellp ? item->getEnabled() && ('\0' == cellp->getValue().asString()[0]) : false; - if (select) - { - selectItem(item, -1); - found = true; - break; - } - } - } - else - { - if (!case_sensitive) - { - // do comparisons in lower case - LLWStringUtil::toLower(target_trimmed); - } - - for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - - // Only select enabled items with matching names - LLScrollListCell* cellp = item->getColumn(column == -1 ? getSearchColumn() : column); - if (!cellp) - { - continue; - } - LLWString item_label = utf8str_to_wstring(cellp->getValue().asString()); - if (!case_sensitive) - { - LLWStringUtil::toLower(item_label); - } - // remove extraneous whitespace from searchable label - LLWString trimmed_label = item_label; - LLWStringUtil::trim(trimmed_label); - - bool select = item->getEnabled() && trimmed_label.compare(0, target_trimmed.size(), target_trimmed) == 0; - - if (select) - { - // find offset of matching text (might have leading whitespace) - S32 offset = item_label.find(target_trimmed); - cellp->highlightText(offset, target_trimmed.size()); - selectItem(item, -1); - found = true; - break; - } - } - } - - if (mCommitOnSelectionChange) - { - commitIfChanged(); - } - - return found; -} - -U32 LLScrollListCtrl::searchItems(const std::string& substring, bool case_sensitive, bool focus) -{ - return searchItems(utf8str_to_wstring(substring), case_sensitive, focus); -} - -U32 LLScrollListCtrl::searchItems(const LLWString& substring, bool case_sensitive, bool focus) -{ - U32 found = 0; - - LLWString substring_trimmed(substring); - S32 len = substring_trimmed.size(); - - if (0 == len) - { - // at the moment search for empty element is not supported - return 0; - } - else - { - deselectAllItems(true); - if (!case_sensitive) - { - // do comparisons in lower case - LLWStringUtil::toLower(substring_trimmed); - } - - for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - // Only select enabled items with matching names - if (!item->getEnabled()) - { - continue; - } - LLScrollListCell* cellp = item->getColumn(getSearchColumn()); - if (!cellp) - { - continue; - } - LLWString item_label = utf8str_to_wstring(cellp->getValue().asString()); - if (!case_sensitive) - { - LLWStringUtil::toLower(item_label); - } - // remove extraneous whitespace from searchable label - LLWStringUtil::trim(item_label); - - size_t found_iter = item_label.find(substring_trimmed); - - if (found_iter != std::string::npos) - { - // find offset of matching text - cellp->highlightText(found_iter, substring_trimmed.size()); - selectItem(item, -1, false); - - found++; - - if (!mAllowMultipleSelection) - { - break; - } - } - } - } - - if (focus && found != 0) - { - mNeedsScroll = true; - } - - if (mCommitOnSelectionChange) - { - commitIfChanged(); - } - - return found; -} - -const std::string LLScrollListCtrl::getSelectedItemLabel(S32 column) const -{ - LLScrollListItem* item; - - item = getFirstSelected(); - if (item) - { - return item->getColumn(column)->getValue().asString(); - } - - return LLStringUtil::null; -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which -// has an associated, unique UUID, and only one of which can be selected at a time. - -LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos, bool enabled) -{ - if (getItemCount() < mMaxItemCount) - { - LLScrollListItem::Params item_p; - item_p.enabled(enabled); - item_p.value(id); - item_p.columns.add().value(item_text).type("text"); - - return addRow( item_p, pos ); - } - return NULL; -} - -// Select the line or lines that match this UUID -bool LLScrollListCtrl::selectByID( const LLUUID& id ) -{ - return selectByValue( LLSD(id) ); -} - -bool LLScrollListCtrl::setSelectedByValue(const LLSD& value, bool selected) -{ - bool found = false; - - if (selected && !mAllowMultipleSelection) deselectAllItems(true); - - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - if (item->getEnabled()) - { - if (value.isBinary()) - { - if (item->getValue().isBinary()) - { - LLSD::Binary data1 = value.asBinary(); - LLSD::Binary data2 = item->getValue().asBinary(); - found = std::equal(data1.begin(), data1.end(), data2.begin()); - } - } - else - { - found = item->getValue().asString() == value.asString(); - } - - if (found) - { - if (selected) - { - selectItem(item, -1); - } - else - { - deselectItem(item); - } - break; - } - } - } - - if (mCommitOnSelectionChange) - { - commitIfChanged(); - } - - return found; -} - -bool LLScrollListCtrl::isSelected(const LLSD& value) const -{ - item_list::const_iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - if (item->getValue().asString() == value.asString()) - { - return item->getSelected(); - } - } - return false; -} - -LLUUID LLScrollListCtrl::getStringUUIDSelectedItem() const -{ - LLScrollListItem* item = getFirstSelected(); - - if (item) - { - return item->getUUID(); - } - - return LLUUID::null; -} - -LLSD LLScrollListCtrl::getSelectedValue() -{ - LLScrollListItem* item = getFirstSelected(); - - if (item) - { - return item->getValue(); - } - else - { - return LLSD(); - } -} - -void LLScrollListCtrl::drawItems() -{ - S32 x = mItemListRect.mLeft; - S32 y = mItemListRect.mTop - mLineHeight; - - // allow for partial line at bottom - S32 num_page_lines = getLinesPerPage(); - - LLRect item_rect; - - LLGLSUIDefault gls_ui; - - F32 alpha = getDrawContext().mAlpha; - - { - LLLocalClipRect clip(mItemListRect); - - S32 cur_y = y; - - S32 max_columns = 0; - - LLColor4 highlight_color = LLColor4::white; // ex: text inside cells - static LLUICachedControl type_ahead_timeout ("TypeAheadTimeout", 0); - highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout(), 0.4f, 0.f); - - S32 first_line = mScrollLines; - S32 last_line = llmin((S32)mItemList.size() - 1, mScrollLines + getLinesPerPage()); - - if (first_line >= mItemList.size()) - { - return; - } - item_list::iterator iter; - for (S32 line = first_line; line <= last_line; line++) - { - LLScrollListItem* item = mItemList[line]; - - item_rect.setOriginAndSize( - x, - cur_y, - mItemListRect.getWidth(), - mLineHeight ); - item->setRect(item_rect); - - max_columns = llmax(max_columns, item->getNumColumns()); - - LLColor4 fg_color; - LLColor4 hover_color(LLColor4::transparent); - LLColor4 select_color(LLColor4::transparent); - - if( mScrollLines <= line && line < mScrollLines + num_page_lines ) - { - fg_color = (item->getEnabled() ? mFgUnselectedColor.get() : mFgDisabledColor.get()); - if( item->getSelected() && mCanSelect) - { - if(item->getHighlighted()) // if it's highlighted, average the colors - { - select_color = lerp(mBgSelectedColor.get(), mHighlightedColor.get(), 0.5f); - } - else // otherwise just select-highlight it - { - select_color = mBgSelectedColor.get(); - } - - fg_color = (item->getEnabled() ? mFgSelectedColor.get() : mFgDisabledColor.get()); - } - if (mHighlightedItem == line && mCanSelect) - { - if(item->getHighlighted()) // if it's highlighted, average the colors - { - hover_color = lerp(mHoveredColor.get(), mHighlightedColor.get(), 0.5f); - } - else // otherwise just hover-highlight it - { - hover_color = mHoveredColor.get(); - } - } - else if (item->getHighlighted()) - { - hover_color = mHighlightedColor.get(); - } - else - { - if (mDrawStripes && (line % 2 == 0) && (max_columns > 1)) - { - hover_color = mBgStripeColor.get(); - } - } - - if (!item->getEnabled()) - { - hover_color = mBgReadOnlyColor.get(); - } - - item->draw(item_rect, fg_color % alpha, hover_color% alpha, select_color% alpha, highlight_color % alpha, mColumnPadding); - - cur_y -= mLineHeight; - } - } - } -} - - -void LLScrollListCtrl::draw() -{ - LLLocalClipRect clip(getLocalRect()); - - // if user specifies sort, make sure it is maintained - updateSort(); - - if (mNeedsScroll) - { - scrollToShowSelected(); - mNeedsScroll = false; - } - LLRect background(0, getRect().getHeight(), getRect().getWidth(), 0); - // Draw background - if (mBackgroundVisible) - { - F32 alpha = getCurrentTransparency(); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() % alpha : mBgReadOnlyColor.get() % alpha ); - } - - updateColumns(); - - getChildView("comment_text")->setVisible(mItemList.empty()); - - drawItems(); - - if (mBorder) - { - mBorder->setKeyboardFocusHighlight(hasFocus()); - } - - LLUICtrl::draw(); -} - -void LLScrollListCtrl::setEnabled(bool enabled) -{ - mCanSelect = enabled; - setTabStop(enabled); - mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize()); -} - -bool LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - bool handled = false; - // Pretend the mouse is over the scrollbar - handled = mScrollbar->handleScrollWheel( 0, 0, clicks ); - - if (mMouseWheelOpaque) - { - return true; - } - - return handled; -} - -bool LLScrollListCtrl::handleScrollHWheel(S32 x, S32 y, S32 clicks) -{ - bool handled = false; - // Pretend the mouse is over the scrollbar - handled = mScrollbar->handleScrollHWheel( 0, 0, clicks ); - - if (mMouseWheelOpaque) - { - return true; - } - - return handled; -} - -// *NOTE: Requires a valid row_index and column_index -LLRect LLScrollListCtrl::getCellRect(S32 row_index, S32 column_index) -{ - LLRect cell_rect; - S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft; - S32 rect_bottom = getRowOffsetFromIndex(row_index); - LLScrollListColumn* columnp = getColumn(column_index); - cell_rect.setOriginAndSize(rect_left, rect_bottom, - /*rect_left + */columnp->getWidth(), mLineHeight); - return cell_rect; -} - -bool LLScrollListCtrl::handleToolTip(S32 x, S32 y, MASK mask) -{ - S32 column_index = getColumnIndexFromOffset(x); - LLScrollListColumn* columnp = getColumn(column_index); - - if (columnp == NULL) return false; - - bool handled = false; - // show tooltip for full name of hovered item if it has been truncated - LLScrollListItem* hit_item = hitItem(x, y); - if (hit_item) - { - LLScrollListCell* hit_cell = hit_item->getColumn(column_index); - if (!hit_cell) return false; - if (hit_cell - && hit_cell->isText() - && hit_cell->needsToolTip()) - { - S32 row_index = getItemIndex(hit_item); - LLRect cell_rect = getCellRect(row_index, column_index); - // Convert rect local to screen coordinates - LLRect sticky_rect; - localRectToScreen(cell_rect, &sticky_rect); - - // display tooltip exactly over original cell, in same font - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(hit_cell->getToolTip()) - .font(LLFontGL::getFontEmojiSmall()) - .pos(LLCoordGL(sticky_rect.mLeft - 5, sticky_rect.mTop + 6)) - .delay_time(0.2f) - .sticky_rect(sticky_rect)); - } - handled = true; - } - - // otherwise, look for a tooltip associated with this column - LLScrollColumnHeader* headerp = columnp->mHeader; - if (headerp && !handled) - { - handled = headerp->handleToolTip(x, y, mask); - } - - return handled; -} - -bool LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask) -{ - if (!mCanSelect) return false; - - bool selection_changed = false; - - LLScrollListItem* hit_item = hitItem(x, y); - - if( hit_item ) - { - if( mAllowMultipleSelection ) - { - if (mask & MASK_SHIFT) - { - if (mLastSelected == NULL) - { - selectItem(hit_item, getColumnIndexFromOffset(x)); - } - else - { - // Select everthing between mLastSelected and hit_item - bool selecting = false; - item_list::iterator itor; - // If we multiselect backwards, we'll stomp on mLastSelected, - // meaning that we never stop selecting until hitting max or - // the end of the list. - LLScrollListItem* lastSelected = mLastSelected; - for (itor = mItemList.begin(); itor != mItemList.end(); ++itor) - { - if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable) - { - if(mOnMaximumSelectCallback) - { - mOnMaximumSelectCallback(); - } - break; - } - LLScrollListItem *item = *itor; - if (item == hit_item || item == lastSelected) - { - selectItem(item, getColumnIndexFromOffset(x), false); - selecting = !selecting; - if (hit_item == lastSelected) - { - // stop selecting now, since we just clicked on our last selected item - selecting = false; - } - } - if (selecting) - { - selectItem(item, getColumnIndexFromOffset(x), false); - } - } - } - } - else if (mask & MASK_CONTROL) - { - if (hit_item->getSelected()) - { - deselectItem(hit_item); - } - else - { - if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)) - { - selectItem(hit_item, getColumnIndexFromOffset(x), false); - } - else - { - if(mOnMaximumSelectCallback) - { - mOnMaximumSelectCallback(); - } - } - } - } - else - { - deselectAllItems(true); - selectItem(hit_item, getColumnIndexFromOffset(x)); - } - } - else - { - selectItem(hit_item, getColumnIndexFromOffset(x)); - } - - selection_changed = mSelectionChanged; - if (mCommitOnSelectionChange) - { - commitIfChanged(); - } - - // clear search string on mouse operations - mSearchString.clear(); - } - else - { - //mLastSelected = NULL; - //deselectAllItems(true); - } - - return selection_changed; -} - - -bool LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = childrenHandleMouseDown(x, y, mask) != NULL; - - if( !handled ) - { - // set keyboard focus first, in case click action wants to move focus elsewhere - setFocus(true); - - // clear selection changed flag because user is starting a selection operation - mSelectionChanged = false; - - handleClick(x, y, mask); - } - - return true; -} - -bool LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (hasMouseCapture()) - { - // release mouse capture immediately so - // scroll to show selected logic will work - gFocusMgr.setMouseCapture(NULL); - if(mask == MASK_NONE) - { - selectItemAt(x, y, mask); - mNeedsScroll = true; - } - } - - // always commit when mouse operation is completed inside list - if (mItemListRect.pointInRect(x,y)) - { - mDirty = mDirty || mSelectionChanged; - mSelectionChanged = false; - onCommit(); - } - - return LLUICtrl::handleMouseUp(x, y, mask); -} - -// virtual -bool LLScrollListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - LLScrollListItem *item = hitItem(x, y); - if (item) - { - // check to see if we have a UUID for this row - std::string id = item->getValue().asString(); - LLUUID uuid(id); - if (! uuid.isNull() && mContextMenuType != MENU_NONE) - { - // set up the callbacks for all of the avatar/group menu items - // (N.B. callbacks don't take const refs as id is local scope) - bool is_group = (mContextMenuType == MENU_GROUP); - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("Url.ShowProfile", boost::bind(&LLScrollListCtrl::showProfile, id, is_group)); - registrar.add("Url.SendIM", boost::bind(&LLScrollListCtrl::sendIM, id)); - registrar.add("Url.AddFriend", boost::bind(&LLScrollListCtrl::addFriend, id)); - registrar.add("Url.RemoveFriend", boost::bind(&LLScrollListCtrl::removeFriend, id)); - registrar.add("Url.ReportAbuse", boost::bind(&LLScrollListCtrl::reportAbuse, id, is_group)); - registrar.add("Url.Execute", boost::bind(&LLScrollListCtrl::showNameDetails, id, is_group)); - registrar.add("Url.CopyLabel", boost::bind(&LLScrollListCtrl::copyNameToClipboard, id, is_group)); - registrar.add("Url.CopyUrl", boost::bind(&LLScrollListCtrl::copySLURLToClipboard, id, is_group)); - - // create the context menu from the XUI file and display it - std::string menu_name = is_group ? "menu_url_group.xml" : "menu_url_agent.xml"; - auto menu = mPopupMenuHandle.get(); - if (menu) - { - menu->die(); - mPopupMenuHandle.markDead(); - } - llassert(LLMenuGL::sMenuContainer != NULL); - menu = LLUICtrlFactory::getInstance()->createFromFile( - menu_name, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mPopupMenuHandle = menu->getHandle(); - if (mIsFriendSignal) - { - bool isFriend = *(*mIsFriendSignal)(uuid); - LLView* addFriendButton = menu->getChild("add_friend"); - LLView* removeFriendButton = menu->getChild("remove_friend"); - - if (addFriendButton && removeFriendButton) - { - addFriendButton->setEnabled(!isFriend); - removeFriendButton->setEnabled(isFriend); - } - } - - menu->show(x, y); - LLMenuGL::showPopup(this, menu, x, y); - return true; - } - } - return LLUICtrl::handleRightMouseDown(x, y, mask); - } - return false; -} - -void LLScrollListCtrl::showProfile(std::string id, bool is_group) -{ - // show the resident's profile or the group profile - std::string sltype = is_group ? "group" : "agent"; - std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about"; - LLUrlAction::showProfile(slurl); -} - -void LLScrollListCtrl::sendIM(std::string id) -{ - // send im to the resident - std::string slurl = "secondlife:///app/agent/" + id + "/about"; - LLUrlAction::sendIM(slurl); -} - -void LLScrollListCtrl::addFriend(std::string id) -{ - // add resident to friends list - std::string slurl = "secondlife:///app/agent/" + id + "/about"; - LLUrlAction::addFriend(slurl); -} - -void LLScrollListCtrl::removeFriend(std::string id) -{ - std::string slurl = "secondlife:///app/agent/" + id + "/about"; - LLUrlAction::removeFriend(slurl); -} - -void LLScrollListCtrl::reportAbuse(std::string id, bool is_group) -{ - if (!is_group) - { - std::string slurl = "secondlife:///app/agent/" + id + "/about"; - LLUrlAction::reportAbuse(slurl); - } -} - -void LLScrollListCtrl::showNameDetails(std::string id, bool is_group) -{ - // open the resident's details or the group details - std::string sltype = is_group ? "group" : "agent"; - std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about"; - LLUrlAction::clickAction(slurl, true); -} - -void LLScrollListCtrl::copyNameToClipboard(std::string id, bool is_group) -{ - // copy the name of the avatar or group to the clipboard - std::string name; - if (is_group) - { - gCacheName->getGroupName(LLUUID(id), name); - } - else - { - LLAvatarName av_name; - LLAvatarNameCache::get(LLUUID(id), &av_name); - name = av_name.getAccountName(); - } - LLUrlAction::copyURLToClipboard(name); -} - -void LLScrollListCtrl::copySLURLToClipboard(std::string id, bool is_group) -{ - // copy a SLURL for the avatar or group to the clipboard - std::string sltype = is_group ? "group" : "agent"; - std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about"; - LLUrlAction::copyURLToClipboard(slurl); -} - -bool LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - //bool handled = false; - bool handled = handleClick(x, y, mask); - - if (!handled) - { - // Offer the click to the children, even if we aren't enabled - // so the scroll bars will work. - if (NULL == LLView::childrenHandleDoubleClick(x, y, mask)) - { - // Run the callback only if an item is being double-clicked. - if( mCanSelect && hitItem(x, y) && mOnDoubleClickCallback ) - { - mOnDoubleClickCallback(); - } - } - } - - return true; -} - -bool LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask) -{ - // which row was clicked on? - LLScrollListItem* hit_item = hitItem(x, y); - if (!hit_item) return false; - - // get appropriate cell from that row - S32 column_index = getColumnIndexFromOffset(x); - LLScrollListCell* hit_cell = hit_item->getColumn(column_index); - if (!hit_cell) return false; - - // if cell handled click directly (i.e. clicked on an embedded checkbox) - if (hit_cell->handleClick()) - { - // if item not currently selected, select it - if (!hit_item->getSelected()) - { - selectItemAt(x, y, mask); - gFocusMgr.setMouseCapture(this); - mNeedsScroll = true; - } - - // propagate state of cell to rest of selected column - { - // propagate value of this cell to other selected items - // and commit the respective widgets - LLSD item_value = hit_cell->getValue(); - for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - if (item->getSelected()) - { - LLScrollListCell* cellp = item->getColumn(column_index); - cellp->setValue(item_value); - cellp->onCommit(); - if (mLastSelected == NULL) - { - break; - } - } - } - //FIXME: find a better way to signal cell changes - onCommit(); - } - // eat click (e.g. do not trigger double click callback) - return true; - } - else - { - // treat this as a normal single item selection - selectItemAt(x, y, mask); - gFocusMgr.setMouseCapture(this); - mNeedsScroll = true; - // do not eat click (allow double click callback) - return false; - } -} - -LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y ) -{ - // Excludes disabled items. - LLScrollListItem* hit_item = NULL; - - updateSort(); - - LLRect item_rect; - item_rect.setLeftTopAndSize( - mItemListRect.mLeft, - mItemListRect.mTop, - mItemListRect.getWidth(), - mLineHeight ); - - // allow for partial line at bottom - S32 num_page_lines = getLinesPerPage(); - - S32 line = 0; - item_list::iterator iter; - for(iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem* item = *iter; - if( mScrollLines <= line && line < mScrollLines + num_page_lines ) - { - if( item->getEnabled() && item_rect.pointInRect( x, y ) ) - { - hit_item = item; - break; - } - - item_rect.translate(0, -mLineHeight); - } - line++; - } - - return hit_item; -} - -S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x) -{ - // which column did we hit? - S32 left = 0; - S32 right = 0; - S32 width = 0; - S32 column_index = 0; - - ordered_columns_t::const_iterator iter = mColumnsIndexed.begin(); - ordered_columns_t::const_iterator end = mColumnsIndexed.end(); - for ( ; iter != end; ++iter) - { - width = (*iter)->getWidth() + mColumnPadding; - right += width; - if (left <= x && x < right ) - { - break; - } - - // set left for next column as right of current column - left = right; - column_index++; - } - - return llclamp(column_index, 0, getNumColumns() - 1); -} - - -S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index) -{ - S32 column_offset = 0; - ordered_columns_t::const_iterator iter = mColumnsIndexed.begin(); - ordered_columns_t::const_iterator end = mColumnsIndexed.end(); - for ( ; iter != end; ++iter) - { - if (index-- <= 0) - { - return column_offset; - } - column_offset += (*iter)->getWidth() + mColumnPadding; - } - - // when running off the end, return the rightmost pixel - return mItemListRect.mRight; -} - -S32 LLScrollListCtrl::getRowOffsetFromIndex(S32 index) -{ - S32 row_bottom = (mItemListRect.mTop - ((index - mScrollLines + 1) * mLineHeight) ); - return row_bottom; -} - - -bool LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask) -{ - bool handled = false; - - if (hasMouseCapture()) - { - if(mask == MASK_NONE) - { - selectItemAt(x, y, mask); - mNeedsScroll = true; - } - } - else - if (mCanSelect) - { - LLScrollListItem* item = hitItem(x, y); - if (item) - { - mouseOverHighlightNthItem(getItemIndex(item)); - switch (mSelectionType) - { - case CELL: - item->setHoverCell(getColumnIndexFromOffset(x)); - break; - case HEADER: - { - S32 cell = getColumnIndexFromOffset(x); - if (cell > 0) - { - item->setHoverCell(cell); - } - else - { - item->setHoverCell(-1); - } - break; - } - case ROW: - break; - } - } - else - { - mouseOverHighlightNthItem(-1); - } - } - - handled = LLUICtrl::handleHover( x, y, mask ); - - return handled; -} - -void LLScrollListCtrl::onMouseLeave(S32 x, S32 y, MASK mask) -{ - // clear mouse highlight - mouseOverHighlightNthItem(-1); -} - -bool LLScrollListCtrl::handleKeyHere(KEY key,MASK mask ) -{ - bool handled = false; - - // not called from parent means we have keyboard focus or a child does - if (mCanSelect) - { - if (mask == MASK_NONE) - { - switch(key) - { - case KEY_UP: - if (mAllowKeyboardMovement || hasFocus()) - { - // commit implicit in call - selectPrevItem(false); - mNeedsScroll = true; - handled = true; - } - break; - case KEY_DOWN: - if (mAllowKeyboardMovement || hasFocus()) - { - // commit implicit in call - selectNextItem(false); - mNeedsScroll = true; - handled = true; - } - break; - case KEY_LEFT: - if (mAllowKeyboardMovement || hasFocus()) - { - // TODO: support multi-select - LLScrollListItem *item = getFirstSelected(); - if (item) - { - S32 cell = item->getSelectedCell(); - switch (mSelectionType) - { - case CELL: - if (cell < mColumns.size()) cell++; - break; - case HEADER: - if (cell == -1) cell = 1; - else if (cell > 1 && cell < mColumns.size()) cell++; // skip header - break; - case ROW: - cell = -1; - break; - } - item->setSelectedCell(cell); - handled = true; - } - } - break; - case KEY_RIGHT: - if (mAllowKeyboardMovement || hasFocus()) - { - // TODO: support multi-select - LLScrollListItem *item = getFirstSelected(); - if (item) - { - S32 cell = item->getSelectedCell(); - switch (mSelectionType) - { - case CELL: - if (cell >= 0) cell--; - break; - case HEADER: - if (cell > 1) cell--; - else if (cell == 1) cell = -1; // skip header - break; - case ROW: - cell = -1; - break; - } - item->setSelectedCell(cell); - handled = true; - } - } - break; - case KEY_PAGE_UP: - if (mAllowKeyboardMovement || hasFocus()) - { - selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1)); - mNeedsScroll = true; - if (mCommitOnKeyboardMovement - && !mCommitOnSelectionChange) - { - onCommit(); - } - handled = true; - } - break; - case KEY_PAGE_DOWN: - if (mAllowKeyboardMovement || hasFocus()) - { - selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1)); - mNeedsScroll = true; - if (mCommitOnKeyboardMovement - && !mCommitOnSelectionChange) - { - onCommit(); - } - handled = true; - } - break; - case KEY_HOME: - if (mAllowKeyboardMovement || hasFocus()) - { - selectFirstItem(); - mNeedsScroll = true; - if (mCommitOnKeyboardMovement - && !mCommitOnSelectionChange) - { - onCommit(); - } - handled = true; - } - break; - case KEY_END: - if (mAllowKeyboardMovement || hasFocus()) - { - selectNthItem(getItemCount() - 1); - mNeedsScroll = true; - if (mCommitOnKeyboardMovement - && !mCommitOnSelectionChange) - { - onCommit(); - } - handled = true; - } - break; - case KEY_RETURN: - // JC - Special case: Only claim to have handled it - // if we're the special non-commit-on-move - // type. AND we are visible - if (!mCommitOnKeyboardMovement && mask == MASK_NONE) - { - onCommit(); - mSearchString.clear(); - handled = true; - } - break; - case KEY_BACKSPACE: - mSearchTimer.reset(); - if (mSearchString.size()) - { - mSearchString.erase(mSearchString.size() - 1, 1); - } - if (mSearchString.empty()) - { - if (getFirstSelected()) - { - LLScrollListCell* cellp = getFirstSelected()->getColumn(getSearchColumn()); - if (cellp) - { - cellp->highlightText(0, 0); - } - } - } - else if (selectItemByPrefix(wstring_to_utf8str(mSearchString), false)) - { - mNeedsScroll = true; - // update search string only on successful match - mSearchTimer.reset(); - - if (mCommitOnKeyboardMovement - && !mCommitOnSelectionChange) - { - onCommit(); - } - } - break; - default: - break; - } - } - // TODO: multiple: shift-up, shift-down, shift-home, shift-end, select all - } - - return handled; -} - -bool LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char) -{ - if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL - { - return false; - } - - // perform incremental search based on keyboard input - static LLUICachedControl type_ahead_timeout ("TypeAheadTimeout", 0); - if (mSearchTimer.getElapsedTimeF32() > type_ahead_timeout) - { - mSearchString.clear(); - } - - // type ahead search is case insensitive - uni_char = LLStringOps::toLower((llwchar)uni_char); - - if (selectItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), false)) - { - // update search string only on successful match - mNeedsScroll = true; - mSearchString += uni_char; - mSearchTimer.reset(); - - if (mCommitOnKeyboardMovement - && !mCommitOnSelectionChange) - { - onCommit(); - } - } - // handle iterating over same starting character - else if (isRepeatedChars(mSearchString + (llwchar)uni_char) && !mItemList.empty()) - { - // start from last selected item, in case we previously had a successful match against - // duplicated characters ('AA' matches 'Aaron') - item_list::iterator start_iter = mItemList.begin(); - S32 first_selected = getFirstSelectedIndex(); - - // if we have a selection (> -1) then point iterator at the selected item - if (first_selected > 0) - { - // point iterator to first selected item - start_iter += first_selected; - } - - // start search at first item after current selection - item_list::iterator iter = start_iter; - ++iter; - if (iter == mItemList.end()) - { - iter = mItemList.begin(); - } - - // loop around once, back to previous selection - while(iter != start_iter) - { - LLScrollListItem* item = *iter; - - LLScrollListCell* cellp = item->getColumn(getSearchColumn()); - if (cellp) - { - // Only select enabled items with matching first characters - LLWString item_label = utf8str_to_wstring(cellp->getValue().asString()); - if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char) - { - selectItem(item, -1); - mNeedsScroll = true; - cellp->highlightText(0, 1); - mSearchTimer.reset(); - - if (mCommitOnKeyboardMovement - && !mCommitOnSelectionChange) - { - onCommit(); - } - - break; - } - } - - ++iter; - if (iter == mItemList.end()) - { - iter = mItemList.begin(); - } - } - } - - return true; -} - - -void LLScrollListCtrl::reportInvalidInput() -{ - make_ui_sound("UISndBadKeystroke"); -} - -bool LLScrollListCtrl::isRepeatedChars(const LLWString& string) const -{ - if (string.empty()) - { - return false; - } - - llwchar first_char = string[0]; - - for (U32 i = 0; i < string.size(); i++) - { - if (string[i] != first_char) - { - return false; - } - } - - return true; -} - -void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, S32 cell, bool select_single_item) -{ - if (!itemp) return; - - if (!itemp->getSelected()) - { - if (mLastSelected) - { - LLScrollListCell* cellp = mLastSelected->getColumn(getSearchColumn()); - if (cellp) - { - cellp->highlightText(0, 0); - } - } - if (select_single_item) - { - deselectAllItems(true); - } - itemp->setSelected(true); - switch (mSelectionType) - { - case CELL: - itemp->setSelectedCell(cell); - break; - case HEADER: - itemp->setSelectedCell(cell <= 0 ? -1 : cell); - break; - case ROW: - itemp->setSelectedCell(-1); - break; - } - mLastSelected = itemp; - mSelectionChanged = true; - } -} - -void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp) -{ - if (!itemp) return; - - if (itemp->getSelected()) - { - if (mLastSelected == itemp) - { - mLastSelected = NULL; - } - - itemp->setSelected(false); - LLScrollListCell* cellp = itemp->getColumn(getSearchColumn()); - if (cellp) - { - cellp->highlightText(0, 0); - } - mSelectionChanged = true; - } -} - -void LLScrollListCtrl::commitIfChanged() -{ - if (mSelectionChanged) - { - mDirty = true; - mSelectionChanged = false; - onCommit(); - } -} - -struct SameSortColumn -{ - SameSortColumn(S32 column) : mColumn(column) {} - S32 mColumn; - - bool operator()(std::pair sort_column) { return sort_column.first == mColumn; } -}; - -bool LLScrollListCtrl::setSort(S32 column_idx, bool ascending) -{ - LLScrollListColumn* sort_column = getColumn(column_idx); - if (!sort_column) return false; - - sort_column->mSortDirection = ascending ? LLScrollListColumn::ASCENDING : LLScrollListColumn::DESCENDING; - - sort_column_t new_sort_column(column_idx, ascending); - - setNeedsSort(); - - if (mSortColumns.empty()) - { - mSortColumns.push_back(new_sort_column); - return true; - } - else - { - // grab current sort column - sort_column_t cur_sort_column = mSortColumns.back(); - - // remove any existing sort criterion referencing this column - // and add the new one - mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column_idx)), mSortColumns.end()); - mSortColumns.push_back(new_sort_column); - - // did the sort criteria change? - return (cur_sort_column != new_sort_column); - } -} - -S32 LLScrollListCtrl::getLinesPerPage() -{ - //if mPageLines is NOT provided display all item - if (mPageLines) - { - return mPageLines; - } - else - { - return mLineHeight ? mItemListRect.getHeight() / mLineHeight : getItemCount(); - } -} - - -// Called by scrollbar -void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar ) -{ - mScrollLines = new_pos; -} - - -void LLScrollListCtrl::sortByColumn(const std::string& name, bool ascending) -{ - column_map_t::iterator itor = mColumns.find(name); - if (itor != mColumns.end()) - { - sortByColumnIndex((*itor).second->mIndex, ascending); - } -} - -// First column is column 0 -void LLScrollListCtrl::sortByColumnIndex(U32 column, bool ascending) -{ - setSort(column, ascending); - updateSort(); -} - -void LLScrollListCtrl::updateSort() const -{ - if (hasSortOrder() && !isSorted()) - { - // do stable sort to preserve any previous sorts - std::stable_sort( - mItemList.begin(), - mItemList.end(), - SortScrollListItem(mSortColumns,mSortCallback, mAlternateSort)); - - mSorted = true; - } -} - -// for one-shot sorts, does not save sort column/order -void LLScrollListCtrl::sortOnce(S32 column, bool ascending) -{ - std::vector > sort_column; - sort_column.push_back(std::make_pair(column, ascending)); - - // do stable sort to preserve any previous sorts - std::stable_sort( - mItemList.begin(), - mItemList.end(), - SortScrollListItem(sort_column,mSortCallback,mAlternateSort)); -} - -void LLScrollListCtrl::dirtyColumns() -{ - mColumnsDirty = true; - mColumnWidthsDirty = true; - - // need to keep mColumnsIndexed up to date - // just in case someone indexes into it immediately - mColumnsIndexed.resize(mColumns.size()); - - column_map_t::iterator column_itor; - for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor) - { - LLScrollListColumn *column = column_itor->second; - mColumnsIndexed[column_itor->second->mIndex] = column; - } -} - - -S32 LLScrollListCtrl::getScrollPos() const -{ - return mScrollbar->getDocPos(); -} - - -void LLScrollListCtrl::setScrollPos( S32 pos ) -{ - mScrollbar->setDocPos( pos ); - - onScrollChange(mScrollbar->getDocPos(), mScrollbar); -} - - -void LLScrollListCtrl::scrollToShowSelected() -{ - // don't scroll automatically when capturing mouse input - // as that will change what is currently under the mouse cursor - if (hasMouseCapture()) - { - return; - } - - updateSort(); - - S32 index = getFirstSelectedIndex(); - if (index < 0) - { - return; - } - - LLScrollListItem* item = mItemList[index]; - if (!item) - { - // I don't THINK this should ever happen. - return; - } - - S32 lowest = mScrollLines; - S32 page_lines = getLinesPerPage(); - S32 highest = mScrollLines + page_lines; - - if (index < lowest) - { - // need to scroll to show item - setScrollPos(index); - } - else if (highest <= index) - { - setScrollPos(index - page_lines + 1); - } -} - -void LLScrollListCtrl::updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width) -{ - mTotalStaticColumnWidth += llmax(0, new_width) - llmax(0, col->getWidth()); -} - -// LLEditMenuHandler functions - -// virtual -void LLScrollListCtrl::copy() -{ - std::string buffer; - - std::vector items = getAllSelected(); - std::vector::iterator itor; - for (itor = items.begin(); itor != items.end(); ++itor) - { - buffer += (*itor)->getContentsCSV() + "\n"; - } - LLClipboard::instance().copyToClipboard(utf8str_to_wstring(buffer), 0, buffer.length()); -} - -// virtual -bool LLScrollListCtrl::canCopy() const -{ - return (getFirstSelected() != NULL); -} - -// virtual -void LLScrollListCtrl::cut() -{ - copy(); - doDelete(); -} - -// virtual -bool LLScrollListCtrl::canCut() const -{ - return canCopy() && canDoDelete(); -} - -// virtual -void LLScrollListCtrl::selectAll() -{ - // Deselects all other items - item_list::iterator iter; - for (iter = mItemList.begin(); iter != mItemList.end(); iter++) - { - LLScrollListItem *itemp = *iter; - if( itemp->getEnabled() ) - { - selectItem(itemp, -1, false); - } - } - - if (mCommitOnSelectionChange) - { - commitIfChanged(); - } -} - -// virtual -bool LLScrollListCtrl::canSelectAll() const -{ - return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable); -} - -// virtual -void LLScrollListCtrl::deselect() -{ - deselectAllItems(); -} - -// virtual -bool LLScrollListCtrl::canDeselect() const -{ - return getCanSelect(); -} - -void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) -{ - LLScrollListColumn::Params p; - LLParamSDParser parser; - parser.readSD(column, p); - addColumn(p, pos); -} - -void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params, EAddPosition pos) -{ - if (!column_params.validateBlock()) return; - - std::string name = column_params.name; - // if no column name provided, just use ordinal as name - if (name.empty()) - { - name = llformat("%d", mColumnsIndexed.size()); - } - - if (mColumns.find(name) == mColumns.end()) - { - // Add column - mColumns[name] = new LLScrollListColumn(column_params, this); - LLScrollListColumn* new_column = mColumns[name]; - new_column->mIndex = mColumns.size()-1; - - // Add button - if (new_column->getWidth() > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth) - { - if (getNumColumns() > 0) - { - mTotalColumnPadding += mColumnPadding; - } - if (new_column->mRelWidth >= 0) - { - new_column->setWidth((S32)ll_round(new_column->mRelWidth*mItemListRect.getWidth())); - } - else if(new_column->mDynamicWidth) - { - mNumDynamicWidthColumns++; - new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns); - } - S32 top = mItemListRect.mTop; - - S32 left = mItemListRect.mLeft; - for (column_map_t::iterator itor = mColumns.begin(); - itor != mColumns.end(); - ++itor) - { - if (itor->second->mIndex < new_column->mIndex && - itor->second->getWidth() > 0) - { - left += itor->second->getWidth() + mColumnPadding; - } - } - - S32 right = left+new_column->getWidth(); - if (new_column->mIndex != (S32)mColumns.size()-1) - { - right += mColumnPadding; - } - - LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top); - - LLScrollColumnHeader::Params params(LLUICtrlFactory::getDefaultParams()); - params.name = "btn_" + name; - params.rect = temp_rect; - params.column = new_column; - params.tool_tip = column_params.tool_tip; - params.tab_stop = false; - params.visible = mDisplayColumnHeaders; - - if(column_params.header.image.isProvided()) - { - params.image_selected = column_params.header.image; - params.image_unselected = column_params.header.image; - } - else - { - params.label = column_params.header.label; - } - - new_column->mHeader = LLUICtrlFactory::create(params); - addChild(new_column->mHeader); - - sendChildToFront(mScrollbar); - } - } - - dirtyColumns(); -} - -// static -void LLScrollListCtrl::onClickColumn(void *userdata) -{ - LLScrollListColumn *info = (LLScrollListColumn*)userdata; - if (!info) return; - - LLScrollListCtrl *parent = info->mParentCtrl; - if (!parent) return; - - if (!parent->mCanSort) return; - - S32 column_index = info->mIndex; - - LLScrollListColumn* column = parent->mColumnsIndexed[info->mIndex]; - bool ascending = column->mSortDirection == LLScrollListColumn::ASCENDING; - if (column->mSortingColumn != column->mName - && parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end()) - { - LLScrollListColumn* info_redir = parent->mColumns[column->mSortingColumn]; - column_index = info_redir->mIndex; - } - - // if this column is the primary sort key, reverse the direction - if (!parent->mSortColumns.empty() && parent->mSortColumns.back().first == column_index) - { - ascending = !parent->mSortColumns.back().second; - } - - parent->sortByColumnIndex(column_index, ascending); - - if (parent->mOnSortChangedCallback) - { - parent->mOnSortChangedCallback(); - } -} - -std::string LLScrollListCtrl::getSortColumnName() -{ - LLScrollListColumn* column = mSortColumns.empty() ? NULL : mColumnsIndexed[mSortColumns.back().first]; - - if (column) return column->mName; - else return ""; -} - -bool LLScrollListCtrl::hasSortOrder() const -{ - return !mSortColumns.empty(); -} - -void LLScrollListCtrl::clearSortOrder() -{ - mSortColumns.clear(); -} - -void LLScrollListCtrl::clearColumns() -{ - column_map_t::iterator itor; - for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) - { - LLScrollColumnHeader *header = itor->second->mHeader; - if (header) - { - removeChild(header); - delete header; - } - } - std::for_each(mColumns.begin(), mColumns.end(), DeletePairedPointer()); - mColumns.clear(); - mSortColumns.clear(); - mTotalStaticColumnWidth = 0; - mTotalColumnPadding = 0; - - dirtyColumns(); // Clears mColumnsIndexed -} - -void LLScrollListCtrl::setColumnLabel(const std::string& column, const std::string& label) -{ - LLScrollListColumn* columnp = getColumn(column); - if (columnp) - { - columnp->mLabel = label; - if (columnp->mHeader) - { - columnp->mHeader->setLabel(label); - } - } -} - -LLScrollListColumn* LLScrollListCtrl::getColumn(S32 index) -{ - if (index < 0 || index >= (S32)mColumnsIndexed.size()) - { - return NULL; - } - return mColumnsIndexed[index]; -} - -LLScrollListColumn* LLScrollListCtrl::getColumn(const std::string& name) -{ - column_map_t::iterator column_itor = mColumns.find(name); - if (column_itor != mColumns.end()) - { - return column_itor->second; - } - return NULL; -} - -LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - LLScrollListItem::Params item_params; - LLParamSDParser parser; - parser.readSD(element, item_params); - item_params.userdata = userdata; - return addRow(item_params, pos); -} - -LLScrollListItem* LLScrollListCtrl::addRow(const LLScrollListItem::Params& item_p, EAddPosition pos) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - LLScrollListItem *new_item = new LLScrollListItem(item_p); - return addRow(new_item, item_p, pos); -} - -LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& item_p, EAddPosition pos) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - if (!item_p.validateBlock() || !new_item) return NULL; - new_item->setNumColumns(mColumns.size()); - - // Add any columns we don't already have - S32 col_index = 0; - - for(LLInitParam::ParamIterator::const_iterator itor = item_p.columns.begin(); - itor != item_p.columns.end(); - ++itor) - { - LLScrollListCell::Params cell_p = *itor; - std::string column = cell_p.column; - - // empty columns strings index by ordinal - if (column.empty()) - { - column = llformat("%d", col_index); - } - - LLScrollListColumn* columnp = getColumn(column); - - // create new column on demand - if (!columnp) - { - LLScrollListColumn::Params new_column; - new_column.name = column; - new_column.header.label = column; - - // if width supplied for column, use it, otherwise - // use adaptive width - if (cell_p.width.isProvided()) - { - new_column.width.pixel_width = cell_p.width; - } - addColumn(new_column); - columnp = mColumns[column]; - new_item->setNumColumns(mColumns.size()); - } - - S32 index = columnp->mIndex; - if (!cell_p.width.isProvided()) - { - cell_p.width = columnp->getWidth(); - } - - LLScrollListCell* cell = LLScrollListCell::create(cell_p); - - if (cell) - { - new_item->setColumn(index, cell); - if (columnp->mHeader - && cell->isText() - && !cell->getValue().asString().empty()) - { - columnp->mHeader->setHasResizableElement(true); - } - } - - col_index++; - } - - if (item_p.columns.empty()) - { - if (mColumns.empty()) - { - LLScrollListColumn::Params new_column; - new_column.name = "0"; - - addColumn(new_column); - new_item->setNumColumns(mColumns.size()); - } - - LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value)); - if (cell) - { - LLScrollListColumn* columnp = mColumns.begin()->second; - - new_item->setColumn(0, cell); - if (columnp->mHeader - && cell->isText() - && !cell->getValue().asString().empty()) - { - columnp->mHeader->setHasResizableElement(true); - } - } - } - - // add dummy cells for missing columns - for (column_map_t::iterator column_it = mColumns.begin(); column_it != mColumns.end(); ++column_it) - { - S32 column_idx = column_it->second->mIndex; - if (new_item->getColumn(column_idx) == NULL) - { - LLScrollListColumn* column_ptr = column_it->second; - LLScrollListCell::Params cell_p; - cell_p.width = column_ptr->getWidth(); - - new_item->setColumn(column_idx, new LLScrollListSpacer(cell_p)); - } - } - - addItem(new_item, pos); - return new_item; -} - -LLScrollListItem* LLScrollListCtrl::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id) -{ - LLSD entry_id = id; - - if (id.isUndefined()) - { - entry_id = value; - } - - LLScrollListItem::Params item_params; - item_params.value(entry_id); - item_params.columns.add() - .value(value) - .font(LLFontGL::getFontEmojiSmall()); - - return addRow(item_params, pos); -} - -void LLScrollListCtrl::setValue(const LLSD& value ) -{ - LLSD::array_const_iterator itor; - for (itor = value.beginArray(); itor != value.endArray(); ++itor) - { - addElement(*itor); - } -} - -LLSD LLScrollListCtrl::getValue() const -{ - LLScrollListItem *item = getFirstSelected(); - if (!item) return LLSD(); - return item->getValue(); -} - -bool LLScrollListCtrl::operateOnSelection(EOperation op) -{ - if (op == OP_DELETE) - { - deleteSelectedItems(); - return true; - } - else if (op == OP_DESELECT) - { - deselectAllItems(); - } - return false; -} - -bool LLScrollListCtrl::operateOnAll(EOperation op) -{ - if (op == OP_DELETE) - { - clearRows(); - return true; - } - else if (op == OP_DESELECT) - { - deselectAllItems(); - } - else if (op == OP_SELECT) - { - selectAll(); - } - return false; -} -//virtual -void LLScrollListCtrl::setFocus(bool b) -{ - // for tabbing into pristine scroll lists (Finder) - if (!getFirstSelected()) - { - selectFirstItem(); - //onCommit(); // SJB: selectFirstItem() will call onCommit() if appropriate - } - LLUICtrl::setFocus(b); -} - - -// virtual -bool LLScrollListCtrl::isDirty() const -{ - bool grubby = mDirty; - if ( !mAllowMultipleSelection ) - { - grubby = (mOriginalSelection != getFirstSelectedIndex()); - } - return grubby; -} - -// Clear dirty state -void LLScrollListCtrl::resetDirty() -{ - mDirty = false; - mOriginalSelection = getFirstSelectedIndex(); -} - - -//virtual -void LLScrollListCtrl::onFocusReceived() -{ - // forget latent selection changes when getting focus - mSelectionChanged = false; - LLUICtrl::onFocusReceived(); -} - -//virtual -void LLScrollListCtrl::onFocusLost() -{ - if (hasMouseCapture()) - { - gFocusMgr.setMouseCapture(NULL); - } - - mSearchString.clear(); - - LLUICtrl::onFocusLost(); -} - -boost::signals2::connection LLScrollListCtrl::setIsFriendCallback(const is_friend_signal_t::slot_type& cb) -{ - if (!mIsFriendSignal) - { - mIsFriendSignal = new is_friend_signal_t(); - } - return mIsFriendSignal->connect(cb); -} - -bool LLScrollListCtrl::highlightMatchingItems(const std::string& filter_str) -{ - if (filter_str == "" || filter_str == " ") - { - clearHighlightedItems(); - return false; - } - - bool res = false; - - setHighlightedColor(LLUIColorTable::instance().getColor("SearchableControlHighlightColor", LLColor4::red)); - - std::string filter_str_lc(filter_str); - LLStringUtil::toLower(filter_str_lc); - - std::vector data = getAllData(); - std::vector::iterator iter = data.begin(); - while (iter != data.end()) - { - LLScrollListCell* cell = (*iter)->getColumn(0); - if (cell) - { - std::string value = cell->getValue().asString(); - LLStringUtil::toLower(value); - if (value.find(filter_str_lc) == std::string::npos) - { - (*iter)->setHighlighted(false); - } - else - { - (*iter)->setHighlighted(true); - res = true; - } - } - iter++; - } - return res; -} + /** + * @file llscrolllistctrl.cpp + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $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 "llscrolllistctrl.h" + +#include + +#include "llstl.h" +#include "llboost.h" +//#include "indra_constants.h" + +#include "llavatarnamecache.h" +#include "llcheckboxctrl.h" +#include "llclipboard.h" +#include "llfocusmgr.h" +#include "llgl.h" // LLGLSUIDefault() +#include "lllocalcliprect.h" +//#include "llrender.h" +#include "llresmgr.h" +#include "llscrollbar.h" +#include "llscrolllistcell.h" +#include "llstring.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llwindow.h" +#include "llcontrol.h" +#include "llkeyboard.h" +#include "llviewborder.h" +#include "lltextbox.h" +#include "llsdparam.h" +#include "llcachename.h" +#include "llmenugl.h" +#include "llurlaction.h" +#include "lltooltip.h" + +#include + +static LLDefaultChildRegistry::Register r("scroll_list"); + +// local structures & classes. +struct SortScrollListItem +{ + SortScrollListItem(const std::vector >& sort_orders,const LLScrollListCtrl::sort_signal_t* sort_signal, bool alternate_sort) + : mSortOrders(sort_orders) + , mSortSignal(sort_signal) + , mAltSort(alternate_sort) + {} + + bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2) + { + // sort over all columns in order specified by mSortOrders + S32 sort_result = 0; + for (sort_order_t::const_reverse_iterator it = mSortOrders.rbegin(); + it != mSortOrders.rend(); ++it) + { + S32 col_idx = it->first; + bool sort_ascending = it->second; + + S32 order = sort_ascending ? 1 : -1; // ascending or descending sort for this column? + + const LLScrollListCell *cell1 = i1->getColumn(col_idx); + const LLScrollListCell *cell2 = i2->getColumn(col_idx); + if (cell1 && cell2) + { + if(mSortSignal) + { + sort_result = order * (*mSortSignal)(col_idx,i1, i2); + } + else + { + if (mAltSort && !cell1->getAltValue().asString().empty() && !cell2->getAltValue().asString().empty()) + { + sort_result = order * LLStringUtil::compareDict(cell1->getAltValue().asString(), cell2->getAltValue().asString()); + } + else + { + sort_result = order * LLStringUtil::compareDict(cell1->getValue().asString(), cell2->getValue().asString()); + } + } + if (sort_result != 0) + { + break; // we have a sort order! + } + } + } + + return sort_result < 0; + } + + + typedef std::vector > sort_order_t; + const LLScrollListCtrl::sort_signal_t* mSortSignal; + const sort_order_t& mSortOrders; + const bool mAltSort; +}; + +//--------------------------------------------------------------------------- +// LLScrollListCtrl +//--------------------------------------------------------------------------- + +void LLScrollListCtrl::SelectionTypeNames::declareValues() +{ + declare("row", LLScrollListCtrl::ROW); + declare("cell", LLScrollListCtrl::CELL); + declare("header", LLScrollListCtrl::HEADER); +} + +LLScrollListCtrl::Contents::Contents() +: columns("column"), + rows("row") +{ + addSynonym(columns, "columns"); + addSynonym(rows, "rows"); +} + +LLScrollListCtrl::Params::Params() +: multi_select("multi_select", false), + has_border("draw_border"), + draw_heading("draw_heading"), + search_column("search_column", 0), + selection_type("selection_type", ROW), + sort_column("sort_column", -1), + sort_ascending("sort_ascending", true), + can_sort("can_sort", true), + mouse_wheel_opaque("mouse_wheel_opaque", false), + commit_on_keyboard_movement("commit_on_keyboard_movement", true), + commit_on_selection_change("commit_on_selection_change", false), + heading_height("heading_height"), + page_lines("page_lines", 0), + background_visible("background_visible"), + draw_stripes("draw_stripes"), + column_padding("column_padding"), + row_padding("row_padding", 2), + fg_unselected_color("fg_unselected_color"), + fg_selected_color("fg_selected_color"), + bg_selected_color("bg_selected_color"), + fg_disable_color("fg_disable_color"), + bg_writeable_color("bg_writeable_color"), + bg_readonly_color("bg_readonly_color"), + bg_stripe_color("bg_stripe_color"), + hovered_color("hovered_color"), + highlighted_color("highlighted_color"), + contents(""), + scroll_bar_bg_visible("scroll_bar_bg_visible"), + scroll_bar_bg_color("scroll_bar_bg_color"), + border("border") +{} + +LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) +: LLUICtrl(p), + mLineHeight(0), + mScrollLines(0), + mMouseWheelOpaque(p.mouse_wheel_opaque), + mPageLines(p.page_lines), + mMaxSelectable(0), + mAllowKeyboardMovement(true), + mCommitOnKeyboardMovement(p.commit_on_keyboard_movement), + mCommitOnSelectionChange(p.commit_on_selection_change), + mSelectionChanged(false), + mSelectionType(p.selection_type), + mNeedsScroll(false), + mCanSelect(true), + mCanSort(p.can_sort), + mColumnsDirty(false), + mMaxItemCount(INT_MAX), + mBorderThickness( 2 ), + mOnDoubleClickCallback( NULL ), + mOnMaximumSelectCallback( NULL ), + mOnSortChangedCallback( NULL ), + mHighlightedItem(-1), + mBorder(NULL), + mSortCallback(NULL), + mCommentTextView(NULL), + mNumDynamicWidthColumns(0), + mTotalStaticColumnWidth(0), + mTotalColumnPadding(0), + mSorted(false), + mDirty(false), + mOriginalSelection(-1), + mLastSelected(NULL), + mHeadingHeight(p.heading_height), + mAllowMultipleSelection(p.multi_select), + mDisplayColumnHeaders(p.draw_heading), + mBackgroundVisible(p.background_visible), + mDrawStripes(p.draw_stripes), + mBgWriteableColor(p.bg_writeable_color()), + mBgReadOnlyColor(p.bg_readonly_color()), + mBgSelectedColor(p.bg_selected_color()), + mBgStripeColor(p.bg_stripe_color()), + mFgSelectedColor(p.fg_selected_color()), + mFgUnselectedColor(p.fg_unselected_color()), + mFgDisabledColor(p.fg_disable_color()), + mHighlightedColor(p.highlighted_color()), + mHoveredColor(p.hovered_color()), + mSearchColumn(p.search_column), + mColumnPadding(p.column_padding), + mRowPadding(p.row_padding), + mAlternateSort(false), + mContextMenuType(MENU_NONE), + mIsFriendSignal(NULL) +{ + mItemListRect.setOriginAndSize( + mBorderThickness, + mBorderThickness, + getRect().getWidth() - 2 * mBorderThickness, + getRect().getHeight() - 2 * mBorderThickness ); + + updateLineHeight(); + + // Init the scrollbar + static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); + + LLRect scroll_rect; + scroll_rect.setOriginAndSize( + getRect().getWidth() - mBorderThickness - scrollbar_size, + mItemListRect.mBottom, + scrollbar_size, + mItemListRect.getHeight()); + + LLScrollbar::Params sbparams; + sbparams.name("Scrollbar"); + sbparams.rect(scroll_rect); + sbparams.orientation(LLScrollbar::VERTICAL); + sbparams.doc_size(getItemCount()); + sbparams.doc_pos(mScrollLines); + sbparams.page_size( getLinesPerPage() ); + sbparams.change_callback(boost::bind(&LLScrollListCtrl::onScrollChange, this, _1, _2)); + sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM); + sbparams.visible(false); + sbparams.bg_visible(p.scroll_bar_bg_visible); + sbparams.bg_color(p.scroll_bar_bg_color); + mScrollbar = LLUICtrlFactory::create (sbparams); + addChild(mScrollbar); + + // Border + if (p.has_border) + { + LLRect border_rect = getLocalRect(); + LLViewBorder::Params params = p.border; + params.rect(border_rect); + mBorder = LLUICtrlFactory::create (params); + addChild(mBorder); + } + + // set border *after* rect is fully initialized + if (mBorder) + { + mBorder->setRect(getLocalRect()); + mBorder->reshape(getRect().getWidth(), getRect().getHeight()); + } + + if (p.sort_column >= 0) + { + sortByColumnIndex(p.sort_column, p.sort_ascending); + } + + + for (LLInitParam::ParamIterator::const_iterator row_it = p.contents.columns.begin(); + row_it != p.contents.columns.end(); + ++row_it) + { + addColumn(*row_it); + } + + for (LLInitParam::ParamIterator::const_iterator row_it = p.contents.rows.begin(); + row_it != p.contents.rows.end(); + ++row_it) + { + addRow(*row_it); + } + + LLTextBox::Params text_p; + text_p.name("comment_text"); + text_p.border_visible(false); + text_p.rect(mItemListRect); + text_p.follows.flags(FOLLOWS_ALL); + // word wrap was added accroding to the EXT-6841 + text_p.wrap(true); + addChild(LLUICtrlFactory::create(text_p)); +} + +S32 LLScrollListCtrl::getSearchColumn() +{ + // search for proper search column + if (mSearchColumn < 0) + { + LLScrollListItem* itemp = getFirstData(); + if (itemp) + { + for(S32 column = 0; column < getNumColumns(); column++) + { + LLScrollListCell* cell = itemp->getColumn(column); + if (cell && cell->isText()) + { + mSearchColumn = column; + break; + } + } + } + } + return llclamp(mSearchColumn, 0, getNumColumns()); +} +/*virtual*/ +bool LLScrollListCtrl::preProcessChildNode(LLXMLNodePtr child) +{ + if (child->hasName("column") || child->hasName("row")) + { + return true; // skip + } + else + { + return false; + } +} + +LLScrollListCtrl::~LLScrollListCtrl() +{ + delete mSortCallback; + + std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + mItemList.clear(); + clearColumns(); //clears columns and deletes headers + delete mIsFriendSignal; + + auto menu = mPopupMenuHandle.get(); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } +} + + +bool LLScrollListCtrl::setMaxItemCount(S32 max_count) +{ + if (max_count >= getItemCount()) + { + mMaxItemCount = max_count; + } + return (max_count == mMaxItemCount); +} + +S32 LLScrollListCtrl::isEmpty() const +{ + return mItemList.empty(); +} + +S32 LLScrollListCtrl::getItemCount() const +{ + return mItemList.size(); +} + +bool LLScrollListCtrl::hasSelectedItem() const +{ + item_list::iterator iter; + for (iter = mItemList.begin(); iter < mItemList.end(); ) + { + LLScrollListItem* itemp = *iter; + if (itemp && itemp->getSelected()) + { + return true; + } + iter++; + } + return false; +} + +// virtual LLScrolListInterface function (was deleteAllItems) +void LLScrollListCtrl::clearRows() +{ + std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + mItemList.clear(); + //mItemCount = 0; + + // Scroll the bar back up to the top. + mScrollbar->setDocParams(0, 0); + + mScrollLines = 0; + mLastSelected = NULL; + updateLayout(); + mDirty = false; +} + + +LLScrollListItem* LLScrollListCtrl::getFirstSelected() const +{ + item_list::const_iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + return item; + } + } + return NULL; +} + +std::vector LLScrollListCtrl::getAllSelected() const +{ + std::vector ret; + item_list::const_iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + ret.push_back(item); + } + } + return ret; +} + +S32 LLScrollListCtrl::getNumSelected() const +{ + S32 numSelected = 0; + + for(item_list::const_iterator iter = mItemList.begin(); iter != mItemList.end(); ++iter) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + ++numSelected; + } + } + + return numSelected; +} + +S32 LLScrollListCtrl::getFirstSelectedIndex() const +{ + S32 CurSelectedIndex = 0; + + // make sure sort is up to date before returning an index + updateSort(); + + item_list::const_iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + return CurSelectedIndex; + } + CurSelectedIndex++; + } + + return -1; +} + +LLScrollListItem* LLScrollListCtrl::getFirstData() const +{ + if (mItemList.size() == 0) + { + return NULL; + } + return mItemList[0]; +} + +LLScrollListItem* LLScrollListCtrl::getLastData() const +{ + if (mItemList.size() == 0) + { + return NULL; + } + return mItemList[mItemList.size() - 1]; +} + +std::vector LLScrollListCtrl::getAllData() const +{ + std::vector ret; + item_list::const_iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + ret.push_back(item); + } + return ret; +} + +// returns first matching item +LLScrollListItem* LLScrollListCtrl::getItem(const LLSD& sd) const +{ + std::string string_val = sd.asString(); + + item_list::const_iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + // assumes string representation is good enough for comparison + if (item->getValue().asString() == string_val) + { + return item; + } + } + return NULL; +} + + +void LLScrollListCtrl::reshape( S32 width, S32 height, bool called_from_parent ) +{ + LLUICtrl::reshape( width, height, called_from_parent ); + + updateLayout(); +} + +void LLScrollListCtrl::updateLayout() +{ + static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); + // reserve room for column headers, if needed + S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0); + mItemListRect.setOriginAndSize( + mBorderThickness, + mBorderThickness, + getRect().getWidth() - 2 * mBorderThickness, + getRect().getHeight() - (2 * mBorderThickness ) - heading_size ); + + if (mCommentTextView == NULL) + { + mCommentTextView = getChildView("comment_text"); + } + + mCommentTextView->setShape(mItemListRect); + + // how many lines of content in a single "page" + S32 page_lines = getLinesPerPage(); + + bool scrollbar_visible = mLineHeight * getItemCount() > mItemListRect.getHeight(); + if (scrollbar_visible) + { + // provide space on the right for scrollbar + mItemListRect.mRight = getRect().getWidth() - mBorderThickness - scrollbar_size; + } + + mScrollbar->setOrigin(getRect().getWidth() - mBorderThickness - scrollbar_size, mItemListRect.mBottom); + mScrollbar->reshape(scrollbar_size, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0)); + mScrollbar->setPageSize(page_lines); + mScrollbar->setDocSize( getItemCount() ); + mScrollbar->setVisible(scrollbar_visible); + + dirtyColumns(); +} + +// Attempt to size the control to show all items. +// Do not make larger than width or height. +void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height) +{ + S32 height = llmin( getRequiredRect().getHeight(), max_height ); + if(mPageLines) + height = llmin( mPageLines * mLineHeight + 2*mBorderThickness + (mDisplayColumnHeaders ? mHeadingHeight : 0), height ); + + S32 width = getRect().getWidth(); + + reshape( width, height ); +} + + +LLRect LLScrollListCtrl::getRequiredRect() +{ + S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0); + S32 height = (mLineHeight * getItemCount()) + + (2 * mBorderThickness ) + + heading_size; + S32 width = getRect().getWidth(); + + return LLRect(0, height, width, 0); +} + + +bool LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, bool requires_column ) +{ + bool not_too_big = getItemCount() < mMaxItemCount; + if (not_too_big) + { + switch( pos ) + { + case ADD_TOP: + mItemList.push_front(item); + setNeedsSort(); + break; + + case ADD_DEFAULT: + case ADD_BOTTOM: + mItemList.push_back(item); + setNeedsSort(); + break; + + default: + llassert(0); + mItemList.push_back(item); + setNeedsSort(); + break; + } + + // create new column on demand + if (mColumns.empty() && requires_column) + { + LLScrollListColumn::Params col_params; + col_params.name = "default_column"; + col_params.header.label = ""; + col_params.width.dynamic_width = true; + addColumn(col_params); + } + + S32 num_cols = item->getNumColumns(); + S32 i = 0; + for (LLScrollListCell* cell = item->getColumn(i); i < num_cols; cell = item->getColumn(++i)) + { + if (i >= (S32)mColumnsIndexed.size()) break; + + cell->setWidth(mColumnsIndexed[i]->getWidth()); + } + + updateLineHeightInsert(item); + + updateLayout(); + } + + return not_too_big; +} + +// NOTE: This is *very* expensive for large lists, especially when we are dirtying the list every frame +// while receiving a long list of names. +// *TODO: Use bookkeeping to make this an incramental cost with item additions +S32 LLScrollListCtrl::calcMaxContentWidth() +{ + const S32 HEADING_TEXT_PADDING = 25; + const S32 COLUMN_TEXT_PADDING = 10; + + S32 max_item_width = 0; + + ordered_columns_t::iterator column_itor; + for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor) + { + LLScrollListColumn* column = *column_itor; + if (!column) continue; + + if (mColumnWidthsDirty) + { + // update max content width for this column, by looking at all items + column->mMaxContentWidth = column->mHeader ? LLFontGL::getFontSansSerifSmall()->getWidth(column->mLabel) + mColumnPadding + HEADING_TEXT_PADDING : 0; + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListCell* cellp = (*iter)->getColumn(column->mIndex); + if (!cellp) continue; + + column->mMaxContentWidth = llmax(LLFontGL::getFontSansSerifSmall()->getWidth(cellp->getValue().asString()) + mColumnPadding + COLUMN_TEXT_PADDING, column->mMaxContentWidth); + } + } + max_item_width += column->mMaxContentWidth; + } + mColumnWidthsDirty = false; + + return max_item_width; +} + +bool LLScrollListCtrl::updateColumnWidths() +{ + bool width_changed = false; + ordered_columns_t::iterator column_itor; + for (column_itor = mColumnsIndexed.begin(); column_itor != mColumnsIndexed.end(); ++column_itor) + { + LLScrollListColumn* column = *column_itor; + if (!column) continue; + + // update column width + S32 new_width = 0; + if (column->mRelWidth >= 0) + { + new_width = (S32)ll_round(column->mRelWidth*mItemListRect.getWidth()); + } + else if (column->mDynamicWidth) + { + new_width = (mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns; + } + else + { + new_width = column->getWidth(); + } + + if (column->getWidth() != new_width) + { + column->setWidth(new_width); + width_changed = true; + } + } + return width_changed; +} + +// Line height is the max height of all the cells in all the items. +void LLScrollListCtrl::updateLineHeight() +{ + mLineHeight = 0; + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + S32 num_cols = itemp->getNumColumns(); + S32 i = 0; + for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i)) + { + mLineHeight = llmax( mLineHeight, cell->getHeight() + mRowPadding ); + } + } +} + +// when the only change to line height is from an insert, we needn't scan the entire list +void LLScrollListCtrl::updateLineHeightInsert(LLScrollListItem* itemp) +{ + S32 num_cols = itemp->getNumColumns(); + S32 i = 0; + for (const LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i)) + { + mLineHeight = llmax( mLineHeight, cell->getHeight() + mRowPadding ); + } +} + + +void LLScrollListCtrl::updateColumns(bool force_update) +{ + if (!mColumnsDirty && !force_update) + return; + + mColumnsDirty = false; + + bool columns_changed_width = updateColumnWidths(); + + // update column headers + std::vector::iterator column_ordered_it; + S32 left = mItemListRect.mLeft; + LLScrollColumnHeader* last_header = NULL; + for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it) + { + LLScrollListColumn* column = *column_ordered_it; + if (!column || column->getWidth() < 0) + { + // skip hidden columns + continue; + } + + if (column->mHeader) + { + column->mHeader->updateResizeBars(); + + last_header = column->mHeader; + S32 top = mItemListRect.mTop; + S32 right = left + column->getWidth(); + + if (column->mIndex != (S32)mColumnsIndexed.size()-1) + { + right += mColumnPadding; + } + right = llmax(left, llmin(mItemListRect.getWidth(), right)); + S32 header_width = right - left; + + last_header->reshape(header_width, mHeadingHeight); + last_header->translate( + left - last_header->getRect().mLeft, + top - last_header->getRect().mBottom); + last_header->setVisible(mDisplayColumnHeaders && header_width > 0); + left = right; + } + } + + bool header_changed_width = false; + // expand last column header we encountered to full list width + if (last_header) + { + S32 old_width = last_header->getColumn()->getWidth(); + S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft); + last_header->reshape(new_width, last_header->getRect().getHeight()); + last_header->setVisible(mDisplayColumnHeaders && new_width > 0); + if (old_width != new_width) + { + last_header->getColumn()->setWidth(new_width); + header_changed_width = true; + } + } + + // propagate column widths to individual cells + if (columns_changed_width || force_update) + { + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + S32 num_cols = itemp->getNumColumns(); + S32 i = 0; + for (LLScrollListCell* cell = itemp->getColumn(i); i < num_cols; cell = itemp->getColumn(++i)) + { + if (i >= (S32)mColumnsIndexed.size()) break; + + cell->setWidth(mColumnsIndexed[i]->getWidth()); + } + } + } + else if (header_changed_width) + { + item_list::iterator iter; + S32 index = last_header->getColumn()->mIndex; // Not always identical to last column! + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + LLScrollListCell* cell = itemp->getColumn(index); + if (cell) + { + cell->setWidth(last_header->getColumn()->getWidth()); + } + } + } +} + +void LLScrollListCtrl::setHeadingHeight(S32 heading_height) +{ + mHeadingHeight = heading_height; + + updateLayout(); + +} +void LLScrollListCtrl::setPageLines(S32 new_page_lines) +{ + mPageLines = new_page_lines; + + updateLayout(); +} + +bool LLScrollListCtrl::selectFirstItem() +{ + bool success = false; + + // our $%&@#$()^%#$()*^ iterators don't let us check against the first item inside out iteration + bool first_item = true; + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if( first_item && itemp->getEnabled() ) + { + if (!itemp->getSelected()) + { + switch (mSelectionType) + { + case CELL: + selectItem(itemp, 0); + break; + case HEADER: + case ROW: + selectItem(itemp, -1); + } + } + success = true; + mOriginalSelection = 0; + } + else + { + deselectItem(itemp); + } + first_item = false; + } + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + return success; +} + +// Deselects all other items +// virtual +bool LLScrollListCtrl::selectNthItem( S32 target_index ) +{ + return selectItemRange(target_index, target_index); +} + +// virtual +bool LLScrollListCtrl::selectItemRange( S32 first_index, S32 last_index ) +{ + if (mItemList.empty()) + { + return false; + } + + // make sure sort is up to date + updateSort(); + + S32 listlen = (S32)mItemList.size(); + first_index = llclamp(first_index, 0, listlen-1); + + if (last_index < 0) + last_index = listlen-1; + else + last_index = llclamp(last_index, first_index, listlen-1); + + bool success = false; + S32 index = 0; + for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); ) + { + LLScrollListItem *itemp = *iter; + if(!itemp) + { + iter = mItemList.erase(iter); + continue ; + } + + if( index >= first_index && index <= last_index ) + { + if( itemp->getEnabled() ) + { + // TODO: support range selection for cells + selectItem(itemp, -1, false); + success = true; + } + } + else + { + deselectItem(itemp); + } + index++; + iter++ ; + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + mSearchString.clear(); + + return success; +} + + +void LLScrollListCtrl::swapWithNext(S32 index) +{ + if (index >= ((S32)mItemList.size() - 1)) + { + // At end of list, doesn't do anything + return; + } + updateSort(); + LLScrollListItem *cur_itemp = mItemList[index]; + mItemList[index] = mItemList[index + 1]; + mItemList[index + 1] = cur_itemp; +} + + +void LLScrollListCtrl::swapWithPrevious(S32 index) +{ + if (index <= 0) + { + // At beginning of list, don't do anything + } + + updateSort(); + LLScrollListItem *cur_itemp = mItemList[index]; + mItemList[index] = mItemList[index - 1]; + mItemList[index - 1] = cur_itemp; +} + + +void LLScrollListCtrl::deleteSingleItem(S32 target_index) +{ + if (target_index < 0 || target_index >= (S32)mItemList.size()) + { + return; + } + + updateSort(); + + LLScrollListItem *itemp; + itemp = mItemList[target_index]; + if (itemp == mLastSelected) + { + mLastSelected = NULL; + } + delete itemp; + mItemList.erase(mItemList.begin() + target_index); + dirtyColumns(); +} + +//FIXME: refactor item deletion +void LLScrollListCtrl::deleteItems(const LLSD& sd) +{ + item_list::iterator iter; + for (iter = mItemList.begin(); iter < mItemList.end(); ) + { + LLScrollListItem* itemp = *iter; + if (itemp->getValue().asString() == sd.asString()) + { + if (itemp == mLastSelected) + { + mLastSelected = NULL; + } + delete itemp; + iter = mItemList.erase(iter); + } + else + { + iter++; + } + } + + dirtyColumns(); +} + +void LLScrollListCtrl::deleteSelectedItems() +{ + item_list::iterator iter; + for (iter = mItemList.begin(); iter < mItemList.end(); ) + { + LLScrollListItem* itemp = *iter; + if (itemp->getSelected()) + { + delete itemp; + iter = mItemList.erase(iter); + } + else + { + iter++; + } + } + mLastSelected = NULL; + dirtyColumns(); +} + +void LLScrollListCtrl::clearHighlightedItems() +{ + for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); ++iter) + { + (*iter)->setHighlighted(false); + } +} + +void LLScrollListCtrl::mouseOverHighlightNthItem(S32 target_index) +{ + if (mHighlightedItem != target_index) + { + if (mHighlightedItem >= 0 && mHighlightedItem < mItemList.size()) + { + mItemList[mHighlightedItem]->setHoverCell(-1); + } + mHighlightedItem = target_index; + } +} + +S32 LLScrollListCtrl::selectMultiple( uuid_vec_t ids ) +{ + item_list::iterator iter; + S32 count = 0; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + uuid_vec_t::iterator iditr; + for(iditr = ids.begin(); iditr != ids.end(); ++iditr) + { + if (item->getEnabled() && (item->getUUID() == (*iditr))) + { + // TODO: support multiple selection for cells + selectItem(item, -1, false); + ++count; + break; + } + } + if(ids.end() != iditr) ids.erase(iditr); + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + return count; +} + +S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const +{ + updateSort(); + + S32 index = 0; + item_list::const_iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if (target_item == itemp) + { + return index; + } + index++; + } + return -1; +} + +S32 LLScrollListCtrl::getItemIndex( const LLUUID& target_id ) const +{ + updateSort(); + + S32 index = 0; + item_list::const_iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if (target_id == itemp->getUUID()) + { + return index; + } + index++; + } + return -1; +} + +void LLScrollListCtrl::selectPrevItem( bool extend_selection) +{ + LLScrollListItem* prev_item = NULL; + + if (!getFirstSelected()) + { + // select last item + selectNthItem(getItemCount() - 1); + } + else + { + updateSort(); + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* cur_item = *iter; + + if (cur_item->getSelected()) + { + if (prev_item) + { + selectItem(prev_item, cur_item->getSelectedCell(), !extend_selection); + } + else + { + reportInvalidInput(); + } + break; + } + + // don't allow navigation to disabled elements + prev_item = cur_item->getEnabled() ? cur_item : prev_item; + } + } + + if ((mCommitOnSelectionChange || mCommitOnKeyboardMovement)) + { + commitIfChanged(); + } + + mSearchString.clear(); +} + + +void LLScrollListCtrl::selectNextItem( bool extend_selection) +{ + LLScrollListItem* next_item = NULL; + + if (!getFirstSelected()) + { + selectFirstItem(); + } + else + { + updateSort(); + + item_list::reverse_iterator iter; + for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++) + { + LLScrollListItem* cur_item = *iter; + + if (cur_item->getSelected()) + { + if (next_item) + { + selectItem(next_item, cur_item->getSelectedCell(), !extend_selection); + } + else + { + reportInvalidInput(); + } + break; + } + + // don't allow navigation to disabled items + next_item = cur_item->getEnabled() ? cur_item : next_item; + } + } + + if (mCommitOnKeyboardMovement) + { + onCommit(); + } + + mSearchString.clear(); +} + + + +void LLScrollListCtrl::deselectAllItems(bool no_commit_on_change) +{ + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + deselectItem(item); + } + + if (mCommitOnSelectionChange && !no_commit_on_change) + { + commitIfChanged(); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Use this to add comment text such as "Searching", which ignores column settings of list + +void LLScrollListCtrl::setCommentText(const std::string& comment_text) +{ + getChild("comment_text")->setValue(comment_text); +} + +LLScrollListItem* LLScrollListCtrl::addSeparator(EAddPosition pos) +{ + LLScrollListItem::Params separator_params; + separator_params.enabled(false); + LLScrollListCell::Params column_params; + column_params.type = "icon"; + column_params.value = "menu_separator"; + column_params.color = LLColor4(0.f, 0.f, 0.f, 0.7f); + column_params.font_halign = LLFontGL::HCENTER; + separator_params.columns.add(column_params); + return addRow( separator_params, pos ); +} + +// Selects first enabled item of the given name. +// Returns false if item not found. +// Calls getItemByLabel in order to combine functionality +bool LLScrollListCtrl::selectItemByLabel(const std::string& label, bool case_sensitive, S32 column/* = 0*/) +{ + deselectAllItems(true); // ensure that no stale items are selected, even if we don't find a match + LLScrollListItem* item = getItemByLabel(label, case_sensitive, column); + + bool found = NULL != item; + if (found) + { + selectItem(item, -1); + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + +LLScrollListItem* LLScrollListCtrl::getItemByLabel(const std::string& label, bool case_sensitive, S32 column) +{ + if (label.empty()) //RN: assume no empty items + { + return NULL; + } + + std::string target_text = label; + if (!case_sensitive) + { + LLStringUtil::toLower(target_text); + } + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + std::string item_text = item->getColumn(column)->getValue().asString(); // Only select enabled items with matching names + if (!case_sensitive) + { + LLStringUtil::toLower(item_text); + } + if(item_text == target_text) + { + return item; + } + } + return NULL; +} + + +bool LLScrollListCtrl::selectItemByPrefix(const std::string& target, bool case_sensitive, S32 column) +{ + return selectItemByPrefix(utf8str_to_wstring(target), case_sensitive, column); +} + +// Selects first enabled item that has a name where the name's first part matched the target string. +// Returns false if item not found. +bool LLScrollListCtrl::selectItemByPrefix(const LLWString& target, bool case_sensitive, S32 column) +{ + bool found = false; + + LLWString target_trimmed( target ); + S32 target_len = target_trimmed.size(); + + if( 0 == target_len ) + { + // Is "" a valid choice? + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + // Only select enabled items with matching names + LLScrollListCell* cellp = item->getColumn(column == -1 ? getSearchColumn() : column); + bool select = cellp ? item->getEnabled() && ('\0' == cellp->getValue().asString()[0]) : false; + if (select) + { + selectItem(item, -1); + found = true; + break; + } + } + } + else + { + if (!case_sensitive) + { + // do comparisons in lower case + LLWStringUtil::toLower(target_trimmed); + } + + for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + + // Only select enabled items with matching names + LLScrollListCell* cellp = item->getColumn(column == -1 ? getSearchColumn() : column); + if (!cellp) + { + continue; + } + LLWString item_label = utf8str_to_wstring(cellp->getValue().asString()); + if (!case_sensitive) + { + LLWStringUtil::toLower(item_label); + } + // remove extraneous whitespace from searchable label + LLWString trimmed_label = item_label; + LLWStringUtil::trim(trimmed_label); + + bool select = item->getEnabled() && trimmed_label.compare(0, target_trimmed.size(), target_trimmed) == 0; + + if (select) + { + // find offset of matching text (might have leading whitespace) + S32 offset = item_label.find(target_trimmed); + cellp->highlightText(offset, target_trimmed.size()); + selectItem(item, -1); + found = true; + break; + } + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + +U32 LLScrollListCtrl::searchItems(const std::string& substring, bool case_sensitive, bool focus) +{ + return searchItems(utf8str_to_wstring(substring), case_sensitive, focus); +} + +U32 LLScrollListCtrl::searchItems(const LLWString& substring, bool case_sensitive, bool focus) +{ + U32 found = 0; + + LLWString substring_trimmed(substring); + S32 len = substring_trimmed.size(); + + if (0 == len) + { + // at the moment search for empty element is not supported + return 0; + } + else + { + deselectAllItems(true); + if (!case_sensitive) + { + // do comparisons in lower case + LLWStringUtil::toLower(substring_trimmed); + } + + for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + // Only select enabled items with matching names + if (!item->getEnabled()) + { + continue; + } + LLScrollListCell* cellp = item->getColumn(getSearchColumn()); + if (!cellp) + { + continue; + } + LLWString item_label = utf8str_to_wstring(cellp->getValue().asString()); + if (!case_sensitive) + { + LLWStringUtil::toLower(item_label); + } + // remove extraneous whitespace from searchable label + LLWStringUtil::trim(item_label); + + size_t found_iter = item_label.find(substring_trimmed); + + if (found_iter != std::string::npos) + { + // find offset of matching text + cellp->highlightText(found_iter, substring_trimmed.size()); + selectItem(item, -1, false); + + found++; + + if (!mAllowMultipleSelection) + { + break; + } + } + } + } + + if (focus && found != 0) + { + mNeedsScroll = true; + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + +const std::string LLScrollListCtrl::getSelectedItemLabel(S32 column) const +{ + LLScrollListItem* item; + + item = getFirstSelected(); + if (item) + { + return item->getColumn(column)->getValue().asString(); + } + + return LLStringUtil::null; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which +// has an associated, unique UUID, and only one of which can be selected at a time. + +LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos, bool enabled) +{ + if (getItemCount() < mMaxItemCount) + { + LLScrollListItem::Params item_p; + item_p.enabled(enabled); + item_p.value(id); + item_p.columns.add().value(item_text).type("text"); + + return addRow( item_p, pos ); + } + return NULL; +} + +// Select the line or lines that match this UUID +bool LLScrollListCtrl::selectByID( const LLUUID& id ) +{ + return selectByValue( LLSD(id) ); +} + +bool LLScrollListCtrl::setSelectedByValue(const LLSD& value, bool selected) +{ + bool found = false; + + if (selected && !mAllowMultipleSelection) deselectAllItems(true); + + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getEnabled()) + { + if (value.isBinary()) + { + if (item->getValue().isBinary()) + { + LLSD::Binary data1 = value.asBinary(); + LLSD::Binary data2 = item->getValue().asBinary(); + found = std::equal(data1.begin(), data1.end(), data2.begin()); + } + } + else + { + found = item->getValue().asString() == value.asString(); + } + + if (found) + { + if (selected) + { + selectItem(item, -1); + } + else + { + deselectItem(item); + } + break; + } + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + return found; +} + +bool LLScrollListCtrl::isSelected(const LLSD& value) const +{ + item_list::const_iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getValue().asString() == value.asString()) + { + return item->getSelected(); + } + } + return false; +} + +LLUUID LLScrollListCtrl::getStringUUIDSelectedItem() const +{ + LLScrollListItem* item = getFirstSelected(); + + if (item) + { + return item->getUUID(); + } + + return LLUUID::null; +} + +LLSD LLScrollListCtrl::getSelectedValue() +{ + LLScrollListItem* item = getFirstSelected(); + + if (item) + { + return item->getValue(); + } + else + { + return LLSD(); + } +} + +void LLScrollListCtrl::drawItems() +{ + S32 x = mItemListRect.mLeft; + S32 y = mItemListRect.mTop - mLineHeight; + + // allow for partial line at bottom + S32 num_page_lines = getLinesPerPage(); + + LLRect item_rect; + + LLGLSUIDefault gls_ui; + + F32 alpha = getDrawContext().mAlpha; + + { + LLLocalClipRect clip(mItemListRect); + + S32 cur_y = y; + + S32 max_columns = 0; + + LLColor4 highlight_color = LLColor4::white; // ex: text inside cells + static LLUICachedControl type_ahead_timeout ("TypeAheadTimeout", 0); + highlight_color.mV[VALPHA] = clamp_rescale(mSearchTimer.getElapsedTimeF32(), type_ahead_timeout * 0.7f, type_ahead_timeout(), 0.4f, 0.f); + + S32 first_line = mScrollLines; + S32 last_line = llmin((S32)mItemList.size() - 1, mScrollLines + getLinesPerPage()); + + if (first_line >= mItemList.size()) + { + return; + } + item_list::iterator iter; + for (S32 line = first_line; line <= last_line; line++) + { + LLScrollListItem* item = mItemList[line]; + + item_rect.setOriginAndSize( + x, + cur_y, + mItemListRect.getWidth(), + mLineHeight ); + item->setRect(item_rect); + + max_columns = llmax(max_columns, item->getNumColumns()); + + LLColor4 fg_color; + LLColor4 hover_color(LLColor4::transparent); + LLColor4 select_color(LLColor4::transparent); + + if( mScrollLines <= line && line < mScrollLines + num_page_lines ) + { + fg_color = (item->getEnabled() ? mFgUnselectedColor.get() : mFgDisabledColor.get()); + if( item->getSelected() && mCanSelect) + { + if(item->getHighlighted()) // if it's highlighted, average the colors + { + select_color = lerp(mBgSelectedColor.get(), mHighlightedColor.get(), 0.5f); + } + else // otherwise just select-highlight it + { + select_color = mBgSelectedColor.get(); + } + + fg_color = (item->getEnabled() ? mFgSelectedColor.get() : mFgDisabledColor.get()); + } + if (mHighlightedItem == line && mCanSelect) + { + if(item->getHighlighted()) // if it's highlighted, average the colors + { + hover_color = lerp(mHoveredColor.get(), mHighlightedColor.get(), 0.5f); + } + else // otherwise just hover-highlight it + { + hover_color = mHoveredColor.get(); + } + } + else if (item->getHighlighted()) + { + hover_color = mHighlightedColor.get(); + } + else + { + if (mDrawStripes && (line % 2 == 0) && (max_columns > 1)) + { + hover_color = mBgStripeColor.get(); + } + } + + if (!item->getEnabled()) + { + hover_color = mBgReadOnlyColor.get(); + } + + item->draw(item_rect, fg_color % alpha, hover_color% alpha, select_color% alpha, highlight_color % alpha, mColumnPadding); + + cur_y -= mLineHeight; + } + } + } +} + + +void LLScrollListCtrl::draw() +{ + LLLocalClipRect clip(getLocalRect()); + + // if user specifies sort, make sure it is maintained + updateSort(); + + if (mNeedsScroll) + { + scrollToShowSelected(); + mNeedsScroll = false; + } + LLRect background(0, getRect().getHeight(), getRect().getWidth(), 0); + // Draw background + if (mBackgroundVisible) + { + F32 alpha = getCurrentTransparency(); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() % alpha : mBgReadOnlyColor.get() % alpha ); + } + + updateColumns(); + + getChildView("comment_text")->setVisible(mItemList.empty()); + + drawItems(); + + if (mBorder) + { + mBorder->setKeyboardFocusHighlight(hasFocus()); + } + + LLUICtrl::draw(); +} + +void LLScrollListCtrl::setEnabled(bool enabled) +{ + mCanSelect = enabled; + setTabStop(enabled); + mScrollbar->setTabStop(!enabled && mScrollbar->getPageSize() < mScrollbar->getDocSize()); +} + +bool LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + bool handled = false; + // Pretend the mouse is over the scrollbar + handled = mScrollbar->handleScrollWheel( 0, 0, clicks ); + + if (mMouseWheelOpaque) + { + return true; + } + + return handled; +} + +bool LLScrollListCtrl::handleScrollHWheel(S32 x, S32 y, S32 clicks) +{ + bool handled = false; + // Pretend the mouse is over the scrollbar + handled = mScrollbar->handleScrollHWheel( 0, 0, clicks ); + + if (mMouseWheelOpaque) + { + return true; + } + + return handled; +} + +// *NOTE: Requires a valid row_index and column_index +LLRect LLScrollListCtrl::getCellRect(S32 row_index, S32 column_index) +{ + LLRect cell_rect; + S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft; + S32 rect_bottom = getRowOffsetFromIndex(row_index); + LLScrollListColumn* columnp = getColumn(column_index); + cell_rect.setOriginAndSize(rect_left, rect_bottom, + /*rect_left + */columnp->getWidth(), mLineHeight); + return cell_rect; +} + +bool LLScrollListCtrl::handleToolTip(S32 x, S32 y, MASK mask) +{ + S32 column_index = getColumnIndexFromOffset(x); + LLScrollListColumn* columnp = getColumn(column_index); + + if (columnp == NULL) return false; + + bool handled = false; + // show tooltip for full name of hovered item if it has been truncated + LLScrollListItem* hit_item = hitItem(x, y); + if (hit_item) + { + LLScrollListCell* hit_cell = hit_item->getColumn(column_index); + if (!hit_cell) return false; + if (hit_cell + && hit_cell->isText() + && hit_cell->needsToolTip()) + { + S32 row_index = getItemIndex(hit_item); + LLRect cell_rect = getCellRect(row_index, column_index); + // Convert rect local to screen coordinates + LLRect sticky_rect; + localRectToScreen(cell_rect, &sticky_rect); + + // display tooltip exactly over original cell, in same font + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(hit_cell->getToolTip()) + .font(LLFontGL::getFontEmojiSmall()) + .pos(LLCoordGL(sticky_rect.mLeft - 5, sticky_rect.mTop + 6)) + .delay_time(0.2f) + .sticky_rect(sticky_rect)); + } + handled = true; + } + + // otherwise, look for a tooltip associated with this column + LLScrollColumnHeader* headerp = columnp->mHeader; + if (headerp && !handled) + { + handled = headerp->handleToolTip(x, y, mask); + } + + return handled; +} + +bool LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask) +{ + if (!mCanSelect) return false; + + bool selection_changed = false; + + LLScrollListItem* hit_item = hitItem(x, y); + + if( hit_item ) + { + if( mAllowMultipleSelection ) + { + if (mask & MASK_SHIFT) + { + if (mLastSelected == NULL) + { + selectItem(hit_item, getColumnIndexFromOffset(x)); + } + else + { + // Select everthing between mLastSelected and hit_item + bool selecting = false; + item_list::iterator itor; + // If we multiselect backwards, we'll stomp on mLastSelected, + // meaning that we never stop selecting until hitting max or + // the end of the list. + LLScrollListItem* lastSelected = mLastSelected; + for (itor = mItemList.begin(); itor != mItemList.end(); ++itor) + { + if(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable) + { + if(mOnMaximumSelectCallback) + { + mOnMaximumSelectCallback(); + } + break; + } + LLScrollListItem *item = *itor; + if (item == hit_item || item == lastSelected) + { + selectItem(item, getColumnIndexFromOffset(x), false); + selecting = !selecting; + if (hit_item == lastSelected) + { + // stop selecting now, since we just clicked on our last selected item + selecting = false; + } + } + if (selecting) + { + selectItem(item, getColumnIndexFromOffset(x), false); + } + } + } + } + else if (mask & MASK_CONTROL) + { + if (hit_item->getSelected()) + { + deselectItem(hit_item); + } + else + { + if(!(mMaxSelectable > 0 && getAllSelected().size() >= mMaxSelectable)) + { + selectItem(hit_item, getColumnIndexFromOffset(x), false); + } + else + { + if(mOnMaximumSelectCallback) + { + mOnMaximumSelectCallback(); + } + } + } + } + else + { + deselectAllItems(true); + selectItem(hit_item, getColumnIndexFromOffset(x)); + } + } + else + { + selectItem(hit_item, getColumnIndexFromOffset(x)); + } + + selection_changed = mSelectionChanged; + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } + + // clear search string on mouse operations + mSearchString.clear(); + } + else + { + //mLastSelected = NULL; + //deselectAllItems(true); + } + + return selection_changed; +} + + +bool LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = childrenHandleMouseDown(x, y, mask) != NULL; + + if( !handled ) + { + // set keyboard focus first, in case click action wants to move focus elsewhere + setFocus(true); + + // clear selection changed flag because user is starting a selection operation + mSelectionChanged = false; + + handleClick(x, y, mask); + } + + return true; +} + +bool LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (hasMouseCapture()) + { + // release mouse capture immediately so + // scroll to show selected logic will work + gFocusMgr.setMouseCapture(NULL); + if(mask == MASK_NONE) + { + selectItemAt(x, y, mask); + mNeedsScroll = true; + } + } + + // always commit when mouse operation is completed inside list + if (mItemListRect.pointInRect(x,y)) + { + mDirty = mDirty || mSelectionChanged; + mSelectionChanged = false; + onCommit(); + } + + return LLUICtrl::handleMouseUp(x, y, mask); +} + +// virtual +bool LLScrollListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLScrollListItem *item = hitItem(x, y); + if (item) + { + // check to see if we have a UUID for this row + std::string id = item->getValue().asString(); + LLUUID uuid(id); + if (! uuid.isNull() && mContextMenuType != MENU_NONE) + { + // set up the callbacks for all of the avatar/group menu items + // (N.B. callbacks don't take const refs as id is local scope) + bool is_group = (mContextMenuType == MENU_GROUP); + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + registrar.add("Url.ShowProfile", boost::bind(&LLScrollListCtrl::showProfile, id, is_group)); + registrar.add("Url.SendIM", boost::bind(&LLScrollListCtrl::sendIM, id)); + registrar.add("Url.AddFriend", boost::bind(&LLScrollListCtrl::addFriend, id)); + registrar.add("Url.RemoveFriend", boost::bind(&LLScrollListCtrl::removeFriend, id)); + registrar.add("Url.ReportAbuse", boost::bind(&LLScrollListCtrl::reportAbuse, id, is_group)); + registrar.add("Url.Execute", boost::bind(&LLScrollListCtrl::showNameDetails, id, is_group)); + registrar.add("Url.CopyLabel", boost::bind(&LLScrollListCtrl::copyNameToClipboard, id, is_group)); + registrar.add("Url.CopyUrl", boost::bind(&LLScrollListCtrl::copySLURLToClipboard, id, is_group)); + + // create the context menu from the XUI file and display it + std::string menu_name = is_group ? "menu_url_group.xml" : "menu_url_agent.xml"; + auto menu = mPopupMenuHandle.get(); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } + llassert(LLMenuGL::sMenuContainer != NULL); + menu = LLUICtrlFactory::getInstance()->createFromFile( + menu_name, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mPopupMenuHandle = menu->getHandle(); + if (mIsFriendSignal) + { + bool isFriend = *(*mIsFriendSignal)(uuid); + LLView* addFriendButton = menu->getChild("add_friend"); + LLView* removeFriendButton = menu->getChild("remove_friend"); + + if (addFriendButton && removeFriendButton) + { + addFriendButton->setEnabled(!isFriend); + removeFriendButton->setEnabled(isFriend); + } + } + + menu->show(x, y); + LLMenuGL::showPopup(this, menu, x, y); + return true; + } + } + return LLUICtrl::handleRightMouseDown(x, y, mask); + } + return false; +} + +void LLScrollListCtrl::showProfile(std::string id, bool is_group) +{ + // show the resident's profile or the group profile + std::string sltype = is_group ? "group" : "agent"; + std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about"; + LLUrlAction::showProfile(slurl); +} + +void LLScrollListCtrl::sendIM(std::string id) +{ + // send im to the resident + std::string slurl = "secondlife:///app/agent/" + id + "/about"; + LLUrlAction::sendIM(slurl); +} + +void LLScrollListCtrl::addFriend(std::string id) +{ + // add resident to friends list + std::string slurl = "secondlife:///app/agent/" + id + "/about"; + LLUrlAction::addFriend(slurl); +} + +void LLScrollListCtrl::removeFriend(std::string id) +{ + std::string slurl = "secondlife:///app/agent/" + id + "/about"; + LLUrlAction::removeFriend(slurl); +} + +void LLScrollListCtrl::reportAbuse(std::string id, bool is_group) +{ + if (!is_group) + { + std::string slurl = "secondlife:///app/agent/" + id + "/about"; + LLUrlAction::reportAbuse(slurl); + } +} + +void LLScrollListCtrl::showNameDetails(std::string id, bool is_group) +{ + // open the resident's details or the group details + std::string sltype = is_group ? "group" : "agent"; + std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about"; + LLUrlAction::clickAction(slurl, true); +} + +void LLScrollListCtrl::copyNameToClipboard(std::string id, bool is_group) +{ + // copy the name of the avatar or group to the clipboard + std::string name; + if (is_group) + { + gCacheName->getGroupName(LLUUID(id), name); + } + else + { + LLAvatarName av_name; + LLAvatarNameCache::get(LLUUID(id), &av_name); + name = av_name.getAccountName(); + } + LLUrlAction::copyURLToClipboard(name); +} + +void LLScrollListCtrl::copySLURLToClipboard(std::string id, bool is_group) +{ + // copy a SLURL for the avatar or group to the clipboard + std::string sltype = is_group ? "group" : "agent"; + std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about"; + LLUrlAction::copyURLToClipboard(slurl); +} + +bool LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + //bool handled = false; + bool handled = handleClick(x, y, mask); + + if (!handled) + { + // Offer the click to the children, even if we aren't enabled + // so the scroll bars will work. + if (NULL == LLView::childrenHandleDoubleClick(x, y, mask)) + { + // Run the callback only if an item is being double-clicked. + if( mCanSelect && hitItem(x, y) && mOnDoubleClickCallback ) + { + mOnDoubleClickCallback(); + } + } + } + + return true; +} + +bool LLScrollListCtrl::handleClick(S32 x, S32 y, MASK mask) +{ + // which row was clicked on? + LLScrollListItem* hit_item = hitItem(x, y); + if (!hit_item) return false; + + // get appropriate cell from that row + S32 column_index = getColumnIndexFromOffset(x); + LLScrollListCell* hit_cell = hit_item->getColumn(column_index); + if (!hit_cell) return false; + + // if cell handled click directly (i.e. clicked on an embedded checkbox) + if (hit_cell->handleClick()) + { + // if item not currently selected, select it + if (!hit_item->getSelected()) + { + selectItemAt(x, y, mask); + gFocusMgr.setMouseCapture(this); + mNeedsScroll = true; + } + + // propagate state of cell to rest of selected column + { + // propagate value of this cell to other selected items + // and commit the respective widgets + LLSD item_value = hit_cell->getValue(); + for (item_list::iterator iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if (item->getSelected()) + { + LLScrollListCell* cellp = item->getColumn(column_index); + cellp->setValue(item_value); + cellp->onCommit(); + if (mLastSelected == NULL) + { + break; + } + } + } + //FIXME: find a better way to signal cell changes + onCommit(); + } + // eat click (e.g. do not trigger double click callback) + return true; + } + else + { + // treat this as a normal single item selection + selectItemAt(x, y, mask); + gFocusMgr.setMouseCapture(this); + mNeedsScroll = true; + // do not eat click (allow double click callback) + return false; + } +} + +LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y ) +{ + // Excludes disabled items. + LLScrollListItem* hit_item = NULL; + + updateSort(); + + LLRect item_rect; + item_rect.setLeftTopAndSize( + mItemListRect.mLeft, + mItemListRect.mTop, + mItemListRect.getWidth(), + mLineHeight ); + + // allow for partial line at bottom + S32 num_page_lines = getLinesPerPage(); + + S32 line = 0; + item_list::iterator iter; + for(iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem* item = *iter; + if( mScrollLines <= line && line < mScrollLines + num_page_lines ) + { + if( item->getEnabled() && item_rect.pointInRect( x, y ) ) + { + hit_item = item; + break; + } + + item_rect.translate(0, -mLineHeight); + } + line++; + } + + return hit_item; +} + +S32 LLScrollListCtrl::getColumnIndexFromOffset(S32 x) +{ + // which column did we hit? + S32 left = 0; + S32 right = 0; + S32 width = 0; + S32 column_index = 0; + + ordered_columns_t::const_iterator iter = mColumnsIndexed.begin(); + ordered_columns_t::const_iterator end = mColumnsIndexed.end(); + for ( ; iter != end; ++iter) + { + width = (*iter)->getWidth() + mColumnPadding; + right += width; + if (left <= x && x < right ) + { + break; + } + + // set left for next column as right of current column + left = right; + column_index++; + } + + return llclamp(column_index, 0, getNumColumns() - 1); +} + + +S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index) +{ + S32 column_offset = 0; + ordered_columns_t::const_iterator iter = mColumnsIndexed.begin(); + ordered_columns_t::const_iterator end = mColumnsIndexed.end(); + for ( ; iter != end; ++iter) + { + if (index-- <= 0) + { + return column_offset; + } + column_offset += (*iter)->getWidth() + mColumnPadding; + } + + // when running off the end, return the rightmost pixel + return mItemListRect.mRight; +} + +S32 LLScrollListCtrl::getRowOffsetFromIndex(S32 index) +{ + S32 row_bottom = (mItemListRect.mTop - ((index - mScrollLines + 1) * mLineHeight) ); + return row_bottom; +} + + +bool LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask) +{ + bool handled = false; + + if (hasMouseCapture()) + { + if(mask == MASK_NONE) + { + selectItemAt(x, y, mask); + mNeedsScroll = true; + } + } + else + if (mCanSelect) + { + LLScrollListItem* item = hitItem(x, y); + if (item) + { + mouseOverHighlightNthItem(getItemIndex(item)); + switch (mSelectionType) + { + case CELL: + item->setHoverCell(getColumnIndexFromOffset(x)); + break; + case HEADER: + { + S32 cell = getColumnIndexFromOffset(x); + if (cell > 0) + { + item->setHoverCell(cell); + } + else + { + item->setHoverCell(-1); + } + break; + } + case ROW: + break; + } + } + else + { + mouseOverHighlightNthItem(-1); + } + } + + handled = LLUICtrl::handleHover( x, y, mask ); + + return handled; +} + +void LLScrollListCtrl::onMouseLeave(S32 x, S32 y, MASK mask) +{ + // clear mouse highlight + mouseOverHighlightNthItem(-1); +} + +bool LLScrollListCtrl::handleKeyHere(KEY key,MASK mask ) +{ + bool handled = false; + + // not called from parent means we have keyboard focus or a child does + if (mCanSelect) + { + if (mask == MASK_NONE) + { + switch(key) + { + case KEY_UP: + if (mAllowKeyboardMovement || hasFocus()) + { + // commit implicit in call + selectPrevItem(false); + mNeedsScroll = true; + handled = true; + } + break; + case KEY_DOWN: + if (mAllowKeyboardMovement || hasFocus()) + { + // commit implicit in call + selectNextItem(false); + mNeedsScroll = true; + handled = true; + } + break; + case KEY_LEFT: + if (mAllowKeyboardMovement || hasFocus()) + { + // TODO: support multi-select + LLScrollListItem *item = getFirstSelected(); + if (item) + { + S32 cell = item->getSelectedCell(); + switch (mSelectionType) + { + case CELL: + if (cell < mColumns.size()) cell++; + break; + case HEADER: + if (cell == -1) cell = 1; + else if (cell > 1 && cell < mColumns.size()) cell++; // skip header + break; + case ROW: + cell = -1; + break; + } + item->setSelectedCell(cell); + handled = true; + } + } + break; + case KEY_RIGHT: + if (mAllowKeyboardMovement || hasFocus()) + { + // TODO: support multi-select + LLScrollListItem *item = getFirstSelected(); + if (item) + { + S32 cell = item->getSelectedCell(); + switch (mSelectionType) + { + case CELL: + if (cell >= 0) cell--; + break; + case HEADER: + if (cell > 1) cell--; + else if (cell == 1) cell = -1; // skip header + break; + case ROW: + cell = -1; + break; + } + item->setSelectedCell(cell); + handled = true; + } + } + break; + case KEY_PAGE_UP: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getFirstSelectedIndex() - (mScrollbar->getPageSize() - 1)); + mNeedsScroll = true; + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = true; + } + break; + case KEY_PAGE_DOWN: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getFirstSelectedIndex() + (mScrollbar->getPageSize() - 1)); + mNeedsScroll = true; + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = true; + } + break; + case KEY_HOME: + if (mAllowKeyboardMovement || hasFocus()) + { + selectFirstItem(); + mNeedsScroll = true; + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = true; + } + break; + case KEY_END: + if (mAllowKeyboardMovement || hasFocus()) + { + selectNthItem(getItemCount() - 1); + mNeedsScroll = true; + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + handled = true; + } + break; + case KEY_RETURN: + // JC - Special case: Only claim to have handled it + // if we're the special non-commit-on-move + // type. AND we are visible + if (!mCommitOnKeyboardMovement && mask == MASK_NONE) + { + onCommit(); + mSearchString.clear(); + handled = true; + } + break; + case KEY_BACKSPACE: + mSearchTimer.reset(); + if (mSearchString.size()) + { + mSearchString.erase(mSearchString.size() - 1, 1); + } + if (mSearchString.empty()) + { + if (getFirstSelected()) + { + LLScrollListCell* cellp = getFirstSelected()->getColumn(getSearchColumn()); + if (cellp) + { + cellp->highlightText(0, 0); + } + } + } + else if (selectItemByPrefix(wstring_to_utf8str(mSearchString), false)) + { + mNeedsScroll = true; + // update search string only on successful match + mSearchTimer.reset(); + + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + } + break; + default: + break; + } + } + // TODO: multiple: shift-up, shift-down, shift-home, shift-end, select all + } + + return handled; +} + +bool LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return false; + } + + // perform incremental search based on keyboard input + static LLUICachedControl type_ahead_timeout ("TypeAheadTimeout", 0); + if (mSearchTimer.getElapsedTimeF32() > type_ahead_timeout) + { + mSearchString.clear(); + } + + // type ahead search is case insensitive + uni_char = LLStringOps::toLower((llwchar)uni_char); + + if (selectItemByPrefix(wstring_to_utf8str(mSearchString + (llwchar)uni_char), false)) + { + // update search string only on successful match + mNeedsScroll = true; + mSearchString += uni_char; + mSearchTimer.reset(); + + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + } + // handle iterating over same starting character + else if (isRepeatedChars(mSearchString + (llwchar)uni_char) && !mItemList.empty()) + { + // start from last selected item, in case we previously had a successful match against + // duplicated characters ('AA' matches 'Aaron') + item_list::iterator start_iter = mItemList.begin(); + S32 first_selected = getFirstSelectedIndex(); + + // if we have a selection (> -1) then point iterator at the selected item + if (first_selected > 0) + { + // point iterator to first selected item + start_iter += first_selected; + } + + // start search at first item after current selection + item_list::iterator iter = start_iter; + ++iter; + if (iter == mItemList.end()) + { + iter = mItemList.begin(); + } + + // loop around once, back to previous selection + while(iter != start_iter) + { + LLScrollListItem* item = *iter; + + LLScrollListCell* cellp = item->getColumn(getSearchColumn()); + if (cellp) + { + // Only select enabled items with matching first characters + LLWString item_label = utf8str_to_wstring(cellp->getValue().asString()); + if (item->getEnabled() && LLStringOps::toLower(item_label[0]) == uni_char) + { + selectItem(item, -1); + mNeedsScroll = true; + cellp->highlightText(0, 1); + mSearchTimer.reset(); + + if (mCommitOnKeyboardMovement + && !mCommitOnSelectionChange) + { + onCommit(); + } + + break; + } + } + + ++iter; + if (iter == mItemList.end()) + { + iter = mItemList.begin(); + } + } + } + + return true; +} + + +void LLScrollListCtrl::reportInvalidInput() +{ + make_ui_sound("UISndBadKeystroke"); +} + +bool LLScrollListCtrl::isRepeatedChars(const LLWString& string) const +{ + if (string.empty()) + { + return false; + } + + llwchar first_char = string[0]; + + for (U32 i = 0; i < string.size(); i++) + { + if (string[i] != first_char) + { + return false; + } + } + + return true; +} + +void LLScrollListCtrl::selectItem(LLScrollListItem* itemp, S32 cell, bool select_single_item) +{ + if (!itemp) return; + + if (!itemp->getSelected()) + { + if (mLastSelected) + { + LLScrollListCell* cellp = mLastSelected->getColumn(getSearchColumn()); + if (cellp) + { + cellp->highlightText(0, 0); + } + } + if (select_single_item) + { + deselectAllItems(true); + } + itemp->setSelected(true); + switch (mSelectionType) + { + case CELL: + itemp->setSelectedCell(cell); + break; + case HEADER: + itemp->setSelectedCell(cell <= 0 ? -1 : cell); + break; + case ROW: + itemp->setSelectedCell(-1); + break; + } + mLastSelected = itemp; + mSelectionChanged = true; + } +} + +void LLScrollListCtrl::deselectItem(LLScrollListItem* itemp) +{ + if (!itemp) return; + + if (itemp->getSelected()) + { + if (mLastSelected == itemp) + { + mLastSelected = NULL; + } + + itemp->setSelected(false); + LLScrollListCell* cellp = itemp->getColumn(getSearchColumn()); + if (cellp) + { + cellp->highlightText(0, 0); + } + mSelectionChanged = true; + } +} + +void LLScrollListCtrl::commitIfChanged() +{ + if (mSelectionChanged) + { + mDirty = true; + mSelectionChanged = false; + onCommit(); + } +} + +struct SameSortColumn +{ + SameSortColumn(S32 column) : mColumn(column) {} + S32 mColumn; + + bool operator()(std::pair sort_column) { return sort_column.first == mColumn; } +}; + +bool LLScrollListCtrl::setSort(S32 column_idx, bool ascending) +{ + LLScrollListColumn* sort_column = getColumn(column_idx); + if (!sort_column) return false; + + sort_column->mSortDirection = ascending ? LLScrollListColumn::ASCENDING : LLScrollListColumn::DESCENDING; + + sort_column_t new_sort_column(column_idx, ascending); + + setNeedsSort(); + + if (mSortColumns.empty()) + { + mSortColumns.push_back(new_sort_column); + return true; + } + else + { + // grab current sort column + sort_column_t cur_sort_column = mSortColumns.back(); + + // remove any existing sort criterion referencing this column + // and add the new one + mSortColumns.erase(remove_if(mSortColumns.begin(), mSortColumns.end(), SameSortColumn(column_idx)), mSortColumns.end()); + mSortColumns.push_back(new_sort_column); + + // did the sort criteria change? + return (cur_sort_column != new_sort_column); + } +} + +S32 LLScrollListCtrl::getLinesPerPage() +{ + //if mPageLines is NOT provided display all item + if (mPageLines) + { + return mPageLines; + } + else + { + return mLineHeight ? mItemListRect.getHeight() / mLineHeight : getItemCount(); + } +} + + +// Called by scrollbar +void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar ) +{ + mScrollLines = new_pos; +} + + +void LLScrollListCtrl::sortByColumn(const std::string& name, bool ascending) +{ + column_map_t::iterator itor = mColumns.find(name); + if (itor != mColumns.end()) + { + sortByColumnIndex((*itor).second->mIndex, ascending); + } +} + +// First column is column 0 +void LLScrollListCtrl::sortByColumnIndex(U32 column, bool ascending) +{ + setSort(column, ascending); + updateSort(); +} + +void LLScrollListCtrl::updateSort() const +{ + if (hasSortOrder() && !isSorted()) + { + // do stable sort to preserve any previous sorts + std::stable_sort( + mItemList.begin(), + mItemList.end(), + SortScrollListItem(mSortColumns,mSortCallback, mAlternateSort)); + + mSorted = true; + } +} + +// for one-shot sorts, does not save sort column/order +void LLScrollListCtrl::sortOnce(S32 column, bool ascending) +{ + std::vector > sort_column; + sort_column.push_back(std::make_pair(column, ascending)); + + // do stable sort to preserve any previous sorts + std::stable_sort( + mItemList.begin(), + mItemList.end(), + SortScrollListItem(sort_column,mSortCallback,mAlternateSort)); +} + +void LLScrollListCtrl::dirtyColumns() +{ + mColumnsDirty = true; + mColumnWidthsDirty = true; + + // need to keep mColumnsIndexed up to date + // just in case someone indexes into it immediately + mColumnsIndexed.resize(mColumns.size()); + + column_map_t::iterator column_itor; + for (column_itor = mColumns.begin(); column_itor != mColumns.end(); ++column_itor) + { + LLScrollListColumn *column = column_itor->second; + mColumnsIndexed[column_itor->second->mIndex] = column; + } +} + + +S32 LLScrollListCtrl::getScrollPos() const +{ + return mScrollbar->getDocPos(); +} + + +void LLScrollListCtrl::setScrollPos( S32 pos ) +{ + mScrollbar->setDocPos( pos ); + + onScrollChange(mScrollbar->getDocPos(), mScrollbar); +} + + +void LLScrollListCtrl::scrollToShowSelected() +{ + // don't scroll automatically when capturing mouse input + // as that will change what is currently under the mouse cursor + if (hasMouseCapture()) + { + return; + } + + updateSort(); + + S32 index = getFirstSelectedIndex(); + if (index < 0) + { + return; + } + + LLScrollListItem* item = mItemList[index]; + if (!item) + { + // I don't THINK this should ever happen. + return; + } + + S32 lowest = mScrollLines; + S32 page_lines = getLinesPerPage(); + S32 highest = mScrollLines + page_lines; + + if (index < lowest) + { + // need to scroll to show item + setScrollPos(index); + } + else if (highest <= index) + { + setScrollPos(index - page_lines + 1); + } +} + +void LLScrollListCtrl::updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width) +{ + mTotalStaticColumnWidth += llmax(0, new_width) - llmax(0, col->getWidth()); +} + +// LLEditMenuHandler functions + +// virtual +void LLScrollListCtrl::copy() +{ + std::string buffer; + + std::vector items = getAllSelected(); + std::vector::iterator itor; + for (itor = items.begin(); itor != items.end(); ++itor) + { + buffer += (*itor)->getContentsCSV() + "\n"; + } + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(buffer), 0, buffer.length()); +} + +// virtual +bool LLScrollListCtrl::canCopy() const +{ + return (getFirstSelected() != NULL); +} + +// virtual +void LLScrollListCtrl::cut() +{ + copy(); + doDelete(); +} + +// virtual +bool LLScrollListCtrl::canCut() const +{ + return canCopy() && canDoDelete(); +} + +// virtual +void LLScrollListCtrl::selectAll() +{ + // Deselects all other items + item_list::iterator iter; + for (iter = mItemList.begin(); iter != mItemList.end(); iter++) + { + LLScrollListItem *itemp = *iter; + if( itemp->getEnabled() ) + { + selectItem(itemp, -1, false); + } + } + + if (mCommitOnSelectionChange) + { + commitIfChanged(); + } +} + +// virtual +bool LLScrollListCtrl::canSelectAll() const +{ + return getCanSelect() && mAllowMultipleSelection && !(mMaxSelectable > 0 && mItemList.size() > mMaxSelectable); +} + +// virtual +void LLScrollListCtrl::deselect() +{ + deselectAllItems(); +} + +// virtual +bool LLScrollListCtrl::canDeselect() const +{ + return getCanSelect(); +} + +void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) +{ + LLScrollListColumn::Params p; + LLParamSDParser parser; + parser.readSD(column, p); + addColumn(p, pos); +} + +void LLScrollListCtrl::addColumn(const LLScrollListColumn::Params& column_params, EAddPosition pos) +{ + if (!column_params.validateBlock()) return; + + std::string name = column_params.name; + // if no column name provided, just use ordinal as name + if (name.empty()) + { + name = llformat("%d", mColumnsIndexed.size()); + } + + if (mColumns.find(name) == mColumns.end()) + { + // Add column + mColumns[name] = new LLScrollListColumn(column_params, this); + LLScrollListColumn* new_column = mColumns[name]; + new_column->mIndex = mColumns.size()-1; + + // Add button + if (new_column->getWidth() > 0 || new_column->mRelWidth > 0 || new_column->mDynamicWidth) + { + if (getNumColumns() > 0) + { + mTotalColumnPadding += mColumnPadding; + } + if (new_column->mRelWidth >= 0) + { + new_column->setWidth((S32)ll_round(new_column->mRelWidth*mItemListRect.getWidth())); + } + else if(new_column->mDynamicWidth) + { + mNumDynamicWidthColumns++; + new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns); + } + S32 top = mItemListRect.mTop; + + S32 left = mItemListRect.mLeft; + for (column_map_t::iterator itor = mColumns.begin(); + itor != mColumns.end(); + ++itor) + { + if (itor->second->mIndex < new_column->mIndex && + itor->second->getWidth() > 0) + { + left += itor->second->getWidth() + mColumnPadding; + } + } + + S32 right = left+new_column->getWidth(); + if (new_column->mIndex != (S32)mColumns.size()-1) + { + right += mColumnPadding; + } + + LLRect temp_rect = LLRect(left,top+mHeadingHeight,right,top); + + LLScrollColumnHeader::Params params(LLUICtrlFactory::getDefaultParams()); + params.name = "btn_" + name; + params.rect = temp_rect; + params.column = new_column; + params.tool_tip = column_params.tool_tip; + params.tab_stop = false; + params.visible = mDisplayColumnHeaders; + + if(column_params.header.image.isProvided()) + { + params.image_selected = column_params.header.image; + params.image_unselected = column_params.header.image; + } + else + { + params.label = column_params.header.label; + } + + new_column->mHeader = LLUICtrlFactory::create(params); + addChild(new_column->mHeader); + + sendChildToFront(mScrollbar); + } + } + + dirtyColumns(); +} + +// static +void LLScrollListCtrl::onClickColumn(void *userdata) +{ + LLScrollListColumn *info = (LLScrollListColumn*)userdata; + if (!info) return; + + LLScrollListCtrl *parent = info->mParentCtrl; + if (!parent) return; + + if (!parent->mCanSort) return; + + S32 column_index = info->mIndex; + + LLScrollListColumn* column = parent->mColumnsIndexed[info->mIndex]; + bool ascending = column->mSortDirection == LLScrollListColumn::ASCENDING; + if (column->mSortingColumn != column->mName + && parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end()) + { + LLScrollListColumn* info_redir = parent->mColumns[column->mSortingColumn]; + column_index = info_redir->mIndex; + } + + // if this column is the primary sort key, reverse the direction + if (!parent->mSortColumns.empty() && parent->mSortColumns.back().first == column_index) + { + ascending = !parent->mSortColumns.back().second; + } + + parent->sortByColumnIndex(column_index, ascending); + + if (parent->mOnSortChangedCallback) + { + parent->mOnSortChangedCallback(); + } +} + +std::string LLScrollListCtrl::getSortColumnName() +{ + LLScrollListColumn* column = mSortColumns.empty() ? NULL : mColumnsIndexed[mSortColumns.back().first]; + + if (column) return column->mName; + else return ""; +} + +bool LLScrollListCtrl::hasSortOrder() const +{ + return !mSortColumns.empty(); +} + +void LLScrollListCtrl::clearSortOrder() +{ + mSortColumns.clear(); +} + +void LLScrollListCtrl::clearColumns() +{ + column_map_t::iterator itor; + for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + { + LLScrollColumnHeader *header = itor->second->mHeader; + if (header) + { + removeChild(header); + delete header; + } + } + std::for_each(mColumns.begin(), mColumns.end(), DeletePairedPointer()); + mColumns.clear(); + mSortColumns.clear(); + mTotalStaticColumnWidth = 0; + mTotalColumnPadding = 0; + + dirtyColumns(); // Clears mColumnsIndexed +} + +void LLScrollListCtrl::setColumnLabel(const std::string& column, const std::string& label) +{ + LLScrollListColumn* columnp = getColumn(column); + if (columnp) + { + columnp->mLabel = label; + if (columnp->mHeader) + { + columnp->mHeader->setLabel(label); + } + } +} + +LLScrollListColumn* LLScrollListCtrl::getColumn(S32 index) +{ + if (index < 0 || index >= (S32)mColumnsIndexed.size()) + { + return NULL; + } + return mColumnsIndexed[index]; +} + +LLScrollListColumn* LLScrollListCtrl::getColumn(const std::string& name) +{ + column_map_t::iterator column_itor = mColumns.find(name); + if (column_itor != mColumns.end()) + { + return column_itor->second; + } + return NULL; +} + +LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + LLScrollListItem::Params item_params; + LLParamSDParser parser; + parser.readSD(element, item_params); + item_params.userdata = userdata; + return addRow(item_params, pos); +} + +LLScrollListItem* LLScrollListCtrl::addRow(const LLScrollListItem::Params& item_p, EAddPosition pos) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + LLScrollListItem *new_item = new LLScrollListItem(item_p); + return addRow(new_item, item_p, pos); +} + +LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& item_p, EAddPosition pos) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + if (!item_p.validateBlock() || !new_item) return NULL; + new_item->setNumColumns(mColumns.size()); + + // Add any columns we don't already have + S32 col_index = 0; + + for(LLInitParam::ParamIterator::const_iterator itor = item_p.columns.begin(); + itor != item_p.columns.end(); + ++itor) + { + LLScrollListCell::Params cell_p = *itor; + std::string column = cell_p.column; + + // empty columns strings index by ordinal + if (column.empty()) + { + column = llformat("%d", col_index); + } + + LLScrollListColumn* columnp = getColumn(column); + + // create new column on demand + if (!columnp) + { + LLScrollListColumn::Params new_column; + new_column.name = column; + new_column.header.label = column; + + // if width supplied for column, use it, otherwise + // use adaptive width + if (cell_p.width.isProvided()) + { + new_column.width.pixel_width = cell_p.width; + } + addColumn(new_column); + columnp = mColumns[column]; + new_item->setNumColumns(mColumns.size()); + } + + S32 index = columnp->mIndex; + if (!cell_p.width.isProvided()) + { + cell_p.width = columnp->getWidth(); + } + + LLScrollListCell* cell = LLScrollListCell::create(cell_p); + + if (cell) + { + new_item->setColumn(index, cell); + if (columnp->mHeader + && cell->isText() + && !cell->getValue().asString().empty()) + { + columnp->mHeader->setHasResizableElement(true); + } + } + + col_index++; + } + + if (item_p.columns.empty()) + { + if (mColumns.empty()) + { + LLScrollListColumn::Params new_column; + new_column.name = "0"; + + addColumn(new_column); + new_item->setNumColumns(mColumns.size()); + } + + LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value)); + if (cell) + { + LLScrollListColumn* columnp = mColumns.begin()->second; + + new_item->setColumn(0, cell); + if (columnp->mHeader + && cell->isText() + && !cell->getValue().asString().empty()) + { + columnp->mHeader->setHasResizableElement(true); + } + } + } + + // add dummy cells for missing columns + for (column_map_t::iterator column_it = mColumns.begin(); column_it != mColumns.end(); ++column_it) + { + S32 column_idx = column_it->second->mIndex; + if (new_item->getColumn(column_idx) == NULL) + { + LLScrollListColumn* column_ptr = column_it->second; + LLScrollListCell::Params cell_p; + cell_p.width = column_ptr->getWidth(); + + new_item->setColumn(column_idx, new LLScrollListSpacer(cell_p)); + } + } + + addItem(new_item, pos); + return new_item; +} + +LLScrollListItem* LLScrollListCtrl::addSimpleElement(const std::string& value, EAddPosition pos, const LLSD& id) +{ + LLSD entry_id = id; + + if (id.isUndefined()) + { + entry_id = value; + } + + LLScrollListItem::Params item_params; + item_params.value(entry_id); + item_params.columns.add() + .value(value) + .font(LLFontGL::getFontEmojiSmall()); + + return addRow(item_params, pos); +} + +void LLScrollListCtrl::setValue(const LLSD& value ) +{ + LLSD::array_const_iterator itor; + for (itor = value.beginArray(); itor != value.endArray(); ++itor) + { + addElement(*itor); + } +} + +LLSD LLScrollListCtrl::getValue() const +{ + LLScrollListItem *item = getFirstSelected(); + if (!item) return LLSD(); + return item->getValue(); +} + +bool LLScrollListCtrl::operateOnSelection(EOperation op) +{ + if (op == OP_DELETE) + { + deleteSelectedItems(); + return true; + } + else if (op == OP_DESELECT) + { + deselectAllItems(); + } + return false; +} + +bool LLScrollListCtrl::operateOnAll(EOperation op) +{ + if (op == OP_DELETE) + { + clearRows(); + return true; + } + else if (op == OP_DESELECT) + { + deselectAllItems(); + } + else if (op == OP_SELECT) + { + selectAll(); + } + return false; +} +//virtual +void LLScrollListCtrl::setFocus(bool b) +{ + // for tabbing into pristine scroll lists (Finder) + if (!getFirstSelected()) + { + selectFirstItem(); + //onCommit(); // SJB: selectFirstItem() will call onCommit() if appropriate + } + LLUICtrl::setFocus(b); +} + + +// virtual +bool LLScrollListCtrl::isDirty() const +{ + bool grubby = mDirty; + if ( !mAllowMultipleSelection ) + { + grubby = (mOriginalSelection != getFirstSelectedIndex()); + } + return grubby; +} + +// Clear dirty state +void LLScrollListCtrl::resetDirty() +{ + mDirty = false; + mOriginalSelection = getFirstSelectedIndex(); +} + + +//virtual +void LLScrollListCtrl::onFocusReceived() +{ + // forget latent selection changes when getting focus + mSelectionChanged = false; + LLUICtrl::onFocusReceived(); +} + +//virtual +void LLScrollListCtrl::onFocusLost() +{ + if (hasMouseCapture()) + { + gFocusMgr.setMouseCapture(NULL); + } + + mSearchString.clear(); + + LLUICtrl::onFocusLost(); +} + +boost::signals2::connection LLScrollListCtrl::setIsFriendCallback(const is_friend_signal_t::slot_type& cb) +{ + if (!mIsFriendSignal) + { + mIsFriendSignal = new is_friend_signal_t(); + } + return mIsFriendSignal->connect(cb); +} + +bool LLScrollListCtrl::highlightMatchingItems(const std::string& filter_str) +{ + if (filter_str == "" || filter_str == " ") + { + clearHighlightedItems(); + return false; + } + + bool res = false; + + setHighlightedColor(LLUIColorTable::instance().getColor("SearchableControlHighlightColor", LLColor4::red)); + + std::string filter_str_lc(filter_str); + LLStringUtil::toLower(filter_str_lc); + + std::vector data = getAllData(); + std::vector::iterator iter = data.begin(); + while (iter != data.end()) + { + LLScrollListCell* cell = (*iter)->getColumn(0); + if (cell) + { + std::string value = cell->getValue().asString(); + LLStringUtil::toLower(value); + if (value.find(filter_str_lc) == std::string::npos) + { + (*iter)->setHighlighted(false); + } + else + { + (*iter)->setHighlighted(true); + res = true; + } + } + iter++; + } + return res; +} diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index d6ba9240b7..356d40ce3c 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -1,564 +1,564 @@ -/** - * @file llscrolllistctrl.h - * @brief A scrolling list of items. This is the one you want to use - * in UI code. LLScrollListCell, LLScrollListItem, etc. are utility - * classes. - * - * $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$ - */ - -#ifndef LL_SCROLLLISTCTRL_H -#define LL_SCROLLLISTCTRL_H - -#include -#include - -#include "lluictrl.h" -#include "llctrlselectioninterface.h" -#include "llfontgl.h" -#include "llui.h" -#include "llstring.h" // LLWString -#include "lleditmenuhandler.h" -#include "llframetimer.h" - -#include "llscrollbar.h" -#include "lldate.h" -#include "llscrolllistitem.h" -#include "llscrolllistcolumn.h" -#include "llviewborder.h" - -class LLScrollListCell; -class LLTextBox; -class LLContextMenu; - -class LLScrollListCtrl : public LLUICtrl, public LLEditMenuHandler, - public LLCtrlListInterface, public LLCtrlScrollInterface -{ -public: - typedef enum e_selection_type - { - ROW, // default - CELL, // does not support multi-selection - HEADER, // when pointing to cells in column 0 will highlight whole row, otherwise cell, no multi-select - } ESelectionType; - - struct SelectionTypeNames : public LLInitParam::TypeValuesHelper - { - static void declareValues(); - }; - - struct Contents : public LLInitParam::Block - { - Multiple columns; - Multiple rows; - - //Multiple groups; - - Contents(); - }; - - // *TODO: Add callbacks to Params - typedef boost::function callback_t; - - template struct maximum - { - typedef T result_type; - - template - T operator()(InputIterator first, InputIterator last) const - { - // If there are no slots to call, just return the - // default-constructed value - if(first == last ) return T(); - T max_value = *first++; - while (first != last) { - if (max_value < *first) - max_value = *first; - ++first; - } - - return max_value; - } - }; - - - typedef boost::signals2::signal > sort_signal_t; - typedef boost::signals2::signal is_friend_signal_t; - - struct Params : public LLInitParam::Block - { - // behavioral flags - Optional multi_select, - commit_on_keyboard_movement, - commit_on_selection_change, - mouse_wheel_opaque; - - Optional selection_type; - - // display flags - Optional has_border, - draw_heading, - draw_stripes, - background_visible, - scroll_bar_bg_visible; - - // layout - Optional column_padding, - row_padding, - page_lines, - heading_height; - - // sort and search behavior - Optional search_column, - sort_column; - Optional sort_ascending, - can_sort; // whether user is allowed to sort - - // colors - Optional fg_unselected_color, - fg_selected_color, - bg_selected_color, - fg_disable_color, - bg_writeable_color, - bg_readonly_color, - bg_stripe_color, - hovered_color, - highlighted_color, - scroll_bar_bg_color; - - Optional contents; - - Optional border; - - Params(); - }; - -protected: - friend class LLUICtrlFactory; - - LLScrollListCtrl(const Params&); - -public: - virtual ~LLScrollListCtrl(); - - S32 isEmpty() const; - - void deleteAllItems() { clearRows(); } - - // Sets an array of column descriptors - void setColumnHeadings(const LLSD& headings); - void sortByColumnIndex(U32 column, bool ascending); - - // LLCtrlListInterface functions - virtual S32 getItemCount() const; - // Adds a single column descriptor: ["name" : string, "label" : string, "width" : integer, "relwidth" : integer ] - virtual void addColumn(const LLScrollListColumn::Params& column, EAddPosition pos = ADD_BOTTOM); - virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM); - virtual void clearColumns(); - virtual void setColumnLabel(const std::string& column, const std::string& label); - virtual bool preProcessChildNode(LLXMLNodePtr child); - virtual LLScrollListColumn* getColumn(S32 index); - virtual LLScrollListColumn* getColumn(const std::string& name); - virtual S32 getNumColumns() const { return mColumnsIndexed.size(); } - - // Adds a single element, from an array of: - // "columns" => [ "column" => column name, "value" => value, "type" => type, "font" => font, "font-style" => style ], "id" => uuid - // Creates missing columns automatically. - virtual LLScrollListItem* addElement(const LLSD& element, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); - virtual LLScrollListItem* addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& value, EAddPosition pos = ADD_BOTTOM); - virtual LLScrollListItem* addRow(const LLScrollListItem::Params& value, EAddPosition pos = ADD_BOTTOM); - // Simple add element. Takes a single array of: - // [ "value" => value, "font" => font, "font-style" => style ] - virtual void clearRows(); // clears all elements - virtual void sortByColumn(const std::string& name, bool ascending); - - // These functions take and return an array of arrays of elements, as above - virtual void setValue(const LLSD& value ); - virtual LLSD getValue() const; - - LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; } - LLCtrlListInterface* getListInterface() { return (LLCtrlListInterface*)this; } - LLCtrlScrollInterface* getScrollInterface() { return (LLCtrlScrollInterface*)this; } - - // DEPRECATED: Use setSelectedByValue() below. - bool setCurrentByID( const LLUUID& id ) { return selectByID(id); } - virtual LLUUID getCurrentID() const { return getStringUUIDSelectedItem(); } - - bool operateOnSelection(EOperation op); - bool operateOnAll(EOperation op); - - // returns false if unable to set the max count so low - bool setMaxItemCount(S32 max_count); - - bool selectByID( const LLUUID& id ); // false if item not found - - // Match item by value.asString(), which should work for string, integer, uuid. - // Returns false if not found. - bool setSelectedByValue(const LLSD& value, bool selected); - - bool isSorted() const { return mSorted; } - - virtual bool isSelected(const LLSD& value) const; - - bool hasSelectedItem() const; - - bool handleClick(S32 x, S32 y, MASK mask); - bool selectFirstItem(); - bool selectNthItem( S32 index ); - bool selectItemRange( S32 first, S32 last ); - bool selectItemAt(S32 x, S32 y, MASK mask); - - void deleteSingleItem( S32 index ); - void deleteItems(const LLSD& sd); - void deleteSelectedItems(); - void deselectAllItems(bool no_commit_on_change = false); // by default, go ahead and commit on selection change - - void clearHighlightedItems(); - - virtual void mouseOverHighlightNthItem( S32 index ); - - S32 getHighlightedItemInx() const { return mHighlightedItem; } - - void setDoubleClickCallback( callback_t cb ) { mOnDoubleClickCallback = cb; } - void setMaximumSelectCallback( callback_t cb) { mOnMaximumSelectCallback = cb; } - void setSortChangedCallback( callback_t cb) { mOnSortChangedCallback = cb; } - // Convenience function; *TODO: replace with setter above + boost::bind() in calling code - void setDoubleClickCallback( boost::function cb, void* userdata) { mOnDoubleClickCallback = boost::bind(cb, userdata); } - - void swapWithNext(S32 index); - void swapWithPrevious(S32 index); - - void setCanSelect(bool can_select) { mCanSelect = can_select; } - virtual bool getCanSelect() const { return mCanSelect; } - - S32 getItemIndex( LLScrollListItem* item ) const; - S32 getItemIndex( const LLUUID& item_id ) const; - - void setCommentText( const std::string& comment_text); - LLScrollListItem* addSeparator(EAddPosition pos); - - // "Simple" interface: use this when you're creating a list that contains only unique strings, only - // one of which can be selected at a time. - virtual LLScrollListItem* addSimpleElement(const std::string& value, EAddPosition pos = ADD_BOTTOM, const LLSD& id = LLSD()); - - bool selectItemByLabel( const std::string& item, bool case_sensitive = true, S32 column = 0 ); // false if item not found - bool selectItemByPrefix(const std::string& target, bool case_sensitive = true, S32 column = -1); - bool selectItemByPrefix(const LLWString& target, bool case_sensitive = true, S32 column = -1); - LLScrollListItem* getItemByLabel( const std::string& item, bool case_sensitive = true, S32 column = 0 ); - const std::string getSelectedItemLabel(S32 column = 0) const; - LLSD getSelectedValue(); - - // If multi select is on, select all element that include substring, - // otherwise select first match only. - // If focus is true will scroll to selection. - // Returns number of results. - // Note: at the moment search happens in one go and is expensive - U32 searchItems(const std::string& substring, bool case_sensitive = false, bool focus = true); - U32 searchItems(const LLWString& substring, bool case_sensitive = false, bool focus = true); - - // DEPRECATED: Use LLSD versions of setCommentText() and getSelectedValue(). - // "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which - // has an associated, unique UUID, and only one of which can be selected at a time. - LLScrollListItem* addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos = ADD_BOTTOM, bool enabled = true); - LLUUID getStringUUIDSelectedItem() const; - - LLScrollListItem* getFirstSelected() const; - virtual S32 getFirstSelectedIndex() const; - std::vector getAllSelected() const; - S32 getNumSelected() const; - LLScrollListItem* getLastSelectedItem() const { return mLastSelected; } - - // iterate over all items - LLScrollListItem* getFirstData() const; - LLScrollListItem* getLastData() const; - std::vector getAllData() const; - - LLScrollListItem* getItem(const LLSD& sd) const; - - void setAllowMultipleSelection(bool mult ) { mAllowMultipleSelection = mult; } - - void setBgWriteableColor(const LLColor4 &c) { mBgWriteableColor = c; } - void setReadOnlyBgColor(const LLColor4 &c) { mBgReadOnlyColor = c; } - void setBgSelectedColor(const LLColor4 &c) { mBgSelectedColor = c; } - void setBgStripeColor(const LLColor4& c) { mBgStripeColor = c; } - void setFgSelectedColor(const LLColor4 &c) { mFgSelectedColor = c; } - void setFgUnselectedColor(const LLColor4 &c){ mFgUnselectedColor = c; } - void setHoveredColor(const LLColor4 &c) { mHoveredColor = c; } - void setHighlightedColor(const LLColor4 &c) { mHighlightedColor = c; } - void setFgDisableColor(const LLColor4 &c) { mFgDisabledColor = c; } - - void setBackgroundVisible(bool b) { mBackgroundVisible = b; } - void setDrawStripes(bool b) { mDrawStripes = b; } - void setColumnPadding(const S32 c) { mColumnPadding = c; } - S32 getColumnPadding() const { return mColumnPadding; } - void setRowPadding(const S32 c) { mColumnPadding = c; } - S32 getRowPadding() const { return mColumnPadding; } - void setCommitOnKeyboardMovement(bool b) { mCommitOnKeyboardMovement = b; } - void setCommitOnSelectionChange(bool b) { mCommitOnSelectionChange = b; } - void setAllowKeyboardMovement(bool b) { mAllowKeyboardMovement = b; } - - void setMaxSelectable(U32 max_selected) { mMaxSelectable = max_selected; } - S32 getMaxSelectable() { return mMaxSelectable; } - - - virtual S32 getScrollPos() const; - virtual void setScrollPos( S32 pos ); - S32 getSearchColumn(); - void setSearchColumn(S32 column) { mSearchColumn = column; } - S32 getColumnIndexFromOffset(S32 x); - S32 getColumnOffsetFromIndex(S32 index); - S32 getRowOffsetFromIndex(S32 index); - - void clearSearchString() { mSearchString.clear(); } - - // support right-click context menus for avatar/group lists - enum ContextMenuType { MENU_NONE, MENU_AVATAR, MENU_GROUP }; - void setContextMenu(const ContextMenuType &menu) { mContextMenuType = menu; } - ContextMenuType getContextMenuType() { return mContextMenuType; } - - // Overridden from LLView - /*virtual*/ void draw(); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - /*virtual*/ bool handleUnicodeCharHere(llwchar uni_char); - /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ bool handleScrollHWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - /*virtual*/ void setEnabled(bool enabled); - /*virtual*/ void setFocus( bool b ); - /*virtual*/ void onFocusReceived(); - /*virtual*/ void onFocusLost(); - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - - virtual bool isDirty() const; - virtual void resetDirty(); // Clear dirty state - - virtual void updateLayout(); - virtual void fitContents(S32 max_width, S32 max_height); - - virtual LLRect getRequiredRect(); - static bool rowPreceeds(LLScrollListItem *new_row, LLScrollListItem *test_row); - - LLRect getItemListRect() { return mItemListRect; } - - /// Returns rect, in local coords, of a given row/column - LLRect getCellRect(S32 row_index, S32 column_index); - - // Used "internally" by the scroll bar. - void onScrollChange( S32 new_pos, LLScrollbar* src ); - - static void onClickColumn(void *userdata); - - virtual void updateColumns(bool force_update = false); - S32 calcMaxContentWidth(); - bool updateColumnWidths(); - - void setHeadingHeight(S32 heading_height); - /** - * Sets max visible lines without scroolbar, if this value equals to 0, - * then display all items. - */ - void setPageLines(S32 page_lines ); - void setCollapseEmptyColumns(bool collapse); - - LLScrollListItem* hitItem(S32 x,S32 y); - virtual void scrollToShowSelected(); - - // LLEditMenuHandler functions - virtual void copy(); - virtual bool canCopy() const; - virtual void cut(); - virtual bool canCut() const; - virtual void selectAll(); - virtual bool canSelectAll() const; - virtual void deselect(); - virtual bool canDeselect() const; - - void setNumDynamicColumns(S32 num) { mNumDynamicWidthColumns = num; } - void updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width); - S32 getTotalStaticColumnWidth() { return mTotalStaticColumnWidth; } - - std::string getSortColumnName(); - bool getSortAscending() { return mSortColumns.empty() ? true : mSortColumns.back().second; } - bool hasSortOrder() const; - void clearSortOrder(); - - void setAlternateSort() { mAlternateSort = true; } - - void selectPrevItem(bool extend_selection = false); - void selectNextItem(bool extend_selection = false); - S32 selectMultiple(uuid_vec_t ids); - // conceptually const, but mutates mItemList - void updateSort() const; - // sorts a list without affecting the permanent sort order (so further list insertions can be unsorted, for example) - void sortOnce(S32 column, bool ascending); - - // manually call this whenever editing list items in place to flag need for resorting - void setNeedsSort(bool val = true) { mSorted = !val; } - void dirtyColumns(); // some operation has potentially affected column layout or ordering - - bool highlightMatchingItems(const std::string& filter_str); - - boost::signals2::connection setSortCallback(sort_signal_t::slot_type cb ) - { - if (!mSortCallback) mSortCallback = new sort_signal_t(); - return mSortCallback->connect(cb); - } - - boost::signals2::connection setIsFriendCallback(const is_friend_signal_t::slot_type& cb); - - -protected: - // "Full" interface: use this when you're creating a list that has one or more of the following: - // * contains icons - // * contains multiple columns - // * allows multiple selection - // * has items that are not guarenteed to have unique names - // * has additional per-item data (e.g. a UUID or void* userdata) - // - // To add items using this approach, create new LLScrollListItems and LLScrollListCells. Add the - // cells (column entries) to each item, and add the item to the LLScrollListCtrl. - // - // The LLScrollListCtrl owns its items and is responsible for deleting them - // (except in the case that the addItem() call fails, in which case it is up - // to the caller to delete the item) - // - // returns false if item faile to be added to list, does NOT delete 'item' - bool addItem( LLScrollListItem* item, EAddPosition pos = ADD_BOTTOM, bool requires_column = true ); - - typedef std::deque item_list; - item_list& getItemList() { return mItemList; } - - void updateLineHeight(); - -private: - void drawItems(); - - void updateLineHeightInsert(LLScrollListItem* item); - void reportInvalidInput(); - bool isRepeatedChars(const LLWString& string) const; - void selectItem(LLScrollListItem* itemp, S32 cell, bool single_select = true); - void deselectItem(LLScrollListItem* itemp); - void commitIfChanged(); - bool setSort(S32 column, bool ascending); - S32 getLinesPerPage(); - - static void showProfile(std::string id, bool is_group); - static void sendIM(std::string id); - static void addFriend(std::string id); - static void removeFriend(std::string id); - static void reportAbuse(std::string id, bool is_group); - static void showNameDetails(std::string id, bool is_group); - static void copyNameToClipboard(std::string id, bool is_group); - static void copySLURLToClipboard(std::string id, bool is_group); - - S32 mLineHeight; // the max height of a single line - S32 mScrollLines; // how many lines we've scrolled down - S32 mPageLines; // max number of lines is it possible to see on the screen given mRect and mLineHeight - S32 mHeadingHeight; // the height of the column header buttons, if visible - U32 mMaxSelectable; - LLScrollbar* mScrollbar; - bool mAllowMultipleSelection; - bool mAllowKeyboardMovement; - bool mCommitOnKeyboardMovement; - bool mCommitOnSelectionChange; - bool mSelectionChanged; - ESelectionType mSelectionType; - bool mNeedsScroll; - bool mMouseWheelOpaque; - bool mCanSelect; - bool mCanSort; // Whether user is allowed to sort - bool mDisplayColumnHeaders; - bool mColumnsDirty; - bool mColumnWidthsDirty; - - bool mAlternateSort; - - mutable item_list mItemList; - - LLScrollListItem *mLastSelected; - - S32 mMaxItemCount; - - LLRect mItemListRect; - S32 mColumnPadding; - S32 mRowPadding; - - bool mBackgroundVisible; - bool mDrawStripes; - - LLUIColor mBgWriteableColor; - LLUIColor mBgReadOnlyColor; - LLUIColor mBgSelectedColor; - LLUIColor mBgStripeColor; - LLUIColor mFgSelectedColor; - LLUIColor mFgUnselectedColor; - LLUIColor mFgDisabledColor; - LLUIColor mHoveredColor; - LLUIColor mHighlightedColor; - - S32 mBorderThickness; - callback_t mOnDoubleClickCallback; - callback_t mOnMaximumSelectCallback; - callback_t mOnSortChangedCallback; - - S32 mHighlightedItem; - class LLViewBorder* mBorder; - LLHandle mPopupMenuHandle; - - LLView *mCommentTextView; - - LLWString mSearchString; - LLFrameTimer mSearchTimer; - - S32 mSearchColumn; - S32 mNumDynamicWidthColumns; - S32 mTotalStaticColumnWidth; - S32 mTotalColumnPadding; - - mutable bool mSorted; - - typedef std::map column_map_t; - column_map_t mColumns; - - bool mDirty; - S32 mOriginalSelection; - - ContextMenuType mContextMenuType; - - typedef std::vector ordered_columns_t; - ordered_columns_t mColumnsIndexed; - - typedef std::pair sort_column_t; - std::vector mSortColumns; - - sort_signal_t* mSortCallback; - - is_friend_signal_t* mIsFriendSignal; -}; // end class LLScrollListCtrl - -#endif // LL_SCROLLLISTCTRL_H +/** + * @file llscrolllistctrl.h + * @brief A scrolling list of items. This is the one you want to use + * in UI code. LLScrollListCell, LLScrollListItem, etc. are utility + * classes. + * + * $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$ + */ + +#ifndef LL_SCROLLLISTCTRL_H +#define LL_SCROLLLISTCTRL_H + +#include +#include + +#include "lluictrl.h" +#include "llctrlselectioninterface.h" +#include "llfontgl.h" +#include "llui.h" +#include "llstring.h" // LLWString +#include "lleditmenuhandler.h" +#include "llframetimer.h" + +#include "llscrollbar.h" +#include "lldate.h" +#include "llscrolllistitem.h" +#include "llscrolllistcolumn.h" +#include "llviewborder.h" + +class LLScrollListCell; +class LLTextBox; +class LLContextMenu; + +class LLScrollListCtrl : public LLUICtrl, public LLEditMenuHandler, + public LLCtrlListInterface, public LLCtrlScrollInterface +{ +public: + typedef enum e_selection_type + { + ROW, // default + CELL, // does not support multi-selection + HEADER, // when pointing to cells in column 0 will highlight whole row, otherwise cell, no multi-select + } ESelectionType; + + struct SelectionTypeNames : public LLInitParam::TypeValuesHelper + { + static void declareValues(); + }; + + struct Contents : public LLInitParam::Block + { + Multiple columns; + Multiple rows; + + //Multiple groups; + + Contents(); + }; + + // *TODO: Add callbacks to Params + typedef boost::function callback_t; + + template struct maximum + { + typedef T result_type; + + template + T operator()(InputIterator first, InputIterator last) const + { + // If there are no slots to call, just return the + // default-constructed value + if(first == last ) return T(); + T max_value = *first++; + while (first != last) { + if (max_value < *first) + max_value = *first; + ++first; + } + + return max_value; + } + }; + + + typedef boost::signals2::signal > sort_signal_t; + typedef boost::signals2::signal is_friend_signal_t; + + struct Params : public LLInitParam::Block + { + // behavioral flags + Optional multi_select, + commit_on_keyboard_movement, + commit_on_selection_change, + mouse_wheel_opaque; + + Optional selection_type; + + // display flags + Optional has_border, + draw_heading, + draw_stripes, + background_visible, + scroll_bar_bg_visible; + + // layout + Optional column_padding, + row_padding, + page_lines, + heading_height; + + // sort and search behavior + Optional search_column, + sort_column; + Optional sort_ascending, + can_sort; // whether user is allowed to sort + + // colors + Optional fg_unselected_color, + fg_selected_color, + bg_selected_color, + fg_disable_color, + bg_writeable_color, + bg_readonly_color, + bg_stripe_color, + hovered_color, + highlighted_color, + scroll_bar_bg_color; + + Optional contents; + + Optional border; + + Params(); + }; + +protected: + friend class LLUICtrlFactory; + + LLScrollListCtrl(const Params&); + +public: + virtual ~LLScrollListCtrl(); + + S32 isEmpty() const; + + void deleteAllItems() { clearRows(); } + + // Sets an array of column descriptors + void setColumnHeadings(const LLSD& headings); + void sortByColumnIndex(U32 column, bool ascending); + + // LLCtrlListInterface functions + virtual S32 getItemCount() const; + // Adds a single column descriptor: ["name" : string, "label" : string, "width" : integer, "relwidth" : integer ] + virtual void addColumn(const LLScrollListColumn::Params& column, EAddPosition pos = ADD_BOTTOM); + virtual void addColumn(const LLSD& column, EAddPosition pos = ADD_BOTTOM); + virtual void clearColumns(); + virtual void setColumnLabel(const std::string& column, const std::string& label); + virtual bool preProcessChildNode(LLXMLNodePtr child); + virtual LLScrollListColumn* getColumn(S32 index); + virtual LLScrollListColumn* getColumn(const std::string& name); + virtual S32 getNumColumns() const { return mColumnsIndexed.size(); } + + // Adds a single element, from an array of: + // "columns" => [ "column" => column name, "value" => value, "type" => type, "font" => font, "font-style" => style ], "id" => uuid + // Creates missing columns automatically. + virtual LLScrollListItem* addElement(const LLSD& element, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); + virtual LLScrollListItem* addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& value, EAddPosition pos = ADD_BOTTOM); + virtual LLScrollListItem* addRow(const LLScrollListItem::Params& value, EAddPosition pos = ADD_BOTTOM); + // Simple add element. Takes a single array of: + // [ "value" => value, "font" => font, "font-style" => style ] + virtual void clearRows(); // clears all elements + virtual void sortByColumn(const std::string& name, bool ascending); + + // These functions take and return an array of arrays of elements, as above + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + + LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; } + LLCtrlListInterface* getListInterface() { return (LLCtrlListInterface*)this; } + LLCtrlScrollInterface* getScrollInterface() { return (LLCtrlScrollInterface*)this; } + + // DEPRECATED: Use setSelectedByValue() below. + bool setCurrentByID( const LLUUID& id ) { return selectByID(id); } + virtual LLUUID getCurrentID() const { return getStringUUIDSelectedItem(); } + + bool operateOnSelection(EOperation op); + bool operateOnAll(EOperation op); + + // returns false if unable to set the max count so low + bool setMaxItemCount(S32 max_count); + + bool selectByID( const LLUUID& id ); // false if item not found + + // Match item by value.asString(), which should work for string, integer, uuid. + // Returns false if not found. + bool setSelectedByValue(const LLSD& value, bool selected); + + bool isSorted() const { return mSorted; } + + virtual bool isSelected(const LLSD& value) const; + + bool hasSelectedItem() const; + + bool handleClick(S32 x, S32 y, MASK mask); + bool selectFirstItem(); + bool selectNthItem( S32 index ); + bool selectItemRange( S32 first, S32 last ); + bool selectItemAt(S32 x, S32 y, MASK mask); + + void deleteSingleItem( S32 index ); + void deleteItems(const LLSD& sd); + void deleteSelectedItems(); + void deselectAllItems(bool no_commit_on_change = false); // by default, go ahead and commit on selection change + + void clearHighlightedItems(); + + virtual void mouseOverHighlightNthItem( S32 index ); + + S32 getHighlightedItemInx() const { return mHighlightedItem; } + + void setDoubleClickCallback( callback_t cb ) { mOnDoubleClickCallback = cb; } + void setMaximumSelectCallback( callback_t cb) { mOnMaximumSelectCallback = cb; } + void setSortChangedCallback( callback_t cb) { mOnSortChangedCallback = cb; } + // Convenience function; *TODO: replace with setter above + boost::bind() in calling code + void setDoubleClickCallback( boost::function cb, void* userdata) { mOnDoubleClickCallback = boost::bind(cb, userdata); } + + void swapWithNext(S32 index); + void swapWithPrevious(S32 index); + + void setCanSelect(bool can_select) { mCanSelect = can_select; } + virtual bool getCanSelect() const { return mCanSelect; } + + S32 getItemIndex( LLScrollListItem* item ) const; + S32 getItemIndex( const LLUUID& item_id ) const; + + void setCommentText( const std::string& comment_text); + LLScrollListItem* addSeparator(EAddPosition pos); + + // "Simple" interface: use this when you're creating a list that contains only unique strings, only + // one of which can be selected at a time. + virtual LLScrollListItem* addSimpleElement(const std::string& value, EAddPosition pos = ADD_BOTTOM, const LLSD& id = LLSD()); + + bool selectItemByLabel( const std::string& item, bool case_sensitive = true, S32 column = 0 ); // false if item not found + bool selectItemByPrefix(const std::string& target, bool case_sensitive = true, S32 column = -1); + bool selectItemByPrefix(const LLWString& target, bool case_sensitive = true, S32 column = -1); + LLScrollListItem* getItemByLabel( const std::string& item, bool case_sensitive = true, S32 column = 0 ); + const std::string getSelectedItemLabel(S32 column = 0) const; + LLSD getSelectedValue(); + + // If multi select is on, select all element that include substring, + // otherwise select first match only. + // If focus is true will scroll to selection. + // Returns number of results. + // Note: at the moment search happens in one go and is expensive + U32 searchItems(const std::string& substring, bool case_sensitive = false, bool focus = true); + U32 searchItems(const LLWString& substring, bool case_sensitive = false, bool focus = true); + + // DEPRECATED: Use LLSD versions of setCommentText() and getSelectedValue(). + // "StringUUID" interface: use this when you're creating a list that contains non-unique strings each of which + // has an associated, unique UUID, and only one of which can be selected at a time. + LLScrollListItem* addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos = ADD_BOTTOM, bool enabled = true); + LLUUID getStringUUIDSelectedItem() const; + + LLScrollListItem* getFirstSelected() const; + virtual S32 getFirstSelectedIndex() const; + std::vector getAllSelected() const; + S32 getNumSelected() const; + LLScrollListItem* getLastSelectedItem() const { return mLastSelected; } + + // iterate over all items + LLScrollListItem* getFirstData() const; + LLScrollListItem* getLastData() const; + std::vector getAllData() const; + + LLScrollListItem* getItem(const LLSD& sd) const; + + void setAllowMultipleSelection(bool mult ) { mAllowMultipleSelection = mult; } + + void setBgWriteableColor(const LLColor4 &c) { mBgWriteableColor = c; } + void setReadOnlyBgColor(const LLColor4 &c) { mBgReadOnlyColor = c; } + void setBgSelectedColor(const LLColor4 &c) { mBgSelectedColor = c; } + void setBgStripeColor(const LLColor4& c) { mBgStripeColor = c; } + void setFgSelectedColor(const LLColor4 &c) { mFgSelectedColor = c; } + void setFgUnselectedColor(const LLColor4 &c){ mFgUnselectedColor = c; } + void setHoveredColor(const LLColor4 &c) { mHoveredColor = c; } + void setHighlightedColor(const LLColor4 &c) { mHighlightedColor = c; } + void setFgDisableColor(const LLColor4 &c) { mFgDisabledColor = c; } + + void setBackgroundVisible(bool b) { mBackgroundVisible = b; } + void setDrawStripes(bool b) { mDrawStripes = b; } + void setColumnPadding(const S32 c) { mColumnPadding = c; } + S32 getColumnPadding() const { return mColumnPadding; } + void setRowPadding(const S32 c) { mColumnPadding = c; } + S32 getRowPadding() const { return mColumnPadding; } + void setCommitOnKeyboardMovement(bool b) { mCommitOnKeyboardMovement = b; } + void setCommitOnSelectionChange(bool b) { mCommitOnSelectionChange = b; } + void setAllowKeyboardMovement(bool b) { mAllowKeyboardMovement = b; } + + void setMaxSelectable(U32 max_selected) { mMaxSelectable = max_selected; } + S32 getMaxSelectable() { return mMaxSelectable; } + + + virtual S32 getScrollPos() const; + virtual void setScrollPos( S32 pos ); + S32 getSearchColumn(); + void setSearchColumn(S32 column) { mSearchColumn = column; } + S32 getColumnIndexFromOffset(S32 x); + S32 getColumnOffsetFromIndex(S32 index); + S32 getRowOffsetFromIndex(S32 index); + + void clearSearchString() { mSearchString.clear(); } + + // support right-click context menus for avatar/group lists + enum ContextMenuType { MENU_NONE, MENU_AVATAR, MENU_GROUP }; + void setContextMenu(const ContextMenuType &menu) { mContextMenuType = menu; } + ContextMenuType getContextMenuType() { return mContextMenuType; } + + // Overridden from LLView + /*virtual*/ void draw(); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + /*virtual*/ bool handleUnicodeCharHere(llwchar uni_char); + /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ bool handleScrollHWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + /*virtual*/ void setEnabled(bool enabled); + /*virtual*/ void setFocus( bool b ); + /*virtual*/ void onFocusReceived(); + /*virtual*/ void onFocusLost(); + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + + virtual bool isDirty() const; + virtual void resetDirty(); // Clear dirty state + + virtual void updateLayout(); + virtual void fitContents(S32 max_width, S32 max_height); + + virtual LLRect getRequiredRect(); + static bool rowPreceeds(LLScrollListItem *new_row, LLScrollListItem *test_row); + + LLRect getItemListRect() { return mItemListRect; } + + /// Returns rect, in local coords, of a given row/column + LLRect getCellRect(S32 row_index, S32 column_index); + + // Used "internally" by the scroll bar. + void onScrollChange( S32 new_pos, LLScrollbar* src ); + + static void onClickColumn(void *userdata); + + virtual void updateColumns(bool force_update = false); + S32 calcMaxContentWidth(); + bool updateColumnWidths(); + + void setHeadingHeight(S32 heading_height); + /** + * Sets max visible lines without scroolbar, if this value equals to 0, + * then display all items. + */ + void setPageLines(S32 page_lines ); + void setCollapseEmptyColumns(bool collapse); + + LLScrollListItem* hitItem(S32 x,S32 y); + virtual void scrollToShowSelected(); + + // LLEditMenuHandler functions + virtual void copy(); + virtual bool canCopy() const; + virtual void cut(); + virtual bool canCut() const; + virtual void selectAll(); + virtual bool canSelectAll() const; + virtual void deselect(); + virtual bool canDeselect() const; + + void setNumDynamicColumns(S32 num) { mNumDynamicWidthColumns = num; } + void updateStaticColumnWidth(LLScrollListColumn* col, S32 new_width); + S32 getTotalStaticColumnWidth() { return mTotalStaticColumnWidth; } + + std::string getSortColumnName(); + bool getSortAscending() { return mSortColumns.empty() ? true : mSortColumns.back().second; } + bool hasSortOrder() const; + void clearSortOrder(); + + void setAlternateSort() { mAlternateSort = true; } + + void selectPrevItem(bool extend_selection = false); + void selectNextItem(bool extend_selection = false); + S32 selectMultiple(uuid_vec_t ids); + // conceptually const, but mutates mItemList + void updateSort() const; + // sorts a list without affecting the permanent sort order (so further list insertions can be unsorted, for example) + void sortOnce(S32 column, bool ascending); + + // manually call this whenever editing list items in place to flag need for resorting + void setNeedsSort(bool val = true) { mSorted = !val; } + void dirtyColumns(); // some operation has potentially affected column layout or ordering + + bool highlightMatchingItems(const std::string& filter_str); + + boost::signals2::connection setSortCallback(sort_signal_t::slot_type cb ) + { + if (!mSortCallback) mSortCallback = new sort_signal_t(); + return mSortCallback->connect(cb); + } + + boost::signals2::connection setIsFriendCallback(const is_friend_signal_t::slot_type& cb); + + +protected: + // "Full" interface: use this when you're creating a list that has one or more of the following: + // * contains icons + // * contains multiple columns + // * allows multiple selection + // * has items that are not guarenteed to have unique names + // * has additional per-item data (e.g. a UUID or void* userdata) + // + // To add items using this approach, create new LLScrollListItems and LLScrollListCells. Add the + // cells (column entries) to each item, and add the item to the LLScrollListCtrl. + // + // The LLScrollListCtrl owns its items and is responsible for deleting them + // (except in the case that the addItem() call fails, in which case it is up + // to the caller to delete the item) + // + // returns false if item faile to be added to list, does NOT delete 'item' + bool addItem( LLScrollListItem* item, EAddPosition pos = ADD_BOTTOM, bool requires_column = true ); + + typedef std::deque item_list; + item_list& getItemList() { return mItemList; } + + void updateLineHeight(); + +private: + void drawItems(); + + void updateLineHeightInsert(LLScrollListItem* item); + void reportInvalidInput(); + bool isRepeatedChars(const LLWString& string) const; + void selectItem(LLScrollListItem* itemp, S32 cell, bool single_select = true); + void deselectItem(LLScrollListItem* itemp); + void commitIfChanged(); + bool setSort(S32 column, bool ascending); + S32 getLinesPerPage(); + + static void showProfile(std::string id, bool is_group); + static void sendIM(std::string id); + static void addFriend(std::string id); + static void removeFriend(std::string id); + static void reportAbuse(std::string id, bool is_group); + static void showNameDetails(std::string id, bool is_group); + static void copyNameToClipboard(std::string id, bool is_group); + static void copySLURLToClipboard(std::string id, bool is_group); + + S32 mLineHeight; // the max height of a single line + S32 mScrollLines; // how many lines we've scrolled down + S32 mPageLines; // max number of lines is it possible to see on the screen given mRect and mLineHeight + S32 mHeadingHeight; // the height of the column header buttons, if visible + U32 mMaxSelectable; + LLScrollbar* mScrollbar; + bool mAllowMultipleSelection; + bool mAllowKeyboardMovement; + bool mCommitOnKeyboardMovement; + bool mCommitOnSelectionChange; + bool mSelectionChanged; + ESelectionType mSelectionType; + bool mNeedsScroll; + bool mMouseWheelOpaque; + bool mCanSelect; + bool mCanSort; // Whether user is allowed to sort + bool mDisplayColumnHeaders; + bool mColumnsDirty; + bool mColumnWidthsDirty; + + bool mAlternateSort; + + mutable item_list mItemList; + + LLScrollListItem *mLastSelected; + + S32 mMaxItemCount; + + LLRect mItemListRect; + S32 mColumnPadding; + S32 mRowPadding; + + bool mBackgroundVisible; + bool mDrawStripes; + + LLUIColor mBgWriteableColor; + LLUIColor mBgReadOnlyColor; + LLUIColor mBgSelectedColor; + LLUIColor mBgStripeColor; + LLUIColor mFgSelectedColor; + LLUIColor mFgUnselectedColor; + LLUIColor mFgDisabledColor; + LLUIColor mHoveredColor; + LLUIColor mHighlightedColor; + + S32 mBorderThickness; + callback_t mOnDoubleClickCallback; + callback_t mOnMaximumSelectCallback; + callback_t mOnSortChangedCallback; + + S32 mHighlightedItem; + class LLViewBorder* mBorder; + LLHandle mPopupMenuHandle; + + LLView *mCommentTextView; + + LLWString mSearchString; + LLFrameTimer mSearchTimer; + + S32 mSearchColumn; + S32 mNumDynamicWidthColumns; + S32 mTotalStaticColumnWidth; + S32 mTotalColumnPadding; + + mutable bool mSorted; + + typedef std::map column_map_t; + column_map_t mColumns; + + bool mDirty; + S32 mOriginalSelection; + + ContextMenuType mContextMenuType; + + typedef std::vector ordered_columns_t; + ordered_columns_t mColumnsIndexed; + + typedef std::pair sort_column_t; + std::vector mSortColumns; + + sort_signal_t* mSortCallback; + + is_friend_signal_t* mIsFriendSignal; +}; // end class LLScrollListCtrl + +#endif // LL_SCROLLLISTCTRL_H diff --git a/indra/llui/llscrolllistitem.cpp b/indra/llui/llscrolllistitem.cpp index 8cfe971fa6..85da55e3e6 100644 --- a/indra/llui/llscrolllistitem.cpp +++ b/indra/llui/llscrolllistitem.cpp @@ -1,204 +1,204 @@ -/** - * @file llscrolllistitem.cpp - * @brief Scroll lists are composed of rows (items), each of which - * contains columns (cells). - * - * $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 "llscrolllistitem.h" - -#include "llrect.h" -#include "llui.h" - - -//--------------------------------------------------------------------------- -// LLScrollListItem -//--------------------------------------------------------------------------- - -LLScrollListItem::LLScrollListItem( const Params& p ) -: mSelected(false), - mHighlighted(false), - mHoverIndex(-1), - mSelectedIndex(-1), - mEnabled(p.enabled), - mUserdata(p.userdata), - mItemValue(p.value), - mItemAltValue(p.alt_value) -{ -} - - -LLScrollListItem::~LLScrollListItem() -{ - std::for_each(mColumns.begin(), mColumns.end(), DeletePointer()); - mColumns.clear(); -} - -void LLScrollListItem::setSelected(bool b) -{ - mSelected = b; - mSelectedIndex = -1; -} - -void LLScrollListItem::setHighlighted(bool b) -{ - mHighlighted = b; - mHoverIndex = -1; -} - -void LLScrollListItem::setHoverCell(S32 cell) -{ - mHoverIndex = cell; -} - -void LLScrollListItem::setSelectedCell(S32 cell) -{ - mSelectedIndex = cell; -} - -void LLScrollListItem::addColumn(const LLScrollListCell::Params& p) -{ - mColumns.push_back(LLScrollListCell::create(p)); -} - -void LLScrollListItem::setNumColumns(S32 columns) -{ - S32 prev_columns = mColumns.size(); - if (columns < prev_columns) - { - std::for_each(mColumns.begin()+columns, mColumns.end(), DeletePointer()); - } - - mColumns.resize(columns); - - for (S32 col = prev_columns; col < columns; ++col) - { - mColumns[col] = NULL; - } -} - -void LLScrollListItem::setColumn( S32 column, LLScrollListCell *cell ) -{ - if (column < (S32)mColumns.size()) - { - delete mColumns[column]; - mColumns[column] = cell; - } - else - { - LL_ERRS() << "LLScrollListItem::setColumn: bad column: " << column << LL_ENDL; - } -} - - -S32 LLScrollListItem::getNumColumns() const -{ - return mColumns.size(); -} - -LLScrollListCell* LLScrollListItem::getColumn(const S32 i) const -{ - if (0 <= i && i < (S32)mColumns.size()) - { - return mColumns[i]; - } - return NULL; -} - -std::string LLScrollListItem::getContentsCSV() const -{ - std::string ret; - - S32 count = getNumColumns(); - for (S32 i=0; igetValue().asString(); - if (i < count-1) - { - ret += ", "; - } - } - - return ret; -} - - -void LLScrollListItem::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& hover_color, const LLColor4& select_color, const LLColor4& highlight_color, S32 column_padding) -{ - // draw background rect - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLRect bg_rect = rect; - if (mSelectedIndex < 0 && getSelected()) - { - // Whole item is highlighted/selected - gl_rect_2d(bg_rect, select_color); - } - else if (mHoverIndex < 0) - { - // Whole item is highlighted/selected - gl_rect_2d(bg_rect, hover_color); - } - - S32 cur_x = rect.mLeft; - S32 num_cols = getNumColumns(); - S32 cur_col = 0; - - for (LLScrollListCell* cell = getColumn(0); cur_col < num_cols; cell = getColumn(++cur_col)) - { - // Two ways a cell could be hidden - if (cell->getWidth() < 0 - || !cell->getVisible()) continue; - - LLUI::pushMatrix(); - { - LLUI::translate((F32) cur_x, (F32) rect.mBottom); - - if (mSelectedIndex == cur_col) - { - // select specific cell - LLRect highlight_rect(0, - cell->getHeight(), - cell->getWidth(), - 0); - gl_rect_2d(highlight_rect, select_color); - } - else if (mHoverIndex == cur_col) - { - // highlight specific cell - LLRect highlight_rect(0, - cell->getHeight(), - cell->getWidth() , - 0); - gl_rect_2d(highlight_rect, hover_color); - } - - cell->draw( fg_color, highlight_color ); - } - LLUI::popMatrix(); - - cur_x += cell->getWidth() + column_padding; - } -} - +/** + * @file llscrolllistitem.cpp + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $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 "llscrolllistitem.h" + +#include "llrect.h" +#include "llui.h" + + +//--------------------------------------------------------------------------- +// LLScrollListItem +//--------------------------------------------------------------------------- + +LLScrollListItem::LLScrollListItem( const Params& p ) +: mSelected(false), + mHighlighted(false), + mHoverIndex(-1), + mSelectedIndex(-1), + mEnabled(p.enabled), + mUserdata(p.userdata), + mItemValue(p.value), + mItemAltValue(p.alt_value) +{ +} + + +LLScrollListItem::~LLScrollListItem() +{ + std::for_each(mColumns.begin(), mColumns.end(), DeletePointer()); + mColumns.clear(); +} + +void LLScrollListItem::setSelected(bool b) +{ + mSelected = b; + mSelectedIndex = -1; +} + +void LLScrollListItem::setHighlighted(bool b) +{ + mHighlighted = b; + mHoverIndex = -1; +} + +void LLScrollListItem::setHoverCell(S32 cell) +{ + mHoverIndex = cell; +} + +void LLScrollListItem::setSelectedCell(S32 cell) +{ + mSelectedIndex = cell; +} + +void LLScrollListItem::addColumn(const LLScrollListCell::Params& p) +{ + mColumns.push_back(LLScrollListCell::create(p)); +} + +void LLScrollListItem::setNumColumns(S32 columns) +{ + S32 prev_columns = mColumns.size(); + if (columns < prev_columns) + { + std::for_each(mColumns.begin()+columns, mColumns.end(), DeletePointer()); + } + + mColumns.resize(columns); + + for (S32 col = prev_columns; col < columns; ++col) + { + mColumns[col] = NULL; + } +} + +void LLScrollListItem::setColumn( S32 column, LLScrollListCell *cell ) +{ + if (column < (S32)mColumns.size()) + { + delete mColumns[column]; + mColumns[column] = cell; + } + else + { + LL_ERRS() << "LLScrollListItem::setColumn: bad column: " << column << LL_ENDL; + } +} + + +S32 LLScrollListItem::getNumColumns() const +{ + return mColumns.size(); +} + +LLScrollListCell* LLScrollListItem::getColumn(const S32 i) const +{ + if (0 <= i && i < (S32)mColumns.size()) + { + return mColumns[i]; + } + return NULL; +} + +std::string LLScrollListItem::getContentsCSV() const +{ + std::string ret; + + S32 count = getNumColumns(); + for (S32 i=0; igetValue().asString(); + if (i < count-1) + { + ret += ", "; + } + } + + return ret; +} + + +void LLScrollListItem::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& hover_color, const LLColor4& select_color, const LLColor4& highlight_color, S32 column_padding) +{ + // draw background rect + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLRect bg_rect = rect; + if (mSelectedIndex < 0 && getSelected()) + { + // Whole item is highlighted/selected + gl_rect_2d(bg_rect, select_color); + } + else if (mHoverIndex < 0) + { + // Whole item is highlighted/selected + gl_rect_2d(bg_rect, hover_color); + } + + S32 cur_x = rect.mLeft; + S32 num_cols = getNumColumns(); + S32 cur_col = 0; + + for (LLScrollListCell* cell = getColumn(0); cur_col < num_cols; cell = getColumn(++cur_col)) + { + // Two ways a cell could be hidden + if (cell->getWidth() < 0 + || !cell->getVisible()) continue; + + LLUI::pushMatrix(); + { + LLUI::translate((F32) cur_x, (F32) rect.mBottom); + + if (mSelectedIndex == cur_col) + { + // select specific cell + LLRect highlight_rect(0, + cell->getHeight(), + cell->getWidth(), + 0); + gl_rect_2d(highlight_rect, select_color); + } + else if (mHoverIndex == cur_col) + { + // highlight specific cell + LLRect highlight_rect(0, + cell->getHeight(), + cell->getWidth() , + 0); + gl_rect_2d(highlight_rect, hover_color); + } + + cell->draw( fg_color, highlight_color ); + } + LLUI::popMatrix(); + + cur_x += cell->getWidth() + column_padding; + } +} + diff --git a/indra/llui/llscrolllistitem.h b/indra/llui/llscrolllistitem.h index 3e9e6e084c..ee8a8bb556 100644 --- a/indra/llui/llscrolllistitem.h +++ b/indra/llui/llscrolllistitem.h @@ -1,142 +1,142 @@ -/** - * @file llscrolllistitem.h - * @brief Scroll lists are composed of rows (items), each of which - * contains columns (cells). - * - * $LicenseInfo:firstyear=2007&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$ - */ - -#ifndef LLSCROLLLISTITEM_H -#define LLSCROLLLISTITEM_H - -#include "llpointer.h" // LLPointer<> -#include "llsd.h" -#include "v4color.h" -#include "llinitparam.h" -#include "llscrolllistcell.h" -#include "llcoord.h" - -#include - -class LLCheckBoxCtrl; -class LLResizeBar; -class LLScrollListCtrl; -class LLScrollColumnHeader; -class LLUIImage; - -//--------------------------------------------------------------------------- -// LLScrollListItem -//--------------------------------------------------------------------------- -class LLScrollListItem -{ - friend class LLScrollListCtrl; -public: - struct Params : public LLInitParam::Block - { - Optional enabled; - Optional userdata; - Optional value; - Optional alt_value; - - Ignored name; // use for localization tools - Ignored type; - Ignored length; - - Multiple columns; - - Params() - : enabled("enabled", true), - value("value"), - alt_value("alt_value"), - name("name"), - type("type"), - length("length"), - columns("columns") - { - addSynonym(columns, "column"); - addSynonym(value, "id"); - } - }; - - virtual ~LLScrollListItem(); - - void setSelected( bool b ); - bool getSelected() const { return mSelected; } - - void setEnabled( bool b ) { mEnabled = b; } - bool getEnabled() const { return mEnabled; } - - void setHighlighted( bool b ); - bool getHighlighted() const { return mHighlighted; } - - void setSelectedCell( S32 cell ); - S32 getSelectedCell() const { return mSelectedIndex; } - - void setHoverCell( S32 cell ); - S32 getHoverCell() const { return mHoverIndex; } - - void setUserdata( void* userdata ) { mUserdata = userdata; } - void* getUserdata() const { return mUserdata; } - - virtual LLUUID getUUID() const { return mItemValue.asUUID(); } - LLSD getValue() const { return mItemValue; } - LLSD getAltValue() const { return mItemAltValue; } - - void setRect(LLRect rect) { mRectangle = rect; } - LLRect getRect() const { return mRectangle; } - - void addColumn( const LLScrollListCell::Params& p ); - - void setNumColumns(S32 columns); - - void setColumn( S32 column, LLScrollListCell *cell ); - - S32 getNumColumns() const; - - LLScrollListCell *getColumn(const S32 i) const; - - std::string getContentsCSV() const; - - virtual void draw(const LLRect& rect, - const LLColor4& fg_color, - const LLColor4& hover_color, // highlight/hover selection of whole item or cell - const LLColor4& select_color, // highlight/hover selection of whole item or cell - const LLColor4& highlight_color, // highlights contents of cells (ex: text) - S32 column_padding); - -protected: - LLScrollListItem( const Params& ); - -private: - bool mSelected; - bool mHighlighted; - S32 mHoverIndex; - S32 mSelectedIndex; - bool mEnabled; - void* mUserdata; - LLSD mItemValue; - LLSD mItemAltValue; - std::vector mColumns; - LLRect mRectangle; -}; - -#endif +/** + * @file llscrolllistitem.h + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2007&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$ + */ + +#ifndef LLSCROLLLISTITEM_H +#define LLSCROLLLISTITEM_H + +#include "llpointer.h" // LLPointer<> +#include "llsd.h" +#include "v4color.h" +#include "llinitparam.h" +#include "llscrolllistcell.h" +#include "llcoord.h" + +#include + +class LLCheckBoxCtrl; +class LLResizeBar; +class LLScrollListCtrl; +class LLScrollColumnHeader; +class LLUIImage; + +//--------------------------------------------------------------------------- +// LLScrollListItem +//--------------------------------------------------------------------------- +class LLScrollListItem +{ + friend class LLScrollListCtrl; +public: + struct Params : public LLInitParam::Block + { + Optional enabled; + Optional userdata; + Optional value; + Optional alt_value; + + Ignored name; // use for localization tools + Ignored type; + Ignored length; + + Multiple columns; + + Params() + : enabled("enabled", true), + value("value"), + alt_value("alt_value"), + name("name"), + type("type"), + length("length"), + columns("columns") + { + addSynonym(columns, "column"); + addSynonym(value, "id"); + } + }; + + virtual ~LLScrollListItem(); + + void setSelected( bool b ); + bool getSelected() const { return mSelected; } + + void setEnabled( bool b ) { mEnabled = b; } + bool getEnabled() const { return mEnabled; } + + void setHighlighted( bool b ); + bool getHighlighted() const { return mHighlighted; } + + void setSelectedCell( S32 cell ); + S32 getSelectedCell() const { return mSelectedIndex; } + + void setHoverCell( S32 cell ); + S32 getHoverCell() const { return mHoverIndex; } + + void setUserdata( void* userdata ) { mUserdata = userdata; } + void* getUserdata() const { return mUserdata; } + + virtual LLUUID getUUID() const { return mItemValue.asUUID(); } + LLSD getValue() const { return mItemValue; } + LLSD getAltValue() const { return mItemAltValue; } + + void setRect(LLRect rect) { mRectangle = rect; } + LLRect getRect() const { return mRectangle; } + + void addColumn( const LLScrollListCell::Params& p ); + + void setNumColumns(S32 columns); + + void setColumn( S32 column, LLScrollListCell *cell ); + + S32 getNumColumns() const; + + LLScrollListCell *getColumn(const S32 i) const; + + std::string getContentsCSV() const; + + virtual void draw(const LLRect& rect, + const LLColor4& fg_color, + const LLColor4& hover_color, // highlight/hover selection of whole item or cell + const LLColor4& select_color, // highlight/hover selection of whole item or cell + const LLColor4& highlight_color, // highlights contents of cells (ex: text) + S32 column_padding); + +protected: + LLScrollListItem( const Params& ); + +private: + bool mSelected; + bool mHighlighted; + S32 mHoverIndex; + S32 mSelectedIndex; + bool mEnabled; + void* mUserdata; + LLSD mItemValue; + LLSD mItemAltValue; + std::vector mColumns; + LLRect mRectangle; +}; + +#endif diff --git a/indra/llui/llsearcheditor.cpp b/indra/llui/llsearcheditor.cpp index 6229bf031a..a0c1e9d0c0 100644 --- a/indra/llui/llsearcheditor.cpp +++ b/indra/llui/llsearcheditor.cpp @@ -1,218 +1,218 @@ -/** - * @file llsearcheditor.cpp - * @brief LLSearchEditor implementation - * - * $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$ - */ - -// Text editor widget to let users enter a single line. - -#include "linden_common.h" - -#include "llsearcheditor.h" -#include "llkeyboard.h" - -LLSearchEditor::LLSearchEditor(const LLSearchEditor::Params& p) -: LLUICtrl(p), - mSearchButton(NULL), - mClearButton(NULL), - mEditorImage(p.background_image), - mEditorImageFocused(p.background_image_focused), - mEditorSearchImage(p.background_image_highlight), - mHighlightTextField(p.highlight_text_field) -{ - S32 srch_btn_top = p.search_button.top_pad + p.search_button.rect.height; - S32 srch_btn_right = p.search_button.rect.width + p.search_button.left_pad; - LLRect srch_btn_rect(p.search_button.left_pad, srch_btn_top, srch_btn_right, p.search_button.top_pad); - - S32 clr_btn_top = p.clear_button.rect.bottom + p.clear_button.rect.height; - S32 clr_btn_right = getRect().getWidth() - p.clear_button.pad_right; - S32 clr_btn_left = clr_btn_right - p.clear_button.rect.width; - LLRect clear_btn_rect(clr_btn_left, clr_btn_top, clr_btn_right, p.clear_button.rect.bottom); - - S32 text_pad_left = p.text_pad_left; - S32 text_pad_right = p.text_pad_right; - - if (p.search_button_visible) - text_pad_left += srch_btn_rect.getWidth(); - - if (p.clear_button_visible) - text_pad_right = getRect().getWidth() - clr_btn_left + p.clear_button.pad_left; - - // Set up line editor. - LLLineEditor::Params line_editor_params(p); - line_editor_params.name("filter edit box"); - line_editor_params.background_image(p.background_image); - line_editor_params.background_image_focused(p.background_image_focused); - line_editor_params.rect(getLocalRect()); - line_editor_params.follows.flags(FOLLOWS_ALL); - line_editor_params.text_pad_left(text_pad_left); - line_editor_params.text_pad_right(text_pad_right); - line_editor_params.revert_on_esc(false); - line_editor_params.commit_callback.function(boost::bind(&LLUICtrl::onCommit, this)); - line_editor_params.keystroke_callback(boost::bind(&LLSearchEditor::handleKeystroke, this)); - - mSearchEditor = LLUICtrlFactory::create(line_editor_params); - mSearchEditor->setPassDelete(true); - addChild(mSearchEditor); - - if (p.search_button_visible) - { - // Set up search button. - LLButton::Params srch_btn_params(p.search_button); - srch_btn_params.name(std::string("search button")); - srch_btn_params.rect(srch_btn_rect) ; - srch_btn_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_TOP); - srch_btn_params.tab_stop(false); - srch_btn_params.click_callback.function(boost::bind(&LLUICtrl::onCommit, this)); - - mSearchButton = LLUICtrlFactory::create(srch_btn_params); - mSearchEditor->addChild(mSearchButton); - } - - if (p.clear_button_visible) - { - // Set up clear button. - LLButton::Params clr_btn_params(p.clear_button); - clr_btn_params.name(std::string("clear button")); - clr_btn_params.rect(clear_btn_rect) ; - clr_btn_params.follows.flags(FOLLOWS_RIGHT|FOLLOWS_TOP); - clr_btn_params.tab_stop(false); - clr_btn_params.click_callback.function(boost::bind(&LLSearchEditor::onClearButtonClick, this, _2)); - - mClearButton = LLUICtrlFactory::create(clr_btn_params); - mSearchEditor->addChild(mClearButton); - } -} - -LLSearchEditor::~LLSearchEditor() -{ - mKeystrokeCallback = NULL; - mTextChangedCallback = NULL; - setCommitOnFocusLost(false); - - mSearchButton = NULL; - mClearButton = NULL; - mSearchEditor->deleteAllChildren(); - deleteAllChildren(); -} - -//virtual -void LLSearchEditor::draw() -{ - if (mClearButton) - mClearButton->setVisible(!mSearchEditor->getWText().empty()); - - if (mHighlightTextField) - { - if (!mSearchEditor->getWText().empty()) - { - mSearchEditor->setBgImage(mEditorSearchImage); - mSearchEditor->setBgImageFocused(mEditorSearchImage); - } - else - { - mSearchEditor->setBgImage(mEditorImage); - mSearchEditor->setBgImageFocused(mEditorImageFocused); - } - } - - LLUICtrl::draw(); -} - -//virtual -void LLSearchEditor::setValue(const LLSD& value ) -{ - mSearchEditor->setValue(value); -} - -//virtual -LLSD LLSearchEditor::getValue() const -{ - return mSearchEditor->getValue(); -} - -//virtual -bool LLSearchEditor::setTextArg( const std::string& key, const LLStringExplicit& text ) -{ - return mSearchEditor->setTextArg(key, text); -} - -//virtual -bool LLSearchEditor::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - return mSearchEditor->setLabelArg(key, text); -} - -//virtual -void LLSearchEditor::setLabel( const LLStringExplicit &new_label ) -{ - mSearchEditor->setLabel(new_label); -} - -//virtual -void LLSearchEditor::clear() -{ - if (mSearchEditor) - { - mSearchEditor->clear(); - } -} - -//virtual -void LLSearchEditor::setFocus( bool b ) -{ - if (mSearchEditor) - { - mSearchEditor->setFocus(b); - } -} - -void LLSearchEditor::onClearButtonClick(const LLSD& data) -{ - setText(LLStringUtil::null); - if (mTextChangedCallback) - { - mTextChangedCallback(this, getValue()); - } - mSearchEditor->onCommit(); // force keystroke callback -} - -void LLSearchEditor::handleKeystroke() -{ - if (mKeystrokeCallback) - { - mKeystrokeCallback(this, getValue()); - } - - KEY key = gKeyboard->currentKey(); - if (key == KEY_LEFT || - key == KEY_RIGHT) - { - return; - } - - if (mTextChangedCallback) - { - mTextChangedCallback(this, getValue()); - } -} +/** + * @file llsearcheditor.cpp + * @brief LLSearchEditor implementation + * + * $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$ + */ + +// Text editor widget to let users enter a single line. + +#include "linden_common.h" + +#include "llsearcheditor.h" +#include "llkeyboard.h" + +LLSearchEditor::LLSearchEditor(const LLSearchEditor::Params& p) +: LLUICtrl(p), + mSearchButton(NULL), + mClearButton(NULL), + mEditorImage(p.background_image), + mEditorImageFocused(p.background_image_focused), + mEditorSearchImage(p.background_image_highlight), + mHighlightTextField(p.highlight_text_field) +{ + S32 srch_btn_top = p.search_button.top_pad + p.search_button.rect.height; + S32 srch_btn_right = p.search_button.rect.width + p.search_button.left_pad; + LLRect srch_btn_rect(p.search_button.left_pad, srch_btn_top, srch_btn_right, p.search_button.top_pad); + + S32 clr_btn_top = p.clear_button.rect.bottom + p.clear_button.rect.height; + S32 clr_btn_right = getRect().getWidth() - p.clear_button.pad_right; + S32 clr_btn_left = clr_btn_right - p.clear_button.rect.width; + LLRect clear_btn_rect(clr_btn_left, clr_btn_top, clr_btn_right, p.clear_button.rect.bottom); + + S32 text_pad_left = p.text_pad_left; + S32 text_pad_right = p.text_pad_right; + + if (p.search_button_visible) + text_pad_left += srch_btn_rect.getWidth(); + + if (p.clear_button_visible) + text_pad_right = getRect().getWidth() - clr_btn_left + p.clear_button.pad_left; + + // Set up line editor. + LLLineEditor::Params line_editor_params(p); + line_editor_params.name("filter edit box"); + line_editor_params.background_image(p.background_image); + line_editor_params.background_image_focused(p.background_image_focused); + line_editor_params.rect(getLocalRect()); + line_editor_params.follows.flags(FOLLOWS_ALL); + line_editor_params.text_pad_left(text_pad_left); + line_editor_params.text_pad_right(text_pad_right); + line_editor_params.revert_on_esc(false); + line_editor_params.commit_callback.function(boost::bind(&LLUICtrl::onCommit, this)); + line_editor_params.keystroke_callback(boost::bind(&LLSearchEditor::handleKeystroke, this)); + + mSearchEditor = LLUICtrlFactory::create(line_editor_params); + mSearchEditor->setPassDelete(true); + addChild(mSearchEditor); + + if (p.search_button_visible) + { + // Set up search button. + LLButton::Params srch_btn_params(p.search_button); + srch_btn_params.name(std::string("search button")); + srch_btn_params.rect(srch_btn_rect) ; + srch_btn_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_TOP); + srch_btn_params.tab_stop(false); + srch_btn_params.click_callback.function(boost::bind(&LLUICtrl::onCommit, this)); + + mSearchButton = LLUICtrlFactory::create(srch_btn_params); + mSearchEditor->addChild(mSearchButton); + } + + if (p.clear_button_visible) + { + // Set up clear button. + LLButton::Params clr_btn_params(p.clear_button); + clr_btn_params.name(std::string("clear button")); + clr_btn_params.rect(clear_btn_rect) ; + clr_btn_params.follows.flags(FOLLOWS_RIGHT|FOLLOWS_TOP); + clr_btn_params.tab_stop(false); + clr_btn_params.click_callback.function(boost::bind(&LLSearchEditor::onClearButtonClick, this, _2)); + + mClearButton = LLUICtrlFactory::create(clr_btn_params); + mSearchEditor->addChild(mClearButton); + } +} + +LLSearchEditor::~LLSearchEditor() +{ + mKeystrokeCallback = NULL; + mTextChangedCallback = NULL; + setCommitOnFocusLost(false); + + mSearchButton = NULL; + mClearButton = NULL; + mSearchEditor->deleteAllChildren(); + deleteAllChildren(); +} + +//virtual +void LLSearchEditor::draw() +{ + if (mClearButton) + mClearButton->setVisible(!mSearchEditor->getWText().empty()); + + if (mHighlightTextField) + { + if (!mSearchEditor->getWText().empty()) + { + mSearchEditor->setBgImage(mEditorSearchImage); + mSearchEditor->setBgImageFocused(mEditorSearchImage); + } + else + { + mSearchEditor->setBgImage(mEditorImage); + mSearchEditor->setBgImageFocused(mEditorImageFocused); + } + } + + LLUICtrl::draw(); +} + +//virtual +void LLSearchEditor::setValue(const LLSD& value ) +{ + mSearchEditor->setValue(value); +} + +//virtual +LLSD LLSearchEditor::getValue() const +{ + return mSearchEditor->getValue(); +} + +//virtual +bool LLSearchEditor::setTextArg( const std::string& key, const LLStringExplicit& text ) +{ + return mSearchEditor->setTextArg(key, text); +} + +//virtual +bool LLSearchEditor::setLabelArg( const std::string& key, const LLStringExplicit& text ) +{ + return mSearchEditor->setLabelArg(key, text); +} + +//virtual +void LLSearchEditor::setLabel( const LLStringExplicit &new_label ) +{ + mSearchEditor->setLabel(new_label); +} + +//virtual +void LLSearchEditor::clear() +{ + if (mSearchEditor) + { + mSearchEditor->clear(); + } +} + +//virtual +void LLSearchEditor::setFocus( bool b ) +{ + if (mSearchEditor) + { + mSearchEditor->setFocus(b); + } +} + +void LLSearchEditor::onClearButtonClick(const LLSD& data) +{ + setText(LLStringUtil::null); + if (mTextChangedCallback) + { + mTextChangedCallback(this, getValue()); + } + mSearchEditor->onCommit(); // force keystroke callback +} + +void LLSearchEditor::handleKeystroke() +{ + if (mKeystrokeCallback) + { + mKeystrokeCallback(this, getValue()); + } + + KEY key = gKeyboard->currentKey(); + if (key == KEY_LEFT || + key == KEY_RIGHT) + { + return; + } + + if (mTextChangedCallback) + { + mTextChangedCallback(this, getValue()); + } +} diff --git a/indra/llui/llsearcheditor.h b/indra/llui/llsearcheditor.h index 7c6e5dc554..d99dc6f200 100644 --- a/indra/llui/llsearcheditor.h +++ b/indra/llui/llsearcheditor.h @@ -1,114 +1,114 @@ -/** - * @file llsearcheditor.h - * @brief Text editor widget that represents a search operation - * - * Features: - * Text entry of a single line (text, delete, left and right arrow, insert, return). - * Callbacks either on every keystroke or just on the return key. - * Focus (allow multiple text entry widgets) - * Clipboard (cut, copy, and paste) - * Horizontal scrolling to allow strings longer than widget size allows - * Pre-validation (limit which keys can be used) - * Optional line history so previous entries can be recalled by CTRL UP/DOWN - * - * $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$ - */ - -#ifndef LL_SEARCHEDITOR_H -#define LL_SEARCHEDITOR_H - -#include "lllineeditor.h" -#include "llbutton.h" - -class LLSearchEditor : public LLUICtrl -{ -public: - struct Params : public LLInitParam::Block - { - Optional search_button, - clear_button; - Optional search_button_visible, - clear_button_visible, - highlight_text_field; - Optional keystroke_callback; - - Optional background_image, - background_image_focused, - background_image_highlight; - - Params() - : search_button("search_button"), - search_button_visible("search_button_visible"), - clear_button("clear_button"), - clear_button_visible("clear_button_visible"), - highlight_text_field("highlight_text_field"), - background_image("background_image"), - background_image_focused("background_image_focused"), - background_image_highlight("background_image_highlight") - {} - }; - - void setCommitOnFocusLost(bool b) { if (mSearchEditor) mSearchEditor->setCommitOnFocusLost(b); } - -protected: - LLSearchEditor(const Params&); - friend class LLUICtrlFactory; - -public: - virtual ~LLSearchEditor(); - - /*virtual*/ void draw(); - - void setText(const LLStringExplicit &new_text) { mSearchEditor->setText(new_text); } - const std::string& getText() const { return mSearchEditor->getText(); } - - // LLUICtrl interface - virtual void setValue(const LLSD& value ); - virtual LLSD getValue() const; - virtual bool setTextArg( const std::string& key, const LLStringExplicit& text ); - virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); - virtual void setLabel( const LLStringExplicit &new_label ); - virtual void clear(); - virtual void setFocus( bool b ); - - void setKeystrokeCallback( commit_callback_t cb ) { mKeystrokeCallback = cb; } - void setTextChangedCallback( commit_callback_t cb ) { mTextChangedCallback = cb; } - -protected: - void onClearButtonClick(const LLSD& data); - virtual void handleKeystroke(); - - commit_callback_t mKeystrokeCallback; - commit_callback_t mTextChangedCallback; - LLLineEditor* mSearchEditor; - LLButton* mSearchButton; - LLButton* mClearButton; - - LLPointer mEditorImage; - LLPointer mEditorImageFocused; - LLPointer mEditorSearchImage; - LLPointer mEditorSearchImageFocused; - - bool mHighlightTextField; -}; - -#endif // LL_SEARCHEDITOR_H +/** + * @file llsearcheditor.h + * @brief Text editor widget that represents a search operation + * + * Features: + * Text entry of a single line (text, delete, left and right arrow, insert, return). + * Callbacks either on every keystroke or just on the return key. + * Focus (allow multiple text entry widgets) + * Clipboard (cut, copy, and paste) + * Horizontal scrolling to allow strings longer than widget size allows + * Pre-validation (limit which keys can be used) + * Optional line history so previous entries can be recalled by CTRL UP/DOWN + * + * $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$ + */ + +#ifndef LL_SEARCHEDITOR_H +#define LL_SEARCHEDITOR_H + +#include "lllineeditor.h" +#include "llbutton.h" + +class LLSearchEditor : public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block + { + Optional search_button, + clear_button; + Optional search_button_visible, + clear_button_visible, + highlight_text_field; + Optional keystroke_callback; + + Optional background_image, + background_image_focused, + background_image_highlight; + + Params() + : search_button("search_button"), + search_button_visible("search_button_visible"), + clear_button("clear_button"), + clear_button_visible("clear_button_visible"), + highlight_text_field("highlight_text_field"), + background_image("background_image"), + background_image_focused("background_image_focused"), + background_image_highlight("background_image_highlight") + {} + }; + + void setCommitOnFocusLost(bool b) { if (mSearchEditor) mSearchEditor->setCommitOnFocusLost(b); } + +protected: + LLSearchEditor(const Params&); + friend class LLUICtrlFactory; + +public: + virtual ~LLSearchEditor(); + + /*virtual*/ void draw(); + + void setText(const LLStringExplicit &new_text) { mSearchEditor->setText(new_text); } + const std::string& getText() const { return mSearchEditor->getText(); } + + // LLUICtrl interface + virtual void setValue(const LLSD& value ); + virtual LLSD getValue() const; + virtual bool setTextArg( const std::string& key, const LLStringExplicit& text ); + virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); + virtual void setLabel( const LLStringExplicit &new_label ); + virtual void clear(); + virtual void setFocus( bool b ); + + void setKeystrokeCallback( commit_callback_t cb ) { mKeystrokeCallback = cb; } + void setTextChangedCallback( commit_callback_t cb ) { mTextChangedCallback = cb; } + +protected: + void onClearButtonClick(const LLSD& data); + virtual void handleKeystroke(); + + commit_callback_t mKeystrokeCallback; + commit_callback_t mTextChangedCallback; + LLLineEditor* mSearchEditor; + LLButton* mSearchButton; + LLButton* mClearButton; + + LLPointer mEditorImage; + LLPointer mEditorImageFocused; + LLPointer mEditorSearchImage; + LLPointer mEditorSearchImageFocused; + + bool mHighlightTextField; +}; + +#endif // LL_SEARCHEDITOR_H diff --git a/indra/llui/llslider.cpp b/indra/llui/llslider.cpp index f892816116..0507733fd8 100644 --- a/indra/llui/llslider.cpp +++ b/indra/llui/llslider.cpp @@ -1,383 +1,383 @@ -/** - * @file llslider.cpp - * @brief LLSlider base class - * - * $LicenseInfo:firstyear=2002&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 "llslider.h" -#include "llui.h" - -#include "llgl.h" -#include "llwindow.h" -#include "llfocusmgr.h" -#include "llkeyboard.h" // for the MASK constants -#include "llcontrol.h" -#include "lluictrlfactory.h" - -static LLDefaultChildRegistry::Register r1("slider_bar"); -//FIXME: make this into an unregistered template so that code constructed sliders don't -// have ambigious template lookup problem - -LLSlider::Params::Params() -: orientation ("orientation", std::string ("horizontal")), - thumb_outline_color("thumb_outline_color"), - thumb_center_color("thumb_center_color"), - thumb_image("thumb_image"), - thumb_image_pressed("thumb_image_pressed"), - thumb_image_disabled("thumb_image_disabled"), - track_image_horizontal("track_image_horizontal"), - track_image_vertical("track_image_vertical"), - track_highlight_horizontal_image("track_highlight_horizontal_image"), - track_highlight_vertical_image("track_highlight_vertical_image"), - mouse_down_callback("mouse_down_callback"), - mouse_up_callback("mouse_up_callback") -{} - -LLSlider::LLSlider(const LLSlider::Params& p) -: LLF32UICtrl(p), - mMouseOffset( 0 ), - mOrientation ((p.orientation() == "horizontal") ? HORIZONTAL : VERTICAL), - mThumbOutlineColor(p.thumb_outline_color()), - mThumbCenterColor(p.thumb_center_color()), - mThumbImage(p.thumb_image), - mThumbImagePressed(p.thumb_image_pressed), - mThumbImageDisabled(p.thumb_image_disabled), - mTrackImageHorizontal(p.track_image_horizontal), - mTrackImageVertical(p.track_image_vertical), - mTrackHighlightHorizontalImage(p.track_highlight_horizontal_image), - mTrackHighlightVerticalImage(p.track_highlight_vertical_image), - mMouseDownSignal(NULL), - mMouseUpSignal(NULL) -{ - mViewModel->setValue(p.initial_value); - updateThumbRect(); - mDragStartThumbRect = mThumbRect; - setControlName(p.control_name, NULL); - setValue(getValueF32()); - - if (p.mouse_down_callback.isProvided()) - { - setMouseDownCallback(initCommitCallback(p.mouse_down_callback)); - } - if (p.mouse_up_callback.isProvided()) - { - setMouseUpCallback(initCommitCallback(p.mouse_up_callback)); - } -} - -LLSlider::~LLSlider() -{ - delete mMouseDownSignal; - delete mMouseUpSignal; -} - -void LLSlider::setValue(F32 value, bool from_event) -{ - value = llclamp( value, mMinValue, mMaxValue ); - - // Round to nearest increment (bias towards rounding down) - value -= mMinValue; - value += mIncrement/2.0001f; - value -= fmod(value, mIncrement); - value += mMinValue; - - if (!from_event && getValueF32() != value) - { - setControlValue(value); - } - - LLF32UICtrl::setValue(value); - updateThumbRect(); -} - -void LLSlider::updateThumbRect() -{ - const S32 DEFAULT_THUMB_SIZE = 16; - F32 t = (getValueF32() - mMinValue) / (mMaxValue - mMinValue); - - S32 thumb_width = mThumbImage ? mThumbImage->getWidth() : DEFAULT_THUMB_SIZE; - S32 thumb_height = mThumbImage ? mThumbImage->getHeight() : DEFAULT_THUMB_SIZE; - - if ( mOrientation == HORIZONTAL ) - { - S32 left_edge = (thumb_width / 2); - S32 right_edge = getRect().getWidth() - (thumb_width / 2); - - S32 x = left_edge + S32( t * (right_edge - left_edge) ); - mThumbRect.mLeft = x - (thumb_width / 2); - mThumbRect.mRight = mThumbRect.mLeft + thumb_width; - mThumbRect.mBottom = getLocalRect().getCenterY() - (thumb_height / 2); - mThumbRect.mTop = mThumbRect.mBottom + thumb_height; - } - else - { - S32 top_edge = (thumb_height / 2); - S32 bottom_edge = getRect().getHeight() - (thumb_height / 2); - - S32 y = top_edge + S32( t * (bottom_edge - top_edge) ); - mThumbRect.mLeft = getLocalRect().getCenterX() - (thumb_width / 2); - mThumbRect.mRight = mThumbRect.mLeft + thumb_width; - mThumbRect.mBottom = y - (thumb_height / 2); - mThumbRect.mTop = mThumbRect.mBottom + thumb_height; - } -} - - -void LLSlider::setValueAndCommit(F32 value) -{ - F32 old_value = getValueF32(); - setValue(value); - - if (getValueF32() != old_value) - { - onCommit(); - } -} - - -bool LLSlider::handleHover(S32 x, S32 y, MASK mask) -{ - if( hasMouseCapture() ) - { - if ( mOrientation == HORIZONTAL ) - { - S32 thumb_half_width = mThumbImage->getWidth()/2; - S32 left_edge = thumb_half_width; - S32 right_edge = getRect().getWidth() - (thumb_half_width); - - x += mMouseOffset; - x = llclamp( x, left_edge, right_edge ); - - F32 t = F32(x - left_edge) / (right_edge - left_edge); - setValueAndCommit(t * (mMaxValue - mMinValue) + mMinValue ); - } - else // mOrientation == VERTICAL - { - S32 thumb_half_height = mThumbImage->getHeight()/2; - S32 top_edge = thumb_half_height; - S32 bottom_edge = getRect().getHeight() - (thumb_half_height); - - y += mMouseOffset; - y = llclamp(y, top_edge, bottom_edge); - - F32 t = F32(y - top_edge) / (bottom_edge - top_edge); - setValueAndCommit(t * (mMaxValue - mMinValue) + mMinValue ); - } - getWindow()->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; - } - else - { - getWindow()->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; - } - return true; -} - -bool LLSlider::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - if( hasMouseCapture() ) - { - gFocusMgr.setMouseCapture( NULL ); - - if (mMouseUpSignal) - (*mMouseUpSignal)( this, getValueF32() ); - - handled = true; - make_ui_sound("UISndClickRelease"); - } - else - { - handled = true; - } - - return handled; -} - -bool LLSlider::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // only do sticky-focus on non-chrome widgets - if (!getIsChrome()) - { - setFocus(true); - } - if (mMouseDownSignal) - (*mMouseDownSignal)( this, getValueF32() ); - - if (MASK_CONTROL & mask) // if CTRL is modifying - { - setValueAndCommit(mInitialValue); - } - else - { - // Find the offset of the actual mouse location from the center of the thumb. - if (mThumbRect.pointInRect(x,y)) - { - mMouseOffset = (mOrientation == HORIZONTAL) - ? (mThumbRect.mLeft + mThumbImage->getWidth()/2) - x - : (mThumbRect.mBottom + mThumbImage->getHeight()/2) - y; - } - else - { - mMouseOffset = 0; - } - - // Start dragging the thumb - // No handler needed for focus lost since this class has no state that depends on it. - gFocusMgr.setMouseCapture( this ); - mDragStartThumbRect = mThumbRect; - } - make_ui_sound("UISndClick"); - - return true; -} - -bool LLSlider::handleKeyHere(KEY key, MASK mask) -{ - bool handled = false; - switch(key) - { - case KEY_DOWN: - case KEY_LEFT: - setValueAndCommit(getValueF32() - getIncrement()); - handled = true; - break; - case KEY_UP: - case KEY_RIGHT: - setValueAndCommit(getValueF32() + getIncrement()); - handled = true; - break; - default: - break; - } - return handled; -} - -bool LLSlider::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - if ( mOrientation == VERTICAL ) - { - F32 new_val = getValueF32() - clicks * getIncrement(); - setValueAndCommit(new_val); - return true; - } - return LLF32UICtrl::handleScrollWheel(x,y,clicks); -} - -void LLSlider::draw() -{ - F32 alpha = getDrawContext().mAlpha; - - // since thumb image might still be decoding, need thumb to accomodate image size - updateThumbRect(); - - // Draw background and thumb. - - // drawing solids requires texturing be disabled - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - // Track - LLPointer& trackImage = ( mOrientation == HORIZONTAL ) - ? mTrackImageHorizontal - : mTrackImageVertical; - - LLPointer& trackHighlightImage = ( mOrientation == HORIZONTAL ) - ? mTrackHighlightHorizontalImage - : mTrackHighlightVerticalImage; - - LLRect track_rect; - LLRect highlight_rect; - - if ( mOrientation == HORIZONTAL ) - { - track_rect.set(mThumbImage->getWidth() / 2, - getLocalRect().getCenterY() + (trackImage->getHeight() / 2), - getRect().getWidth() - mThumbImage->getWidth() / 2, - getLocalRect().getCenterY() - (trackImage->getHeight() / 2) ); - highlight_rect.set(track_rect.mLeft, track_rect.mTop, mThumbRect.getCenterX(), track_rect.mBottom); - } - else - { - track_rect.set(getLocalRect().getCenterX() - (trackImage->getWidth() / 2), - getRect().getHeight(), - getLocalRect().getCenterX() + (trackImage->getWidth() / 2), - 0); - highlight_rect.set(track_rect.mLeft, track_rect.mTop, track_rect.mRight, track_rect.mBottom); - } - - LLColor4 color = isInEnabledChain() ? LLColor4::white % alpha : LLColor4::white % (0.6f * alpha); - trackImage->draw(track_rect, color); - trackHighlightImage->draw(highlight_rect, color); - - // Thumb - if (hasFocus()) - { - // Draw focus highlighting. - mThumbImage->drawBorder(mThumbRect, gFocusMgr.getFocusColor() % alpha, gFocusMgr.getFocusFlashWidth()); - } - - if( hasMouseCapture() ) // currently clicking on slider - { - // Show ghost where thumb was before dragging began. - if (mThumbImage.notNull()) - { - mThumbImage->draw(mDragStartThumbRect, mThumbCenterColor.get() % (0.3f * alpha)); - } - if (mThumbImagePressed.notNull()) - { - mThumbImagePressed->draw(mThumbRect, mThumbOutlineColor % alpha); - } - } - else if (!isInEnabledChain()) - { - if (mThumbImageDisabled.notNull()) - { - mThumbImageDisabled->draw(mThumbRect, mThumbCenterColor % alpha); - } - } - else - { - if (mThumbImage.notNull()) - { - mThumbImage->draw(mThumbRect, mThumbCenterColor % alpha); - } - } - - LLUICtrl::draw(); -} - -boost::signals2::connection LLSlider::setMouseDownCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t(); - return mMouseDownSignal->connect(cb); -} - -boost::signals2::connection LLSlider::setMouseUpCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mMouseUpSignal) mMouseUpSignal = new commit_signal_t(); - return mMouseUpSignal->connect(cb); -} +/** + * @file llslider.cpp + * @brief LLSlider base class + * + * $LicenseInfo:firstyear=2002&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 "llslider.h" +#include "llui.h" + +#include "llgl.h" +#include "llwindow.h" +#include "llfocusmgr.h" +#include "llkeyboard.h" // for the MASK constants +#include "llcontrol.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register r1("slider_bar"); +//FIXME: make this into an unregistered template so that code constructed sliders don't +// have ambigious template lookup problem + +LLSlider::Params::Params() +: orientation ("orientation", std::string ("horizontal")), + thumb_outline_color("thumb_outline_color"), + thumb_center_color("thumb_center_color"), + thumb_image("thumb_image"), + thumb_image_pressed("thumb_image_pressed"), + thumb_image_disabled("thumb_image_disabled"), + track_image_horizontal("track_image_horizontal"), + track_image_vertical("track_image_vertical"), + track_highlight_horizontal_image("track_highlight_horizontal_image"), + track_highlight_vertical_image("track_highlight_vertical_image"), + mouse_down_callback("mouse_down_callback"), + mouse_up_callback("mouse_up_callback") +{} + +LLSlider::LLSlider(const LLSlider::Params& p) +: LLF32UICtrl(p), + mMouseOffset( 0 ), + mOrientation ((p.orientation() == "horizontal") ? HORIZONTAL : VERTICAL), + mThumbOutlineColor(p.thumb_outline_color()), + mThumbCenterColor(p.thumb_center_color()), + mThumbImage(p.thumb_image), + mThumbImagePressed(p.thumb_image_pressed), + mThumbImageDisabled(p.thumb_image_disabled), + mTrackImageHorizontal(p.track_image_horizontal), + mTrackImageVertical(p.track_image_vertical), + mTrackHighlightHorizontalImage(p.track_highlight_horizontal_image), + mTrackHighlightVerticalImage(p.track_highlight_vertical_image), + mMouseDownSignal(NULL), + mMouseUpSignal(NULL) +{ + mViewModel->setValue(p.initial_value); + updateThumbRect(); + mDragStartThumbRect = mThumbRect; + setControlName(p.control_name, NULL); + setValue(getValueF32()); + + if (p.mouse_down_callback.isProvided()) + { + setMouseDownCallback(initCommitCallback(p.mouse_down_callback)); + } + if (p.mouse_up_callback.isProvided()) + { + setMouseUpCallback(initCommitCallback(p.mouse_up_callback)); + } +} + +LLSlider::~LLSlider() +{ + delete mMouseDownSignal; + delete mMouseUpSignal; +} + +void LLSlider::setValue(F32 value, bool from_event) +{ + value = llclamp( value, mMinValue, mMaxValue ); + + // Round to nearest increment (bias towards rounding down) + value -= mMinValue; + value += mIncrement/2.0001f; + value -= fmod(value, mIncrement); + value += mMinValue; + + if (!from_event && getValueF32() != value) + { + setControlValue(value); + } + + LLF32UICtrl::setValue(value); + updateThumbRect(); +} + +void LLSlider::updateThumbRect() +{ + const S32 DEFAULT_THUMB_SIZE = 16; + F32 t = (getValueF32() - mMinValue) / (mMaxValue - mMinValue); + + S32 thumb_width = mThumbImage ? mThumbImage->getWidth() : DEFAULT_THUMB_SIZE; + S32 thumb_height = mThumbImage ? mThumbImage->getHeight() : DEFAULT_THUMB_SIZE; + + if ( mOrientation == HORIZONTAL ) + { + S32 left_edge = (thumb_width / 2); + S32 right_edge = getRect().getWidth() - (thumb_width / 2); + + S32 x = left_edge + S32( t * (right_edge - left_edge) ); + mThumbRect.mLeft = x - (thumb_width / 2); + mThumbRect.mRight = mThumbRect.mLeft + thumb_width; + mThumbRect.mBottom = getLocalRect().getCenterY() - (thumb_height / 2); + mThumbRect.mTop = mThumbRect.mBottom + thumb_height; + } + else + { + S32 top_edge = (thumb_height / 2); + S32 bottom_edge = getRect().getHeight() - (thumb_height / 2); + + S32 y = top_edge + S32( t * (bottom_edge - top_edge) ); + mThumbRect.mLeft = getLocalRect().getCenterX() - (thumb_width / 2); + mThumbRect.mRight = mThumbRect.mLeft + thumb_width; + mThumbRect.mBottom = y - (thumb_height / 2); + mThumbRect.mTop = mThumbRect.mBottom + thumb_height; + } +} + + +void LLSlider::setValueAndCommit(F32 value) +{ + F32 old_value = getValueF32(); + setValue(value); + + if (getValueF32() != old_value) + { + onCommit(); + } +} + + +bool LLSlider::handleHover(S32 x, S32 y, MASK mask) +{ + if( hasMouseCapture() ) + { + if ( mOrientation == HORIZONTAL ) + { + S32 thumb_half_width = mThumbImage->getWidth()/2; + S32 left_edge = thumb_half_width; + S32 right_edge = getRect().getWidth() - (thumb_half_width); + + x += mMouseOffset; + x = llclamp( x, left_edge, right_edge ); + + F32 t = F32(x - left_edge) / (right_edge - left_edge); + setValueAndCommit(t * (mMaxValue - mMinValue) + mMinValue ); + } + else // mOrientation == VERTICAL + { + S32 thumb_half_height = mThumbImage->getHeight()/2; + S32 top_edge = thumb_half_height; + S32 bottom_edge = getRect().getHeight() - (thumb_half_height); + + y += mMouseOffset; + y = llclamp(y, top_edge, bottom_edge); + + F32 t = F32(y - top_edge) / (bottom_edge - top_edge); + setValueAndCommit(t * (mMaxValue - mMinValue) + mMinValue ); + } + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; + } + else + { + getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (inactive)" << LL_ENDL; + } + return true; +} + +bool LLSlider::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + if( hasMouseCapture() ) + { + gFocusMgr.setMouseCapture( NULL ); + + if (mMouseUpSignal) + (*mMouseUpSignal)( this, getValueF32() ); + + handled = true; + make_ui_sound("UISndClickRelease"); + } + else + { + handled = true; + } + + return handled; +} + +bool LLSlider::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // only do sticky-focus on non-chrome widgets + if (!getIsChrome()) + { + setFocus(true); + } + if (mMouseDownSignal) + (*mMouseDownSignal)( this, getValueF32() ); + + if (MASK_CONTROL & mask) // if CTRL is modifying + { + setValueAndCommit(mInitialValue); + } + else + { + // Find the offset of the actual mouse location from the center of the thumb. + if (mThumbRect.pointInRect(x,y)) + { + mMouseOffset = (mOrientation == HORIZONTAL) + ? (mThumbRect.mLeft + mThumbImage->getWidth()/2) - x + : (mThumbRect.mBottom + mThumbImage->getHeight()/2) - y; + } + else + { + mMouseOffset = 0; + } + + // Start dragging the thumb + // No handler needed for focus lost since this class has no state that depends on it. + gFocusMgr.setMouseCapture( this ); + mDragStartThumbRect = mThumbRect; + } + make_ui_sound("UISndClick"); + + return true; +} + +bool LLSlider::handleKeyHere(KEY key, MASK mask) +{ + bool handled = false; + switch(key) + { + case KEY_DOWN: + case KEY_LEFT: + setValueAndCommit(getValueF32() - getIncrement()); + handled = true; + break; + case KEY_UP: + case KEY_RIGHT: + setValueAndCommit(getValueF32() + getIncrement()); + handled = true; + break; + default: + break; + } + return handled; +} + +bool LLSlider::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if ( mOrientation == VERTICAL ) + { + F32 new_val = getValueF32() - clicks * getIncrement(); + setValueAndCommit(new_val); + return true; + } + return LLF32UICtrl::handleScrollWheel(x,y,clicks); +} + +void LLSlider::draw() +{ + F32 alpha = getDrawContext().mAlpha; + + // since thumb image might still be decoding, need thumb to accomodate image size + updateThumbRect(); + + // Draw background and thumb. + + // drawing solids requires texturing be disabled + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + // Track + LLPointer& trackImage = ( mOrientation == HORIZONTAL ) + ? mTrackImageHorizontal + : mTrackImageVertical; + + LLPointer& trackHighlightImage = ( mOrientation == HORIZONTAL ) + ? mTrackHighlightHorizontalImage + : mTrackHighlightVerticalImage; + + LLRect track_rect; + LLRect highlight_rect; + + if ( mOrientation == HORIZONTAL ) + { + track_rect.set(mThumbImage->getWidth() / 2, + getLocalRect().getCenterY() + (trackImage->getHeight() / 2), + getRect().getWidth() - mThumbImage->getWidth() / 2, + getLocalRect().getCenterY() - (trackImage->getHeight() / 2) ); + highlight_rect.set(track_rect.mLeft, track_rect.mTop, mThumbRect.getCenterX(), track_rect.mBottom); + } + else + { + track_rect.set(getLocalRect().getCenterX() - (trackImage->getWidth() / 2), + getRect().getHeight(), + getLocalRect().getCenterX() + (trackImage->getWidth() / 2), + 0); + highlight_rect.set(track_rect.mLeft, track_rect.mTop, track_rect.mRight, track_rect.mBottom); + } + + LLColor4 color = isInEnabledChain() ? LLColor4::white % alpha : LLColor4::white % (0.6f * alpha); + trackImage->draw(track_rect, color); + trackHighlightImage->draw(highlight_rect, color); + + // Thumb + if (hasFocus()) + { + // Draw focus highlighting. + mThumbImage->drawBorder(mThumbRect, gFocusMgr.getFocusColor() % alpha, gFocusMgr.getFocusFlashWidth()); + } + + if( hasMouseCapture() ) // currently clicking on slider + { + // Show ghost where thumb was before dragging began. + if (mThumbImage.notNull()) + { + mThumbImage->draw(mDragStartThumbRect, mThumbCenterColor.get() % (0.3f * alpha)); + } + if (mThumbImagePressed.notNull()) + { + mThumbImagePressed->draw(mThumbRect, mThumbOutlineColor % alpha); + } + } + else if (!isInEnabledChain()) + { + if (mThumbImageDisabled.notNull()) + { + mThumbImageDisabled->draw(mThumbRect, mThumbCenterColor % alpha); + } + } + else + { + if (mThumbImage.notNull()) + { + mThumbImage->draw(mThumbRect, mThumbCenterColor % alpha); + } + } + + LLUICtrl::draw(); +} + +boost::signals2::connection LLSlider::setMouseDownCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t(); + return mMouseDownSignal->connect(cb); +} + +boost::signals2::connection LLSlider::setMouseUpCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseUpSignal) mMouseUpSignal = new commit_signal_t(); + return mMouseUpSignal->connect(cb); +} diff --git a/indra/llui/llslider.h b/indra/llui/llslider.h index 9cd1c9cee4..03fad5a05d 100644 --- a/indra/llui/llslider.h +++ b/indra/llui/llslider.h @@ -1,108 +1,108 @@ -/** - * @file llslider.h - * @brief A simple slider with no label. - * - * $LicenseInfo:firstyear=2002&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$ - */ - -#ifndef LL_LLSLIDER_H -#define LL_LLSLIDER_H - -#include "llf32uictrl.h" -#include "v4color.h" -#include "lluiimage.h" - -class LLSlider : public LLF32UICtrl -{ -public: - struct Params : public LLInitParam::Block - { - Optional orientation; - - Optional thumb_outline_color, - thumb_center_color; - - Optional thumb_image, - thumb_image_pressed, - thumb_image_disabled, - track_image_horizontal, - track_image_vertical, - track_highlight_horizontal_image, - track_highlight_vertical_image; - - Optional mouse_down_callback, - mouse_up_callback; - - - Params(); - }; -protected: - LLSlider(const Params&); - friend class LLUICtrlFactory; -public: - virtual ~LLSlider(); - void setValue( F32 value, bool from_event = false ); - // overrides for LLF32UICtrl methods - virtual void setValue(const LLSD& value ) { setValue((F32)value.asReal(), true); } - - virtual void setMinValue(const LLSD& min_value) { setMinValue((F32)min_value.asReal()); } - virtual void setMaxValue(const LLSD& max_value) { setMaxValue((F32)max_value.asReal()); } - virtual void setMinValue(F32 min_value) { LLF32UICtrl::setMinValue(min_value); updateThumbRect(); } - virtual void setMaxValue(F32 max_value) { LLF32UICtrl::setMaxValue(max_value); updateThumbRect(); } - - boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ); - boost::signals2::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ); - - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleKeyHere(KEY key, MASK mask); - virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks); - virtual void draw(); - -private: - void setValueAndCommit(F32 value); - void updateThumbRect(); - - bool mVolumeSlider; - S32 mMouseOffset; - LLRect mDragStartThumbRect; - - LLPointer mThumbImage; - LLPointer mThumbImagePressed; - LLPointer mThumbImageDisabled; - LLPointer mTrackImageHorizontal; - LLPointer mTrackImageVertical; - LLPointer mTrackHighlightHorizontalImage; - LLPointer mTrackHighlightVerticalImage; - - const EOrientation mOrientation; - - LLRect mThumbRect; - LLUIColor mThumbOutlineColor; - LLUIColor mThumbCenterColor; - - commit_signal_t* mMouseDownSignal; - commit_signal_t* mMouseUpSignal; -}; - -#endif // LL_LLSLIDER_H +/** + * @file llslider.h + * @brief A simple slider with no label. + * + * $LicenseInfo:firstyear=2002&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$ + */ + +#ifndef LL_LLSLIDER_H +#define LL_LLSLIDER_H + +#include "llf32uictrl.h" +#include "v4color.h" +#include "lluiimage.h" + +class LLSlider : public LLF32UICtrl +{ +public: + struct Params : public LLInitParam::Block + { + Optional orientation; + + Optional thumb_outline_color, + thumb_center_color; + + Optional thumb_image, + thumb_image_pressed, + thumb_image_disabled, + track_image_horizontal, + track_image_vertical, + track_highlight_horizontal_image, + track_highlight_vertical_image; + + Optional mouse_down_callback, + mouse_up_callback; + + + Params(); + }; +protected: + LLSlider(const Params&); + friend class LLUICtrlFactory; +public: + virtual ~LLSlider(); + void setValue( F32 value, bool from_event = false ); + // overrides for LLF32UICtrl methods + virtual void setValue(const LLSD& value ) { setValue((F32)value.asReal(), true); } + + virtual void setMinValue(const LLSD& min_value) { setMinValue((F32)min_value.asReal()); } + virtual void setMaxValue(const LLSD& max_value) { setMaxValue((F32)max_value.asReal()); } + virtual void setMinValue(F32 min_value) { LLF32UICtrl::setMinValue(min_value); updateThumbRect(); } + virtual void setMaxValue(F32 max_value) { LLF32UICtrl::setMaxValue(max_value); updateThumbRect(); } + + boost::signals2::connection setMouseDownCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setMouseUpCallback( const commit_signal_t::slot_type& cb ); + + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleKeyHere(KEY key, MASK mask); + virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks); + virtual void draw(); + +private: + void setValueAndCommit(F32 value); + void updateThumbRect(); + + bool mVolumeSlider; + S32 mMouseOffset; + LLRect mDragStartThumbRect; + + LLPointer mThumbImage; + LLPointer mThumbImagePressed; + LLPointer mThumbImageDisabled; + LLPointer mTrackImageHorizontal; + LLPointer mTrackImageVertical; + LLPointer mTrackHighlightHorizontalImage; + LLPointer mTrackHighlightVerticalImage; + + const EOrientation mOrientation; + + LLRect mThumbRect; + LLUIColor mThumbOutlineColor; + LLUIColor mThumbCenterColor; + + commit_signal_t* mMouseDownSignal; + commit_signal_t* mMouseUpSignal; +}; + +#endif // LL_LLSLIDER_H diff --git a/indra/llui/llsliderctrl.cpp b/indra/llui/llsliderctrl.cpp index e7e25716ad..22579205d8 100644 --- a/indra/llui/llsliderctrl.cpp +++ b/indra/llui/llsliderctrl.cpp @@ -1,489 +1,489 @@ -/** - * @file llsliderctrl.cpp - * @brief LLSliderCtrl base class - * - * $LicenseInfo:firstyear=2002&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 "llsliderctrl.h" - -#include "llmath.h" -#include "llfontgl.h" -#include "llgl.h" -#include "llkeyboard.h" -#include "lllineeditor.h" -#include "llslider.h" -#include "llstring.h" -#include "lltextbox.h" -#include "llui.h" -#include "lluiconstants.h" -#include "llcontrol.h" -#include "llfocusmgr.h" -#include "llresmgr.h" -#include "lluictrlfactory.h" - -static LLDefaultChildRegistry::Register r("slider"); - -LLSliderCtrl::LLSliderCtrl(const LLSliderCtrl::Params& p) -: LLF32UICtrl(p), - mLabelBox( NULL ), - mEditor( NULL ), - mTextBox( NULL ), - mFont(p.font), - mShowText(p.show_text), - mCanEditText(p.can_edit_text), - mPrecision(p.decimal_digits), - mTextEnabledColor(p.text_color()), - mTextDisabledColor(p.text_disabled_color()), - mLabelWidth(p.label_width), - mEditorCommitSignal(NULL) -{ - S32 top = getRect().getHeight(); - S32 bottom = 0; - S32 left = 0; - - S32 label_width = p.label_width; - S32 text_width = p.text_width; - - // Label - if( !p.label().empty() ) - { - if (!p.label_width.isProvided()) - { - label_width = p.font()->getWidth(p.label); - } - LLRect label_rect( left, top, label_width, bottom ); - LLTextBox::Params params(p.slider_label); - if (!params.rect.isProvided()) - { - params.rect = label_rect; - } - if (!params.font.isProvided()) - { - params.font = p.font; - } - params.initial_value(p.label()); - mLabelBox = LLUICtrlFactory::create (params); - addChild(mLabelBox); - mLabelFont = params.font(); - } - - if (p.show_text && !p.text_width.isProvided()) - { - // calculate the size of the text box (log max_value is number of digits - 1 so plus 1) - if ( p.max_value ) - text_width = p.font()->getWidth(std::string("0")) * ( static_cast < S32 > ( log10 ( p.max_value ) ) + p.decimal_digits + 1 ); - - if ( p.increment < 1.0f ) - text_width += p.font()->getWidth(std::string(".")); // (mostly) take account of decimal point in value - - if ( p.min_value < 0.0f || p.max_value < 0.0f ) - text_width += p.font()->getWidth(std::string("-")); // (mostly) take account of minus sign - - // padding to make things look nicer - text_width += 8; - } - - - S32 text_left = getRect().getWidth() - text_width; - static LLUICachedControl sliderctrl_spacing ("UISliderctrlSpacing", 0); - - S32 slider_right = getRect().getWidth(); - if( p.show_text ) - { - slider_right = text_left - sliderctrl_spacing; - } - - S32 slider_left = label_width ? label_width + sliderctrl_spacing : 0; - LLSlider::Params slider_p(p.slider_bar); - slider_p.name("slider_bar"); - if (!slider_p.rect.isProvided()) - { - slider_p.rect = LLRect(slider_left,top,slider_right,bottom); - } - if (!slider_p.initial_value.isProvided()) - { - slider_p.initial_value = p.initial_value().asReal(); - } - if (!slider_p.min_value.isProvided()) - { - slider_p.min_value = p.min_value; - } - if (!slider_p.max_value.isProvided()) - { - slider_p.max_value = p.max_value; - } - if (!slider_p.increment.isProvided()) - { - slider_p.increment = p.increment; - } - if (!slider_p.orientation.isProvided()) - { - slider_p.orientation = p.orientation; - } - - slider_p.commit_callback.function = &LLSliderCtrl::onSliderCommit; - slider_p.control_name = p.control_name; - slider_p.mouse_down_callback( p.mouse_down_callback ); - slider_p.mouse_up_callback( p.mouse_up_callback ); - mSlider = LLUICtrlFactory::create (slider_p); - - addChild( mSlider ); - - if( p.show_text() ) - { - LLRect text_rect( text_left, top, getRect().getWidth(), bottom ); - if( p.can_edit_text() ) - { - LLLineEditor::Params line_p(p.value_editor); - if (!line_p.rect.isProvided()) - { - line_p.rect = text_rect; - } - if (!line_p.font.isProvided()) - { - line_p.font = p.font; - } - - line_p.commit_callback.function(&LLSliderCtrl::onEditorCommit); - line_p.prevalidator(&LLTextValidate::validateFloat); - mEditor = LLUICtrlFactory::create(line_p); - - mEditor->setFocusReceivedCallback( boost::bind(&LLSliderCtrl::onEditorGainFocus, _1, this )); - // don't do this, as selecting the entire text is single clicking in some cases - // and double clicking in others - //mEditor->setSelectAllonFocusReceived(true); - addChild(mEditor); - } - else - { - LLTextBox::Params text_p(p.value_text); - if (!text_p.rect.isProvided()) - { - text_p.rect = text_rect; - } - if (!text_p.font.isProvided()) - { - text_p.font = p.font; - } - mTextBox = LLUICtrlFactory::create(text_p); - addChild(mTextBox); - } - } - - updateText(); -} - -LLSliderCtrl::~LLSliderCtrl() -{ - delete mEditorCommitSignal; -} - -// static -void LLSliderCtrl::onEditorGainFocus( LLFocusableElement* caller, void *userdata ) -{ - LLSliderCtrl* self = (LLSliderCtrl*) userdata; - llassert( caller == self->mEditor ); - - self->onFocusReceived(); -} - - -void LLSliderCtrl::setValue(F32 v, bool from_event) -{ - mSlider->setValue( v, from_event ); - mValue = mSlider->getValueF32(); - updateText(); -} - -bool LLSliderCtrl::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - bool res = false; - if (mLabelBox) - { - res = mLabelBox->setTextArg(key, text); - if (res && mLabelFont && mLabelWidth == 0) - { - S32 label_width = mLabelFont->getWidth(mLabelBox->getText()); - LLRect rect = mLabelBox->getRect(); - S32 prev_right = rect.mRight; - rect.mRight = rect.mLeft + label_width; - mLabelBox->setRect(rect); - - S32 delta = rect.mRight - prev_right; - rect = mSlider->getRect(); - S32 left = rect.mLeft + delta; - static LLUICachedControl sliderctrl_spacing ("UISliderctrlSpacing", 0); - left = llclamp(left, 0, rect.mRight - sliderctrl_spacing); - rect.mLeft = left; - mSlider->setRect(rect); - } - } - return res; -} - -void LLSliderCtrl::clear() -{ - setValue(0.0f); - if( mEditor ) - { - mEditor->setText( LLStringUtil::null ); - } - if( mTextBox ) - { - mTextBox->setText( LLStringUtil::null ); - } - -} - -void LLSliderCtrl::updateText() -{ - if( mEditor || mTextBox ) - { - LLLocale locale(LLLocale::USER_LOCALE); - - // Don't display very small negative values as -0.000 - F32 displayed_value = (F32)(floor(getValueF32() * pow(10.0, (F64)mPrecision) + 0.5) / pow(10.0, (F64)mPrecision)); - - std::string format = llformat("%%.%df", mPrecision); - std::string text = llformat(format.c_str(), displayed_value); - if( mEditor ) - { - // Setting editor text here to "" before using actual text is here because if text which - // is set is the same as the one which is actually typed into lineeditor, LLLineEditor::setText() - // will exit at it's beginning, so text for revert on escape won't be saved. (EXT-8536) - mEditor->setText( LLStringUtil::null ); - mEditor->setText( text ); - } - else - { - mTextBox->setText( text ); - } - } -} - -void LLSliderCtrl::updateSliderRect() -{ - S32 right = getRect().getWidth(); - S32 top = getRect().getHeight(); - S32 bottom = 0; - S32 left = 0; - static LLUICachedControl sliderctrl_spacing("UISliderctrlSpacing", 0); - if (mEditor) - { - LLRect editor_rect = mEditor->getRect(); - S32 editor_width = editor_rect.getWidth(); - editor_rect.mRight = right; - editor_rect.mLeft = right - editor_width; - mEditor->setRect(editor_rect); - - right -= editor_width + sliderctrl_spacing; - } - if (mTextBox) - { - right -= mTextBox->getRect().getWidth() + sliderctrl_spacing; - } - if (mLabelBox) - { - left += mLabelBox->getRect().getWidth() + sliderctrl_spacing; - } - - mSlider->setRect(LLRect(left, top,right,bottom)); -} - -// static -void LLSliderCtrl::onEditorCommit( LLUICtrl* ctrl, const LLSD& userdata ) -{ - LLSliderCtrl* self = dynamic_cast(ctrl->getParent()); - if (!self) - return; - - bool success = false; - F32 val = self->mValue; - F32 saved_val = self->mValue; - - std::string text = self->mEditor->getText(); - if( LLLineEditor::postvalidateFloat( text ) ) - { - LLLocale locale(LLLocale::USER_LOCALE); - val = (F32) atof( text.c_str() ); - if( self->mSlider->getMinValue() <= val && val <= self->mSlider->getMaxValue() ) - { - self->setValue( val ); // set the value temporarily so that the callback can retrieve it. - if( !self->mValidateSignal || (*(self->mValidateSignal))( self, val ) ) - { - success = true; - } - } - } - - if( success ) - { - self->onCommit(); - if (self->mEditorCommitSignal) - (*(self->mEditorCommitSignal))(self, self->getValueF32()); - } - else - { - if( self->getValueF32() != saved_val ) - { - self->setValue( saved_val ); - } - self->reportInvalidData(); - } - self->updateText(); -} - -// static -void LLSliderCtrl::onSliderCommit( LLUICtrl* ctrl, const LLSD& userdata ) -{ - LLSliderCtrl* self = dynamic_cast(ctrl->getParent()); - if (!self) - return; - - bool success = false; - F32 saved_val = self->mValue; - F32 new_val = self->mSlider->getValueF32(); - - self->mValue = new_val; // set the value temporarily so that the callback can retrieve it. - if( !self->mValidateSignal || (*(self->mValidateSignal))( self, new_val ) ) - { - success = true; - } - - if( success ) - { - self->onCommit(); - } - else - { - if( self->mValue != saved_val ) - { - self->setValue( saved_val ); - } - self->reportInvalidData(); - } - self->updateText(); -} - -void LLSliderCtrl::setEnabled(bool b) -{ - LLView::setEnabled( b ); - - if( mLabelBox ) - { - mLabelBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); - } - - mSlider->setEnabled( b ); - - if( mEditor ) - { - mEditor->setEnabled( b ); - } - - if( mTextBox ) - { - mTextBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); - } -} - - -void LLSliderCtrl::setTentative(bool b) -{ - if( mEditor ) - { - mEditor->setTentative(b); - } - LLF32UICtrl::setTentative(b); -} - - -void LLSliderCtrl::onCommit() -{ - setTentative(false); - - if( mEditor ) - { - mEditor->setTentative(false); - } - - setControlValue(getValueF32()); - LLF32UICtrl::onCommit(); -} - -void LLSliderCtrl::setRect(const LLRect& rect) -{ - LLF32UICtrl::setRect(rect); - updateSliderRect(); -} - -//virtual -void LLSliderCtrl::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLF32UICtrl::reshape(width, height, called_from_parent); - updateSliderRect(); -} - -void LLSliderCtrl::setPrecision(S32 precision) -{ - if (precision < 0 || precision > 10) - { - LL_ERRS() << "LLSliderCtrl::setPrecision - precision out of range" << LL_ENDL; - return; - } - - mPrecision = precision; - updateText(); -} - -boost::signals2::connection LLSliderCtrl::setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ) -{ - return mSlider->setMouseDownCallback( cb ); -} - -boost::signals2::connection LLSliderCtrl::setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ) -{ - return mSlider->setMouseUpCallback( cb ); -} - -boost::signals2::connection LLSliderCtrl::setSliderEditorCommitCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mEditorCommitSignal) mEditorCommitSignal = new commit_signal_t(); - return mEditorCommitSignal->connect(cb); -} -void LLSliderCtrl::onTabInto() -{ - if( mEditor ) - { - mEditor->onTabInto(); - } - LLF32UICtrl::onTabInto(); -} - -void LLSliderCtrl::reportInvalidData() -{ - make_ui_sound("UISndBadKeystroke"); -} - +/** + * @file llsliderctrl.cpp + * @brief LLSliderCtrl base class + * + * $LicenseInfo:firstyear=2002&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 "llsliderctrl.h" + +#include "llmath.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llslider.h" +#include "llstring.h" +#include "lltextbox.h" +#include "llui.h" +#include "lluiconstants.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "llresmgr.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register r("slider"); + +LLSliderCtrl::LLSliderCtrl(const LLSliderCtrl::Params& p) +: LLF32UICtrl(p), + mLabelBox( NULL ), + mEditor( NULL ), + mTextBox( NULL ), + mFont(p.font), + mShowText(p.show_text), + mCanEditText(p.can_edit_text), + mPrecision(p.decimal_digits), + mTextEnabledColor(p.text_color()), + mTextDisabledColor(p.text_disabled_color()), + mLabelWidth(p.label_width), + mEditorCommitSignal(NULL) +{ + S32 top = getRect().getHeight(); + S32 bottom = 0; + S32 left = 0; + + S32 label_width = p.label_width; + S32 text_width = p.text_width; + + // Label + if( !p.label().empty() ) + { + if (!p.label_width.isProvided()) + { + label_width = p.font()->getWidth(p.label); + } + LLRect label_rect( left, top, label_width, bottom ); + LLTextBox::Params params(p.slider_label); + if (!params.rect.isProvided()) + { + params.rect = label_rect; + } + if (!params.font.isProvided()) + { + params.font = p.font; + } + params.initial_value(p.label()); + mLabelBox = LLUICtrlFactory::create (params); + addChild(mLabelBox); + mLabelFont = params.font(); + } + + if (p.show_text && !p.text_width.isProvided()) + { + // calculate the size of the text box (log max_value is number of digits - 1 so plus 1) + if ( p.max_value ) + text_width = p.font()->getWidth(std::string("0")) * ( static_cast < S32 > ( log10 ( p.max_value ) ) + p.decimal_digits + 1 ); + + if ( p.increment < 1.0f ) + text_width += p.font()->getWidth(std::string(".")); // (mostly) take account of decimal point in value + + if ( p.min_value < 0.0f || p.max_value < 0.0f ) + text_width += p.font()->getWidth(std::string("-")); // (mostly) take account of minus sign + + // padding to make things look nicer + text_width += 8; + } + + + S32 text_left = getRect().getWidth() - text_width; + static LLUICachedControl sliderctrl_spacing ("UISliderctrlSpacing", 0); + + S32 slider_right = getRect().getWidth(); + if( p.show_text ) + { + slider_right = text_left - sliderctrl_spacing; + } + + S32 slider_left = label_width ? label_width + sliderctrl_spacing : 0; + LLSlider::Params slider_p(p.slider_bar); + slider_p.name("slider_bar"); + if (!slider_p.rect.isProvided()) + { + slider_p.rect = LLRect(slider_left,top,slider_right,bottom); + } + if (!slider_p.initial_value.isProvided()) + { + slider_p.initial_value = p.initial_value().asReal(); + } + if (!slider_p.min_value.isProvided()) + { + slider_p.min_value = p.min_value; + } + if (!slider_p.max_value.isProvided()) + { + slider_p.max_value = p.max_value; + } + if (!slider_p.increment.isProvided()) + { + slider_p.increment = p.increment; + } + if (!slider_p.orientation.isProvided()) + { + slider_p.orientation = p.orientation; + } + + slider_p.commit_callback.function = &LLSliderCtrl::onSliderCommit; + slider_p.control_name = p.control_name; + slider_p.mouse_down_callback( p.mouse_down_callback ); + slider_p.mouse_up_callback( p.mouse_up_callback ); + mSlider = LLUICtrlFactory::create (slider_p); + + addChild( mSlider ); + + if( p.show_text() ) + { + LLRect text_rect( text_left, top, getRect().getWidth(), bottom ); + if( p.can_edit_text() ) + { + LLLineEditor::Params line_p(p.value_editor); + if (!line_p.rect.isProvided()) + { + line_p.rect = text_rect; + } + if (!line_p.font.isProvided()) + { + line_p.font = p.font; + } + + line_p.commit_callback.function(&LLSliderCtrl::onEditorCommit); + line_p.prevalidator(&LLTextValidate::validateFloat); + mEditor = LLUICtrlFactory::create(line_p); + + mEditor->setFocusReceivedCallback( boost::bind(&LLSliderCtrl::onEditorGainFocus, _1, this )); + // don't do this, as selecting the entire text is single clicking in some cases + // and double clicking in others + //mEditor->setSelectAllonFocusReceived(true); + addChild(mEditor); + } + else + { + LLTextBox::Params text_p(p.value_text); + if (!text_p.rect.isProvided()) + { + text_p.rect = text_rect; + } + if (!text_p.font.isProvided()) + { + text_p.font = p.font; + } + mTextBox = LLUICtrlFactory::create(text_p); + addChild(mTextBox); + } + } + + updateText(); +} + +LLSliderCtrl::~LLSliderCtrl() +{ + delete mEditorCommitSignal; +} + +// static +void LLSliderCtrl::onEditorGainFocus( LLFocusableElement* caller, void *userdata ) +{ + LLSliderCtrl* self = (LLSliderCtrl*) userdata; + llassert( caller == self->mEditor ); + + self->onFocusReceived(); +} + + +void LLSliderCtrl::setValue(F32 v, bool from_event) +{ + mSlider->setValue( v, from_event ); + mValue = mSlider->getValueF32(); + updateText(); +} + +bool LLSliderCtrl::setLabelArg( const std::string& key, const LLStringExplicit& text ) +{ + bool res = false; + if (mLabelBox) + { + res = mLabelBox->setTextArg(key, text); + if (res && mLabelFont && mLabelWidth == 0) + { + S32 label_width = mLabelFont->getWidth(mLabelBox->getText()); + LLRect rect = mLabelBox->getRect(); + S32 prev_right = rect.mRight; + rect.mRight = rect.mLeft + label_width; + mLabelBox->setRect(rect); + + S32 delta = rect.mRight - prev_right; + rect = mSlider->getRect(); + S32 left = rect.mLeft + delta; + static LLUICachedControl sliderctrl_spacing ("UISliderctrlSpacing", 0); + left = llclamp(left, 0, rect.mRight - sliderctrl_spacing); + rect.mLeft = left; + mSlider->setRect(rect); + } + } + return res; +} + +void LLSliderCtrl::clear() +{ + setValue(0.0f); + if( mEditor ) + { + mEditor->setText( LLStringUtil::null ); + } + if( mTextBox ) + { + mTextBox->setText( LLStringUtil::null ); + } + +} + +void LLSliderCtrl::updateText() +{ + if( mEditor || mTextBox ) + { + LLLocale locale(LLLocale::USER_LOCALE); + + // Don't display very small negative values as -0.000 + F32 displayed_value = (F32)(floor(getValueF32() * pow(10.0, (F64)mPrecision) + 0.5) / pow(10.0, (F64)mPrecision)); + + std::string format = llformat("%%.%df", mPrecision); + std::string text = llformat(format.c_str(), displayed_value); + if( mEditor ) + { + // Setting editor text here to "" before using actual text is here because if text which + // is set is the same as the one which is actually typed into lineeditor, LLLineEditor::setText() + // will exit at it's beginning, so text for revert on escape won't be saved. (EXT-8536) + mEditor->setText( LLStringUtil::null ); + mEditor->setText( text ); + } + else + { + mTextBox->setText( text ); + } + } +} + +void LLSliderCtrl::updateSliderRect() +{ + S32 right = getRect().getWidth(); + S32 top = getRect().getHeight(); + S32 bottom = 0; + S32 left = 0; + static LLUICachedControl sliderctrl_spacing("UISliderctrlSpacing", 0); + if (mEditor) + { + LLRect editor_rect = mEditor->getRect(); + S32 editor_width = editor_rect.getWidth(); + editor_rect.mRight = right; + editor_rect.mLeft = right - editor_width; + mEditor->setRect(editor_rect); + + right -= editor_width + sliderctrl_spacing; + } + if (mTextBox) + { + right -= mTextBox->getRect().getWidth() + sliderctrl_spacing; + } + if (mLabelBox) + { + left += mLabelBox->getRect().getWidth() + sliderctrl_spacing; + } + + mSlider->setRect(LLRect(left, top,right,bottom)); +} + +// static +void LLSliderCtrl::onEditorCommit( LLUICtrl* ctrl, const LLSD& userdata ) +{ + LLSliderCtrl* self = dynamic_cast(ctrl->getParent()); + if (!self) + return; + + bool success = false; + F32 val = self->mValue; + F32 saved_val = self->mValue; + + std::string text = self->mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) + { + LLLocale locale(LLLocale::USER_LOCALE); + val = (F32) atof( text.c_str() ); + if( self->mSlider->getMinValue() <= val && val <= self->mSlider->getMaxValue() ) + { + self->setValue( val ); // set the value temporarily so that the callback can retrieve it. + if( !self->mValidateSignal || (*(self->mValidateSignal))( self, val ) ) + { + success = true; + } + } + } + + if( success ) + { + self->onCommit(); + if (self->mEditorCommitSignal) + (*(self->mEditorCommitSignal))(self, self->getValueF32()); + } + else + { + if( self->getValueF32() != saved_val ) + { + self->setValue( saved_val ); + } + self->reportInvalidData(); + } + self->updateText(); +} + +// static +void LLSliderCtrl::onSliderCommit( LLUICtrl* ctrl, const LLSD& userdata ) +{ + LLSliderCtrl* self = dynamic_cast(ctrl->getParent()); + if (!self) + return; + + bool success = false; + F32 saved_val = self->mValue; + F32 new_val = self->mSlider->getValueF32(); + + self->mValue = new_val; // set the value temporarily so that the callback can retrieve it. + if( !self->mValidateSignal || (*(self->mValidateSignal))( self, new_val ) ) + { + success = true; + } + + if( success ) + { + self->onCommit(); + } + else + { + if( self->mValue != saved_val ) + { + self->setValue( saved_val ); + } + self->reportInvalidData(); + } + self->updateText(); +} + +void LLSliderCtrl::setEnabled(bool b) +{ + LLView::setEnabled( b ); + + if( mLabelBox ) + { + mLabelBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); + } + + mSlider->setEnabled( b ); + + if( mEditor ) + { + mEditor->setEnabled( b ); + } + + if( mTextBox ) + { + mTextBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); + } +} + + +void LLSliderCtrl::setTentative(bool b) +{ + if( mEditor ) + { + mEditor->setTentative(b); + } + LLF32UICtrl::setTentative(b); +} + + +void LLSliderCtrl::onCommit() +{ + setTentative(false); + + if( mEditor ) + { + mEditor->setTentative(false); + } + + setControlValue(getValueF32()); + LLF32UICtrl::onCommit(); +} + +void LLSliderCtrl::setRect(const LLRect& rect) +{ + LLF32UICtrl::setRect(rect); + updateSliderRect(); +} + +//virtual +void LLSliderCtrl::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLF32UICtrl::reshape(width, height, called_from_parent); + updateSliderRect(); +} + +void LLSliderCtrl::setPrecision(S32 precision) +{ + if (precision < 0 || precision > 10) + { + LL_ERRS() << "LLSliderCtrl::setPrecision - precision out of range" << LL_ENDL; + return; + } + + mPrecision = precision; + updateText(); +} + +boost::signals2::connection LLSliderCtrl::setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ) +{ + return mSlider->setMouseDownCallback( cb ); +} + +boost::signals2::connection LLSliderCtrl::setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ) +{ + return mSlider->setMouseUpCallback( cb ); +} + +boost::signals2::connection LLSliderCtrl::setSliderEditorCommitCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mEditorCommitSignal) mEditorCommitSignal = new commit_signal_t(); + return mEditorCommitSignal->connect(cb); +} +void LLSliderCtrl::onTabInto() +{ + if( mEditor ) + { + mEditor->onTabInto(); + } + LLF32UICtrl::onTabInto(); +} + +void LLSliderCtrl::reportInvalidData() +{ + make_ui_sound("UISndBadKeystroke"); +} + diff --git a/indra/llui/llsliderctrl.h b/indra/llui/llsliderctrl.h index 993cea4d2f..a55e3bf6e9 100644 --- a/indra/llui/llsliderctrl.h +++ b/indra/llui/llsliderctrl.h @@ -1,176 +1,176 @@ -/** - * @file llsliderctrl.h - * @brief Decorated wrapper for a LLSlider. - * - * $LicenseInfo:firstyear=2002&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$ - */ - -#ifndef LL_LLSLIDERCTRL_H -#define LL_LLSLIDERCTRL_H - -#include "lluictrl.h" -#include "v4color.h" -#include "llslider.h" -#include "lltextbox.h" -#include "llrect.h" -#include "lllineeditor.h" - - -class LLSliderCtrl: public LLF32UICtrl, public ll::ui::SearchableControl -{ -public: - struct Params : public LLInitParam::Block - { - Optional orientation; - Optional label_width; - Optional text_width; - Optional show_text; - Optional can_edit_text; - Optional is_volume_slider; - Optional decimal_digits; - - Optional text_color, - text_disabled_color; - - Optional mouse_down_callback, - mouse_up_callback; - - Optional slider_bar; - Optional value_editor; - Optional value_text; - Optional slider_label; - - Params() - : text_width("text_width"), - label_width("label_width"), - show_text("show_text"), - can_edit_text("can_edit_text"), - is_volume_slider("volume"), - decimal_digits("decimal_digits", 3), - text_color("text_color"), - text_disabled_color("text_disabled_color"), - slider_bar("slider_bar"), - value_editor("value_editor"), - value_text("value_text"), - slider_label("slider_label"), - mouse_down_callback("mouse_down_callback"), - mouse_up_callback("mouse_up_callback"), - orientation("orientation", std::string ("horizontal")) - {} - }; -protected: - LLSliderCtrl(const Params&); - friend class LLUICtrlFactory; -public: - virtual ~LLSliderCtrl(); - - /*virtual*/ F32 getValueF32() const { return mSlider->getValueF32(); } - void setValue(F32 v, bool from_event = false); - - /*virtual*/ void setValue(const LLSD& value) { setValue((F32)value.asReal(), true); } - /*virtual*/ LLSD getValue() const { return LLSD(getValueF32()); } - /*virtual*/ bool setLabelArg( const std::string& key, const LLStringExplicit& text ); - - bool isMouseHeldDown() const { return mSlider->hasMouseCapture(); } - - virtual void setPrecision(S32 precision); - - /*virtual*/ void setEnabled( bool b ); - /*virtual*/ void clear(); - - /*virtual*/ void setMinValue(const LLSD& min_value) { setMinValue((F32)min_value.asReal()); } - /*virtual*/ void setMaxValue(const LLSD& max_value) { setMaxValue((F32)max_value.asReal()); } - /*virtual*/ void setMinValue(F32 min_value) { mSlider->setMinValue(min_value); updateText(); } - /*virtual*/ void setMaxValue(F32 max_value) { mSlider->setMaxValue(max_value); updateText(); } - /*virtual*/ void setIncrement(F32 increment) { mSlider->setIncrement(increment);} - - F32 getMinValue() const { return mSlider->getMinValue(); } - F32 getMaxValue() const { return mSlider->getMaxValue(); } - - void setLabel(const LLStringExplicit& label) { if (mLabelBox) mLabelBox->setText(label); } - void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } - void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } - - boost::signals2::connection setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ); - boost::signals2::connection setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ); - boost::signals2::connection setSliderEditorCommitCallback( const commit_signal_t::slot_type& cb ); - - /*virtual*/ void onTabInto(); - - /*virtual*/ void setTentative(bool b); // marks value as tentative - /*virtual*/ void onCommit(); // mark not tentative, then commit - - /*virtual*/ void setControlName(const std::string& control_name, LLView* context) - { - LLUICtrl::setControlName(control_name, context); - mSlider->setControlName(control_name, context); - } - - /*virtual*/ void setRect(const LLRect& rect); - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - - static void onSliderCommit(LLUICtrl* caller, const LLSD& userdata); - - static void onEditorCommit(LLUICtrl* ctrl, const LLSD& userdata); - static void onEditorGainFocus(LLFocusableElement* caller, void *userdata); - static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); - -protected: - virtual std::string _getSearchText() const - { - std::string strLabel; - if( mLabelBox ) - strLabel = mLabelBox->getLabel(); - return strLabel + getToolTip(); - } - virtual void onSetHighlight() const // When highlight, really do highlight the label - { - if( mLabelBox ) - mLabelBox->ll::ui::SearchableControl::setHighlighted( ll::ui::SearchableControl::getHighlighted() ); - } -private: - void updateText(); - void updateSliderRect(); - void reportInvalidData(); - - const LLFontGL* mFont; - const LLFontGL* mLabelFont; - bool mShowText; - bool mCanEditText; - - S32 mPrecision; - LLTextBox* mLabelBox; - S32 mLabelWidth; - - F32 mValue; - LLSlider* mSlider; - class LLLineEditor* mEditor; - LLTextBox* mTextBox; - - LLUIColor mTextEnabledColor; - LLUIColor mTextDisabledColor; - - commit_signal_t* mEditorCommitSignal; -}; - -#endif // LL_LLSLIDERCTRL_H - +/** + * @file llsliderctrl.h + * @brief Decorated wrapper for a LLSlider. + * + * $LicenseInfo:firstyear=2002&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$ + */ + +#ifndef LL_LLSLIDERCTRL_H +#define LL_LLSLIDERCTRL_H + +#include "lluictrl.h" +#include "v4color.h" +#include "llslider.h" +#include "lltextbox.h" +#include "llrect.h" +#include "lllineeditor.h" + + +class LLSliderCtrl: public LLF32UICtrl, public ll::ui::SearchableControl +{ +public: + struct Params : public LLInitParam::Block + { + Optional orientation; + Optional label_width; + Optional text_width; + Optional show_text; + Optional can_edit_text; + Optional is_volume_slider; + Optional decimal_digits; + + Optional text_color, + text_disabled_color; + + Optional mouse_down_callback, + mouse_up_callback; + + Optional slider_bar; + Optional value_editor; + Optional value_text; + Optional slider_label; + + Params() + : text_width("text_width"), + label_width("label_width"), + show_text("show_text"), + can_edit_text("can_edit_text"), + is_volume_slider("volume"), + decimal_digits("decimal_digits", 3), + text_color("text_color"), + text_disabled_color("text_disabled_color"), + slider_bar("slider_bar"), + value_editor("value_editor"), + value_text("value_text"), + slider_label("slider_label"), + mouse_down_callback("mouse_down_callback"), + mouse_up_callback("mouse_up_callback"), + orientation("orientation", std::string ("horizontal")) + {} + }; +protected: + LLSliderCtrl(const Params&); + friend class LLUICtrlFactory; +public: + virtual ~LLSliderCtrl(); + + /*virtual*/ F32 getValueF32() const { return mSlider->getValueF32(); } + void setValue(F32 v, bool from_event = false); + + /*virtual*/ void setValue(const LLSD& value) { setValue((F32)value.asReal(), true); } + /*virtual*/ LLSD getValue() const { return LLSD(getValueF32()); } + /*virtual*/ bool setLabelArg( const std::string& key, const LLStringExplicit& text ); + + bool isMouseHeldDown() const { return mSlider->hasMouseCapture(); } + + virtual void setPrecision(S32 precision); + + /*virtual*/ void setEnabled( bool b ); + /*virtual*/ void clear(); + + /*virtual*/ void setMinValue(const LLSD& min_value) { setMinValue((F32)min_value.asReal()); } + /*virtual*/ void setMaxValue(const LLSD& max_value) { setMaxValue((F32)max_value.asReal()); } + /*virtual*/ void setMinValue(F32 min_value) { mSlider->setMinValue(min_value); updateText(); } + /*virtual*/ void setMaxValue(F32 max_value) { mSlider->setMaxValue(max_value); updateText(); } + /*virtual*/ void setIncrement(F32 increment) { mSlider->setIncrement(increment);} + + F32 getMinValue() const { return mSlider->getMinValue(); } + F32 getMaxValue() const { return mSlider->getMaxValue(); } + + void setLabel(const LLStringExplicit& label) { if (mLabelBox) mLabelBox->setText(label); } + void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } + void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } + + boost::signals2::connection setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setSliderEditorCommitCallback( const commit_signal_t::slot_type& cb ); + + /*virtual*/ void onTabInto(); + + /*virtual*/ void setTentative(bool b); // marks value as tentative + /*virtual*/ void onCommit(); // mark not tentative, then commit + + /*virtual*/ void setControlName(const std::string& control_name, LLView* context) + { + LLUICtrl::setControlName(control_name, context); + mSlider->setControlName(control_name, context); + } + + /*virtual*/ void setRect(const LLRect& rect); + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + + static void onSliderCommit(LLUICtrl* caller, const LLSD& userdata); + + static void onEditorCommit(LLUICtrl* ctrl, const LLSD& userdata); + static void onEditorGainFocus(LLFocusableElement* caller, void *userdata); + static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); + +protected: + virtual std::string _getSearchText() const + { + std::string strLabel; + if( mLabelBox ) + strLabel = mLabelBox->getLabel(); + return strLabel + getToolTip(); + } + virtual void onSetHighlight() const // When highlight, really do highlight the label + { + if( mLabelBox ) + mLabelBox->ll::ui::SearchableControl::setHighlighted( ll::ui::SearchableControl::getHighlighted() ); + } +private: + void updateText(); + void updateSliderRect(); + void reportInvalidData(); + + const LLFontGL* mFont; + const LLFontGL* mLabelFont; + bool mShowText; + bool mCanEditText; + + S32 mPrecision; + LLTextBox* mLabelBox; + S32 mLabelWidth; + + F32 mValue; + LLSlider* mSlider; + class LLLineEditor* mEditor; + LLTextBox* mTextBox; + + LLUIColor mTextEnabledColor; + LLUIColor mTextDisabledColor; + + commit_signal_t* mEditorCommitSignal; +}; + +#endif // LL_LLSLIDERCTRL_H + diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index f361877251..7d41d80334 100644 --- a/indra/llui/llspinctrl.cpp +++ b/indra/llui/llspinctrl.cpp @@ -1,504 +1,504 @@ -/** - * @file llspinctrl.cpp - * @brief LLSpinCtrl base 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 "llspinctrl.h" - -#include "llgl.h" -#include "llui.h" -#include "lluiconstants.h" - -#include "llstring.h" -#include "llfontgl.h" -#include "lllineeditor.h" -#include "llbutton.h" -#include "lltextbox.h" -#include "llkeyboard.h" -#include "llmath.h" -#include "llcontrol.h" -#include "llfocusmgr.h" -#include "llresmgr.h" -#include "lluictrlfactory.h" - -const U32 MAX_STRING_LENGTH = 255; - -static LLDefaultChildRegistry::Register r2("spinner"); - -LLSpinCtrl::Params::Params() -: label_width("label_width"), - decimal_digits("decimal_digits"), - allow_text_entry("allow_text_entry", true), - allow_digits_only("allow_digits_only", false), - label_wrap("label_wrap", false), - text_enabled_color("text_enabled_color"), - text_disabled_color("text_disabled_color"), - up_button("up_button"), - down_button("down_button") -{} - -LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p) -: LLF32UICtrl(p), - mLabelBox(NULL), - mbHasBeenSet( false ), - mPrecision(p.decimal_digits), - mTextEnabledColor(p.text_enabled_color()), - mTextDisabledColor(p.text_disabled_color()) -{ - static LLUICachedControl spinctrl_spacing ("UISpinctrlSpacing", 0); - static LLUICachedControl spinctrl_btn_width ("UISpinctrlBtnWidth", 0); - static LLUICachedControl spinctrl_btn_height ("UISpinctrlBtnHeight", 0); - S32 centered_top = getRect().getHeight(); - S32 centered_bottom = getRect().getHeight() - 2 * spinctrl_btn_height; - S32 btn_left = 0; - // reserve space for spinner - S32 label_width = llclamp(p.label_width(), 0, llmax(0, getRect().getWidth() - 40)); - - // Label - if( !p.label().empty() ) - { - LLRect label_rect( 0, centered_top, label_width, centered_bottom ); - LLTextBox::Params params; - params.wrap(p.label_wrap); - params.name("SpinCtrl Label"); - params.rect(label_rect); - params.initial_value(p.label()); - if (p.font.isProvided()) - { - params.font(p.font); - } - mLabelBox = LLUICtrlFactory::create (params); - addChild(mLabelBox); - - btn_left += label_rect.mRight + spinctrl_spacing; - } - - S32 btn_right = btn_left + spinctrl_btn_width; - - // Spin buttons - LLButton::Params up_button_params(p.up_button); - up_button_params.rect = LLRect(btn_left, getRect().getHeight(), btn_right, getRect().getHeight() - spinctrl_btn_height); - // Click callback starts within the button and ends within the button, - // but LLSpinCtrl handles the action continuosly so subsribers needs to - // be informed about click ending even if outside view, use 'up' instead - up_button_params.mouse_up_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2)); - up_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2)); - up_button_params.commit_on_capture_lost = true; - - mUpBtn = LLUICtrlFactory::create(up_button_params); - addChild(mUpBtn); - - LLButton::Params down_button_params(p.down_button); - down_button_params.rect = LLRect(btn_left, getRect().getHeight() - spinctrl_btn_height, btn_right, getRect().getHeight() - 2 * spinctrl_btn_height); - down_button_params.mouse_up_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2)); - down_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2)); - down_button_params.commit_on_capture_lost = true; - mDownBtn = LLUICtrlFactory::create(down_button_params); - addChild(mDownBtn); - - LLRect editor_rect( btn_right + 1, centered_top, getRect().getWidth(), centered_bottom ); - LLLineEditor::Params params; - params.name("SpinCtrl Editor"); - params.rect(editor_rect); - if (p.font.isProvided()) - { - params.font(p.font); - } - params.max_length.bytes(MAX_STRING_LENGTH); - params.commit_callback.function((boost::bind(&LLSpinCtrl::onEditorCommit, this, _2))); - - //*NOTE: allow entering of any chars for LLCalc, proper input will be evaluated on commit - - params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); - mEditor = LLUICtrlFactory::create (params); - mEditor->setFocusReceivedCallback( boost::bind(&LLSpinCtrl::onEditorGainFocus, _1, this )); - mEditor->setFocusLostCallback( boost::bind(&LLSpinCtrl::onEditorLostFocus, _1, this )); - if (p.allow_digits_only) - { - mEditor->setPrevalidateInput(LLTextValidate::validateNonNegativeS32NoSpace); - } - //RN: this seems to be a BAD IDEA, as it makes the editor behavior different when it has focus - // than when it doesn't. Instead, if you always have to double click to select all the text, - // it's easier to understand - //mEditor->setSelectAllonFocusReceived(true); - mEditor->setSelectAllonCommit(false); - addChild(mEditor); - - updateEditor(); - setUseBoundingRect( true ); -} - -F32 clamp_precision(F32 value, S32 decimal_precision) -{ - // pow() isn't perfect - - F64 clamped_value = value; - for (S32 i = 0; i < decimal_precision; i++) - clamped_value *= 10.0; - - clamped_value = ll_round(clamped_value); - - for (S32 i = 0; i < decimal_precision; i++) - clamped_value /= 10.0; - - return (F32)clamped_value; -} - - -void LLSpinCtrl::onUpBtn( const LLSD& data ) -{ - if( getEnabled() ) - { - std::string text = mEditor->getText(); - if( LLLineEditor::postvalidateFloat( text ) ) - { - - LLLocale locale(LLLocale::USER_LOCALE); - F32 cur_val = (F32) atof(text.c_str()); - - // use getValue()/setValue() to force reload from/to control - F32 val = cur_val + mIncrement; - val = clamp_precision(val, mPrecision); - val = llmin( val, mMaxValue ); - if (val < mMinValue) val = mMinValue; - if (val > mMaxValue) val = mMaxValue; - - F32 saved_val = (F32)getValue().asReal(); - setValue(val); - if( mValidateSignal && !(*mValidateSignal)( this, val ) ) - { - setValue( saved_val ); - reportInvalidData(); - updateEditor(); - return; - } - - updateEditor(); - onCommit(); - } - } -} - -void LLSpinCtrl::onDownBtn( const LLSD& data ) -{ - if( getEnabled() ) - { - std::string text = mEditor->getText(); - if( LLLineEditor::postvalidateFloat( text ) ) - { - - LLLocale locale(LLLocale::USER_LOCALE); - F32 cur_val = (F32) atof(text.c_str()); - - F32 val = cur_val - mIncrement; - val = clamp_precision(val, mPrecision); - val = llmax( val, mMinValue ); - - if (val < mMinValue) val = mMinValue; - if (val > mMaxValue) val = mMaxValue; - - F32 saved_val = (F32)getValue().asReal(); - setValue(val); - if( mValidateSignal && !(*mValidateSignal)( this, val ) ) - { - setValue( saved_val ); - reportInvalidData(); - updateEditor(); - return; - } - - updateEditor(); - onCommit(); - } - } -} - -// static -void LLSpinCtrl::onEditorGainFocus( LLFocusableElement* caller, void *userdata ) -{ - LLSpinCtrl* self = (LLSpinCtrl*) userdata; - llassert( caller == self->mEditor ); - - self->onFocusReceived(); -} - -// static -void LLSpinCtrl::onEditorLostFocus( LLFocusableElement* caller, void *userdata ) -{ - LLSpinCtrl* self = (LLSpinCtrl*) userdata; - llassert( caller == self->mEditor ); - - self->onFocusLost(); - - std::string text = self->mEditor->getText(); - - LLLocale locale(LLLocale::USER_LOCALE); - F32 val = (F32)atof(text.c_str()); - - F32 saved_val = self->getValueF32(); - if (saved_val != val && !self->mEditor->isDirty()) - { - // Editor was focused when value update arrived, string - // in editor is different from one in spin control. - // Since editor is not dirty, it won't commit, so either - // attempt to commit value from editor or revert to a more - // recent value from spin control - self->updateEditor(); - } -} - -void LLSpinCtrl::setValue(const LLSD& value ) -{ - F32 v = (F32)value.asReal(); - if (getValueF32() != v || !mbHasBeenSet) - { - mbHasBeenSet = true; - LLF32UICtrl::setValue(value); - - if (!mEditor->hasFocus()) - { - updateEditor(); - } - } -} - -//no matter if Editor has the focus, update the value -void LLSpinCtrl::forceSetValue(const LLSD& value ) -{ - F32 v = (F32)value.asReal(); - if (getValueF32() != v || !mbHasBeenSet) - { - mbHasBeenSet = true; - LLF32UICtrl::setValue(value); - - updateEditor(); - mEditor->resetScrollPosition(); - } -} - -void LLSpinCtrl::clear() -{ - setValue(mMinValue); - mEditor->clear(); - mbHasBeenSet = false; -} - -void LLSpinCtrl::updateLabelColor() -{ - if( mLabelBox ) - { - mLabelBox->setColor( getEnabled() ? mTextEnabledColor.get() : mTextDisabledColor.get() ); - } -} - -void LLSpinCtrl::updateEditor() -{ - LLLocale locale(LLLocale::USER_LOCALE); - - // Don't display very small negative valu es as -0.000 - F32 displayed_value = clamp_precision((F32)getValue().asReal(), mPrecision); - -// if( S32( displayed_value * pow( 10, mPrecision ) ) == 0 ) -// { -// displayed_value = 0.f; -// } - - std::string format = llformat("%%.%df", mPrecision); - std::string text = llformat(format.c_str(), displayed_value); - mEditor->setText( text ); -} - -void LLSpinCtrl::onEditorCommit( const LLSD& data ) -{ - bool success = false; - - if( mEditor->evaluateFloat() ) - { - std::string text = mEditor->getText(); - - LLLocale locale(LLLocale::USER_LOCALE); - F32 val = (F32) atof(text.c_str()); - - if (val < mMinValue) val = mMinValue; - if (val > mMaxValue) val = mMaxValue; - - F32 saved_val = getValueF32(); - setValue(val); - if( !mValidateSignal || (*mValidateSignal)( this, val ) ) - { - success = true; - onCommit(); - } - else - { - setValue(saved_val); - } - } - updateEditor(); - - if( success ) - { - // We commited and clamped value - // try to display as much as possible - mEditor->resetScrollPosition(); - } - else - { - reportInvalidData(); - } -} - - -void LLSpinCtrl::forceEditorCommit() -{ - onEditorCommit( LLSD() ); -} - - -void LLSpinCtrl::setFocus(bool b) -{ - LLUICtrl::setFocus( b ); - mEditor->setFocus( b ); -} - -void LLSpinCtrl::setEnabled(bool b) -{ - LLView::setEnabled( b ); - mEditor->setEnabled( b ); - updateLabelColor(); -} - - -void LLSpinCtrl::setTentative(bool b) -{ - mEditor->setTentative(b); - LLUICtrl::setTentative(b); -} - - -bool LLSpinCtrl::isMouseHeldDown() const -{ - return - mDownBtn->hasMouseCapture() - || mUpBtn->hasMouseCapture(); -} - -void LLSpinCtrl::onCommit() -{ - setTentative(false); - setControlValue(getValueF32()); - LLF32UICtrl::onCommit(); -} - - -void LLSpinCtrl::setPrecision(S32 precision) -{ - if (precision < 0 || precision > 10) - { - LL_ERRS() << "LLSpinCtrl::setPrecision - precision out of range" << LL_ENDL; - return; - } - - mPrecision = precision; - updateEditor(); -} - -void LLSpinCtrl::setLabel(const LLStringExplicit& label) -{ - if (mLabelBox) - { - mLabelBox->setText(label); - } - else - { - LL_WARNS() << "Attempting to set label on LLSpinCtrl constructed without one " << getName() << LL_ENDL; - } - updateLabelColor(); -} - -void LLSpinCtrl::setAllowEdit(bool allow_edit) -{ - mEditor->setEnabled(allow_edit); - mAllowEdit = allow_edit; -} - -void LLSpinCtrl::onTabInto() -{ - mEditor->onTabInto(); - LLF32UICtrl::onTabInto(); -} - - -void LLSpinCtrl::reportInvalidData() -{ - make_ui_sound("UISndBadKeystroke"); -} - -bool LLSpinCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - if( clicks > 0 ) - { - while( clicks-- ) - { - onDownBtn(getValue()); - } - } - else - while( clicks++ ) - { - onUpBtn(getValue()); - } - - return true; -} - -bool LLSpinCtrl::handleKeyHere(KEY key, MASK mask) -{ - if (mEditor->hasFocus()) - { - if(key == KEY_ESCAPE) - { - // text editors don't support revert normally (due to user confusion) - // but not allowing revert on a spinner seems dangerous - updateEditor(); - mEditor->resetScrollPosition(); - mEditor->setFocus(false); - return true; - } - if(key == KEY_UP) - { - onUpBtn(getValue()); - return true; - } - if(key == KEY_DOWN) - { - onDownBtn(getValue()); - return true; - } - } - return false; -} - +/** + * @file llspinctrl.cpp + * @brief LLSpinCtrl base 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 "llspinctrl.h" + +#include "llgl.h" +#include "llui.h" +#include "lluiconstants.h" + +#include "llstring.h" +#include "llfontgl.h" +#include "lllineeditor.h" +#include "llbutton.h" +#include "lltextbox.h" +#include "llkeyboard.h" +#include "llmath.h" +#include "llcontrol.h" +#include "llfocusmgr.h" +#include "llresmgr.h" +#include "lluictrlfactory.h" + +const U32 MAX_STRING_LENGTH = 255; + +static LLDefaultChildRegistry::Register r2("spinner"); + +LLSpinCtrl::Params::Params() +: label_width("label_width"), + decimal_digits("decimal_digits"), + allow_text_entry("allow_text_entry", true), + allow_digits_only("allow_digits_only", false), + label_wrap("label_wrap", false), + text_enabled_color("text_enabled_color"), + text_disabled_color("text_disabled_color"), + up_button("up_button"), + down_button("down_button") +{} + +LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p) +: LLF32UICtrl(p), + mLabelBox(NULL), + mbHasBeenSet( false ), + mPrecision(p.decimal_digits), + mTextEnabledColor(p.text_enabled_color()), + mTextDisabledColor(p.text_disabled_color()) +{ + static LLUICachedControl spinctrl_spacing ("UISpinctrlSpacing", 0); + static LLUICachedControl spinctrl_btn_width ("UISpinctrlBtnWidth", 0); + static LLUICachedControl spinctrl_btn_height ("UISpinctrlBtnHeight", 0); + S32 centered_top = getRect().getHeight(); + S32 centered_bottom = getRect().getHeight() - 2 * spinctrl_btn_height; + S32 btn_left = 0; + // reserve space for spinner + S32 label_width = llclamp(p.label_width(), 0, llmax(0, getRect().getWidth() - 40)); + + // Label + if( !p.label().empty() ) + { + LLRect label_rect( 0, centered_top, label_width, centered_bottom ); + LLTextBox::Params params; + params.wrap(p.label_wrap); + params.name("SpinCtrl Label"); + params.rect(label_rect); + params.initial_value(p.label()); + if (p.font.isProvided()) + { + params.font(p.font); + } + mLabelBox = LLUICtrlFactory::create (params); + addChild(mLabelBox); + + btn_left += label_rect.mRight + spinctrl_spacing; + } + + S32 btn_right = btn_left + spinctrl_btn_width; + + // Spin buttons + LLButton::Params up_button_params(p.up_button); + up_button_params.rect = LLRect(btn_left, getRect().getHeight(), btn_right, getRect().getHeight() - spinctrl_btn_height); + // Click callback starts within the button and ends within the button, + // but LLSpinCtrl handles the action continuosly so subsribers needs to + // be informed about click ending even if outside view, use 'up' instead + up_button_params.mouse_up_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2)); + up_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2)); + up_button_params.commit_on_capture_lost = true; + + mUpBtn = LLUICtrlFactory::create(up_button_params); + addChild(mUpBtn); + + LLButton::Params down_button_params(p.down_button); + down_button_params.rect = LLRect(btn_left, getRect().getHeight() - spinctrl_btn_height, btn_right, getRect().getHeight() - 2 * spinctrl_btn_height); + down_button_params.mouse_up_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2)); + down_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2)); + down_button_params.commit_on_capture_lost = true; + mDownBtn = LLUICtrlFactory::create(down_button_params); + addChild(mDownBtn); + + LLRect editor_rect( btn_right + 1, centered_top, getRect().getWidth(), centered_bottom ); + LLLineEditor::Params params; + params.name("SpinCtrl Editor"); + params.rect(editor_rect); + if (p.font.isProvided()) + { + params.font(p.font); + } + params.max_length.bytes(MAX_STRING_LENGTH); + params.commit_callback.function((boost::bind(&LLSpinCtrl::onEditorCommit, this, _2))); + + //*NOTE: allow entering of any chars for LLCalc, proper input will be evaluated on commit + + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + mEditor = LLUICtrlFactory::create (params); + mEditor->setFocusReceivedCallback( boost::bind(&LLSpinCtrl::onEditorGainFocus, _1, this )); + mEditor->setFocusLostCallback( boost::bind(&LLSpinCtrl::onEditorLostFocus, _1, this )); + if (p.allow_digits_only) + { + mEditor->setPrevalidateInput(LLTextValidate::validateNonNegativeS32NoSpace); + } + //RN: this seems to be a BAD IDEA, as it makes the editor behavior different when it has focus + // than when it doesn't. Instead, if you always have to double click to select all the text, + // it's easier to understand + //mEditor->setSelectAllonFocusReceived(true); + mEditor->setSelectAllonCommit(false); + addChild(mEditor); + + updateEditor(); + setUseBoundingRect( true ); +} + +F32 clamp_precision(F32 value, S32 decimal_precision) +{ + // pow() isn't perfect + + F64 clamped_value = value; + for (S32 i = 0; i < decimal_precision; i++) + clamped_value *= 10.0; + + clamped_value = ll_round(clamped_value); + + for (S32 i = 0; i < decimal_precision; i++) + clamped_value /= 10.0; + + return (F32)clamped_value; +} + + +void LLSpinCtrl::onUpBtn( const LLSD& data ) +{ + if( getEnabled() ) + { + std::string text = mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) + { + + LLLocale locale(LLLocale::USER_LOCALE); + F32 cur_val = (F32) atof(text.c_str()); + + // use getValue()/setValue() to force reload from/to control + F32 val = cur_val + mIncrement; + val = clamp_precision(val, mPrecision); + val = llmin( val, mMaxValue ); + if (val < mMinValue) val = mMinValue; + if (val > mMaxValue) val = mMaxValue; + + F32 saved_val = (F32)getValue().asReal(); + setValue(val); + if( mValidateSignal && !(*mValidateSignal)( this, val ) ) + { + setValue( saved_val ); + reportInvalidData(); + updateEditor(); + return; + } + + updateEditor(); + onCommit(); + } + } +} + +void LLSpinCtrl::onDownBtn( const LLSD& data ) +{ + if( getEnabled() ) + { + std::string text = mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) + { + + LLLocale locale(LLLocale::USER_LOCALE); + F32 cur_val = (F32) atof(text.c_str()); + + F32 val = cur_val - mIncrement; + val = clamp_precision(val, mPrecision); + val = llmax( val, mMinValue ); + + if (val < mMinValue) val = mMinValue; + if (val > mMaxValue) val = mMaxValue; + + F32 saved_val = (F32)getValue().asReal(); + setValue(val); + if( mValidateSignal && !(*mValidateSignal)( this, val ) ) + { + setValue( saved_val ); + reportInvalidData(); + updateEditor(); + return; + } + + updateEditor(); + onCommit(); + } + } +} + +// static +void LLSpinCtrl::onEditorGainFocus( LLFocusableElement* caller, void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + llassert( caller == self->mEditor ); + + self->onFocusReceived(); +} + +// static +void LLSpinCtrl::onEditorLostFocus( LLFocusableElement* caller, void *userdata ) +{ + LLSpinCtrl* self = (LLSpinCtrl*) userdata; + llassert( caller == self->mEditor ); + + self->onFocusLost(); + + std::string text = self->mEditor->getText(); + + LLLocale locale(LLLocale::USER_LOCALE); + F32 val = (F32)atof(text.c_str()); + + F32 saved_val = self->getValueF32(); + if (saved_val != val && !self->mEditor->isDirty()) + { + // Editor was focused when value update arrived, string + // in editor is different from one in spin control. + // Since editor is not dirty, it won't commit, so either + // attempt to commit value from editor or revert to a more + // recent value from spin control + self->updateEditor(); + } +} + +void LLSpinCtrl::setValue(const LLSD& value ) +{ + F32 v = (F32)value.asReal(); + if (getValueF32() != v || !mbHasBeenSet) + { + mbHasBeenSet = true; + LLF32UICtrl::setValue(value); + + if (!mEditor->hasFocus()) + { + updateEditor(); + } + } +} + +//no matter if Editor has the focus, update the value +void LLSpinCtrl::forceSetValue(const LLSD& value ) +{ + F32 v = (F32)value.asReal(); + if (getValueF32() != v || !mbHasBeenSet) + { + mbHasBeenSet = true; + LLF32UICtrl::setValue(value); + + updateEditor(); + mEditor->resetScrollPosition(); + } +} + +void LLSpinCtrl::clear() +{ + setValue(mMinValue); + mEditor->clear(); + mbHasBeenSet = false; +} + +void LLSpinCtrl::updateLabelColor() +{ + if( mLabelBox ) + { + mLabelBox->setColor( getEnabled() ? mTextEnabledColor.get() : mTextDisabledColor.get() ); + } +} + +void LLSpinCtrl::updateEditor() +{ + LLLocale locale(LLLocale::USER_LOCALE); + + // Don't display very small negative valu es as -0.000 + F32 displayed_value = clamp_precision((F32)getValue().asReal(), mPrecision); + +// if( S32( displayed_value * pow( 10, mPrecision ) ) == 0 ) +// { +// displayed_value = 0.f; +// } + + std::string format = llformat("%%.%df", mPrecision); + std::string text = llformat(format.c_str(), displayed_value); + mEditor->setText( text ); +} + +void LLSpinCtrl::onEditorCommit( const LLSD& data ) +{ + bool success = false; + + if( mEditor->evaluateFloat() ) + { + std::string text = mEditor->getText(); + + LLLocale locale(LLLocale::USER_LOCALE); + F32 val = (F32) atof(text.c_str()); + + if (val < mMinValue) val = mMinValue; + if (val > mMaxValue) val = mMaxValue; + + F32 saved_val = getValueF32(); + setValue(val); + if( !mValidateSignal || (*mValidateSignal)( this, val ) ) + { + success = true; + onCommit(); + } + else + { + setValue(saved_val); + } + } + updateEditor(); + + if( success ) + { + // We commited and clamped value + // try to display as much as possible + mEditor->resetScrollPosition(); + } + else + { + reportInvalidData(); + } +} + + +void LLSpinCtrl::forceEditorCommit() +{ + onEditorCommit( LLSD() ); +} + + +void LLSpinCtrl::setFocus(bool b) +{ + LLUICtrl::setFocus( b ); + mEditor->setFocus( b ); +} + +void LLSpinCtrl::setEnabled(bool b) +{ + LLView::setEnabled( b ); + mEditor->setEnabled( b ); + updateLabelColor(); +} + + +void LLSpinCtrl::setTentative(bool b) +{ + mEditor->setTentative(b); + LLUICtrl::setTentative(b); +} + + +bool LLSpinCtrl::isMouseHeldDown() const +{ + return + mDownBtn->hasMouseCapture() + || mUpBtn->hasMouseCapture(); +} + +void LLSpinCtrl::onCommit() +{ + setTentative(false); + setControlValue(getValueF32()); + LLF32UICtrl::onCommit(); +} + + +void LLSpinCtrl::setPrecision(S32 precision) +{ + if (precision < 0 || precision > 10) + { + LL_ERRS() << "LLSpinCtrl::setPrecision - precision out of range" << LL_ENDL; + return; + } + + mPrecision = precision; + updateEditor(); +} + +void LLSpinCtrl::setLabel(const LLStringExplicit& label) +{ + if (mLabelBox) + { + mLabelBox->setText(label); + } + else + { + LL_WARNS() << "Attempting to set label on LLSpinCtrl constructed without one " << getName() << LL_ENDL; + } + updateLabelColor(); +} + +void LLSpinCtrl::setAllowEdit(bool allow_edit) +{ + mEditor->setEnabled(allow_edit); + mAllowEdit = allow_edit; +} + +void LLSpinCtrl::onTabInto() +{ + mEditor->onTabInto(); + LLF32UICtrl::onTabInto(); +} + + +void LLSpinCtrl::reportInvalidData() +{ + make_ui_sound("UISndBadKeystroke"); +} + +bool LLSpinCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if( clicks > 0 ) + { + while( clicks-- ) + { + onDownBtn(getValue()); + } + } + else + while( clicks++ ) + { + onUpBtn(getValue()); + } + + return true; +} + +bool LLSpinCtrl::handleKeyHere(KEY key, MASK mask) +{ + if (mEditor->hasFocus()) + { + if(key == KEY_ESCAPE) + { + // text editors don't support revert normally (due to user confusion) + // but not allowing revert on a spinner seems dangerous + updateEditor(); + mEditor->resetScrollPosition(); + mEditor->setFocus(false); + return true; + } + if(key == KEY_UP) + { + onUpBtn(getValue()); + return true; + } + if(key == KEY_DOWN) + { + onDownBtn(getValue()); + return true; + } + } + return false; +} + diff --git a/indra/llui/llspinctrl.h b/indra/llui/llspinctrl.h index 471747f404..75f1830d80 100644 --- a/indra/llui/llspinctrl.h +++ b/indra/llui/llspinctrl.h @@ -1,124 +1,124 @@ -/** - * @file llspinctrl.h - * @brief Typical spinner with "up" and "down" arrow buttons. - * - * $LicenseInfo:firstyear=2002&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$ - */ - -#ifndef LL_LLSPINCTRL_H -#define LL_LLSPINCTRL_H - - -#include "stdtypes.h" -#include "llbutton.h" -#include "llf32uictrl.h" -#include "v4color.h" -#include "llrect.h" - - -class LLSpinCtrl -: public LLF32UICtrl -{ -public: - struct Params : public LLInitParam::Block - { - Optional label_width; - Optional decimal_digits; - Optional allow_text_entry; - Optional allow_digits_only; - Optional label_wrap; - - Optional text_enabled_color; - Optional text_disabled_color; - - Optional up_button; - Optional down_button; - - Params(); - }; -protected: - LLSpinCtrl(const Params&); - friend class LLUICtrlFactory; -public: - virtual ~LLSpinCtrl() {} // Children all cleaned up by default view destructor. - - virtual void forceSetValue(const LLSD& value ) ; - virtual void setValue(const LLSD& value ); - F32 get() const { return getValueF32(); } - void set(F32 value) { setValue(value); mInitialValue = value; } - - bool isMouseHeldDown() const; - - virtual void setEnabled( bool b ); - virtual void setFocus( bool b ); - virtual void clear(); - virtual bool isDirty() const { return( getValueF32() != mInitialValue ); } - virtual void resetDirty() { mInitialValue = getValueF32(); } - - virtual void setPrecision(S32 precision); - - void setLabel(const LLStringExplicit& label); - void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; updateLabelColor(); } - void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; updateLabelColor();} - void setAllowEdit(bool allow_edit); - - virtual void onTabInto(); - - virtual void setTentative(bool b); // marks value as tentative - virtual void onCommit(); // mark not tentative, then commit - - void forceEditorCommit(); // for commit on external button - - virtual bool handleScrollWheel(S32 x,S32 y,S32 clicks); - virtual bool handleKeyHere(KEY key, MASK mask); - - void onEditorCommit(const LLSD& data); - static void onEditorGainFocus(LLFocusableElement* caller, void *userdata); - static void onEditorLostFocus(LLFocusableElement* caller, void *userdata); - static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); - - void onUpBtn(const LLSD& data); - void onDownBtn(const LLSD& data); - - const LLColor4& getEnabledTextColor() const { return mTextEnabledColor.get(); } - const LLColor4& getDisabledTextColor() const { return mTextDisabledColor.get(); } - -private: - void updateLabelColor(); - void updateEditor(); - void reportInvalidData(); - - S32 mPrecision; - class LLTextBox* mLabelBox; - - class LLLineEditor* mEditor; - LLUIColor mTextEnabledColor; - LLUIColor mTextDisabledColor; - - class LLButton* mUpBtn; - class LLButton* mDownBtn; - - bool mbHasBeenSet; - bool mAllowEdit; -}; - -#endif // LL_LLSPINCTRL_H +/** + * @file llspinctrl.h + * @brief Typical spinner with "up" and "down" arrow buttons. + * + * $LicenseInfo:firstyear=2002&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$ + */ + +#ifndef LL_LLSPINCTRL_H +#define LL_LLSPINCTRL_H + + +#include "stdtypes.h" +#include "llbutton.h" +#include "llf32uictrl.h" +#include "v4color.h" +#include "llrect.h" + + +class LLSpinCtrl +: public LLF32UICtrl +{ +public: + struct Params : public LLInitParam::Block + { + Optional label_width; + Optional decimal_digits; + Optional allow_text_entry; + Optional allow_digits_only; + Optional label_wrap; + + Optional text_enabled_color; + Optional text_disabled_color; + + Optional up_button; + Optional down_button; + + Params(); + }; +protected: + LLSpinCtrl(const Params&); + friend class LLUICtrlFactory; +public: + virtual ~LLSpinCtrl() {} // Children all cleaned up by default view destructor. + + virtual void forceSetValue(const LLSD& value ) ; + virtual void setValue(const LLSD& value ); + F32 get() const { return getValueF32(); } + void set(F32 value) { setValue(value); mInitialValue = value; } + + bool isMouseHeldDown() const; + + virtual void setEnabled( bool b ); + virtual void setFocus( bool b ); + virtual void clear(); + virtual bool isDirty() const { return( getValueF32() != mInitialValue ); } + virtual void resetDirty() { mInitialValue = getValueF32(); } + + virtual void setPrecision(S32 precision); + + void setLabel(const LLStringExplicit& label); + void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; updateLabelColor(); } + void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; updateLabelColor();} + void setAllowEdit(bool allow_edit); + + virtual void onTabInto(); + + virtual void setTentative(bool b); // marks value as tentative + virtual void onCommit(); // mark not tentative, then commit + + void forceEditorCommit(); // for commit on external button + + virtual bool handleScrollWheel(S32 x,S32 y,S32 clicks); + virtual bool handleKeyHere(KEY key, MASK mask); + + void onEditorCommit(const LLSD& data); + static void onEditorGainFocus(LLFocusableElement* caller, void *userdata); + static void onEditorLostFocus(LLFocusableElement* caller, void *userdata); + static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); + + void onUpBtn(const LLSD& data); + void onDownBtn(const LLSD& data); + + const LLColor4& getEnabledTextColor() const { return mTextEnabledColor.get(); } + const LLColor4& getDisabledTextColor() const { return mTextDisabledColor.get(); } + +private: + void updateLabelColor(); + void updateEditor(); + void reportInvalidData(); + + S32 mPrecision; + class LLTextBox* mLabelBox; + + class LLLineEditor* mEditor; + LLUIColor mTextEnabledColor; + LLUIColor mTextDisabledColor; + + class LLButton* mUpBtn; + class LLButton* mDownBtn; + + bool mbHasBeenSet; + bool mAllowEdit; +}; + +#endif // LL_LLSPINCTRL_H diff --git a/indra/llui/llstatbar.cpp b/indra/llui/llstatbar.cpp index 6e9b19a4de..f125dda916 100644 --- a/indra/llui/llstatbar.cpp +++ b/indra/llui/llstatbar.cpp @@ -1,716 +1,716 @@ -/** - * @file llstatbar.cpp - * @brief A little map of the world with network information - * - * $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 "llviewerprecompiledheaders.h" -#include "linden_common.h" - -#include "llstatbar.h" - -#include "llmath.h" -#include "llui.h" -#include "llgl.h" -#include "llfontgl.h" - -#include "lluictrlfactory.h" -#include "lltracerecording.h" -#include "llcriticaldamp.h" -#include "lltooltip.h" -#include "lllocalcliprect.h" -#include -#include "lltrans.h" - -// rate at which to update display of value that is rapidly changing -const F32 MEAN_VALUE_UPDATE_TIME = 1.f / 4.f; -// time between value changes that qualifies as a "rapid change" -const F32Seconds RAPID_CHANGE_THRESHOLD(0.2f); -// maximum number of rapid changes in RAPID_CHANGE_WINDOW before switching over to displaying the mean -// instead of latest value -const S32 MAX_RAPID_CHANGES_PER_SEC = 10; -// period of time over which to measure rapid changes -const F32Seconds RAPID_CHANGE_WINDOW(1.f); - -F32 calc_tick_value(F32 min, F32 max) -{ - F32 range = max - min; - const S32 DIVISORS[] = {6, 8, 10, 4, 5}; - // try storing - S32 best_decimal_digit_count = S32_MAX; - S32 best_divisor = 10; - for (U32 divisor_idx = 0; divisor_idx < LL_ARRAY_SIZE(DIVISORS); divisor_idx++) - { - S32 divisor = DIVISORS[divisor_idx]; - F32 possible_tick_value = range / divisor; - S32 num_whole_digits = llceil(logf(llabs(min + possible_tick_value)) * OO_LN10); - for (S32 digit_count = -(num_whole_digits - 1); digit_count < 6; digit_count++) - { - F32 test_tick_value = min + (possible_tick_value * pow(10.0, digit_count)); - - if (is_approx_equal((F32)(S32)test_tick_value, test_tick_value)) - { - if (digit_count < best_decimal_digit_count) - { - best_decimal_digit_count = digit_count; - best_divisor = divisor; - } - break; - } - } - } - - return is_approx_equal(range, 0.f) ? 0.f : range / best_divisor; -} - -void calc_auto_scale_range(F32& min, F32& max, F32& tick) -{ - min = llmin(0.f, min, max); - max = llmax(0.f, min, max); - - const F32 RANGES[] = {0.f, 1.f, 1.5f, 2.f, 3.f, 5.f, 10.f}; - const F32 TICKS[] = {0.f, 0.25f, 0.5f, 1.f, 1.f, 1.f, 2.f }; - - const S32 num_digits_max = is_approx_equal(llabs(max), 0.f) - ? S32_MIN + 1 - : llceil(logf(llabs(max)) * OO_LN10); - const S32 num_digits_min = is_approx_equal(llabs(min), 0.f) - ? S32_MIN + 1 - : llceil(logf(llabs(min)) * OO_LN10); - - const S32 num_digits = llmax(num_digits_max, num_digits_min); - const F32 power_of_10 = pow(10.0, num_digits - 1); - const F32 starting_max = power_of_10 * ((max < 0.f) ? -1 : 1); - const F32 starting_min = power_of_10 * ((min < 0.f) ? -1 : 1); - - F32 cur_max = starting_max; - F32 cur_min = starting_min; - F32 out_max = max; - F32 out_min = min; - - F32 cur_tick_min = 0.f; - F32 cur_tick_max = 0.f; - - for (S32 range_idx = 0; range_idx < LL_ARRAY_SIZE(RANGES); range_idx++) - { - cur_max = starting_max * RANGES[range_idx]; - cur_min = starting_min * RANGES[range_idx]; - - if (min > 0.f && cur_min <= min) - { - out_min = cur_min; - cur_tick_min = TICKS[range_idx]; - } - if (max < 0.f && cur_max >= max) - { - out_max = cur_max; - cur_tick_max = TICKS[range_idx]; - } - } - - cur_max = starting_max; - cur_min = starting_min; - for (S32 range_idx = LL_ARRAY_SIZE(RANGES) - 1; range_idx >= 0; range_idx--) - { - cur_max = starting_max * RANGES[range_idx]; - cur_min = starting_min * RANGES[range_idx]; - - if (min < 0.f && cur_min <= min) - { - out_min = cur_min; - cur_tick_min = TICKS[range_idx]; - } - if (max > 0.f && cur_max >= max) - { - out_max = cur_max; - cur_tick_max = TICKS[range_idx]; - } - } - - tick = power_of_10 * llmax(cur_tick_min, cur_tick_max); - min = out_min; - max = out_max; -} - -LLStatBar::Params::Params() -: label("label"), - unit_label("unit_label"), - bar_min("bar_min", 0.f), - bar_max("bar_max", 0.f), - tick_spacing("tick_spacing", 0.f), - decimal_digits("decimal_digits", 3), - show_bar("show_bar", false), - show_median("show_median", false), - show_history("show_history", false), - scale_range("scale_range", true), - num_frames("num_frames", 200), - num_frames_short("num_frames_short", 20), - max_height("max_height", 100), - stat("stat"), - orientation("orientation", VERTICAL) -{ - changeDefault(follows.flags, FOLLOWS_TOP | FOLLOWS_LEFT); -} - -/////////////////////////////////////////////////////////////////////////////////// - -LLStatBar::LLStatBar(const Params& p) -: LLView(p), - mLabel(p.label), - mUnitLabel(p.unit_label), - mTargetMinBar(llmin(p.bar_min, p.bar_max)), - mTargetMaxBar(llmax(p.bar_max, p.bar_min)), - mCurMaxBar(p.bar_max), - mCurMinBar(0), - mDecimalDigits(p.decimal_digits), - mNumHistoryFrames(p.num_frames), - mNumShortHistoryFrames(p.num_frames_short), - mMaxHeight(p.max_height), - mDisplayBar(p.show_bar), - mShowMedian(p.show_median), - mDisplayHistory(p.show_history), - mOrientation(p.orientation), - mAutoScaleMax(!p.bar_max.isProvided()), - mAutoScaleMin(!p.bar_min.isProvided()), - mTickSpacing(p.tick_spacing), - mLastDisplayValue(0.f), - mStatType(STAT_NONE) -{ - mFloatingTargetMinBar = mTargetMinBar; - mFloatingTargetMaxBar = mTargetMaxBar; - - mStat.valid = NULL; - // tick value will be automatically calculated later - if (!p.tick_spacing.isProvided() && p.bar_min.isProvided() && p.bar_max.isProvided()) - { - mTickSpacing = calc_tick_value(mTargetMinBar, mTargetMaxBar); - } - - setStat(p.stat); -} - -bool LLStatBar::handleHover(S32 x, S32 y, MASK mask) -{ - switch(mStatType) - { - case STAT_COUNT: - LLToolTipMgr::instance().show(LLToolTip::Params().message(mStat.countStatp->getDescription()).sticky_rect(calcScreenRect())); - break; - case STAT_EVENT: - LLToolTipMgr::instance().show(LLToolTip::Params().message(mStat.eventStatp->getDescription()).sticky_rect(calcScreenRect())); - break; - case STAT_SAMPLE: - LLToolTipMgr::instance().show(LLToolTip::Params().message(mStat.sampleStatp->getDescription()).sticky_rect(calcScreenRect())); - break; - default: - break; - } - return true; -} - -bool LLStatBar::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = LLView::handleMouseDown(x, y, mask); - if (!handled) - { - if (mDisplayBar) - { - if (mDisplayHistory || mOrientation == HORIZONTAL) - { - mDisplayBar = false; - mDisplayHistory = false; - } - else - { - mDisplayHistory = true; - } - } - else - { - mDisplayBar = true; - if (mOrientation == HORIZONTAL) - { - mDisplayHistory = true; - } - } - LLView* parent = getParent(); - parent->reshape(parent->getRect().getWidth(), parent->getRect().getHeight(), false); - } - return true; -} - -template -S32 calc_num_rapid_changes(LLTrace::PeriodicRecording& periodic_recording, const T& stat, const F32Seconds time_period) -{ - F32Seconds elapsed_time, - time_since_value_changed; - S32 num_rapid_changes = 0; - const F32Seconds RAPID_CHANGE_THRESHOLD = F32Seconds(0.3f); - F64 last_value = periodic_recording.getPrevRecording(1).getLastValue(stat); - - for (S32 i = 2; i < periodic_recording.getNumRecordedPeriods(); i++) - { - LLTrace::Recording& recording = periodic_recording.getPrevRecording(i); - F64 cur_value = recording.getLastValue(stat); - - if (last_value != cur_value) - { - if (time_since_value_changed < RAPID_CHANGE_THRESHOLD) num_rapid_changes++; - time_since_value_changed = (F32Seconds)0; - } - last_value = cur_value; - - elapsed_time += recording.getDuration(); - if (elapsed_time > time_period) break; - } - - return num_rapid_changes; -} - -void LLStatBar::draw() -{ - LLLocalClipRect _(getLocalRect()); - - LLTrace::PeriodicRecording& frame_recording = LLTrace::get_frame_recording(); - LLTrace::Recording& last_frame_recording = frame_recording.getLastRecording(); - - std::string unit_label; - F32 current = 0, - min = 0, - max = 0, - mean = 0, - display_value = 0; - S32 num_frames = mDisplayHistory - ? mNumHistoryFrames - : mNumShortHistoryFrames; - S32 num_rapid_changes = 0; - S32 decimal_digits = mDecimalDigits; - - switch(mStatType) - { - case STAT_COUNT: - { - const LLTrace::StatType& count_stat = *mStat.countStatp; - - unit_label = std::string(count_stat.getUnitLabel()) + "/s"; - current = last_frame_recording.getPerSec(count_stat); - min = frame_recording.getPeriodMinPerSec(count_stat, num_frames); - max = frame_recording.getPeriodMaxPerSec(count_stat, num_frames); - mean = frame_recording.getPeriodMeanPerSec(count_stat, num_frames); - if (mShowMedian) - { - display_value = frame_recording.getPeriodMedianPerSec(count_stat, num_frames); - } - else - { - display_value = mean; - } - } - break; - case STAT_EVENT: - { - const LLTrace::StatType& event_stat = *mStat.eventStatp; - - unit_label = mUnitLabel.empty() ? event_stat.getUnitLabel() : mUnitLabel; - current = last_frame_recording.getLastValue(event_stat); - min = frame_recording.getPeriodMin(event_stat, num_frames); - max = frame_recording.getPeriodMax(event_stat, num_frames); - mean = frame_recording.getPeriodMean(event_stat, num_frames); - display_value = mean; - } - break; - case STAT_SAMPLE: - { - const LLTrace::StatType& sample_stat = *mStat.sampleStatp; - - unit_label = mUnitLabel.empty() ? sample_stat.getUnitLabel() : mUnitLabel; - current = last_frame_recording.getLastValue(sample_stat); - min = frame_recording.getPeriodMin(sample_stat, num_frames); - max = frame_recording.getPeriodMax(sample_stat, num_frames); - mean = frame_recording.getPeriodMean(sample_stat, num_frames); - num_rapid_changes = calc_num_rapid_changes(frame_recording, sample_stat, RAPID_CHANGE_WINDOW); - - if (mShowMedian) - { - display_value = frame_recording.getPeriodMedian(sample_stat, num_frames); - } - else if (num_rapid_changes / RAPID_CHANGE_WINDOW.value() > MAX_RAPID_CHANGES_PER_SEC) - { - display_value = mean; - } - else - { - display_value = current; - // always display current value, don't rate limit - mLastDisplayValue = current; - if (is_approx_equal((F32)(S32)display_value, display_value)) - { - decimal_digits = 0; - } - } - } - break; - default: - break; - } - - LLRect bar_rect; - if (mOrientation == HORIZONTAL) - { - bar_rect.mTop = llmax(5, getRect().getHeight() - 15); - bar_rect.mLeft = 0; - bar_rect.mRight = getRect().getWidth() - 40; - bar_rect.mBottom = llmin(bar_rect.mTop - 5, 0); - } - else // VERTICAL - { - bar_rect.mTop = llmax(5, getRect().getHeight() - 15); - bar_rect.mLeft = 0; - bar_rect.mRight = getRect().getWidth(); - bar_rect.mBottom = llmin(bar_rect.mTop - 5, 20); - } - - mCurMaxBar = LLSmoothInterpolation::lerp(mCurMaxBar, mTargetMaxBar, 0.05f); - mCurMinBar = LLSmoothInterpolation::lerp(mCurMinBar, mTargetMinBar, 0.05f); - - // rate limited updates - if (mLastDisplayValueTimer.getElapsedTimeF32() < MEAN_VALUE_UPDATE_TIME) - { - display_value = mLastDisplayValue; - } - else - { - mLastDisplayValueTimer.reset(); - } - drawLabelAndValue(display_value, unit_label, bar_rect, decimal_digits); - mLastDisplayValue = display_value; - - if (mDisplayBar && mStat.valid) - { - // Draw the tick marks. - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - F32 value_scale; - if (mCurMaxBar == mCurMinBar) - { - value_scale = 0.f; - } - else - { - value_scale = (mOrientation == HORIZONTAL) - ? (bar_rect.getHeight())/(mCurMaxBar - mCurMinBar) - : (bar_rect.getWidth())/(mCurMaxBar - mCurMinBar); - } - - drawTicks(min, max, value_scale, bar_rect); - - // draw background bar. - gl_rect_2d(bar_rect.mLeft, bar_rect.mTop, bar_rect.mRight, bar_rect.mBottom, LLColor4(0.f, 0.f, 0.f, 0.25f)); - - // draw values - if (!llisnan(display_value) && frame_recording.getNumRecordedPeriods() != 0) - { - // draw min and max - S32 begin = (S32) ((min - mCurMinBar) * value_scale); - - if (begin < 0) - { - begin = 0; - } - - S32 end = (S32) ((max - mCurMinBar) * value_scale); - if (mOrientation == HORIZONTAL) - { - gl_rect_2d(bar_rect.mLeft, end, bar_rect.mRight, begin, LLColor4(1.f, 0.f, 0.f, 0.25f)); - } - else // VERTICAL - { - gl_rect_2d(begin, bar_rect.mTop, end, bar_rect.mBottom, LLColor4(1.f, 0.f, 0.f, 0.25f)); - } - - F32 span = (mOrientation == HORIZONTAL) - ? (bar_rect.getWidth()) - : (bar_rect.getHeight()); - - if (mDisplayHistory && mStat.valid) - { - const S32 num_values = frame_recording.getNumRecordedPeriods() - 1; - F32 min_value = 0.f, - max_value = 0.f; - - gGL.color4f(1.f, 0.f, 0.f, 1.f); - gGL.begin( LLRender::QUADS ); - const S32 max_frame = llmin(num_frames, num_values); - U32 num_samples = 0; - for (S32 i = 1; i <= max_frame; i++) - { - F32 offset = ((F32)i / (F32)num_frames) * span; - LLTrace::Recording& recording = frame_recording.getPrevRecording(i); - - switch(mStatType) - { - case STAT_COUNT: - min_value = recording.getPerSec(*mStat.countStatp); - max_value = min_value; - num_samples = recording.getSampleCount(*mStat.countStatp); - break; - case STAT_EVENT: - min_value = recording.getMin(*mStat.eventStatp); - max_value = recording.getMax(*mStat.eventStatp); - num_samples = recording.getSampleCount(*mStat.eventStatp); - break; - case STAT_SAMPLE: - min_value = recording.getMin(*mStat.sampleStatp); - max_value = recording.getMax(*mStat.sampleStatp); - num_samples = recording.getSampleCount(*mStat.sampleStatp); - break; - default: - break; - } - - if (!num_samples) continue; - - F32 min = (min_value - mCurMinBar) * value_scale; - F32 max = llmax(min + 1, (max_value - mCurMinBar) * value_scale); - if (mOrientation == HORIZONTAL) - { - gGL.vertex2f((F32)bar_rect.mRight - offset, max); - gGL.vertex2f((F32)bar_rect.mRight - offset, min); - gGL.vertex2f((F32)bar_rect.mRight - offset - 1, min); - gGL.vertex2f((F32)bar_rect.mRight - offset - 1, max); - } - else - { - gGL.vertex2f(min, (F32)bar_rect.mBottom + offset + 1); - gGL.vertex2f(min, (F32)bar_rect.mBottom + offset); - gGL.vertex2f(max, (F32)bar_rect.mBottom + offset); - gGL.vertex2f(max, (F32)bar_rect.mBottom + offset + 1 ); - } - } - gGL.end(); - } - else - { - S32 begin = (S32) ((current - mCurMinBar) * value_scale) - 1; - S32 end = (S32) ((current - mCurMinBar) * value_scale) + 1; - // draw current - if (mOrientation == HORIZONTAL) - { - gl_rect_2d(bar_rect.mLeft, end, bar_rect.mRight, begin, LLColor4(1.f, 0.f, 0.f, 1.f)); - } - else - { - gl_rect_2d(begin, bar_rect.mTop, end, bar_rect.mBottom, LLColor4(1.f, 0.f, 0.f, 1.f)); - } - } - - // draw mean bar - { - const S32 begin = (S32) ((mean - mCurMinBar) * value_scale) - 1; - const S32 end = (S32) ((mean - mCurMinBar) * value_scale) + 1; - if (mOrientation == HORIZONTAL) - { - gl_rect_2d(bar_rect.mLeft - 2, begin, bar_rect.mRight + 2, end, LLColor4(0.f, 1.f, 0.f, 1.f)); - } - else - { - gl_rect_2d(begin, bar_rect.mTop + 2, end, bar_rect.mBottom - 2, LLColor4(0.f, 1.f, 0.f, 1.f)); - } - } - } - } - - LLView::draw(); -} - -void LLStatBar::setStat(const std::string& stat_name) -{ - using namespace LLTrace; - - if (auto count_stat = StatType::getInstance(stat_name)) - { - mStat.countStatp = count_stat.get(); - mStatType = STAT_COUNT; - } - else if (auto event_stat = StatType::getInstance(stat_name)) - { - mStat.eventStatp = event_stat.get(); - mStatType = STAT_EVENT; - } - else if (auto sample_stat = StatType::getInstance(stat_name)) - { - mStat.sampleStatp = sample_stat.get(); - mStatType = STAT_SAMPLE; - } -} - -void LLStatBar::setRange(F32 bar_min, F32 bar_max) -{ - mTargetMinBar = llmin(bar_min, bar_max); - mTargetMaxBar = llmax(bar_min, bar_max); - mFloatingTargetMinBar = mTargetMinBar; - mFloatingTargetMaxBar = mTargetMaxBar; - mTickSpacing = calc_tick_value(mTargetMinBar, mTargetMaxBar); -} - -LLRect LLStatBar::getRequiredRect() -{ - LLRect rect; - - if (mDisplayBar) - { - if (mDisplayHistory) - { - rect.mTop = mMaxHeight; - } - else - { - rect.mTop = 40; - } - } - else - { - rect.mTop = 14; - } - return rect; -} - -void LLStatBar::drawLabelAndValue( F32 value, std::string &label, LLRect &bar_rect, S32 decimal_digits ) -{ - LLFontGL::getFontMonospace()->renderUTF8(mLabel, 0, 0, getRect().getHeight(), LLColor4(1.f, 1.f, 1.f, 1.f), - LLFontGL::LEFT, LLFontGL::TOP); - - std::string value_str = !llisnan(value) - ? llformat("%10.*f %s", decimal_digits, value, label.c_str()) - : LLTrans::getString("na"); - - // Draw the current value. - if (mOrientation == HORIZONTAL) - { - LLFontGL::getFontMonospace()->renderUTF8(value_str, 0, bar_rect.mRight, getRect().getHeight(), - LLColor4(1.f, 1.f, 1.f, 1.f), - LLFontGL::RIGHT, LLFontGL::TOP); - } - else - { - LLFontGL::getFontMonospace()->renderUTF8(value_str, 0, bar_rect.mRight, getRect().getHeight(), - LLColor4(1.f, 1.f, 1.f, 1.f), - LLFontGL::RIGHT, LLFontGL::TOP); - } -} - -void LLStatBar::drawTicks( F32 min, F32 max, F32 value_scale, LLRect &bar_rect ) -{ - if (!llisnan(min) && (mAutoScaleMax || mAutoScaleMin)) - { - F32 u = LLSmoothInterpolation::getInterpolant(10.f); - mFloatingTargetMinBar = llmin(min, lerp(mFloatingTargetMinBar, min, u)); - mFloatingTargetMaxBar = llmax(max, lerp(mFloatingTargetMaxBar, max, u)); - F32 range_min = mAutoScaleMin ? mFloatingTargetMinBar : mTargetMinBar; - F32 range_max = mAutoScaleMax ? mFloatingTargetMaxBar : mTargetMaxBar; - F32 tick_value = 0.f; - calc_auto_scale_range(range_min, range_max, tick_value); - if (mAutoScaleMin) { mTargetMinBar = range_min; } - if (mAutoScaleMax) { mTargetMaxBar = range_max; } - if (mAutoScaleMin && mAutoScaleMax) - { - mTickSpacing = tick_value; - } - else - { - mTickSpacing = calc_tick_value(mTargetMinBar, mTargetMaxBar); - } - } - - // start counting from actual min, not current, animating min, so that ticks don't float between numbers - // ensure ticks always hit 0 - S32 last_tick = S32_MIN; - S32 last_label = S32_MIN; - if (mTickSpacing > 0.f && value_scale > 0.f) - { - const S32 MIN_TICK_SPACING = mOrientation == HORIZONTAL ? 20 : 30; - const S32 MIN_LABEL_SPACING = mOrientation == HORIZONTAL ? 30 : 60; - const S32 TICK_LENGTH = 4; - const S32 TICK_WIDTH = 1; - - F32 start = mCurMinBar < 0.f - ? llceil(-mCurMinBar / mTickSpacing) * -mTickSpacing - : 0.f; - for (F32 tick_value = start; ;tick_value += mTickSpacing) - { - // clamp to S32_MAX / 2 to avoid floating point to integer overflow resulting in S32_MIN - const S32 tick_begin = llfloor(llmin((F32)(S32_MAX / 2), (tick_value - mCurMinBar)*value_scale)); - const S32 tick_end = tick_begin + TICK_WIDTH; - if (tick_begin < last_tick + MIN_TICK_SPACING) - { - continue; - } - last_tick = tick_begin; - - S32 decimal_digits = mDecimalDigits; - if (is_approx_equal((F32)(S32)tick_value, tick_value)) - { - decimal_digits = 0; - } - std::string tick_label = llformat("%.*f", decimal_digits, tick_value); - S32 tick_label_width = LLFontGL::getFontMonospace()->getWidth(tick_label); - if (mOrientation == HORIZONTAL) - { - if (tick_begin > last_label + MIN_LABEL_SPACING) - { - gl_rect_2d(bar_rect.mLeft, tick_end, bar_rect.mRight - TICK_LENGTH, tick_begin, LLColor4(1.f, 1.f, 1.f, 0.25f)); - LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, bar_rect.mRight, tick_begin, - LLColor4(1.f, 1.f, 1.f, 0.5f), - LLFontGL::LEFT, LLFontGL::VCENTER); - last_label = tick_begin; - } - else - { - gl_rect_2d(bar_rect.mLeft, tick_end, bar_rect.mRight - TICK_LENGTH/2, tick_begin, LLColor4(1.f, 1.f, 1.f, 0.1f)); - } - } - else - { - if (tick_begin > last_label + MIN_LABEL_SPACING) - { - gl_rect_2d(tick_begin, bar_rect.mTop, tick_end, bar_rect.mBottom - TICK_LENGTH, LLColor4(1.f, 1.f, 1.f, 0.25f)); - S32 label_pos = tick_begin - ll_round((F32)tick_label_width * ((F32)tick_begin / (F32)bar_rect.getWidth())); - LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, label_pos, bar_rect.mBottom - TICK_LENGTH, - LLColor4(1.f, 1.f, 1.f, 0.5f), - LLFontGL::LEFT, LLFontGL::TOP); - last_label = label_pos; - } - else - { - gl_rect_2d(tick_begin, bar_rect.mTop, tick_end, bar_rect.mBottom - TICK_LENGTH/2, LLColor4(1.f, 1.f, 1.f, 0.1f)); - } - } - // always draw one tick value past tick_end, so we can see part of the text, if possible - if (tick_value > mCurMaxBar) - { - break; - } - } - } -} +/** + * @file llstatbar.cpp + * @brief A little map of the world with network information + * + * $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 "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "llstatbar.h" + +#include "llmath.h" +#include "llui.h" +#include "llgl.h" +#include "llfontgl.h" + +#include "lluictrlfactory.h" +#include "lltracerecording.h" +#include "llcriticaldamp.h" +#include "lltooltip.h" +#include "lllocalcliprect.h" +#include +#include "lltrans.h" + +// rate at which to update display of value that is rapidly changing +const F32 MEAN_VALUE_UPDATE_TIME = 1.f / 4.f; +// time between value changes that qualifies as a "rapid change" +const F32Seconds RAPID_CHANGE_THRESHOLD(0.2f); +// maximum number of rapid changes in RAPID_CHANGE_WINDOW before switching over to displaying the mean +// instead of latest value +const S32 MAX_RAPID_CHANGES_PER_SEC = 10; +// period of time over which to measure rapid changes +const F32Seconds RAPID_CHANGE_WINDOW(1.f); + +F32 calc_tick_value(F32 min, F32 max) +{ + F32 range = max - min; + const S32 DIVISORS[] = {6, 8, 10, 4, 5}; + // try storing + S32 best_decimal_digit_count = S32_MAX; + S32 best_divisor = 10; + for (U32 divisor_idx = 0; divisor_idx < LL_ARRAY_SIZE(DIVISORS); divisor_idx++) + { + S32 divisor = DIVISORS[divisor_idx]; + F32 possible_tick_value = range / divisor; + S32 num_whole_digits = llceil(logf(llabs(min + possible_tick_value)) * OO_LN10); + for (S32 digit_count = -(num_whole_digits - 1); digit_count < 6; digit_count++) + { + F32 test_tick_value = min + (possible_tick_value * pow(10.0, digit_count)); + + if (is_approx_equal((F32)(S32)test_tick_value, test_tick_value)) + { + if (digit_count < best_decimal_digit_count) + { + best_decimal_digit_count = digit_count; + best_divisor = divisor; + } + break; + } + } + } + + return is_approx_equal(range, 0.f) ? 0.f : range / best_divisor; +} + +void calc_auto_scale_range(F32& min, F32& max, F32& tick) +{ + min = llmin(0.f, min, max); + max = llmax(0.f, min, max); + + const F32 RANGES[] = {0.f, 1.f, 1.5f, 2.f, 3.f, 5.f, 10.f}; + const F32 TICKS[] = {0.f, 0.25f, 0.5f, 1.f, 1.f, 1.f, 2.f }; + + const S32 num_digits_max = is_approx_equal(llabs(max), 0.f) + ? S32_MIN + 1 + : llceil(logf(llabs(max)) * OO_LN10); + const S32 num_digits_min = is_approx_equal(llabs(min), 0.f) + ? S32_MIN + 1 + : llceil(logf(llabs(min)) * OO_LN10); + + const S32 num_digits = llmax(num_digits_max, num_digits_min); + const F32 power_of_10 = pow(10.0, num_digits - 1); + const F32 starting_max = power_of_10 * ((max < 0.f) ? -1 : 1); + const F32 starting_min = power_of_10 * ((min < 0.f) ? -1 : 1); + + F32 cur_max = starting_max; + F32 cur_min = starting_min; + F32 out_max = max; + F32 out_min = min; + + F32 cur_tick_min = 0.f; + F32 cur_tick_max = 0.f; + + for (S32 range_idx = 0; range_idx < LL_ARRAY_SIZE(RANGES); range_idx++) + { + cur_max = starting_max * RANGES[range_idx]; + cur_min = starting_min * RANGES[range_idx]; + + if (min > 0.f && cur_min <= min) + { + out_min = cur_min; + cur_tick_min = TICKS[range_idx]; + } + if (max < 0.f && cur_max >= max) + { + out_max = cur_max; + cur_tick_max = TICKS[range_idx]; + } + } + + cur_max = starting_max; + cur_min = starting_min; + for (S32 range_idx = LL_ARRAY_SIZE(RANGES) - 1; range_idx >= 0; range_idx--) + { + cur_max = starting_max * RANGES[range_idx]; + cur_min = starting_min * RANGES[range_idx]; + + if (min < 0.f && cur_min <= min) + { + out_min = cur_min; + cur_tick_min = TICKS[range_idx]; + } + if (max > 0.f && cur_max >= max) + { + out_max = cur_max; + cur_tick_max = TICKS[range_idx]; + } + } + + tick = power_of_10 * llmax(cur_tick_min, cur_tick_max); + min = out_min; + max = out_max; +} + +LLStatBar::Params::Params() +: label("label"), + unit_label("unit_label"), + bar_min("bar_min", 0.f), + bar_max("bar_max", 0.f), + tick_spacing("tick_spacing", 0.f), + decimal_digits("decimal_digits", 3), + show_bar("show_bar", false), + show_median("show_median", false), + show_history("show_history", false), + scale_range("scale_range", true), + num_frames("num_frames", 200), + num_frames_short("num_frames_short", 20), + max_height("max_height", 100), + stat("stat"), + orientation("orientation", VERTICAL) +{ + changeDefault(follows.flags, FOLLOWS_TOP | FOLLOWS_LEFT); +} + +/////////////////////////////////////////////////////////////////////////////////// + +LLStatBar::LLStatBar(const Params& p) +: LLView(p), + mLabel(p.label), + mUnitLabel(p.unit_label), + mTargetMinBar(llmin(p.bar_min, p.bar_max)), + mTargetMaxBar(llmax(p.bar_max, p.bar_min)), + mCurMaxBar(p.bar_max), + mCurMinBar(0), + mDecimalDigits(p.decimal_digits), + mNumHistoryFrames(p.num_frames), + mNumShortHistoryFrames(p.num_frames_short), + mMaxHeight(p.max_height), + mDisplayBar(p.show_bar), + mShowMedian(p.show_median), + mDisplayHistory(p.show_history), + mOrientation(p.orientation), + mAutoScaleMax(!p.bar_max.isProvided()), + mAutoScaleMin(!p.bar_min.isProvided()), + mTickSpacing(p.tick_spacing), + mLastDisplayValue(0.f), + mStatType(STAT_NONE) +{ + mFloatingTargetMinBar = mTargetMinBar; + mFloatingTargetMaxBar = mTargetMaxBar; + + mStat.valid = NULL; + // tick value will be automatically calculated later + if (!p.tick_spacing.isProvided() && p.bar_min.isProvided() && p.bar_max.isProvided()) + { + mTickSpacing = calc_tick_value(mTargetMinBar, mTargetMaxBar); + } + + setStat(p.stat); +} + +bool LLStatBar::handleHover(S32 x, S32 y, MASK mask) +{ + switch(mStatType) + { + case STAT_COUNT: + LLToolTipMgr::instance().show(LLToolTip::Params().message(mStat.countStatp->getDescription()).sticky_rect(calcScreenRect())); + break; + case STAT_EVENT: + LLToolTipMgr::instance().show(LLToolTip::Params().message(mStat.eventStatp->getDescription()).sticky_rect(calcScreenRect())); + break; + case STAT_SAMPLE: + LLToolTipMgr::instance().show(LLToolTip::Params().message(mStat.sampleStatp->getDescription()).sticky_rect(calcScreenRect())); + break; + default: + break; + } + return true; +} + +bool LLStatBar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLView::handleMouseDown(x, y, mask); + if (!handled) + { + if (mDisplayBar) + { + if (mDisplayHistory || mOrientation == HORIZONTAL) + { + mDisplayBar = false; + mDisplayHistory = false; + } + else + { + mDisplayHistory = true; + } + } + else + { + mDisplayBar = true; + if (mOrientation == HORIZONTAL) + { + mDisplayHistory = true; + } + } + LLView* parent = getParent(); + parent->reshape(parent->getRect().getWidth(), parent->getRect().getHeight(), false); + } + return true; +} + +template +S32 calc_num_rapid_changes(LLTrace::PeriodicRecording& periodic_recording, const T& stat, const F32Seconds time_period) +{ + F32Seconds elapsed_time, + time_since_value_changed; + S32 num_rapid_changes = 0; + const F32Seconds RAPID_CHANGE_THRESHOLD = F32Seconds(0.3f); + F64 last_value = periodic_recording.getPrevRecording(1).getLastValue(stat); + + for (S32 i = 2; i < periodic_recording.getNumRecordedPeriods(); i++) + { + LLTrace::Recording& recording = periodic_recording.getPrevRecording(i); + F64 cur_value = recording.getLastValue(stat); + + if (last_value != cur_value) + { + if (time_since_value_changed < RAPID_CHANGE_THRESHOLD) num_rapid_changes++; + time_since_value_changed = (F32Seconds)0; + } + last_value = cur_value; + + elapsed_time += recording.getDuration(); + if (elapsed_time > time_period) break; + } + + return num_rapid_changes; +} + +void LLStatBar::draw() +{ + LLLocalClipRect _(getLocalRect()); + + LLTrace::PeriodicRecording& frame_recording = LLTrace::get_frame_recording(); + LLTrace::Recording& last_frame_recording = frame_recording.getLastRecording(); + + std::string unit_label; + F32 current = 0, + min = 0, + max = 0, + mean = 0, + display_value = 0; + S32 num_frames = mDisplayHistory + ? mNumHistoryFrames + : mNumShortHistoryFrames; + S32 num_rapid_changes = 0; + S32 decimal_digits = mDecimalDigits; + + switch(mStatType) + { + case STAT_COUNT: + { + const LLTrace::StatType& count_stat = *mStat.countStatp; + + unit_label = std::string(count_stat.getUnitLabel()) + "/s"; + current = last_frame_recording.getPerSec(count_stat); + min = frame_recording.getPeriodMinPerSec(count_stat, num_frames); + max = frame_recording.getPeriodMaxPerSec(count_stat, num_frames); + mean = frame_recording.getPeriodMeanPerSec(count_stat, num_frames); + if (mShowMedian) + { + display_value = frame_recording.getPeriodMedianPerSec(count_stat, num_frames); + } + else + { + display_value = mean; + } + } + break; + case STAT_EVENT: + { + const LLTrace::StatType& event_stat = *mStat.eventStatp; + + unit_label = mUnitLabel.empty() ? event_stat.getUnitLabel() : mUnitLabel; + current = last_frame_recording.getLastValue(event_stat); + min = frame_recording.getPeriodMin(event_stat, num_frames); + max = frame_recording.getPeriodMax(event_stat, num_frames); + mean = frame_recording.getPeriodMean(event_stat, num_frames); + display_value = mean; + } + break; + case STAT_SAMPLE: + { + const LLTrace::StatType& sample_stat = *mStat.sampleStatp; + + unit_label = mUnitLabel.empty() ? sample_stat.getUnitLabel() : mUnitLabel; + current = last_frame_recording.getLastValue(sample_stat); + min = frame_recording.getPeriodMin(sample_stat, num_frames); + max = frame_recording.getPeriodMax(sample_stat, num_frames); + mean = frame_recording.getPeriodMean(sample_stat, num_frames); + num_rapid_changes = calc_num_rapid_changes(frame_recording, sample_stat, RAPID_CHANGE_WINDOW); + + if (mShowMedian) + { + display_value = frame_recording.getPeriodMedian(sample_stat, num_frames); + } + else if (num_rapid_changes / RAPID_CHANGE_WINDOW.value() > MAX_RAPID_CHANGES_PER_SEC) + { + display_value = mean; + } + else + { + display_value = current; + // always display current value, don't rate limit + mLastDisplayValue = current; + if (is_approx_equal((F32)(S32)display_value, display_value)) + { + decimal_digits = 0; + } + } + } + break; + default: + break; + } + + LLRect bar_rect; + if (mOrientation == HORIZONTAL) + { + bar_rect.mTop = llmax(5, getRect().getHeight() - 15); + bar_rect.mLeft = 0; + bar_rect.mRight = getRect().getWidth() - 40; + bar_rect.mBottom = llmin(bar_rect.mTop - 5, 0); + } + else // VERTICAL + { + bar_rect.mTop = llmax(5, getRect().getHeight() - 15); + bar_rect.mLeft = 0; + bar_rect.mRight = getRect().getWidth(); + bar_rect.mBottom = llmin(bar_rect.mTop - 5, 20); + } + + mCurMaxBar = LLSmoothInterpolation::lerp(mCurMaxBar, mTargetMaxBar, 0.05f); + mCurMinBar = LLSmoothInterpolation::lerp(mCurMinBar, mTargetMinBar, 0.05f); + + // rate limited updates + if (mLastDisplayValueTimer.getElapsedTimeF32() < MEAN_VALUE_UPDATE_TIME) + { + display_value = mLastDisplayValue; + } + else + { + mLastDisplayValueTimer.reset(); + } + drawLabelAndValue(display_value, unit_label, bar_rect, decimal_digits); + mLastDisplayValue = display_value; + + if (mDisplayBar && mStat.valid) + { + // Draw the tick marks. + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + F32 value_scale; + if (mCurMaxBar == mCurMinBar) + { + value_scale = 0.f; + } + else + { + value_scale = (mOrientation == HORIZONTAL) + ? (bar_rect.getHeight())/(mCurMaxBar - mCurMinBar) + : (bar_rect.getWidth())/(mCurMaxBar - mCurMinBar); + } + + drawTicks(min, max, value_scale, bar_rect); + + // draw background bar. + gl_rect_2d(bar_rect.mLeft, bar_rect.mTop, bar_rect.mRight, bar_rect.mBottom, LLColor4(0.f, 0.f, 0.f, 0.25f)); + + // draw values + if (!llisnan(display_value) && frame_recording.getNumRecordedPeriods() != 0) + { + // draw min and max + S32 begin = (S32) ((min - mCurMinBar) * value_scale); + + if (begin < 0) + { + begin = 0; + } + + S32 end = (S32) ((max - mCurMinBar) * value_scale); + if (mOrientation == HORIZONTAL) + { + gl_rect_2d(bar_rect.mLeft, end, bar_rect.mRight, begin, LLColor4(1.f, 0.f, 0.f, 0.25f)); + } + else // VERTICAL + { + gl_rect_2d(begin, bar_rect.mTop, end, bar_rect.mBottom, LLColor4(1.f, 0.f, 0.f, 0.25f)); + } + + F32 span = (mOrientation == HORIZONTAL) + ? (bar_rect.getWidth()) + : (bar_rect.getHeight()); + + if (mDisplayHistory && mStat.valid) + { + const S32 num_values = frame_recording.getNumRecordedPeriods() - 1; + F32 min_value = 0.f, + max_value = 0.f; + + gGL.color4f(1.f, 0.f, 0.f, 1.f); + gGL.begin( LLRender::QUADS ); + const S32 max_frame = llmin(num_frames, num_values); + U32 num_samples = 0; + for (S32 i = 1; i <= max_frame; i++) + { + F32 offset = ((F32)i / (F32)num_frames) * span; + LLTrace::Recording& recording = frame_recording.getPrevRecording(i); + + switch(mStatType) + { + case STAT_COUNT: + min_value = recording.getPerSec(*mStat.countStatp); + max_value = min_value; + num_samples = recording.getSampleCount(*mStat.countStatp); + break; + case STAT_EVENT: + min_value = recording.getMin(*mStat.eventStatp); + max_value = recording.getMax(*mStat.eventStatp); + num_samples = recording.getSampleCount(*mStat.eventStatp); + break; + case STAT_SAMPLE: + min_value = recording.getMin(*mStat.sampleStatp); + max_value = recording.getMax(*mStat.sampleStatp); + num_samples = recording.getSampleCount(*mStat.sampleStatp); + break; + default: + break; + } + + if (!num_samples) continue; + + F32 min = (min_value - mCurMinBar) * value_scale; + F32 max = llmax(min + 1, (max_value - mCurMinBar) * value_scale); + if (mOrientation == HORIZONTAL) + { + gGL.vertex2f((F32)bar_rect.mRight - offset, max); + gGL.vertex2f((F32)bar_rect.mRight - offset, min); + gGL.vertex2f((F32)bar_rect.mRight - offset - 1, min); + gGL.vertex2f((F32)bar_rect.mRight - offset - 1, max); + } + else + { + gGL.vertex2f(min, (F32)bar_rect.mBottom + offset + 1); + gGL.vertex2f(min, (F32)bar_rect.mBottom + offset); + gGL.vertex2f(max, (F32)bar_rect.mBottom + offset); + gGL.vertex2f(max, (F32)bar_rect.mBottom + offset + 1 ); + } + } + gGL.end(); + } + else + { + S32 begin = (S32) ((current - mCurMinBar) * value_scale) - 1; + S32 end = (S32) ((current - mCurMinBar) * value_scale) + 1; + // draw current + if (mOrientation == HORIZONTAL) + { + gl_rect_2d(bar_rect.mLeft, end, bar_rect.mRight, begin, LLColor4(1.f, 0.f, 0.f, 1.f)); + } + else + { + gl_rect_2d(begin, bar_rect.mTop, end, bar_rect.mBottom, LLColor4(1.f, 0.f, 0.f, 1.f)); + } + } + + // draw mean bar + { + const S32 begin = (S32) ((mean - mCurMinBar) * value_scale) - 1; + const S32 end = (S32) ((mean - mCurMinBar) * value_scale) + 1; + if (mOrientation == HORIZONTAL) + { + gl_rect_2d(bar_rect.mLeft - 2, begin, bar_rect.mRight + 2, end, LLColor4(0.f, 1.f, 0.f, 1.f)); + } + else + { + gl_rect_2d(begin, bar_rect.mTop + 2, end, bar_rect.mBottom - 2, LLColor4(0.f, 1.f, 0.f, 1.f)); + } + } + } + } + + LLView::draw(); +} + +void LLStatBar::setStat(const std::string& stat_name) +{ + using namespace LLTrace; + + if (auto count_stat = StatType::getInstance(stat_name)) + { + mStat.countStatp = count_stat.get(); + mStatType = STAT_COUNT; + } + else if (auto event_stat = StatType::getInstance(stat_name)) + { + mStat.eventStatp = event_stat.get(); + mStatType = STAT_EVENT; + } + else if (auto sample_stat = StatType::getInstance(stat_name)) + { + mStat.sampleStatp = sample_stat.get(); + mStatType = STAT_SAMPLE; + } +} + +void LLStatBar::setRange(F32 bar_min, F32 bar_max) +{ + mTargetMinBar = llmin(bar_min, bar_max); + mTargetMaxBar = llmax(bar_min, bar_max); + mFloatingTargetMinBar = mTargetMinBar; + mFloatingTargetMaxBar = mTargetMaxBar; + mTickSpacing = calc_tick_value(mTargetMinBar, mTargetMaxBar); +} + +LLRect LLStatBar::getRequiredRect() +{ + LLRect rect; + + if (mDisplayBar) + { + if (mDisplayHistory) + { + rect.mTop = mMaxHeight; + } + else + { + rect.mTop = 40; + } + } + else + { + rect.mTop = 14; + } + return rect; +} + +void LLStatBar::drawLabelAndValue( F32 value, std::string &label, LLRect &bar_rect, S32 decimal_digits ) +{ + LLFontGL::getFontMonospace()->renderUTF8(mLabel, 0, 0, getRect().getHeight(), LLColor4(1.f, 1.f, 1.f, 1.f), + LLFontGL::LEFT, LLFontGL::TOP); + + std::string value_str = !llisnan(value) + ? llformat("%10.*f %s", decimal_digits, value, label.c_str()) + : LLTrans::getString("na"); + + // Draw the current value. + if (mOrientation == HORIZONTAL) + { + LLFontGL::getFontMonospace()->renderUTF8(value_str, 0, bar_rect.mRight, getRect().getHeight(), + LLColor4(1.f, 1.f, 1.f, 1.f), + LLFontGL::RIGHT, LLFontGL::TOP); + } + else + { + LLFontGL::getFontMonospace()->renderUTF8(value_str, 0, bar_rect.mRight, getRect().getHeight(), + LLColor4(1.f, 1.f, 1.f, 1.f), + LLFontGL::RIGHT, LLFontGL::TOP); + } +} + +void LLStatBar::drawTicks( F32 min, F32 max, F32 value_scale, LLRect &bar_rect ) +{ + if (!llisnan(min) && (mAutoScaleMax || mAutoScaleMin)) + { + F32 u = LLSmoothInterpolation::getInterpolant(10.f); + mFloatingTargetMinBar = llmin(min, lerp(mFloatingTargetMinBar, min, u)); + mFloatingTargetMaxBar = llmax(max, lerp(mFloatingTargetMaxBar, max, u)); + F32 range_min = mAutoScaleMin ? mFloatingTargetMinBar : mTargetMinBar; + F32 range_max = mAutoScaleMax ? mFloatingTargetMaxBar : mTargetMaxBar; + F32 tick_value = 0.f; + calc_auto_scale_range(range_min, range_max, tick_value); + if (mAutoScaleMin) { mTargetMinBar = range_min; } + if (mAutoScaleMax) { mTargetMaxBar = range_max; } + if (mAutoScaleMin && mAutoScaleMax) + { + mTickSpacing = tick_value; + } + else + { + mTickSpacing = calc_tick_value(mTargetMinBar, mTargetMaxBar); + } + } + + // start counting from actual min, not current, animating min, so that ticks don't float between numbers + // ensure ticks always hit 0 + S32 last_tick = S32_MIN; + S32 last_label = S32_MIN; + if (mTickSpacing > 0.f && value_scale > 0.f) + { + const S32 MIN_TICK_SPACING = mOrientation == HORIZONTAL ? 20 : 30; + const S32 MIN_LABEL_SPACING = mOrientation == HORIZONTAL ? 30 : 60; + const S32 TICK_LENGTH = 4; + const S32 TICK_WIDTH = 1; + + F32 start = mCurMinBar < 0.f + ? llceil(-mCurMinBar / mTickSpacing) * -mTickSpacing + : 0.f; + for (F32 tick_value = start; ;tick_value += mTickSpacing) + { + // clamp to S32_MAX / 2 to avoid floating point to integer overflow resulting in S32_MIN + const S32 tick_begin = llfloor(llmin((F32)(S32_MAX / 2), (tick_value - mCurMinBar)*value_scale)); + const S32 tick_end = tick_begin + TICK_WIDTH; + if (tick_begin < last_tick + MIN_TICK_SPACING) + { + continue; + } + last_tick = tick_begin; + + S32 decimal_digits = mDecimalDigits; + if (is_approx_equal((F32)(S32)tick_value, tick_value)) + { + decimal_digits = 0; + } + std::string tick_label = llformat("%.*f", decimal_digits, tick_value); + S32 tick_label_width = LLFontGL::getFontMonospace()->getWidth(tick_label); + if (mOrientation == HORIZONTAL) + { + if (tick_begin > last_label + MIN_LABEL_SPACING) + { + gl_rect_2d(bar_rect.mLeft, tick_end, bar_rect.mRight - TICK_LENGTH, tick_begin, LLColor4(1.f, 1.f, 1.f, 0.25f)); + LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, bar_rect.mRight, tick_begin, + LLColor4(1.f, 1.f, 1.f, 0.5f), + LLFontGL::LEFT, LLFontGL::VCENTER); + last_label = tick_begin; + } + else + { + gl_rect_2d(bar_rect.mLeft, tick_end, bar_rect.mRight - TICK_LENGTH/2, tick_begin, LLColor4(1.f, 1.f, 1.f, 0.1f)); + } + } + else + { + if (tick_begin > last_label + MIN_LABEL_SPACING) + { + gl_rect_2d(tick_begin, bar_rect.mTop, tick_end, bar_rect.mBottom - TICK_LENGTH, LLColor4(1.f, 1.f, 1.f, 0.25f)); + S32 label_pos = tick_begin - ll_round((F32)tick_label_width * ((F32)tick_begin / (F32)bar_rect.getWidth())); + LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, label_pos, bar_rect.mBottom - TICK_LENGTH, + LLColor4(1.f, 1.f, 1.f, 0.5f), + LLFontGL::LEFT, LLFontGL::TOP); + last_label = label_pos; + } + else + { + gl_rect_2d(tick_begin, bar_rect.mTop, tick_end, bar_rect.mBottom - TICK_LENGTH/2, LLColor4(1.f, 1.f, 1.f, 0.1f)); + } + } + // always draw one tick value past tick_end, so we can see part of the text, if possible + if (tick_value > mCurMaxBar) + { + break; + } + } + } +} diff --git a/indra/llui/llstatbar.h b/indra/llui/llstatbar.h index 84e9ac6ecb..c36a138566 100644 --- a/indra/llui/llstatbar.h +++ b/indra/llui/llstatbar.h @@ -1,119 +1,119 @@ -/** - * @file llstatbar.h - * @brief A little map of the world with network information - * - * $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$ - */ - -#ifndef LL_LLSTATBAR_H -#define LL_LLSTATBAR_H - -#include "llview.h" -#include "llframetimer.h" -#include "lltracerecording.h" - -class LLStatBar : public LLView -{ -public: - - struct Params : public LLInitParam::Block - { - Optional label, - unit_label; - - Optional bar_min, - bar_max, - tick_spacing; - - Optional show_bar, - show_history, - scale_range, - show_median; // default is mean - - Optional decimal_digits, - num_frames, - num_frames_short, - max_height; - Optional stat; - Optional orientation; - - Params(); - }; - LLStatBar(const Params&); - - virtual void draw(); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - - void setStat(const std::string& stat_name); - - void setRange(F32 bar_min, F32 bar_max); - void getRange(F32& bar_min, F32& bar_max) { bar_min = mTargetMinBar; bar_max = mTargetMaxBar; } - - /*virtual*/ LLRect getRequiredRect(); // Return the height of this object, given the set options. - -private: - void drawLabelAndValue( F32 mean, std::string &unit_label, LLRect &bar_rect, S32 decimal_digits ); - void drawTicks( F32 min, F32 max, F32 value_scale, LLRect &bar_rect ); - - F32 mTargetMinBar, - mTargetMaxBar, - mFloatingTargetMinBar, - mFloatingTargetMaxBar, - mCurMaxBar, - mCurMinBar, - mTickSpacing; - S32 mDecimalDigits, - mNumHistoryFrames, - mNumShortHistoryFrames; - S32 mMaxHeight; - EOrientation mOrientation; - F32 mLastDisplayValue; - LLFrameTimer mLastDisplayValueTimer; - - enum - { - STAT_NONE, - STAT_COUNT, - STAT_EVENT, - STAT_SAMPLE - } mStatType; - - union - { - void* valid; - const LLTrace::StatType* countStatp; - const LLTrace::StatType* eventStatp; - const LLTrace::StatType* sampleStatp; - } mStat; - - LLUIString mLabel; - std::string mUnitLabel; - - bool mDisplayBar, // Display the bar graph. - mDisplayHistory, - mShowMedian, - mAutoScaleMax, - mAutoScaleMin; -}; - -#endif +/** + * @file llstatbar.h + * @brief A little map of the world with network information + * + * $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$ + */ + +#ifndef LL_LLSTATBAR_H +#define LL_LLSTATBAR_H + +#include "llview.h" +#include "llframetimer.h" +#include "lltracerecording.h" + +class LLStatBar : public LLView +{ +public: + + struct Params : public LLInitParam::Block + { + Optional label, + unit_label; + + Optional bar_min, + bar_max, + tick_spacing; + + Optional show_bar, + show_history, + scale_range, + show_median; // default is mean + + Optional decimal_digits, + num_frames, + num_frames_short, + max_height; + Optional stat; + Optional orientation; + + Params(); + }; + LLStatBar(const Params&); + + virtual void draw(); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + + void setStat(const std::string& stat_name); + + void setRange(F32 bar_min, F32 bar_max); + void getRange(F32& bar_min, F32& bar_max) { bar_min = mTargetMinBar; bar_max = mTargetMaxBar; } + + /*virtual*/ LLRect getRequiredRect(); // Return the height of this object, given the set options. + +private: + void drawLabelAndValue( F32 mean, std::string &unit_label, LLRect &bar_rect, S32 decimal_digits ); + void drawTicks( F32 min, F32 max, F32 value_scale, LLRect &bar_rect ); + + F32 mTargetMinBar, + mTargetMaxBar, + mFloatingTargetMinBar, + mFloatingTargetMaxBar, + mCurMaxBar, + mCurMinBar, + mTickSpacing; + S32 mDecimalDigits, + mNumHistoryFrames, + mNumShortHistoryFrames; + S32 mMaxHeight; + EOrientation mOrientation; + F32 mLastDisplayValue; + LLFrameTimer mLastDisplayValueTimer; + + enum + { + STAT_NONE, + STAT_COUNT, + STAT_EVENT, + STAT_SAMPLE + } mStatType; + + union + { + void* valid; + const LLTrace::StatType* countStatp; + const LLTrace::StatType* eventStatp; + const LLTrace::StatType* sampleStatp; + } mStat; + + LLUIString mLabel; + std::string mUnitLabel; + + bool mDisplayBar, // Display the bar graph. + mDisplayHistory, + mShowMedian, + mAutoScaleMax, + mAutoScaleMin; +}; + +#endif diff --git a/indra/llui/llstatgraph.cpp b/indra/llui/llstatgraph.cpp index 58532fb22d..d37f927073 100644 --- a/indra/llui/llstatgraph.cpp +++ b/indra/llui/llstatgraph.cpp @@ -1,126 +1,126 @@ -/** - * @file llstatgraph.cpp - * @brief Simpler compact stat graph with tooltip - * - * $LicenseInfo:firstyear=2002&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 "llviewerprecompiledheaders.h" -#include "linden_common.h" - -#include "llstatgraph.h" -#include "llrender.h" - -#include "llmath.h" -#include "llui.h" -#include "llgl.h" -#include "llglheaders.h" -#include "lltracerecording.h" -#include "lltracethreadrecorder.h" -//#include "llviewercontrol.h" - -/////////////////////////////////////////////////////////////////////////////////// - -LLStatGraph::LLStatGraph(const Params& p) -: LLView(p), - mMin(p.min), - mMax(p.max), - mPerSec(p.per_sec), - mPrecision(p.precision), - mValue(p.value), - mUnits(p.units), - mNewStatFloatp(p.stat.count_stat_float) -{ - setToolTip(p.name()); - - for(LLInitParam::ParamIterator::const_iterator it = p.thresholds.threshold.begin(), end_it = p.thresholds.threshold.end(); - it != end_it; - ++it) - { - mThresholds.push_back(Threshold(it->value(), it->color)); - } -} - -void LLStatGraph::draw() -{ - F32 range, frac; - range = mMax - mMin; - if (mNewStatFloatp) - { - LLTrace::Recording& recording = LLTrace::get_frame_recording().getLastRecording(); - - if (mPerSec) - { - mValue = recording.getPerSec(*mNewStatFloatp); - } - else - { - mValue = recording.getSum(*mNewStatFloatp); - } - } - - frac = (mValue - mMin) / range; - frac = llmax(0.f, frac); - frac = llmin(1.f, frac); - - if (mUpdateTimer.getElapsedTimeF32() > 0.5f) - { - std::string format_str; - std::string tmp_str; - format_str = llformat("%%s%%.%df%%s", mPrecision); - tmp_str = llformat(format_str.c_str(), mLabel.c_str(), mValue, mUnits.c_str()); - setToolTip(tmp_str); - - mUpdateTimer.reset(); - } - - LLColor4 color; - - threshold_vec_t::iterator it = std::lower_bound(mThresholds.begin(), mThresholds.end(), Threshold(mValue / mMax, LLUIColor())); - - if (it != mThresholds.begin()) - { - it--; - } - - color = LLUIColorTable::instance().getColor( "MenuDefaultBgColor" ); - gGL.color4fv(color.mV); - gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, true); - - gGL.color4fv(LLColor4::black.mV); - gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, false); - - color = it->mColor; - gGL.color4fv(color.mV); - gl_rect_2d(1, ll_round(frac*getRect().getHeight()), getRect().getWidth() - 1, 0, true); -} - -void LLStatGraph::setMin(const F32 min) -{ - mMin = min; -} - -void LLStatGraph::setMax(const F32 max) -{ - mMax = max; -} - +/** + * @file llstatgraph.cpp + * @brief Simpler compact stat graph with tooltip + * + * $LicenseInfo:firstyear=2002&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 "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "llstatgraph.h" +#include "llrender.h" + +#include "llmath.h" +#include "llui.h" +#include "llgl.h" +#include "llglheaders.h" +#include "lltracerecording.h" +#include "lltracethreadrecorder.h" +//#include "llviewercontrol.h" + +/////////////////////////////////////////////////////////////////////////////////// + +LLStatGraph::LLStatGraph(const Params& p) +: LLView(p), + mMin(p.min), + mMax(p.max), + mPerSec(p.per_sec), + mPrecision(p.precision), + mValue(p.value), + mUnits(p.units), + mNewStatFloatp(p.stat.count_stat_float) +{ + setToolTip(p.name()); + + for(LLInitParam::ParamIterator::const_iterator it = p.thresholds.threshold.begin(), end_it = p.thresholds.threshold.end(); + it != end_it; + ++it) + { + mThresholds.push_back(Threshold(it->value(), it->color)); + } +} + +void LLStatGraph::draw() +{ + F32 range, frac; + range = mMax - mMin; + if (mNewStatFloatp) + { + LLTrace::Recording& recording = LLTrace::get_frame_recording().getLastRecording(); + + if (mPerSec) + { + mValue = recording.getPerSec(*mNewStatFloatp); + } + else + { + mValue = recording.getSum(*mNewStatFloatp); + } + } + + frac = (mValue - mMin) / range; + frac = llmax(0.f, frac); + frac = llmin(1.f, frac); + + if (mUpdateTimer.getElapsedTimeF32() > 0.5f) + { + std::string format_str; + std::string tmp_str; + format_str = llformat("%%s%%.%df%%s", mPrecision); + tmp_str = llformat(format_str.c_str(), mLabel.c_str(), mValue, mUnits.c_str()); + setToolTip(tmp_str); + + mUpdateTimer.reset(); + } + + LLColor4 color; + + threshold_vec_t::iterator it = std::lower_bound(mThresholds.begin(), mThresholds.end(), Threshold(mValue / mMax, LLUIColor())); + + if (it != mThresholds.begin()) + { + it--; + } + + color = LLUIColorTable::instance().getColor( "MenuDefaultBgColor" ); + gGL.color4fv(color.mV); + gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, true); + + gGL.color4fv(LLColor4::black.mV); + gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, false); + + color = it->mColor; + gGL.color4fv(color.mV); + gl_rect_2d(1, ll_round(frac*getRect().getHeight()), getRect().getWidth() - 1, 0, true); +} + +void LLStatGraph::setMin(const F32 min) +{ + mMin = min; +} + +void LLStatGraph::setMax(const F32 max) +{ + mMax = max; +} + diff --git a/indra/llui/llstatgraph.h b/indra/llui/llstatgraph.h index 1c1b707688..c254821870 100644 --- a/indra/llui/llstatgraph.h +++ b/indra/llui/llstatgraph.h @@ -1,141 +1,141 @@ -/** - * @file llstatgraph.h - * @brief Simpler compact stat graph with tooltip - * - * $LicenseInfo:firstyear=2002&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$ - */ - -#ifndef LL_LLSTATGRAPH_H -#define LL_LLSTATGRAPH_H - -#include "llview.h" -#include "llframetimer.h" -#include "v4color.h" -#include "lltrace.h" - -class LLStatGraph : public LLView -{ -public: - struct ThresholdParams : public LLInitParam::Block - { - Mandatory value; - Optional color; - - ThresholdParams() - : value("value"), - color("color", LLColor4::white) - {} - }; - - struct Thresholds : public LLInitParam::Block - { - Multiple threshold; - - Thresholds() - : threshold("threshold") - {} - }; - - struct StatParams : public LLInitParam::ChoiceBlock - { - Alternative* > count_stat_float; - Alternative* > event_stat_float; - Alternative* > sample_stat_float; - }; - - struct Params : public LLInitParam::Block - { - Mandatory stat; - Optional label, - units; - Optional precision; - Optional min, - max; - Optional per_sec; - Optional value; - - Optional thresholds; - - Params() - : stat("stat"), - label("label"), - units("units"), - precision("precision", 0), - min("min", 0.f), - max("max", 125.f), - per_sec("per_sec", true), - value("value", 0.f), - thresholds("thresholds") - { - Thresholds _thresholds; - _thresholds.threshold.add(ThresholdParams().value(0.f).color(LLColor4::green)) - .add(ThresholdParams().value(0.33f).color(LLColor4::yellow)) - .add(ThresholdParams().value(0.5f).color(LLColor4::red)) - .add(ThresholdParams().value(0.75f).color(LLColor4::red)); - thresholds = _thresholds; - } - }; - LLStatGraph(const Params&); - - void setMin(const F32 min); - void setMax(const F32 max); - - virtual void draw(); - - /*virtual*/ void setValue(const LLSD& value); - -private: - LLTrace::StatType* mNewStatFloatp; - - bool mPerSec; - - F32 mValue; - - F32 mMin; - F32 mMax; - LLFrameTimer mUpdateTimer; - std::string mLabel; - std::string mUnits; - S32 mPrecision; // Num of digits of precision after dot - - struct Threshold - { - Threshold(F32 value, const LLUIColor& color) - : mValue(value), - mColor(color) - {} - - F32 mValue; - LLUIColor mColor; - bool operator <(const Threshold& other) const - { - return mValue < other.mValue; - } - }; - typedef std::vector threshold_vec_t; - threshold_vec_t mThresholds; - //S32 mNumThresholds; - //F32 mThresholds[4]; - //LLColor4 mThresholdColors[4]; -}; - -#endif // LL_LLSTATGRAPH_H +/** + * @file llstatgraph.h + * @brief Simpler compact stat graph with tooltip + * + * $LicenseInfo:firstyear=2002&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$ + */ + +#ifndef LL_LLSTATGRAPH_H +#define LL_LLSTATGRAPH_H + +#include "llview.h" +#include "llframetimer.h" +#include "v4color.h" +#include "lltrace.h" + +class LLStatGraph : public LLView +{ +public: + struct ThresholdParams : public LLInitParam::Block + { + Mandatory value; + Optional color; + + ThresholdParams() + : value("value"), + color("color", LLColor4::white) + {} + }; + + struct Thresholds : public LLInitParam::Block + { + Multiple threshold; + + Thresholds() + : threshold("threshold") + {} + }; + + struct StatParams : public LLInitParam::ChoiceBlock + { + Alternative* > count_stat_float; + Alternative* > event_stat_float; + Alternative* > sample_stat_float; + }; + + struct Params : public LLInitParam::Block + { + Mandatory stat; + Optional label, + units; + Optional precision; + Optional min, + max; + Optional per_sec; + Optional value; + + Optional thresholds; + + Params() + : stat("stat"), + label("label"), + units("units"), + precision("precision", 0), + min("min", 0.f), + max("max", 125.f), + per_sec("per_sec", true), + value("value", 0.f), + thresholds("thresholds") + { + Thresholds _thresholds; + _thresholds.threshold.add(ThresholdParams().value(0.f).color(LLColor4::green)) + .add(ThresholdParams().value(0.33f).color(LLColor4::yellow)) + .add(ThresholdParams().value(0.5f).color(LLColor4::red)) + .add(ThresholdParams().value(0.75f).color(LLColor4::red)); + thresholds = _thresholds; + } + }; + LLStatGraph(const Params&); + + void setMin(const F32 min); + void setMax(const F32 max); + + virtual void draw(); + + /*virtual*/ void setValue(const LLSD& value); + +private: + LLTrace::StatType* mNewStatFloatp; + + bool mPerSec; + + F32 mValue; + + F32 mMin; + F32 mMax; + LLFrameTimer mUpdateTimer; + std::string mLabel; + std::string mUnits; + S32 mPrecision; // Num of digits of precision after dot + + struct Threshold + { + Threshold(F32 value, const LLUIColor& color) + : mValue(value), + mColor(color) + {} + + F32 mValue; + LLUIColor mColor; + bool operator <(const Threshold& other) const + { + return mValue < other.mValue; + } + }; + typedef std::vector threshold_vec_t; + threshold_vec_t mThresholds; + //S32 mNumThresholds; + //F32 mThresholds[4]; + //LLColor4 mThresholdColors[4]; +}; + +#endif // LL_LLSTATGRAPH_H diff --git a/indra/llui/llstatview.cpp b/indra/llui/llstatview.cpp index c2da7b5053..1c7656ecd2 100644 --- a/indra/llui/llstatview.cpp +++ b/indra/llui/llstatview.cpp @@ -1,64 +1,64 @@ -/** - * @file llstatview.cpp - * @brief Container for all statistics info. - * - * $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 "llstatview.h" - -#include "llerror.h" -#include "llstatbar.h" -#include "llfontgl.h" -#include "llgl.h" -#include "llui.h" - -#include "llstatbar.h" - -LLStatView::LLStatView(const LLStatView::Params& p) -: LLContainerView(p), - mSetting(p.setting) -{ - bool isopen = getDisplayChildren(); - if (mSetting.length() > 0) - { - isopen = LLUI::getInstance()->mSettingGroups["config"]->getBOOL(mSetting); - } - setDisplayChildren(isopen); -} - -LLStatView::~LLStatView() -{ - // Children all cleaned up by default view destructor. - if (mSetting.length() > 0) - { - bool isopen = getDisplayChildren(); - LLUI::getInstance()->mSettingGroups["config"]->setBOOL(mSetting, isopen); - } -} - -static StatViewRegistry::Register r1("stat_bar"); -static StatViewRegistry::Register r2("stat_view"); -// stat_view can be a child of panels/etc. -static LLDefaultChildRegistry::Register r3("stat_view"); +/** + * @file llstatview.cpp + * @brief Container for all statistics info. + * + * $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 "llstatview.h" + +#include "llerror.h" +#include "llstatbar.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llui.h" + +#include "llstatbar.h" + +LLStatView::LLStatView(const LLStatView::Params& p) +: LLContainerView(p), + mSetting(p.setting) +{ + bool isopen = getDisplayChildren(); + if (mSetting.length() > 0) + { + isopen = LLUI::getInstance()->mSettingGroups["config"]->getBOOL(mSetting); + } + setDisplayChildren(isopen); +} + +LLStatView::~LLStatView() +{ + // Children all cleaned up by default view destructor. + if (mSetting.length() > 0) + { + bool isopen = getDisplayChildren(); + LLUI::getInstance()->mSettingGroups["config"]->setBOOL(mSetting, isopen); + } +} + +static StatViewRegistry::Register r1("stat_bar"); +static StatViewRegistry::Register r2("stat_view"); +// stat_view can be a child of panels/etc. +static LLDefaultChildRegistry::Register r3("stat_view"); diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp index 19d98824c1..abf6e1284b 100644 --- a/indra/llui/llstyle.cpp +++ b/indra/llui/llstyle.cpp @@ -1,104 +1,104 @@ -/** - * @file llstyle.cpp - * @brief Text style 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 "llstyle.h" - -#include "llfontgl.h" -#include "llstring.h" -#include "llui.h" - -LLStyle::Params::Params() -: visible("visible", true), - drop_shadow("drop_shadow", LLFontGL::NO_SHADOW), - color("color", LLColor4::black), - readonly_color("readonly_color", LLColor4::black), - selected_color("selected_color", LLColor4::black), - font("font", LLFontGL::getFontMonospace()), - image("image"), - link_href("href"), - is_link("is_link") -{} - - -LLStyle::LLStyle(const LLStyle::Params& p) -: mVisible(p.visible), - mColor(p.color), - mReadOnlyColor(p.readonly_color), - mSelectedColor(p.selected_color), - mFont(p.font()), - mLink(p.link_href), - mIsLink(p.is_link.isProvided() ? p.is_link : !p.link_href().empty()), - mDropShadow(p.drop_shadow), - mImagep(p.image()) -{} - -void LLStyle::setFont(const LLFontGL* font) -{ - mFont = font; -} - - -const LLFontGL* LLStyle::getFont() const -{ - return mFont; -} - -void LLStyle::setLinkHREF(const std::string& href) -{ - mLink = href; -} - -bool LLStyle::isLink() const -{ - return mIsLink; -} - -bool LLStyle::isVisible() const -{ - return mVisible; -} - -void LLStyle::setVisible(bool is_visible) -{ - mVisible = is_visible; -} - -LLPointer LLStyle::getImage() const -{ - return mImagep; -} - -void LLStyle::setImage(const LLUUID& src) -{ - mImagep = LLUI::getUIImageByID(src); -} - -void LLStyle::setImage(const std::string& name) -{ - mImagep = LLUI::getUIImage(name); -} +/** + * @file llstyle.cpp + * @brief Text style 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 "llstyle.h" + +#include "llfontgl.h" +#include "llstring.h" +#include "llui.h" + +LLStyle::Params::Params() +: visible("visible", true), + drop_shadow("drop_shadow", LLFontGL::NO_SHADOW), + color("color", LLColor4::black), + readonly_color("readonly_color", LLColor4::black), + selected_color("selected_color", LLColor4::black), + font("font", LLFontGL::getFontMonospace()), + image("image"), + link_href("href"), + is_link("is_link") +{} + + +LLStyle::LLStyle(const LLStyle::Params& p) +: mVisible(p.visible), + mColor(p.color), + mReadOnlyColor(p.readonly_color), + mSelectedColor(p.selected_color), + mFont(p.font()), + mLink(p.link_href), + mIsLink(p.is_link.isProvided() ? p.is_link : !p.link_href().empty()), + mDropShadow(p.drop_shadow), + mImagep(p.image()) +{} + +void LLStyle::setFont(const LLFontGL* font) +{ + mFont = font; +} + + +const LLFontGL* LLStyle::getFont() const +{ + return mFont; +} + +void LLStyle::setLinkHREF(const std::string& href) +{ + mLink = href; +} + +bool LLStyle::isLink() const +{ + return mIsLink; +} + +bool LLStyle::isVisible() const +{ + return mVisible; +} + +void LLStyle::setVisible(bool is_visible) +{ + mVisible = is_visible; +} + +LLPointer LLStyle::getImage() const +{ + return mImagep; +} + +void LLStyle::setImage(const LLUUID& src) +{ + mImagep = LLUI::getUIImageByID(src); +} + +void LLStyle::setImage(const std::string& name) +{ + mImagep = LLUI::getUIImage(name); +} diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h index 57b839e38d..7dbccfff87 100644 --- a/indra/llui/llstyle.h +++ b/indra/llui/llstyle.h @@ -1,118 +1,118 @@ -/** - * @file llstyle.h - * @brief Text style 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$ - */ - -#ifndef LL_LLSTYLE_H -#define LL_LLSTYLE_H - -#include "v4color.h" -#include "llui.h" -#include "llinitparam.h" -#include "lluiimage.h" - -class LLFontGL; - -class LLStyle : public LLRefCount -{ -public: - struct Params : public LLInitParam::Block - { - Optional visible; - Optional drop_shadow; - Optional color, - readonly_color, - selected_color; - Optional font; - Optional image; - Optional link_href; - Optional is_link; - Params(); - }; - LLStyle(const Params& p = Params()); -public: - const LLUIColor& getColor() const { return mColor; } - void setColor(const LLUIColor &color) { mColor = color; } - - const LLUIColor& getReadOnlyColor() const { return mReadOnlyColor; } - void setReadOnlyColor(const LLUIColor& color) { mReadOnlyColor = color; } - - const LLUIColor& getSelectedColor() const { return mSelectedColor; } - void setSelectedColor(const LLUIColor& color) { mSelectedColor = color; } - - bool isVisible() const; - void setVisible(bool is_visible); - - LLFontGL::ShadowType getShadowType() const { return mDropShadow; } - - void setFont(const LLFontGL* font); - const LLFontGL* getFont() const; - - const std::string& getLinkHREF() const { return mLink; } - void setLinkHREF(const std::string& href); - bool isLink() const; - - LLPointer getImage() const; - void setImage(const LLUUID& src); - void setImage(const std::string& name); - - bool isImage() const { return mImagep.notNull(); } - - bool operator==(const LLStyle &rhs) const - { - return - mVisible == rhs.mVisible - && mColor == rhs.mColor - && mReadOnlyColor == rhs.mReadOnlyColor - && mSelectedColor == rhs.mSelectedColor - && mFont == rhs.mFont - && mLink == rhs.mLink - && mImagep == rhs.mImagep - && mDropShadow == rhs.mDropShadow; - } - - bool operator!=(const LLStyle& rhs) const { return !(*this == rhs); } - -public: - LLFontGL::ShadowType mDropShadow; - -protected: - ~LLStyle() { } - -private: - bool mVisible; - LLUIColor mColor; - LLUIColor mReadOnlyColor; - LLUIColor mSelectedColor; - std::string mFontName; - const LLFontGL* mFont; - std::string mLink; - bool mIsLink; - LLPointer mImagep; -}; - -typedef LLPointer LLStyleSP; -typedef LLPointer LLStyleConstSP; - -#endif // LL_LLSTYLE_H +/** + * @file llstyle.h + * @brief Text style 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$ + */ + +#ifndef LL_LLSTYLE_H +#define LL_LLSTYLE_H + +#include "v4color.h" +#include "llui.h" +#include "llinitparam.h" +#include "lluiimage.h" + +class LLFontGL; + +class LLStyle : public LLRefCount +{ +public: + struct Params : public LLInitParam::Block + { + Optional visible; + Optional drop_shadow; + Optional color, + readonly_color, + selected_color; + Optional font; + Optional image; + Optional link_href; + Optional is_link; + Params(); + }; + LLStyle(const Params& p = Params()); +public: + const LLUIColor& getColor() const { return mColor; } + void setColor(const LLUIColor &color) { mColor = color; } + + const LLUIColor& getReadOnlyColor() const { return mReadOnlyColor; } + void setReadOnlyColor(const LLUIColor& color) { mReadOnlyColor = color; } + + const LLUIColor& getSelectedColor() const { return mSelectedColor; } + void setSelectedColor(const LLUIColor& color) { mSelectedColor = color; } + + bool isVisible() const; + void setVisible(bool is_visible); + + LLFontGL::ShadowType getShadowType() const { return mDropShadow; } + + void setFont(const LLFontGL* font); + const LLFontGL* getFont() const; + + const std::string& getLinkHREF() const { return mLink; } + void setLinkHREF(const std::string& href); + bool isLink() const; + + LLPointer getImage() const; + void setImage(const LLUUID& src); + void setImage(const std::string& name); + + bool isImage() const { return mImagep.notNull(); } + + bool operator==(const LLStyle &rhs) const + { + return + mVisible == rhs.mVisible + && mColor == rhs.mColor + && mReadOnlyColor == rhs.mReadOnlyColor + && mSelectedColor == rhs.mSelectedColor + && mFont == rhs.mFont + && mLink == rhs.mLink + && mImagep == rhs.mImagep + && mDropShadow == rhs.mDropShadow; + } + + bool operator!=(const LLStyle& rhs) const { return !(*this == rhs); } + +public: + LLFontGL::ShadowType mDropShadow; + +protected: + ~LLStyle() { } + +private: + bool mVisible; + LLUIColor mColor; + LLUIColor mReadOnlyColor; + LLUIColor mSelectedColor; + std::string mFontName; + const LLFontGL* mFont; + std::string mLink; + bool mIsLink; + LLPointer mImagep; +}; + +typedef LLPointer LLStyleSP; +typedef LLPointer LLStyleConstSP; + +#endif // LL_LLSTYLE_H diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index a55e244711..752ef47d14 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -1,2204 +1,2204 @@ -/** - * @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(const std::string& 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(const std::string& 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 = 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 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(const std::string& title) -{ - for (S32 index = 0 ; index < (S32)mTabList.size(); index++) - { - if (title == mTabList[index]->mButton->getLabelSelected()) - { - return index; - } - } - return -1; -} - -LLPanel* LLTabContainer::getPanelByName(const std::string& 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( 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 = mTabList.size()-1; - while (!selectTab(idx) && idx != mCurrentTabIdx) - { - idx = idx - 1; - if (idx < 0) - idx = mTabList.size()-1; - } - if (tab_has_focus) - { - mTabList[idx]->mButton->setFocus(true); - } -} - -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(const std::string& 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(); -} +/** + * @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(const std::string& 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(const std::string& 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 = 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 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(const std::string& title) +{ + for (S32 index = 0 ; index < (S32)mTabList.size(); index++) + { + if (title == mTabList[index]->mButton->getLabelSelected()) + { + return index; + } + } + return -1; +} + +LLPanel* LLTabContainer::getPanelByName(const std::string& 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( 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 = mTabList.size()-1; + while (!selectTab(idx) && idx != mCurrentTabIdx) + { + idx = idx - 1; + if (idx < 0) + idx = mTabList.size()-1; + } + if (tab_has_focus) + { + mTabList[idx]->mButton->setFocus(true); + } +} + +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(const std::string& 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(); +} diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h index 1684b9570d..b22eec2fe5 100644 --- a/indra/llui/lltabcontainer.h +++ b/indra/llui/lltabcontainer.h @@ -1,330 +1,330 @@ -/** - * @file lltabcontainer.h - * @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$ - */ - -#ifndef LL_TABCONTAINER_H -#define LL_TABCONTAINER_H - -#include "llpanel.h" -#include "lltextbox.h" -#include "llframetimer.h" -#include "lliconctrl.h" -#include "llbutton.h" - -class LLTabTuple; - -class LLTabContainer : public LLPanel -{ -public: - enum TabPosition - { - TOP, - BOTTOM, - LEFT - }; - typedef enum e_insertion_point - { - START, - END, - LEFT_OF_CURRENT, - RIGHT_OF_CURRENT - } eInsertionPoint; - - struct TabPositions : public LLInitParam::TypeValuesHelper - { - static void declareValues(); - }; - - struct TabParams : public LLInitParam::Block - { - Optional tab_top_image_unselected, - tab_top_image_selected, - tab_top_image_flash, - tab_bottom_image_unselected, - tab_bottom_image_selected, - tab_bottom_image_flash, - tab_left_image_unselected, - tab_left_image_selected, - tab_left_image_flash; - TabParams(); - }; - - struct Params - : public LLInitParam::Block - { - Optional tab_position; - Optional tab_width, - tab_min_width, - tab_max_width, - tab_height, - label_pad_bottom, - label_pad_left; - - Optional hide_tabs; - Optional hide_scroll_arrows; - Optional tab_padding_right; - - Optional first_tab, - middle_tab, - last_tab; - - /** - * Tab label horizontal alignment - */ - Optional font_halign; - - /** - * Tab label ellipses - */ - Optional use_ellipses; - - /** - * Use LLCustomButtonIconCtrl or LLButton in LLTabTuple - */ - Optional use_custom_icon_ctrl; - - /** - * Open tabs on hover in drag and drop situations - */ - Optional open_tabs_on_drag_and_drop; - - /** - * Enable tab flashing - */ - Optional enable_tabs_flashing; - Optional tabs_flashing_color; - - /** - * Paddings for LLIconCtrl in case of LLCustomButtonIconCtrl usage(use_custom_icon_ctrl = true) - */ - Optional tab_icon_ctrl_pad; - - Optional use_tab_offset; - - Params(); - }; - -protected: - LLTabContainer(const Params&); - friend class LLUICtrlFactory; - -public: - //LLTabContainer( const std::string& name, const LLRect& rect, TabPosition pos, - // bool bordered, bool is_vertical); - - /*virtual*/ ~LLTabContainer(); - - // from LLView - /*virtual*/ void setValue(const LLSD& value); - - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - /*virtual*/ void draw(); - /*virtual*/ bool handleMouseDown( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleHover( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleMouseUp( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType type, void* cargo_data, - EAcceptance* accept, std::string& tooltip); - /*virtual*/ LLView* getChildView(const std::string& name, bool recurse = true) const; - /*virtual*/ LLView* findChildView(const std::string& name, bool recurse = true) const; - /*virtual*/ void initFromParams(const LLPanel::Params& p); - /*virtual*/ bool addChild(LLView* view, S32 tab_group = 0); - /*virtual*/ bool postBuild(); - - struct TabPanelParams : public LLInitParam::Block - { - Mandatory panel; - - Optional label; - Optional select_tab, - is_placeholder; - Optional indent; - Optional insert_at; - Optional user_data; - - TabPanelParams() - : panel("panel", NULL), - label("label"), - select_tab("select_tab"), - is_placeholder("is_placeholder"), - indent("indent"), - insert_at("insert_at", END) - {} - }; - - void addTabPanel(LLPanel* panel); - void addTabPanel(const TabPanelParams& panel); - void addPlaceholder(LLPanel* child, const std::string& label); - void removeTabPanel( LLPanel* child ); - void lockTabs(S32 num_tabs = 0); - void unlockTabs(); - S32 getNumLockedTabs() { return mLockedTabCount; } - void enableTabButton(S32 which, bool enable); - void deleteAllTabs(); - LLPanel* getCurrentPanel(); - S32 getCurrentPanelIndex(); - S32 getTabCount(); - LLPanel* getPanelByIndex(S32 index); - S32 getIndexForPanel(LLPanel* panel); - S32 getPanelIndexByTitle(const std::string& title); - LLPanel* getPanelByName(const std::string& name); - S32 getTotalTabWidth() const; - void setCurrentTabName(const std::string& name); - - void selectFirstTab(); - void selectLastTab(); - void selectNextTab(); - void selectPrevTab(); - bool selectTabPanel( LLPanel* child ); - bool selectTab(S32 which); - bool selectTabByName(const std::string& title); - void setCurrentPanelIndex(S32 index) { mCurrentTabIdx = index; } - - bool getTabPanelFlashing(LLPanel* child); - void setTabPanelFlashing(LLPanel* child, bool state); - void setTabImage(LLPanel* child, std::string img_name, const LLColor4& color = LLColor4::white); - void setTabImage(LLPanel* child, const LLUUID& img_id, const LLColor4& color = LLColor4::white); - void setTabImage(LLPanel* child, LLIconCtrl* icon); - void setTitle( const std::string& title ); - const std::string getPanelTitle(S32 index); - - void setTopBorderHeight(S32 height); - S32 getTopBorderHeight() const; - - void setRightTabBtnOffset( S32 offset ); - void setPanelTitle(S32 index, const std::string& title); - - TabPosition getTabPosition() const { return mTabPosition; } - void setMinTabWidth(S32 width) { mMinTabWidth = width; } - void setMaxTabWidth(S32 width) { mMaxTabWidth = width; } - S32 getMinTabWidth() const { return mMinTabWidth; } - S32 getMaxTabWidth() const { return mMaxTabWidth; } - - void setTabVisibility( LLPanel const *aPanel, bool ); - - void startDragAndDropDelayTimer() { mDragAndDropDelayTimer.start(); } - - void onTabBtn( const LLSD& data, LLPanel* panel ); - void onNextBtn(const LLSD& data); - void onNextBtnHeld(const LLSD& data); - void onPrevBtn(const LLSD& data); - void onPrevBtnHeld(const LLSD& data); - void onJumpFirstBtn( const LLSD& data ); - void onJumpLastBtn( const LLSD& data ); - -private: - - void initButtons(); - - bool setTab(S32 which); - - LLTabTuple* getTab(S32 index) { return mTabList[index]; } - LLTabTuple* getTabByPanel(LLPanel* child); - void insertTuple(LLTabTuple * tuple, eInsertionPoint insertion_point); - - S32 getScrollPos() const { return mScrollPos; } - void setScrollPos(S32 pos) { mScrollPos = pos; } - S32 getMaxScrollPos() const { return mMaxScrollPos; } - void setMaxScrollPos(S32 pos) { mMaxScrollPos = pos; } - S32 getScrollPosPixels() const { return mScrollPosPixels; } - void setScrollPosPixels(S32 pixels) { mScrollPosPixels = pixels; } - - void setTabsHidden(bool hidden) { mTabsHidden = hidden; } - bool getTabsHidden() const { return mTabsHidden; } - - void scrollPrev() { mScrollPos = llmax(0, mScrollPos-1); } // No wrap - void scrollNext() { mScrollPos = llmin(mScrollPos+1, mMaxScrollPos); } // No wrap - - void updateMaxScrollPos(); - void commitHoveredButton(S32 x, S32 y); - - // updates tab button images given the tuple, tab position and the corresponding params - void update_images(LLTabTuple* tuple, TabParams params, LLTabContainer::TabPosition pos); - void reshapeTuple(LLTabTuple* tuple); - - // Variables - - typedef std::vector tuple_list_t; - tuple_list_t mTabList; - - S32 mCurrentTabIdx; - bool mTabsHidden; - bool mHideScrollArrows; - - bool mScrolled; - LLFrameTimer mScrollTimer; - S32 mScrollPos; - S32 mScrollPosPixels; - S32 mMaxScrollPos; - - LLTextBox* mTitleBox; - - S32 mTopBorderHeight; - TabPosition mTabPosition; - S32 mLockedTabCount; - S32 mMinTabWidth; - LLButton* mPrevArrowBtn; - LLButton* mNextArrowBtn; - - bool mIsVertical; - - // Horizontal specific - LLButton* mJumpPrevArrowBtn; - LLButton* mJumpNextArrowBtn; - - S32 mRightTabBtnOffset; // Extra room to the right of the tab buttons. - - S32 mMaxTabWidth; - S32 mTotalTabWidth; - S32 mTabHeight; - - // Padding under the text labels of tab buttons - S32 mLabelPadBottom; - // Padding to the left of text labels of tab buttons - S32 mLabelPadLeft; - - LLFrameTimer mDragAndDropDelayTimer; - - LLFontGL::HAlign mFontHalign; - const LLFontGL* mFont; - - TabParams mFirstTabParams; - TabParams mMiddleTabParams; - TabParams mLastTabParams; - - bool mCustomIconCtrlUsed; - bool mOpenTabsOnDragAndDrop; - bool mEnableTabsFlashing; - LLUIColor mTabsFlashingColor; - S32 mTabIconCtrlPad; - bool mUseTabEllipses; - LLFrameTimer mMouseDownTimer; - - bool mUseTabOffset; -}; - -#endif // LL_TABCONTAINER_H +/** + * @file lltabcontainer.h + * @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$ + */ + +#ifndef LL_TABCONTAINER_H +#define LL_TABCONTAINER_H + +#include "llpanel.h" +#include "lltextbox.h" +#include "llframetimer.h" +#include "lliconctrl.h" +#include "llbutton.h" + +class LLTabTuple; + +class LLTabContainer : public LLPanel +{ +public: + enum TabPosition + { + TOP, + BOTTOM, + LEFT + }; + typedef enum e_insertion_point + { + START, + END, + LEFT_OF_CURRENT, + RIGHT_OF_CURRENT + } eInsertionPoint; + + struct TabPositions : public LLInitParam::TypeValuesHelper + { + static void declareValues(); + }; + + struct TabParams : public LLInitParam::Block + { + Optional tab_top_image_unselected, + tab_top_image_selected, + tab_top_image_flash, + tab_bottom_image_unselected, + tab_bottom_image_selected, + tab_bottom_image_flash, + tab_left_image_unselected, + tab_left_image_selected, + tab_left_image_flash; + TabParams(); + }; + + struct Params + : public LLInitParam::Block + { + Optional tab_position; + Optional tab_width, + tab_min_width, + tab_max_width, + tab_height, + label_pad_bottom, + label_pad_left; + + Optional hide_tabs; + Optional hide_scroll_arrows; + Optional tab_padding_right; + + Optional first_tab, + middle_tab, + last_tab; + + /** + * Tab label horizontal alignment + */ + Optional font_halign; + + /** + * Tab label ellipses + */ + Optional use_ellipses; + + /** + * Use LLCustomButtonIconCtrl or LLButton in LLTabTuple + */ + Optional use_custom_icon_ctrl; + + /** + * Open tabs on hover in drag and drop situations + */ + Optional open_tabs_on_drag_and_drop; + + /** + * Enable tab flashing + */ + Optional enable_tabs_flashing; + Optional tabs_flashing_color; + + /** + * Paddings for LLIconCtrl in case of LLCustomButtonIconCtrl usage(use_custom_icon_ctrl = true) + */ + Optional tab_icon_ctrl_pad; + + Optional use_tab_offset; + + Params(); + }; + +protected: + LLTabContainer(const Params&); + friend class LLUICtrlFactory; + +public: + //LLTabContainer( const std::string& name, const LLRect& rect, TabPosition pos, + // bool bordered, bool is_vertical); + + /*virtual*/ ~LLTabContainer(); + + // from LLView + /*virtual*/ void setValue(const LLSD& value); + + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + /*virtual*/ void draw(); + /*virtual*/ bool handleMouseDown( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleHover( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleMouseUp( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType type, void* cargo_data, + EAcceptance* accept, std::string& tooltip); + /*virtual*/ LLView* getChildView(const std::string& name, bool recurse = true) const; + /*virtual*/ LLView* findChildView(const std::string& name, bool recurse = true) const; + /*virtual*/ void initFromParams(const LLPanel::Params& p); + /*virtual*/ bool addChild(LLView* view, S32 tab_group = 0); + /*virtual*/ bool postBuild(); + + struct TabPanelParams : public LLInitParam::Block + { + Mandatory panel; + + Optional label; + Optional select_tab, + is_placeholder; + Optional indent; + Optional insert_at; + Optional user_data; + + TabPanelParams() + : panel("panel", NULL), + label("label"), + select_tab("select_tab"), + is_placeholder("is_placeholder"), + indent("indent"), + insert_at("insert_at", END) + {} + }; + + void addTabPanel(LLPanel* panel); + void addTabPanel(const TabPanelParams& panel); + void addPlaceholder(LLPanel* child, const std::string& label); + void removeTabPanel( LLPanel* child ); + void lockTabs(S32 num_tabs = 0); + void unlockTabs(); + S32 getNumLockedTabs() { return mLockedTabCount; } + void enableTabButton(S32 which, bool enable); + void deleteAllTabs(); + LLPanel* getCurrentPanel(); + S32 getCurrentPanelIndex(); + S32 getTabCount(); + LLPanel* getPanelByIndex(S32 index); + S32 getIndexForPanel(LLPanel* panel); + S32 getPanelIndexByTitle(const std::string& title); + LLPanel* getPanelByName(const std::string& name); + S32 getTotalTabWidth() const; + void setCurrentTabName(const std::string& name); + + void selectFirstTab(); + void selectLastTab(); + void selectNextTab(); + void selectPrevTab(); + bool selectTabPanel( LLPanel* child ); + bool selectTab(S32 which); + bool selectTabByName(const std::string& title); + void setCurrentPanelIndex(S32 index) { mCurrentTabIdx = index; } + + bool getTabPanelFlashing(LLPanel* child); + void setTabPanelFlashing(LLPanel* child, bool state); + void setTabImage(LLPanel* child, std::string img_name, const LLColor4& color = LLColor4::white); + void setTabImage(LLPanel* child, const LLUUID& img_id, const LLColor4& color = LLColor4::white); + void setTabImage(LLPanel* child, LLIconCtrl* icon); + void setTitle( const std::string& title ); + const std::string getPanelTitle(S32 index); + + void setTopBorderHeight(S32 height); + S32 getTopBorderHeight() const; + + void setRightTabBtnOffset( S32 offset ); + void setPanelTitle(S32 index, const std::string& title); + + TabPosition getTabPosition() const { return mTabPosition; } + void setMinTabWidth(S32 width) { mMinTabWidth = width; } + void setMaxTabWidth(S32 width) { mMaxTabWidth = width; } + S32 getMinTabWidth() const { return mMinTabWidth; } + S32 getMaxTabWidth() const { return mMaxTabWidth; } + + void setTabVisibility( LLPanel const *aPanel, bool ); + + void startDragAndDropDelayTimer() { mDragAndDropDelayTimer.start(); } + + void onTabBtn( const LLSD& data, LLPanel* panel ); + void onNextBtn(const LLSD& data); + void onNextBtnHeld(const LLSD& data); + void onPrevBtn(const LLSD& data); + void onPrevBtnHeld(const LLSD& data); + void onJumpFirstBtn( const LLSD& data ); + void onJumpLastBtn( const LLSD& data ); + +private: + + void initButtons(); + + bool setTab(S32 which); + + LLTabTuple* getTab(S32 index) { return mTabList[index]; } + LLTabTuple* getTabByPanel(LLPanel* child); + void insertTuple(LLTabTuple * tuple, eInsertionPoint insertion_point); + + S32 getScrollPos() const { return mScrollPos; } + void setScrollPos(S32 pos) { mScrollPos = pos; } + S32 getMaxScrollPos() const { return mMaxScrollPos; } + void setMaxScrollPos(S32 pos) { mMaxScrollPos = pos; } + S32 getScrollPosPixels() const { return mScrollPosPixels; } + void setScrollPosPixels(S32 pixels) { mScrollPosPixels = pixels; } + + void setTabsHidden(bool hidden) { mTabsHidden = hidden; } + bool getTabsHidden() const { return mTabsHidden; } + + void scrollPrev() { mScrollPos = llmax(0, mScrollPos-1); } // No wrap + void scrollNext() { mScrollPos = llmin(mScrollPos+1, mMaxScrollPos); } // No wrap + + void updateMaxScrollPos(); + void commitHoveredButton(S32 x, S32 y); + + // updates tab button images given the tuple, tab position and the corresponding params + void update_images(LLTabTuple* tuple, TabParams params, LLTabContainer::TabPosition pos); + void reshapeTuple(LLTabTuple* tuple); + + // Variables + + typedef std::vector tuple_list_t; + tuple_list_t mTabList; + + S32 mCurrentTabIdx; + bool mTabsHidden; + bool mHideScrollArrows; + + bool mScrolled; + LLFrameTimer mScrollTimer; + S32 mScrollPos; + S32 mScrollPosPixels; + S32 mMaxScrollPos; + + LLTextBox* mTitleBox; + + S32 mTopBorderHeight; + TabPosition mTabPosition; + S32 mLockedTabCount; + S32 mMinTabWidth; + LLButton* mPrevArrowBtn; + LLButton* mNextArrowBtn; + + bool mIsVertical; + + // Horizontal specific + LLButton* mJumpPrevArrowBtn; + LLButton* mJumpNextArrowBtn; + + S32 mRightTabBtnOffset; // Extra room to the right of the tab buttons. + + S32 mMaxTabWidth; + S32 mTotalTabWidth; + S32 mTabHeight; + + // Padding under the text labels of tab buttons + S32 mLabelPadBottom; + // Padding to the left of text labels of tab buttons + S32 mLabelPadLeft; + + LLFrameTimer mDragAndDropDelayTimer; + + LLFontGL::HAlign mFontHalign; + const LLFontGL* mFont; + + TabParams mFirstTabParams; + TabParams mMiddleTabParams; + TabParams mLastTabParams; + + bool mCustomIconCtrlUsed; + bool mOpenTabsOnDragAndDrop; + bool mEnableTabsFlashing; + LLUIColor mTabsFlashingColor; + S32 mTabIconCtrlPad; + bool mUseTabEllipses; + LLFrameTimer mMouseDownTimer; + + bool mUseTabOffset; +}; + +#endif // LL_TABCONTAINER_H diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index f6dd38bce5..1249461be9 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1,3890 +1,3890 @@ -/** - * @file lltextbase.cpp - * @author Martin Reddy - * @brief The base class of text box/editor, providing Url handling support - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2009-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 "lltextbase.h" - -#include "llemojidictionary.h" -#include "llemojihelper.h" -#include "lllocalcliprect.h" -#include "llmenugl.h" -#include "llscrollcontainer.h" -#include "llspellcheck.h" -#include "llstl.h" -#include "lltextparser.h" -#include "lltextutil.h" -#include "lltooltip.h" -#include "lltrans.h" -#include "lluictrl.h" -#include "llurlaction.h" -#include "llurlregistry.h" -#include "llview.h" -#include "llwindow.h" -#include - -const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds -const S32 CURSOR_THICKNESS = 2; -const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click. - -LLTextBase::line_info::line_info(S32 index_start, S32 index_end, LLRect rect, S32 line_num) -: mDocIndexStart(index_start), - mDocIndexEnd(index_end), - mRect(rect), - mLineNum(line_num) -{} - -bool LLTextBase::compare_segment_end::operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const -{ - // sort empty spans (e.g. 11-11) after previous non-empty spans (e.g. 5-11) - if (a->getEnd() == b->getEnd()) - { - return a->getStart() < b->getStart(); - } - else - { - return a->getEnd() < b->getEnd(); - } -} - - -// helper functors -bool LLTextBase::compare_bottom::operator()(const S32& a, const LLTextBase::line_info& b) const -{ - return a > b.mRect.mBottom; // bottom of a is higher than bottom of b -} - -bool LLTextBase::compare_bottom::operator()(const LLTextBase::line_info& a, const S32& b) const -{ - return a.mRect.mBottom > b; // bottom of a is higher than bottom of b -} - -bool LLTextBase::compare_bottom::operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const -{ - return a.mRect.mBottom > b.mRect.mBottom; // bottom of a is higher than bottom of b -} - -// helper functors -bool LLTextBase::compare_top::operator()(const S32& a, const LLTextBase::line_info& b) const -{ - return a > b.mRect.mTop; // top of a is higher than top of b -} - -bool LLTextBase::compare_top::operator()(const LLTextBase::line_info& a, const S32& b) const -{ - return a.mRect.mTop > b; // top of a is higher than top of b -} - -bool LLTextBase::compare_top::operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const -{ - return a.mRect.mTop > b.mRect.mTop; // top of a is higher than top of b -} - -struct LLTextBase::line_end_compare -{ - bool operator()(const S32& pos, const LLTextBase::line_info& info) const - { - return (pos < info.mDocIndexEnd); - } - - bool operator()(const LLTextBase::line_info& info, const S32& pos) const - { - return (info.mDocIndexEnd < pos); - } - - bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const - { - return (a.mDocIndexEnd < b.mDocIndexEnd); - } - -}; - -////////////////////////////////////////////////////////////////////////// -// -// LLTextBase -// - -// register LLTextBase::Params under name "textbase" -static LLWidgetNameRegistry::StaticRegistrar sRegisterTextBaseParams(&typeid(LLTextBase::Params), "textbase"); - -LLTextBase::LineSpacingParams::LineSpacingParams() -: multiple("multiple", 1.f), - pixels("pixels", 0) -{ -} - - -LLTextBase::Params::Params() -: cursor_color("cursor_color"), - text_color("text_color"), - text_readonly_color("text_readonly_color"), - text_tentative_color("text_tentative_color"), - bg_visible("bg_visible", false), - border_visible("border_visible", false), - bg_readonly_color("bg_readonly_color"), - bg_writeable_color("bg_writeable_color"), - bg_focus_color("bg_focus_color"), - text_selected_color("text_selected_color"), - bg_selected_color("bg_selected_color"), - allow_scroll("allow_scroll", true), - plain_text("plain_text",false), - track_end("track_end", false), - read_only("read_only", false), - skip_link_underline("skip_link_underline", false), - spellcheck("spellcheck", false), - v_pad("v_pad", 0), - h_pad("h_pad", 0), - clip("clip", true), - clip_partial("clip_partial", true), - line_spacing("line_spacing"), - max_text_length("max_length", 255), - font_shadow("font_shadow"), - text_valign("text_valign"), - wrap("wrap"), - trusted_content("trusted_content", true), - always_show_icons("always_show_icons", false), - use_ellipses("use_ellipses", false), - use_emoji("use_emoji", true), - use_color("use_color", true), - parse_urls("parse_urls", false), - force_urls_external("force_urls_external", false), - parse_highlights("parse_highlights", false) -{ - addSynonym(track_end, "track_bottom"); - addSynonym(wrap, "word_wrap"); - addSynonym(parse_urls, "allow_html"); -} - - -LLTextBase::LLTextBase(const LLTextBase::Params &p) -: LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)), - mURLClickSignal(NULL), - mIsFriendSignal(NULL), - mIsObjectBlockedSignal(NULL), - mMaxTextByteLength( p.max_text_length ), - mFont(p.font), - mFontShadow(p.font_shadow), - mPopupMenuHandle(), - mReadOnly(p.read_only), - mSkipTripleClick(false), - mSkipLinkUnderline(p.skip_link_underline), - mSpellCheck(p.spellcheck), - mSpellCheckStart(-1), - mSpellCheckEnd(-1), - mCursorColor(p.cursor_color), - mFgColor(p.text_color), - mBorderVisible( p.border_visible ), - mReadOnlyFgColor(p.text_readonly_color), - mTentativeFgColor(p.text_tentative_color()), - mWriteableBgColor(p.bg_writeable_color), - mReadOnlyBgColor(p.bg_readonly_color), - mFocusBgColor(p.bg_focus_color), - mTextSelectedColor(p.text_selected_color), - mSelectedBGColor(p.bg_selected_color), - mReflowIndex(S32_MAX), - mCursorPos( 0 ), - mScrollNeeded(false), - mDesiredXPixel(-1), - mHPad(p.h_pad), - mVPad(p.v_pad), - mHAlign(p.font_halign), - mVAlign(p.font_valign), - mTextVAlign(p.text_valign.isProvided() ? p.text_valign.getValue() : p.font_valign.getValue()), - mLineSpacingMult(p.line_spacing.multiple), - mLineSpacingPixels(p.line_spacing.pixels), - mClip(p.clip), - mClipPartial(p.clip_partial && !p.allow_scroll), - mTrustedContent(p.trusted_content), - mAlwaysShowIcons(p.always_show_icons), - mTrackEnd( p.track_end ), - mScrollIndex(-1), - mSelectionStart( 0 ), - mSelectionEnd( 0 ), - mIsSelecting( false ), - mPlainText ( p.plain_text ), - mWordWrap(p.wrap), - mUseEllipses( p.use_ellipses ), - mUseEmoji(p.use_emoji), - mUseColor(p.use_color), - mParseHTML(p.parse_urls), - mForceUrlsExternal(p.force_urls_external), - mParseHighlights(p.parse_highlights), - mBGVisible(p.bg_visible), - mScroller(NULL), - mStyleDirty(true) -{ - if(p.allow_scroll) - { - LLScrollContainer::Params scroll_params; - scroll_params.name = "text scroller"; - scroll_params.rect = getLocalRect(); - scroll_params.follows.flags = FOLLOWS_ALL; - scroll_params.is_opaque = false; - scroll_params.mouse_opaque = false; - scroll_params.min_auto_scroll_rate = 200; - scroll_params.max_auto_scroll_rate = 800; - scroll_params.border_visible = p.border_visible; - mScroller = LLUICtrlFactory::create(scroll_params); - addChild(mScroller); - } - - LLView::Params view_params; - view_params.name = "text_contents"; - view_params.rect = LLRect(0, 500, 500, 0); - view_params.mouse_opaque = false; - - mDocumentView = LLUICtrlFactory::create(view_params); - if (mScroller) - { - mScroller->addChild(mDocumentView); - } - else - { - addChild(mDocumentView); - } - - if (mSpellCheck) - { - LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLTextBase::onSpellCheckSettingsChange, this)); - } - mSpellCheckTimer.reset(); - - createDefaultSegment(); - - updateRects(); -} - -LLTextBase::~LLTextBase() -{ - mSegments.clear(); - LLContextMenu* menu = static_cast(mPopupMenuHandle.get()); - if (menu) - { - menu->die(); - mPopupMenuHandle.markDead(); - } - delete mURLClickSignal; - delete mIsFriendSignal; - delete mIsObjectBlockedSignal; -} - -void LLTextBase::initFromParams(const LLTextBase::Params& p) -{ - LLUICtrl::initFromParams(p); - resetDirty(); // Update saved text state - updateSegments(); - - // HACK: work around enabled == readonly design bug -- RN - // setEnabled will modify our read only status, so do this after - // LLTextBase::initFromParams - if (p.read_only.isProvided()) - { - mReadOnly = p.read_only; - } -} - -bool LLTextBase::truncate() -{ - bool did_truncate = false; - - // First rough check - if we're less than 1/4th the size, we're OK - if (getLength() >= S32(mMaxTextByteLength / 4)) - { - // Have to check actual byte size - S32 utf8_byte_size = 0; - LLSD value = getViewModel()->getValue(); - if (value.type() == LLSD::TypeString) - { - // save a copy for strings. - utf8_byte_size = value.size(); - } - else - { - // non string LLSDs need explicit conversion to string - utf8_byte_size = value.asString().size(); - } - - if ( utf8_byte_size > mMaxTextByteLength ) - { - // Truncate safely in UTF-8 - std::string temp_utf8_text = value.asString(); - temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength ); - LLWString text = utf8str_to_wstring( temp_utf8_text ); - // remove extra bit of current string, to preserve formatting, etc. - removeStringNoUndo(text.size(), getWText().size() - text.size()); - did_truncate = true; - } - } - - return did_truncate; -} - -const LLStyle::Params& LLTextBase::getStyleParams() -{ - //FIXME: convert mDefaultStyle to a flyweight http://www.boost.org/doc/libs/1_40_0/libs/flyweight/doc/index.html - //and eliminate color member values - if (mStyleDirty) - { - mStyle - .color(LLUIColor(&mFgColor)) // pass linked color instead of copy of mFGColor - .readonly_color(LLUIColor(&mReadOnlyFgColor)) - .selected_color(LLUIColor(&mTextSelectedColor)) - .font(mFont) - .drop_shadow(mFontShadow); - mStyleDirty = false; - } - return mStyle; -} - -void LLTextBase::beforeValueChange() -{ - -} - -void LLTextBase::onValueChange(S32 start, S32 end) -{ -} - -std::vector LLTextBase::getSelectionRects() -{ - // Nor supposed to be called without selection - llassert(hasSelection()); - llassert(!mLineInfoList.empty()); - - std::vector selection_rects; - - S32 selection_left = llmin(mSelectionStart, mSelectionEnd); - S32 selection_right = llmax(mSelectionStart, mSelectionEnd); - - // Skip through the lines we aren't drawing. - LLRect content_display_rect = getVisibleDocumentRect(); - - // binary search for line that starts before top of visible buffer - line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom()); - line_list_t::const_iterator end_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top()); - - bool done = false; - - // Find the coordinates of the selected area - for (; line_iter != end_iter && !done; ++line_iter) - { - // is selection visible on this line? - if (line_iter->mDocIndexEnd > selection_left && line_iter->mDocIndexStart < selection_right) - { - segment_set_t::iterator segment_iter; - S32 segment_offset; - getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset); - - // Use F32 otherwise a string of multiple segments - // will accumulate a large error - F32 left_precise = line_iter->mRect.mLeft; - F32 right_precise = line_iter->mRect.mLeft; - - for (; segment_iter != mSegments.end(); ++segment_iter, segment_offset = 0) - { - LLTextSegmentPtr segmentp = *segment_iter; - - S32 segment_line_start = segmentp->getStart() + segment_offset; - S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd); - - if (segment_line_start > segment_line_end) break; - - F32 segment_width = 0; - S32 segment_height = 0; - - // if selection after beginning of segment - if (selection_left >= segment_line_start) - { - S32 num_chars = llmin(selection_left, segment_line_end) - segment_line_start; - segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height); - left_precise += segment_width; - } - - // if selection_right == segment_line_end then that means we are the first character of the next segment - // or first character of the next line, in either case we want to add the length of the current segment - // to the selection rectangle and continue. - // if selection right > segment_line_end then selection spans end of current segment... - if (selection_right >= segment_line_end) - { - // extend selection slightly beyond end of line - // to indicate selection of newline character (use "n" character to determine width) - S32 num_chars = segment_line_end - segment_line_start; - segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height); - right_precise += segment_width; - } - // else if selection ends on current segment... - else - { - S32 num_chars = selection_right - segment_line_start; - segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height); - right_precise += segment_width; - - break; - } - } - - LLRect selection_rect; - selection_rect.mLeft = left_precise; - selection_rect.mRight = right_precise; - selection_rect.mBottom = line_iter->mRect.mBottom; - selection_rect.mTop = line_iter->mRect.mTop; - - selection_rects.push_back(selection_rect); - } - } - - return selection_rects; -} - -// Draws the black box behind the selected text -void LLTextBase::drawSelectionBackground() -{ - // Draw selection even if we don't have keyboard focus for search/replace - if (hasSelection() && !mLineInfoList.empty()) - { - std::vector selection_rects = getSelectionRects(); - - // Draw the selection box (we're using a box instead of reversing the colors on the selected text). - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - const LLColor4& color = mSelectedBGColor; - F32 alpha = hasFocus() ? 0.7f : 0.3f; - alpha *= getDrawContext().mAlpha; - - LLColor4 selection_color(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], alpha); - LLRect content_display_rect = getVisibleDocumentRect(); - - for (std::vector::iterator rect_it = selection_rects.begin(); - rect_it != selection_rects.end(); - ++rect_it) - { - LLRect selection_rect = *rect_it; - if (mScroller) - { - // If scroller is On content_display_rect has correct rect and safe to use as is - // Note: we might need to account for border - selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom); - } - else - { - // If scroller is Off content_display_rect will have rect from document, adjusted to text width, heigh and position - // and we have to acount for offset depending on position - S32 v_delta = 0; - S32 h_delta = 0; - switch (mVAlign) - { - case LLFontGL::TOP: - v_delta = mVisibleTextRect.mTop - content_display_rect.mTop - mVPad; - break; - case LLFontGL::VCENTER: - v_delta = (llmax(mVisibleTextRect.getHeight() - content_display_rect.mTop, -content_display_rect.mBottom) + (mVisibleTextRect.mBottom - content_display_rect.mBottom)) / 2; - break; - case LLFontGL::BOTTOM: - v_delta = mVisibleTextRect.mBottom - content_display_rect.mBottom; - break; - default: - break; - } - switch (mHAlign) - { - case LLFontGL::LEFT: - h_delta = mVisibleTextRect.mLeft - content_display_rect.mLeft + mHPad; - break; - case LLFontGL::HCENTER: - h_delta = (llmax(mVisibleTextRect.getWidth() - content_display_rect.mLeft, -content_display_rect.mRight) + (mVisibleTextRect.mRight - content_display_rect.mRight)) / 2; - break; - case LLFontGL::RIGHT: - h_delta = mVisibleTextRect.mRight - content_display_rect.mRight; - break; - default: - break; - } - selection_rect.translate(h_delta, v_delta); - } - gl_rect_2d(selection_rect, selection_color); - } - } -} - -void LLTextBase::drawCursor() -{ - F32 alpha = getDrawContext().mAlpha; - - if( hasFocus() - && gFocusMgr.getAppHasFocus() - && !mReadOnly) - { - const LLWString &wtext = getWText(); - const llwchar* text = wtext.c_str(); - - LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); - cursor_rect.translate(-1, 0); - segment_set_t::iterator seg_it = getSegIterContaining(mCursorPos); - - // take style from last segment - LLTextSegmentPtr segmentp; - - if (seg_it != mSegments.end()) - { - segmentp = *seg_it; - } - else - { - return; - } - - // Draw the cursor - // (Flash the cursor every half second starting a fixed time after the last keystroke) - F32 elapsed = mCursorBlinkTimer.getElapsedTimeF32(); - if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) - { - - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) - { - S32 segment_width = 0; - S32 segment_height = 0; - segmentp->getDimensions(mCursorPos - segmentp->getStart(), 1, segment_width, segment_height); - S32 width = llmax(CURSOR_THICKNESS, segment_width); - cursor_rect.mRight = cursor_rect.mLeft + width; - } - else - { - cursor_rect.mRight = cursor_rect.mLeft + CURSOR_THICKNESS; - } - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - LLColor4 cursor_color = mCursorColor.get() % alpha; - gGL.color4fv( cursor_color.mV ); - - gl_rect_2d(cursor_rect); - - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n') - { - LLColor4 text_color; - const LLFontGL* fontp; - text_color = segmentp->getColor(); - fontp = segmentp->getStyle()->getFont(); - fontp->render(text, mCursorPos, cursor_rect, - LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha), - LLFontGL::LEFT, mTextVAlign, - LLFontGL::NORMAL, - LLFontGL::NO_SHADOW, - 1); - } - - // Make sure the IME is in the right place - LLRect screen_pos = calcScreenRect(); - LLCoordGL ime_pos( screen_pos.mLeft + llfloor(cursor_rect.mLeft), screen_pos.mBottom + llfloor(cursor_rect.mTop) ); - - ime_pos.mX = (S32) (ime_pos.mX * LLUI::getScaleFactor().mV[VX]); - ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]); - getWindow()->setLanguageTextInput( ime_pos ); - } - } -} - -void LLTextBase::drawText() -{ - S32 text_len = getLength(); - - if (text_len <= 0 && mLabel.empty()) - { - return; - } - else if (useLabel()) - { - text_len = mLabel.getWString().length(); - } - - S32 selection_left = -1; - S32 selection_right = -1; - // Draw selection even if we don't have keyboard focus for search/replace - if( hasSelection()) - { - selection_left = llmin( mSelectionStart, mSelectionEnd ); - selection_right = llmax( mSelectionStart, mSelectionEnd ); - } - - std::pair line_range = getVisibleLines(mClipPartial); - S32 first_line = line_range.first; - S32 last_line = line_range.second; - if (first_line >= last_line) - { - return; - } - - S32 line_start = getLineStart(first_line); - // find first text segment that spans top of visible portion of text buffer - segment_set_t::iterator seg_iter = getSegIterContaining(line_start); - if (seg_iter == mSegments.end()) - { - return; - } - - // Perform spell check if needed - if ( (getSpellCheck()) && (getWText().length() > 2) ) - { - // Calculate start and end indices for the spell checking range - S32 start = line_start; - S32 end = getLineEnd(last_line); - - if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) ) - { - const LLWString& wstrText = getWText(); - mMisspellRanges.clear(); - - segment_set_t::const_iterator seg_it = getSegIterContaining(start); - while (mSegments.end() != seg_it) - { - LLTextSegmentPtr text_segment = *seg_it; - if ( (text_segment.isNull()) || (text_segment->getStart() >= end) ) - { - break; - } - - if (!text_segment->canEdit()) - { - ++seg_it; - continue; - } - - // Combine adjoining text segments into one - U32 seg_start = text_segment->getStart(), seg_end = llmin(text_segment->getEnd(), end); - while (mSegments.end() != ++seg_it) - { - text_segment = *seg_it; - if ( (text_segment.isNull()) || (!text_segment->canEdit()) || (text_segment->getStart() >= end) ) - { - break; - } - seg_end = llmin(text_segment->getEnd(), end); - } - - // Find the start of the first word - U32 word_start = seg_start, word_end = -1; - U32 text_length = wstrText.length(); - while ( (word_start < text_length) && (!LLStringOps::isAlpha(wstrText[word_start])) ) - { - word_start++; - } - - // Iterate over all words in the text block and check them one by one - while (word_start < seg_end) - { - // Find the end of the current word (special case handling for "'" when it's used as a contraction) - word_end = word_start + 1; - while ( (word_end < seg_end) && - ((LLWStringUtil::isPartOfWord(wstrText[word_end])) || - ((L'\'' == wstrText[word_end]) && - (LLStringOps::isAlnum(wstrText[word_end - 1])) && (LLStringOps::isAlnum(wstrText[word_end + 1])))) ) - { - word_end++; - } - if (word_end > seg_end) - { - break; - } - - if (word_start < text_length && word_end <= text_length && word_end > word_start) - { - std::string word = wstring_to_utf8str(wstrText.substr(word_start, word_end - word_start)); - - // Don't process words shorter than 3 characters - if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) ) - { - mMisspellRanges.push_back(std::pair(word_start, word_end)); - } - } - - // Find the start of the next word - word_start = word_end + 1; - while ( (word_start < seg_end) && (!LLWStringUtil::isPartOfWord(wstrText[word_start])) ) - { - word_start++; - } - } - } - - mSpellCheckStart = start; - mSpellCheckEnd = end; - } - } - else - { - mMisspellRanges.clear(); - } - - LLTextSegmentPtr cur_segment = *seg_iter; - - std::list >::const_iterator misspell_it = std::lower_bound(mMisspellRanges.begin(), mMisspellRanges.end(), std::pair(line_start, 0)); - for (S32 cur_line = first_line; cur_line < last_line; cur_line++) - { - S32 next_line = cur_line + 1; - line_info& line = mLineInfoList[cur_line]; - - S32 next_start = -1; - S32 line_end = text_len; - - if (next_line < getLineCount()) - { - next_start = getLineStart(next_line); - line_end = next_start; - } - - LLRectf text_rect(line.mRect.mLeft, line.mRect.mTop, line.mRect.mRight, line.mRect.mBottom); - text_rect.mRight = mDocumentView->getRect().getWidth(); // clamp right edge to document extents - text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom); // adjust by scroll position - - // draw a single line of text - S32 seg_start = line_start; - while( seg_start < line_end ) - { - while( cur_segment->getEnd() <= seg_start ) - { - seg_iter++; - if (seg_iter == mSegments.end()) - { - LL_WARNS() << "Ran off the segmentation end!" << LL_ENDL; - - return; - } - cur_segment = *seg_iter; - } - - S32 seg_end = llmin(line_end, cur_segment->getEnd()); - S32 clipped_end = seg_end - cur_segment->getStart(); - - if (mUseEllipses // using ellipses - && clipped_end == line_end // last segment on line - && next_line == last_line // this is the last visible line - && last_line < (S32)mLineInfoList.size()) // and there is more text to display - { - // more lines of text to go, but we can't fit them - // so shrink text rect to force ellipses - text_rect.mRight -= 2; - } - - // Draw squiggly lines under any visible misspelled words - while ( (mMisspellRanges.end() != misspell_it) && (misspell_it->first < seg_end) && (misspell_it->second > seg_start) ) - { - // Skip the current word if the user is still busy editing it - if ( (!mSpellCheckTimer.hasExpired()) && (misspell_it->first <= (U32)mCursorPos) && (misspell_it->second >= (U32)mCursorPos) ) - { - ++misspell_it; - continue; - } - - U32 misspell_start = llmax(misspell_it->first, seg_start), misspell_end = llmin(misspell_it->second, seg_end); - S32 squiggle_start = 0, squiggle_end = 0, pony = 0; - cur_segment->getDimensions(seg_start - cur_segment->getStart(), misspell_start - seg_start, squiggle_start, pony); - cur_segment->getDimensions(misspell_start - cur_segment->getStart(), misspell_end - misspell_start, squiggle_end, pony); - squiggle_start += text_rect.mLeft; - - pony = (squiggle_end + 3) / 6; - squiggle_start += squiggle_end / 2 - pony * 3; - squiggle_end = squiggle_start + pony * 6; - - S32 squiggle_bottom = text_rect.mBottom + (S32)cur_segment->getStyle()->getFont()->getDescenderHeight(); - - gGL.color4ub(255, 0, 0, 200); - while (squiggle_start + 1 < squiggle_end) - { - gl_line_2d(squiggle_start, squiggle_bottom, squiggle_start + 2, squiggle_bottom - 2); - if (squiggle_start + 3 < squiggle_end) - { - gl_line_2d(squiggle_start + 2, squiggle_bottom - 3, squiggle_start + 4, squiggle_bottom - 1); - } - squiggle_start += 4; - } - - if (misspell_it->second > seg_end) - { - break; - } - ++misspell_it; - } - - text_rect.mLeft = cur_segment->draw(seg_start - cur_segment->getStart(), clipped_end, selection_left, selection_right, text_rect); - - seg_start = clipped_end + cur_segment->getStart(); - } - - line_start = next_start; - } -} - -/////////////////////////////////////////////////////////////////// -// Returns change in number of characters in mWText - -S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::segment_vec_t* segments ) -{ - beforeValueChange(); - - S32 old_len = getLength(); // length() returns character length - S32 insert_len = wstr.length(); - - pos = getEditableIndex(pos, true); - if (pos > old_len) - { - pos = old_len; - // Should not happen, - // if you encounter this, check where wrong position comes from - llassert(false); - } - - segment_set_t::iterator seg_iter = getEditableSegIterContaining(pos); - - LLTextSegmentPtr default_segment; - - LLTextSegmentPtr segmentp; - if (seg_iter != mSegments.end()) - { - segmentp = *seg_iter; - } - else - { - //segmentp = mSegments.back(); - return pos; - } - - if (segmentp->canEdit()) - { - segmentp->setEnd(segmentp->getEnd() + insert_len); - if (seg_iter != mSegments.end()) - { - ++seg_iter; - } - } - else - { - // create default editable segment to hold new text - LLStyleConstSP sp(new LLStyle(getStyleParams())); - default_segment = new LLNormalTextSegment( sp, pos, pos + insert_len, *this); - } - - // shift remaining segments to right - for(;seg_iter != mSegments.end(); ++seg_iter) - { - LLTextSegmentPtr segmentp = *seg_iter; - segmentp->setStart(segmentp->getStart() + insert_len); - segmentp->setEnd(segmentp->getEnd() + insert_len); - } - - // insert new segments - if (segments) - { - if (default_segment.notNull()) - { - // potentially overwritten by segments passed in - insertSegment(default_segment); - } - for (segment_vec_t::iterator seg_iter = segments->begin(); - seg_iter != segments->end(); - ++seg_iter) - { - LLTextSegment* segmentp = *seg_iter; - insertSegment(segmentp); - } - } - - // Insert special segments where necessary (insertSegment takes care of splitting normal text segments around them for us) - if (mUseEmoji) - { - LLStyleSP emoji_style; - LLEmojiDictionary* ed = LLEmojiDictionary::instanceExists() ? LLEmojiDictionary::getInstance() : NULL; - for (S32 text_kitty = 0, text_len = wstr.size(); text_kitty < text_len; text_kitty++) - { - llwchar code = wstr[text_kitty]; - bool isEmoji = ed ? ed->isEmoji(code) : LLStringOps::isEmoji(code); - if (isEmoji) - { - if (!emoji_style) - { - emoji_style = new LLStyle(getStyleParams()); - emoji_style->setFont(LLFontGL::getFontEmojiLarge()); - } - - S32 new_seg_start = pos + text_kitty; - insertSegment(new LLEmojiTextSegment(emoji_style, new_seg_start, new_seg_start + 1, *this)); - } - } - } - - getViewModel()->getEditableDisplay().insert(pos, wstr); - - if ( truncate() ) - { - insert_len = getLength() - old_len; - } - - onValueChange(pos, pos + insert_len); - needsReflow(pos); - - return insert_len; -} - -S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) -{ - - beforeValueChange(); - segment_set_t::iterator seg_iter = getSegIterContaining(pos); - while(seg_iter != mSegments.end()) - { - LLTextSegmentPtr segmentp = *seg_iter; - S32 end = pos + length; - if (segmentp->getStart() < pos) - { - // deleting from middle of segment - if (segmentp->getEnd() > end) - { - segmentp->setEnd(segmentp->getEnd() - length); - } - // truncating segment - else - { - segmentp->setEnd(pos); - } - } - else if (segmentp->getStart() < end) - { - // deleting entire segment - if (segmentp->getEnd() <= end) - { - // remove segment - segmentp->unlinkFromDocument(this); - segment_set_t::iterator seg_to_erase(seg_iter++); - mSegments.erase(seg_to_erase); - continue; - } - // deleting head of segment - else - { - segmentp->setStart(pos); - segmentp->setEnd(segmentp->getEnd() - length); - } - } - else - { - // shifting segments backward to fill deleted portion - segmentp->setStart(segmentp->getStart() - length); - segmentp->setEnd(segmentp->getEnd() - length); - } - ++seg_iter; - } - - getViewModel()->getEditableDisplay().erase(pos, length); - - // recreate default segment in case we erased everything - createDefaultSegment(); - - onValueChange(pos, pos); - needsReflow(pos); - - return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length -} - -S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) -{ - beforeValueChange(); - - if (pos > (S32)getLength()) - { - return 0; - } - getViewModel()->getEditableDisplay()[pos] = wc; - - onValueChange(pos, pos + 1); - needsReflow(pos); - - return 1; -} - - -void LLTextBase::createDefaultSegment() -{ - // ensures that there is always at least one segment - if (mSegments.empty()) - { - LLStyleConstSP sp(new LLStyle(getStyleParams())); - LLTextSegmentPtr default_segment = new LLNormalTextSegment( sp, 0, getLength() + 1, *this); - mSegments.insert(default_segment); - default_segment->linkToDocument(this); - } -} - -void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) -{ - if (segment_to_insert.isNull()) - { - return; - } - - segment_set_t::iterator cur_seg_iter = getSegIterContaining(segment_to_insert->getStart()); - S32 reflow_start_index = 0; - - if (cur_seg_iter == mSegments.end()) - { - mSegments.insert(segment_to_insert); - segment_to_insert->linkToDocument(this); - reflow_start_index = segment_to_insert->getStart(); - } - else - { - LLTextSegmentPtr cur_segmentp = *cur_seg_iter; - reflow_start_index = cur_segmentp->getStart(); - if (cur_segmentp->getStart() < segment_to_insert->getStart()) - { - S32 old_segment_end = cur_segmentp->getEnd(); - // split old at start point for new segment - cur_segmentp->setEnd(segment_to_insert->getStart()); - // advance to next segment - // insert remainder of old segment - LLStyleConstSP sp = cur_segmentp->getStyle(); - LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( sp, segment_to_insert->getStart(), old_segment_end, *this); - mSegments.insert(cur_seg_iter, remainder_segment); - remainder_segment->linkToDocument(this); - // insert new segment before remainder of old segment - mSegments.insert(cur_seg_iter, segment_to_insert); - - segment_to_insert->linkToDocument(this); - // at this point, there will be two overlapping segments owning the text - // associated with the incoming segment - } - else - { - mSegments.insert(cur_seg_iter, segment_to_insert); - segment_to_insert->linkToDocument(this); - } - - // now delete/truncate remaining segments as necessary - // cur_seg_iter points to segment before incoming segment - while(cur_seg_iter != mSegments.end()) - { - cur_segmentp = *cur_seg_iter; - if (cur_segmentp == segment_to_insert) - { - ++cur_seg_iter; - continue; - } - - if (cur_segmentp->getStart() >= segment_to_insert->getStart()) - { - if(cur_segmentp->getEnd() <= segment_to_insert->getEnd()) - { - cur_segmentp->unlinkFromDocument(this); - // grab copy of iterator to erase, and bump it - segment_set_t::iterator seg_to_erase(cur_seg_iter++); - mSegments.erase(seg_to_erase); - continue; - } - else - { - // last overlapping segment, clip to end of incoming segment - // and stop traversal - cur_segmentp->setStart(segment_to_insert->getEnd()); - break; - } - } - ++cur_seg_iter; - } - } - - // layout potentially changed - needsReflow(reflow_start_index); -} - -//virtual -bool LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // handle triple click - if (!mTripleClickTimer.hasExpired()) - { - if (mSkipTripleClick) - { - return true; - } - - S32 real_line = getLineNumFromDocIndex(mCursorPos, false); - S32 line_start = -1; - S32 line_end = -1; - for (line_list_t::const_iterator it = mLineInfoList.begin(), end_it = mLineInfoList.end(); - it != end_it; - ++it) - { - if (it->mLineNum < real_line) - { - continue; - } - if (it->mLineNum > real_line) - { - break; - } - if (line_start == -1) - { - line_start = it->mDocIndexStart; - } - line_end = it->mDocIndexEnd; - line_end = llclamp(line_end, 0, getLength()); - } - - if (line_start == -1) - { - return true; - } - - mSelectionEnd = line_start; - mSelectionStart = line_end; - setCursorPos(line_start); - - return true; - } - - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleMouseDown(x, y, mask)) - { - return true; - } - - return LLUICtrl::handleMouseDown(x, y, mask); -} - -//virtual -bool LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (hasMouseCapture() && cur_segment && cur_segment->handleMouseUp(x, y, mask)) - { - // Did we just click on a link? - if (mURLClickSignal - && cur_segment->getStyle() - && cur_segment->getStyle()->isLink()) - { - // *TODO: send URL here? - (*mURLClickSignal)(this, LLSD() ); - } - return true; - } - - return LLUICtrl::handleMouseUp(x, y, mask); -} - -//virtual -bool LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleMiddleMouseDown(x, y, mask)) - { - return true; - } - - return LLUICtrl::handleMiddleMouseDown(x, y, mask); -} - -//virtual -bool LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleMiddleMouseUp(x, y, mask)) - { - return true; - } - - return LLUICtrl::handleMiddleMouseUp(x, y, mask); -} - -//virtual -bool LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleRightMouseDown(x, y, mask)) - { - return true; - } - - return LLUICtrl::handleRightMouseDown(x, y, mask); -} - -//virtual -bool LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleRightMouseUp(x, y, mask)) - { - return true; - } - - return LLUICtrl::handleRightMouseUp(x, y, mask); -} - -//virtual -bool LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - //Don't start triple click timer if user have clicked on scrollbar - mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); - if (x >= mVisibleTextRect.mLeft && x <= mVisibleTextRect.mRight - && y >= mVisibleTextRect.mBottom && y <= mVisibleTextRect.mTop) - { - mTripleClickTimer.setTimerExpirySec(TRIPLE_CLICK_INTERVAL); - } - - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleDoubleClick(x, y, mask)) - { - return true; - } - - return LLUICtrl::handleDoubleClick(x, y, mask); -} - -//virtual -bool LLTextBase::handleHover(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleHover(x, y, mask)) - { - return true; - } - - return LLUICtrl::handleHover(x, y, mask); -} - -//virtual -bool LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleScrollWheel(x, y, clicks)) - { - return true; - } - - return LLUICtrl::handleScrollWheel(x, y, clicks); -} - -//virtual -bool LLTextBase::handleToolTip(S32 x, S32 y, MASK mask) -{ - LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); - if (cur_segment && cur_segment->handleToolTip(x, y, mask)) - { - return true; - } - - return LLUICtrl::handleToolTip(x, y, mask); -} - -//virtual -void LLTextBase::reshape(S32 width, S32 height, bool called_from_parent) -{ - if (width != getRect().getWidth() || height != getRect().getHeight() || LLView::sForceReshape) - { - bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false; - - LLUICtrl::reshape( width, height, called_from_parent ); - - if (mScroller && scrolled_to_bottom && mTrackEnd) - { - // keep bottom of text buffer visible - // do this here as well as in reflow to handle case - // where shrinking from top, which causes buffer to temporarily - // not be scrolled to the bottom, since the scroll index - // specified the _top_ of the visible document region - mScroller->goToBottom(); - } - - // do this first after reshape, because other things depend on - // up-to-date mVisibleTextRect - updateRects(); - - needsReflow(); - } -} - -//virtual -void LLTextBase::draw() -{ - // reflow if needed, on demand - reflow(); - - // then update scroll position, as cursor may have moved - if (!mReadOnly) - { - updateScrollFromCursor(); - } - - LLRect text_rect; - if (mScroller) - { - mScroller->localRectToOtherView(mScroller->getContentWindowRect(), &text_rect, this); - } - else - { - LLRect visible_lines_rect; - std::pair line_range = getVisibleLines(mClipPartial); - for (S32 i = line_range.first; i < line_range.second; i++) - { - if (visible_lines_rect.isEmpty()) - { - visible_lines_rect = mLineInfoList[i].mRect; - } - else - { - visible_lines_rect.unionWith(mLineInfoList[i].mRect); - } - } - text_rect = visible_lines_rect; - text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom); - } - - if (mBGVisible) - { - F32 alpha = getCurrentTransparency(); - // clip background rect against extents, if we support scrolling - LLRect bg_rect = mVisibleTextRect; - if (mScroller) - { - bg_rect.intersectWith(text_rect); - } - LLColor4 bg_color = mReadOnly - ? mReadOnlyBgColor.get() - : hasFocus() - ? mFocusBgColor.get() - : mWriteableBgColor.get(); - gl_rect_2d(text_rect, bg_color % alpha, true); - } - - // Draw highlighted if needed - if( ll::ui::SearchableControl::getHighlighted() ) - { - LLColor4 bg_color = ll::ui::SearchableControl::getHighlightColor(); - LLRect bg_rect = mVisibleTextRect; - if( mScroller ) - bg_rect.intersectWith( text_rect ); - - gl_rect_2d( text_rect, bg_color, true ); - } - - bool should_clip = mClip || mScroller != NULL; - { LLLocalClipRect clip(text_rect, should_clip); - - // draw document view - if (mScroller) - { - drawChild(mScroller); - } - else - { - drawChild(mDocumentView); - } - - drawSelectionBackground(); - drawText(); - drawCursor(); - } - - mDocumentView->setVisibleDirect(false); - LLUICtrl::draw(); - mDocumentView->setVisibleDirect(true); -} - - -//virtual -void LLTextBase::setColor( const LLColor4& c ) -{ - mFgColor = c; - mStyleDirty = true; -} - -//virtual -void LLTextBase::setReadOnlyColor(const LLColor4 &c) -{ - mReadOnlyFgColor = c; - mStyleDirty = true; -} - -//virtual -void LLTextBase::onVisibilityChange( bool new_visibility ) -{ - LLContextMenu* menu = static_cast(mPopupMenuHandle.get()); - if(!new_visibility && menu) - { - menu->hide(); - } - LLUICtrl::onVisibilityChange(new_visibility); -} - -//virtual -void LLTextBase::setValue(const LLSD& value ) -{ - setText(value.asString()); -} - -//virtual -bool LLTextBase::canDeselect() const -{ - return hasSelection(); -} - - -//virtual -void LLTextBase::deselect() -{ - mSelectionStart = 0; - mSelectionEnd = 0; - mIsSelecting = false; -} - -bool LLTextBase::getSpellCheck() const -{ - return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); -} - -const std::string& LLTextBase::getSuggestion(U32 index) const -{ - return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null; -} - -U32 LLTextBase::getSuggestionCount() const -{ - return mSuggestionList.size(); -} - -void LLTextBase::replaceWithSuggestion(U32 index) -{ - for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) - { - if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) ) - { - deselect(); - // Insert the suggestion in its place - LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]); - insertStringNoUndo(it->first, utf8str_to_wstring(mSuggestionList[index])); - - // Delete the misspelled word - removeStringNoUndo(it->first + (S32)suggestion.length(), it->second - it->first); - - - setCursorPos(it->first + (S32)suggestion.length()); - onSpellCheckPerformed(); - - break; - } - } - mSpellCheckStart = mSpellCheckEnd = -1; -} - -void LLTextBase::addToDictionary() -{ - if (canAddToDictionary()) - { - LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos)); - } -} - -bool LLTextBase::canAddToDictionary() const -{ - return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); -} - -void LLTextBase::addToIgnore() -{ - if (canAddToIgnore()) - { - LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos)); - } -} - -bool LLTextBase::canAddToIgnore() const -{ - return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); -} - -std::string LLTextBase::getMisspelledWord(U32 pos) const -{ - for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) - { - if ( (it->first <= pos) && (it->second >= pos) ) - { - return wstring_to_utf8str(getWText().substr(it->first, it->second - it->first)); - } - } - return LLStringUtil::null; -} - -bool LLTextBase::isMisspelledWord(U32 pos) const -{ - for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) - { - if ( (it->first <= pos) && (it->second >= pos) ) - { - return true; - } - } - return false; -} - -void LLTextBase::onSpellCheckSettingsChange() -{ - // Recheck the spelling on every change - mMisspellRanges.clear(); - mSpellCheckStart = mSpellCheckEnd = -1; -} - -void LLTextBase::onFocusReceived() -{ - LLUICtrl::onFocusReceived(); - if (!getLength() && !mLabel.empty()) - { - // delete label which is LLLabelTextSegment - clearSegments(); - } -} - -void LLTextBase::onFocusLost() -{ - LLUICtrl::onFocusLost(); - if (!getLength() && !mLabel.empty()) - { - resetLabel(); - } -} - -// Sets the scrollbar from the cursor position -void LLTextBase::updateScrollFromCursor() -{ - // Update scroll position even in read-only mode (when there's no cursor displayed) - // because startOfDoc()/endOfDoc() modify cursor position. See EXT-736. - - if (!mScrollNeeded || !mScroller) - { - return; - } - mScrollNeeded = false; - - // scroll so that the cursor is at the top of the page - LLRect scroller_doc_window = getVisibleDocumentRect(); - LLRect cursor_rect_doc = getDocRectFromDocIndex(mCursorPos); - mScroller->scrollToShowRect(cursor_rect_doc, LLRect(0, scroller_doc_window.getHeight() - 5, scroller_doc_window.getWidth(), 5)); -} - -S32 LLTextBase::getLeftOffset(S32 width) -{ - switch (mHAlign) - { - case LLFontGL::LEFT: - return mHPad; - case LLFontGL::HCENTER: - return mHPad + llmax(0, (mVisibleTextRect.getWidth() - width - mHPad) / 2); - case LLFontGL::RIGHT: - { - // Font's rendering rounds string size, if value gets rounded - // down last symbol might not have enough space to render, - // compensate by adding an extra pixel as padding - const S32 right_padding = 1; - return llmax(mHPad, mVisibleTextRect.getWidth() - width - right_padding); - } - default: - return mHPad; - } -} - -void LLTextBase::reflow() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - - updateSegments(); - - if (mReflowIndex == S32_MAX) - { - return; - } - - bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false; - - LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); - bool follow_selection = getLocalRect().overlaps(cursor_rect); // cursor is (potentially) visible - - // store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing - cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop; - cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom; - - S32 first_line = getFirstVisibleLine(); - - // if scroll anchor not on first line, update it to first character of first line - if ((first_line < mLineInfoList.size()) - && (mScrollIndex < mLineInfoList[first_line].mDocIndexStart - || mScrollIndex >= mLineInfoList[first_line].mDocIndexEnd)) - { - mScrollIndex = mLineInfoList[first_line].mDocIndexStart; - } - LLRect first_char_rect = getLocalRectFromDocIndex(mScrollIndex); - // store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing - first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop; - first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom; - - S32 reflow_count = 0; - while(mReflowIndex < S32_MAX) - { - // we can get into an infinite loop if the document height does not monotonically increase - // with decreasing width (embedded ui elements with alternate layouts). In that case, - // we want to stop reflowing after 2 iterations. We use 2, since we need to handle the case - // of introducing a vertical scrollbar causing a reflow with less width. We should also always - // use an even number of iterations to avoid user visible oscillation of the layout - if(++reflow_count > 2) - { - LL_DEBUGS() << "Breaking out of reflow due to possible infinite loop in " << getName() << LL_ENDL; - break; - } - - S32 start_index = mReflowIndex; - mReflowIndex = S32_MAX; - - // shrink document to minimum size (visible portion of text widget) - // to force inlined widgets with follows set to shrink - if (mWordWrap) - { - mDocumentView->reshape(mVisibleTextRect.getWidth(), mDocumentView->getRect().getHeight()); - } - - S32 cur_top = 0; - - segment_set_t::iterator seg_iter = mSegments.begin(); - S32 seg_offset = 0; - S32 line_start_index = 0; - const F32 text_available_width = mVisibleTextRect.getWidth() - mHPad; // reserve room for margin - F32 remaining_pixels = text_available_width; - S32 line_count = 0; - - // find and erase line info structs starting at start_index and going to end of document - if (!mLineInfoList.empty()) - { - // find first element whose end comes after start_index - line_list_t::iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), start_index, line_end_compare()); - if (iter != mLineInfoList.end()) - { - line_start_index = iter->mDocIndexStart; - line_count = iter->mLineNum; - cur_top = iter->mRect.mTop; - getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset); - mLineInfoList.erase(iter, mLineInfoList.end()); - } - } - - S32 line_height = 0; - S32 seg_line_offset = line_count + 1; - - while(seg_iter != mSegments.end()) - { - LLTextSegmentPtr segment = *seg_iter; - - // track maximum height of any segment on this line - S32 cur_index = segment->getStart() + seg_offset; - - // ask segment how many character fit in remaining space - S32 character_count = segment->getNumChars(getWordWrap() ? llmax(0, ll_round(remaining_pixels)) : S32_MAX, - seg_offset, - cur_index - line_start_index, - S32_MAX, - line_count - seg_line_offset); - - F32 segment_width; - S32 segment_height; - bool force_newline = segment->getDimensionsF32(seg_offset, character_count, segment_width, segment_height); - // grow line height as necessary based on reported height of this segment - line_height = llmax(line_height, segment_height); - remaining_pixels -= segment_width; - - seg_offset += character_count; - - S32 last_segment_char_on_line = segment->getStart() + seg_offset; - - // Note: make sure text will fit in width - use ceil, but also make sure - // ceil is used only once per line - S32 text_actual_width = llceil(text_available_width - remaining_pixels); - S32 text_left = getLeftOffset(text_actual_width); - LLRect line_rect(text_left, - cur_top, - text_left + text_actual_width, - cur_top - line_height); - - // if we didn't finish the current segment... - if (last_segment_char_on_line < segment->getEnd()) - { - // add line info and keep going - mLineInfoList.push_back(line_info( - line_start_index, - last_segment_char_on_line, - line_rect, - line_count)); - - line_start_index = segment->getStart() + seg_offset; - cur_top -= ll_round((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; - remaining_pixels = text_available_width; - line_height = 0; - } - // ...just consumed last segment.. - else if (++segment_set_t::iterator(seg_iter) == mSegments.end()) - { - mLineInfoList.push_back(line_info( - line_start_index, - last_segment_char_on_line, - line_rect, - line_count)); - cur_top -= ll_round((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; - break; - } - // ...or finished a segment and there are segments remaining on this line - else - { - // subtract pixels used and increment segment - if (force_newline) - { - mLineInfoList.push_back(line_info( - line_start_index, - last_segment_char_on_line, - line_rect, - line_count)); - line_start_index = segment->getStart() + seg_offset; - cur_top -= ll_round((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; - line_height = 0; - remaining_pixels = text_available_width; - } - ++seg_iter; - seg_offset = 0; - seg_line_offset = force_newline ? line_count + 1 : line_count; - } - if (force_newline) - { - line_count++; - } - } - - // calculate visible region for diplaying text - updateRects(); - - for (segment_set_t::iterator segment_it = mSegments.begin(); - segment_it != mSegments.end(); - ++segment_it) - { - LLTextSegmentPtr segmentp = *segment_it; - segmentp->updateLayout(*this); - - } - } - - // apply scroll constraints after reflowing text - if (!hasMouseCapture() && mScroller) - { - if (scrolled_to_bottom && mTrackEnd) - { - // keep bottom of text buffer visible - endOfDoc(); - } - else if (hasSelection() && follow_selection) - { - // keep cursor in same vertical position on screen when selecting text - LLRect new_cursor_rect_doc = getDocRectFromDocIndex(mCursorPos); - LLRect old_cursor_rect = cursor_rect; - old_cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop; - old_cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom; - - mScroller->scrollToShowRect(new_cursor_rect_doc, old_cursor_rect); - } - else - { - // keep first line of text visible - LLRect new_first_char_rect = getDocRectFromDocIndex(mScrollIndex); - - // pass in desired rect in the coordinate frame of the document viewport - LLRect old_first_char_rect = first_char_rect; - old_first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop; - old_first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom; - - mScroller->scrollToShowRect(new_first_char_rect, old_first_char_rect); - } - } - - // reset desired x cursor position - updateCursorXPos(); -} - -LLRect LLTextBase::getTextBoundingRect() -{ - reflow(); - return mTextBoundingRect; -} - - -void LLTextBase::clearSegments() -{ - mSegments.clear(); - createDefaultSegment(); -} - -S32 LLTextBase::getLineStart( S32 line ) const -{ - S32 num_lines = getLineCount(); - if (num_lines == 0) - { - return 0; - } - - line = llclamp(line, 0, num_lines-1); - return mLineInfoList[line].mDocIndexStart; -} - -S32 LLTextBase::getLineEnd( S32 line ) const -{ - S32 num_lines = getLineCount(); - if (num_lines == 0) - { - return 0; - } - - line = llclamp(line, 0, num_lines-1); - return mLineInfoList[line].mDocIndexEnd; -} - - - -S32 LLTextBase::getLineNumFromDocIndex( S32 doc_index, bool include_wordwrap) const -{ - if (mLineInfoList.empty()) - { - return 0; - } - else - { - line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_index, line_end_compare()); - if (include_wordwrap) - { - return iter - mLineInfoList.begin(); - } - else - { - if (iter == mLineInfoList.end()) - { - return mLineInfoList.back().mLineNum; - } - else - { - return iter->mLineNum; - } - } - } -} - -// Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line. -S32 LLTextBase::getLineOffsetFromDocIndex( S32 startpos, bool include_wordwrap) const -{ - if (mLineInfoList.empty()) - { - return startpos; - } - else - { - line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), startpos, line_end_compare()); - return startpos - iter->mDocIndexStart; - } -} - -S32 LLTextBase::getFirstVisibleLine() const -{ - LLRect visible_region = getVisibleDocumentRect(); - - // binary search for line that starts before top of visible buffer - line_list_t::const_iterator iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom()); - - return iter - mLineInfoList.begin(); -} - -std::pair LLTextBase::getVisibleLines(bool require_fully_visible) -{ - LLRect visible_region = getVisibleDocumentRect(); - line_list_t::const_iterator first_iter; - line_list_t::const_iterator last_iter; - - // make sure we have an up-to-date mLineInfoList - reflow(); - - if (require_fully_visible) - { - first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_top()); - last_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom()); - } - else - { - first_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom()); - last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_top()); - } - return std::pair(first_iter - mLineInfoList.begin(), last_iter - mLineInfoList.begin()); -} - - - -LLTextViewModel* LLTextBase::getViewModel() const -{ - return (LLTextViewModel*)mViewModel.get(); -} - -void LLTextBase::addDocumentChild(LLView* view) -{ - mDocumentView->addChild(view); -} - -void LLTextBase::removeDocumentChild(LLView* view) -{ - mDocumentView->removeChild(view); -} - - -void LLTextBase::updateSegments() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - createDefaultSegment(); -} - -void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const -{ - *seg_iter = getSegIterContaining(startpos); - if (*seg_iter == mSegments.end()) - { - *offsetp = 0; - } - else - { - *offsetp = startpos - (**seg_iter)->getStart(); - } -} - -void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp ) -{ - *seg_iter = getSegIterContaining(startpos); - if (*seg_iter == mSegments.end()) - { - *offsetp = 0; - } - else - { - *offsetp = startpos - (**seg_iter)->getStart(); - } -} - -LLTextBase::segment_set_t::iterator LLTextBase::getEditableSegIterContaining(S32 index) -{ - segment_set_t::iterator it = getSegIterContaining(index); - segment_set_t::iterator orig_it = it; - - if (it == mSegments.end()) return it; - - if (!(*it)->canEdit() - && index == (*it)->getStart() - && it != mSegments.begin()) - { - it--; - if ((*it)->canEdit()) - { - return it; - } - } - return orig_it; -} - -LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaining(S32 index) const -{ - segment_set_t::const_iterator it = getSegIterContaining(index); - segment_set_t::const_iterator orig_it = it; - if (it == mSegments.end()) return it; - - if (!(*it)->canEdit() - && index == (*it)->getStart() - && it != mSegments.begin()) - { - it--; - if ((*it)->canEdit()) - { - return it; - } - } - return orig_it; -} - -LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index) -{ - static LLPointer index_segment = new LLIndexSegment(); - - // when there are no segments, we return the end iterator, which must be checked by caller - if (mSegments.size() <= 1) { return mSegments.begin(); } - - index_segment->setStart(index); - index_segment->setEnd(index); - segment_set_t::iterator it = mSegments.upper_bound(index_segment); - return it; -} - -LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 index) const -{ - static LLPointer index_segment = new LLIndexSegment(); - - // when there are no segments, we return the end iterator, which must be checked by caller - if (mSegments.size() <= 1) { return mSegments.begin(); } - - index_segment->setStart(index); - index_segment->setEnd(index); - LLTextBase::segment_set_t::const_iterator it = mSegments.upper_bound(index_segment); - return it; -} - -// Finds the text segment (if any) at the give local screen position -LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line) -{ - // Find the cursor position at the requested local screen position - S32 offset = getDocIndexFromLocalCoord( x, y, false, hit_past_end_of_line); - segment_set_t::iterator seg_iter = getSegIterContaining(offset); - if (seg_iter != mSegments.end()) - { - return *seg_iter; - } - else - { - return LLTextSegmentPtr(); - } -} - -void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) -{ - // work out the XUI menu file to use for this url - LLUrlMatch match; - std::string url = in_url; - if (! LLUrlRegistry::instance().findUrl(url, match)) - { - return; - } - - std::string xui_file = match.getMenuName(); - if (xui_file.empty()) - { - return; - } - - // set up the callbacks for all of the potential menu items, N.B. we - // don't use const ref strings in callbacks in case url goes out of scope - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("Url.Open", boost::bind(&LLUrlAction::openURL, url)); - registrar.add("Url.OpenInternal", boost::bind(&LLUrlAction::openURLInternal, url)); - registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url)); - registrar.add("Url.Execute", boost::bind(&LLUrlAction::executeSLURL, url, true)); - registrar.add("Url.Block", boost::bind(&LLUrlAction::blockObject, url)); - registrar.add("Url.Unblock", boost::bind(&LLUrlAction::unblockObject, url)); - registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url)); - registrar.add("Url.ShowProfile", boost::bind(&LLUrlAction::showProfile, url)); - registrar.add("Url.AddFriend", boost::bind(&LLUrlAction::addFriend, url)); - registrar.add("Url.RemoveFriend", boost::bind(&LLUrlAction::removeFriend, url)); - registrar.add("Url.ReportAbuse", boost::bind(&LLUrlAction::reportAbuse, url)); - registrar.add("Url.SendIM", boost::bind(&LLUrlAction::sendIM, url)); - registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url)); - registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url)); - registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url)); - - // create and return the context menu from the XUI file - - LLContextMenu* menu = static_cast(mPopupMenuHandle.get()); - if (menu) - { - menu->die(); - mPopupMenuHandle.markDead(); - } - llassert(LLMenuGL::sMenuContainer != NULL); - menu = LLUICtrlFactory::getInstance()->createFromFile(xui_file, LLMenuGL::sMenuContainer, - LLMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mPopupMenuHandle = menu->getHandle(); - - if (mIsFriendSignal) - { - bool isFriend = *(*mIsFriendSignal)(LLUUID(LLUrlAction::getUserID(url))); - LLView* addFriendButton = menu->getChild("add_friend"); - LLView* removeFriendButton = menu->getChild("remove_friend"); - - if (addFriendButton && removeFriendButton) - { - addFriendButton->setEnabled(!isFriend); - removeFriendButton->setEnabled(isFriend); - } - } - - if (mIsObjectBlockedSignal) - { - bool is_blocked = *(*mIsObjectBlockedSignal)(LLUUID(LLUrlAction::getObjectId(url)), LLUrlAction::getObjectName(url)); - LLView* blockButton = menu->getChild("block_object"); - LLView* unblockButton = menu->getChild("unblock_object"); - - if (blockButton && unblockButton) - { - blockButton->setVisible(!is_blocked); - unblockButton->setVisible(is_blocked); - } - } - menu->show(x, y); - LLMenuGL::showPopup(this, menu, x, y); - } -} - -void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) -{ - // clear out the existing text and segments - getViewModel()->setDisplay(LLWStringUtil::null); - - clearSegments(); -// createDefaultSegment(); - - deselect(); - - // append the new text (supports Url linking) - std::string text(utf8str); - LLStringUtil::removeCRLF(text); - - // appendText modifies mCursorPos... - appendText(text, false, input_params); - // ...so move cursor to top after appending text - if (!mTrackEnd) - { - startOfDoc(); - } - - onValueChange(0, getLength()); -} - -// virtual -const std::string& LLTextBase::getText() const -{ - return getViewModel()->getStringValue(); -} - -// IDEVO - icons can be UI image names or UUID sent from -// server with avatar display name -static LLUIImagePtr image_from_icon_name(const std::string& icon_name) -{ - if (LLUUID::validate(icon_name)) - { - return LLUI::getUIImageByID( LLUUID(icon_name) ); - } - else - { - return LLUI::getUIImage(icon_name); - } -} - - -void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - LLStyle::Params style_params(input_params); - style_params.fillFrom(getStyleParams()); - - S32 part = (S32)LLTextParser::WHOLE; - if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). - { - S32 start=0,end=0; - LLUrlMatch match; - std::string text = new_text; - while (LLUrlRegistry::instance().findUrl(text, match, - boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons)) - { - start = match.getStart(); - end = match.getEnd()+1; - - LLStyle::Params link_params(style_params); - link_params.overwriteFrom(match.getStyle()); - - // output the text before the Url - if (start > 0) - { - if (part == (S32)LLTextParser::WHOLE || - part == (S32)LLTextParser::START) - { - part = (S32)LLTextParser::START; - } - else - { - part = (S32)LLTextParser::MIDDLE; - } - std::string subtext=text.substr(0,start); - appendAndHighlightText(subtext, part, style_params); - } - - // add icon before url if need - LLTextUtil::processUrlMatch(&match, this, isContentTrusted() || match.isTrusted() || mAlwaysShowIcons); - if ((isContentTrusted() || match.isTrusted()) && !match.getIcon().empty() ) - { - setLastSegmentToolTip(LLTrans::getString("TooltipSLIcon")); - } - - // output the styled Url - appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly()); - bool tooltip_required = !match.getTooltip().empty(); - - // set the tooltip for the Url label - if (tooltip_required) - { - setLastSegmentToolTip(match.getTooltip()); - } - - // show query part of url with gray color only for LLUrlEntryHTTP url entries - std::string label = match.getQuery(); - if (label.size()) - { - link_params.color = LLColor4::grey; - link_params.readonly_color = LLColor4::grey; - appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly()); - - // set the tooltip for the query part of url - if (tooltip_required) - { - setLastSegmentToolTip(match.getTooltip()); - } - } - - // move on to the rest of the text after the Url - if (end < (S32)text.length()) - { - text = text.substr(end,text.length() - end); - end=0; - part=(S32)LLTextParser::END; - } - else - { - break; - } - } - if (part != (S32)LLTextParser::WHOLE) - part=(S32)LLTextParser::END; - if (end < (S32)text.length()) - appendAndHighlightText(text, part, style_params); - } - else - { - appendAndHighlightText(new_text, part, style_params); - } -} - -void LLTextBase::setLastSegmentToolTip(const std::string &tooltip) -{ - segment_set_t::iterator it = getSegIterContaining(getLength()-1); - if (it != mSegments.end()) - { - LLTextSegmentPtr segment = *it; - segment->setToolTip(tooltip); - } -} - -void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - if (new_text.empty()) - return; - - if(prepend_newline) - appendLineBreakSegment(input_params); - appendTextImpl(new_text,input_params); -} - -void LLTextBase::setLabel(const LLStringExplicit& label) -{ - mLabel = label; - resetLabel(); -} - -bool LLTextBase::setLabelArg(const std::string& key, const LLStringExplicit& text ) -{ - mLabel.setArg(key, text); - return true; -} - -void LLTextBase::resetLabel() -{ - if (useLabel()) - { - clearSegments(); - - LLStyle* style = new LLStyle(getStyleParams()); - style->setColor(mTentativeFgColor); - LLStyleConstSP sp(style); - - LLTextSegmentPtr label = new LLLabelTextSegment(sp, 0, mLabel.getWString().length() + 1, *this); - insertSegment(label); - } -} - -bool LLTextBase::useLabel() const -{ - return !getLength() && !mLabel.empty() && !hasFocus(); -} - -void LLTextBase::setFont(const LLFontGL* font) -{ - mFont = font; - mStyleDirty = true; -} - -void LLTextBase::needsReflow(S32 index) -{ - LL_DEBUGS() << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << LL_ENDL; - mReflowIndex = llmin(mReflowIndex, index); -} - -S32 LLTextBase::removeFirstLine() -{ - if (!mLineInfoList.empty()) - { - S32 length = getLineEnd(0); - deselect(); - removeStringNoUndo(0, length); - return length; - } - return 0; -} - -void LLTextBase::appendLineBreakSegment(const LLStyle::Params& style_params) -{ - segment_vec_t segments; - LLStyleConstSP sp(new LLStyle(style_params)); - segments.push_back(new LLLineBreakTextSegment(sp, getLength())); - - insertStringNoUndo(getLength(), utf8str_to_wstring("\n"), &segments); -} - -void LLTextBase::appendImageSegment(const LLStyle::Params& style_params) -{ - if(getPlainText()) - { - return; - } - segment_vec_t segments; - LLStyleConstSP sp(new LLStyle(style_params)); - segments.push_back(new LLImageTextSegment(sp, getLength(),*this)); - - insertStringNoUndo(getLength(), utf8str_to_wstring(" "), &segments); -} - -void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo) -{ - segment_vec_t segments; - LLWString widget_wide_text = utf8str_to_wstring(text); - segments.push_back(new LLInlineViewSegment(params, getLength(), getLength() + widget_wide_text.size())); - - insertStringNoUndo(getLength(), widget_wide_text, &segments); -} - -void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) -{ - // Save old state - S32 selection_start = mSelectionStart; - S32 selection_end = mSelectionEnd; - bool was_selecting = mIsSelecting; - S32 cursor_pos = mCursorPos; - S32 old_length = getLength(); - bool cursor_was_at_end = (mCursorPos == old_length); - - deselect(); - - setCursorPos(old_length); - - if (mParseHighlights) - { - LLStyle::Params highlight_params(style_params); - - LLSD pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part); - for (S32 i = 0; i < pieces.size(); i++) - { - LLSD color_llsd = pieces[i]["color"]; - LLColor4 lcolor; - lcolor.setValue(color_llsd); - highlight_params.color = lcolor; - - LLWString wide_text; - wide_text = utf8str_to_wstring(pieces[i]["text"].asString()); - - S32 cur_length = getLength(); - LLStyleConstSP sp(new LLStyle(highlight_params)); - LLTextSegmentPtr segmentp; - if (underline_on_hover_only || mSkipLinkUnderline) - { - highlight_params.font.style("NORMAL"); - LLStyleConstSP normal_sp(new LLStyle(highlight_params)); - segmentp = new LLOnHoverChangeableTextSegment(sp, normal_sp, cur_length, cur_length + wide_text.size(), *this); - } - else - { - segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + wide_text.size(), *this); - } - segment_vec_t segments; - segments.push_back(segmentp); - insertStringNoUndo(cur_length, wide_text, &segments); - } - } - else - { - LLWString wide_text; - wide_text = utf8str_to_wstring(new_text); - - segment_vec_t segments; - S32 segment_start = old_length; - S32 segment_end = old_length + wide_text.size(); - LLStyleConstSP sp(new LLStyle(style_params)); - if (underline_on_hover_only || mSkipLinkUnderline) - { - LLStyle::Params normal_style_params(style_params); - normal_style_params.font.style("NORMAL"); - LLStyleConstSP normal_sp(new LLStyle(normal_style_params)); - segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this)); - } - else - { - segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this)); - } - - insertStringNoUndo(getLength(), wide_text, &segments); - } - - // Set the cursor and scroll position - if (selection_start != selection_end) - { - mSelectionStart = selection_start; - mSelectionEnd = selection_end; - - mIsSelecting = was_selecting; - setCursorPos(cursor_pos); - } - else if (cursor_was_at_end) - { - setCursorPos(getLength()); - } - else - { - setCursorPos(cursor_pos); - } -} - -void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) -{ - if (new_text.empty()) - { - return; - } - - std::string::size_type start = 0; - std::string::size_type pos = new_text.find("\n", start); - - while (pos != std::string::npos) - { - if (pos != start) - { - std::string str = std::string(new_text,start,pos-start); - appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); - } - appendLineBreakSegment(style_params); - start = pos+1; - pos = new_text.find("\n", start); - } - - std::string str = std::string(new_text, start, new_text.length() - start); - appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); -} - - -void LLTextBase::replaceUrl(const std::string &url, - const std::string &label, - const std::string &icon) -{ - // get the full (wide) text for the editor so we can change it - LLWString text = getWText(); - LLWString wlabel = utf8str_to_wstring(label); - bool modified = false; - S32 seg_start = 0; - - // iterate through each segment looking for ones styled as links - segment_set_t::iterator it; - for (it = mSegments.begin(); it != mSegments.end(); ++it) - { - LLTextSegment *seg = *it; - LLStyleConstSP style = seg->getStyle(); - - // update segment start/end length in case we replaced text earlier - S32 seg_length = seg->getEnd() - seg->getStart(); - seg->setStart(seg_start); - seg->setEnd(seg_start + seg_length); - - // if we find a link with our Url, then replace the label - if (style->getLinkHREF() == url) - { - S32 start = seg->getStart(); - S32 end = seg->getEnd(); - text = text.substr(0, start) + wlabel + text.substr(end, text.size() - end + 1); - seg->setEnd(start + wlabel.size()); - modified = true; - } - - // Icon might be updated when more avatar or group info - // becomes available - if (style->isImage() && style->getLinkHREF() == url) - { - LLUIImagePtr image = image_from_icon_name( icon ); - if (image) - { - LLStyle::Params icon_params; - icon_params.image = image; - LLStyleConstSP new_style(new LLStyle(icon_params)); - seg->setStyle(new_style); - modified = true; - } - } - - // work out the character offset for the next segment - seg_start = seg->getEnd(); - } - - // update the editor with the new (wide) text string - if (modified) - { - getViewModel()->setDisplay(text); - deselect(); - setCursorPos(mCursorPos); - needsReflow(); - } -} - - -void LLTextBase::setWText(const LLWString& text) -{ - setText(wstring_to_utf8str(text)); -} - -const LLWString& LLTextBase::getWText() const -{ - return getViewModel()->getDisplay(); -} - -// If round is true, if the position is on the right half of a character, the cursor -// will be put to its right. If round is false, the cursor will always be put to the -// character's left. - -S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, bool round, bool hit_past_end_of_line) const -{ - // Figure out which line we're nearest to. - LLRect doc_rect = mDocumentView->getRect(); - S32 doc_y = local_y - doc_rect.mBottom; - - // binary search for line that starts before local_y - line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_y, compare_bottom()); - - if (!mLineInfoList.size() || line_iter == mLineInfoList.end()) - { - return getLength(); // past the end - } - - S32 pos = getLength(); - F32 start_x = line_iter->mRect.mLeft + doc_rect.mLeft; - - segment_set_t::iterator line_seg_iter; - S32 line_seg_offset; - for(getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset); - line_seg_iter != mSegments.end(); - ++line_seg_iter, line_seg_offset = 0) - { - const LLTextSegmentPtr segmentp = *line_seg_iter; - - S32 segment_line_start = segmentp->getStart() + line_seg_offset; - S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start; - F32 text_width; - S32 text_height; - bool newline = segmentp->getDimensionsF32(line_seg_offset, segment_line_length, text_width, text_height); - - if(newline) - { - pos = segment_line_start + segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round); - break; - } - - // if we've reached a line of text *below* the mouse cursor, doc index is first character on that line - if (hit_past_end_of_line && doc_y > line_iter->mRect.mTop) - { - pos = segment_line_start; - break; - } - if (local_x < start_x + text_width) // cursor to left of right edge of text - { - // Figure out which character we're nearest to. - S32 offset; - if (!segmentp->canEdit()) - { - F32 segment_width; - S32 segment_height; - segmentp->getDimensionsF32(0, segmentp->getEnd() - segmentp->getStart(), segment_width, segment_height); - if (round && local_x - start_x > segment_width / 2) - { - offset = segment_line_length; - } - else - { - offset = 0; - } - } - else - { - offset = segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round); - } - pos = segment_line_start + offset; - break; - } - else if (hit_past_end_of_line && segmentp->getEnd() >= line_iter->mDocIndexEnd) - { - if (getLineNumFromDocIndex(line_iter->mDocIndexEnd - 1) == line_iter->mLineNum) - { - // if segment wraps to the next line we should step one char back - // to compensate for the space char between words - // which is removed due to wrapping - pos = llclamp(line_iter->mDocIndexEnd - 1, 0, getLength()); - } - else - { - pos = llclamp(line_iter->mDocIndexEnd, 0, getLength()); - } - break; - } - start_x += text_width; - } - - return pos; -} - -// returns rectangle of insertion caret -// in document coordinate frame from given index into text -LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const -{ - if (mLineInfoList.empty()) - { - return LLRect(); - } - - // clamp pos to valid values - pos = llclamp(pos, 0, mLineInfoList.back().mDocIndexEnd - 1); - - line_list_t::const_iterator line_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), pos, line_end_compare()); - - segment_set_t::iterator line_seg_iter; - S32 line_seg_offset; - segment_set_t::iterator cursor_seg_iter; - S32 cursor_seg_offset; - getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset); - getSegmentAndOffset(pos, &cursor_seg_iter, &cursor_seg_offset); - - F32 doc_left_precise = line_iter->mRect.mLeft; - - while(line_seg_iter != mSegments.end()) - { - const LLTextSegmentPtr segmentp = *line_seg_iter; - - if (line_seg_iter == cursor_seg_iter) - { - // cursor advanced to right based on difference in offset of cursor to start of line - F32 segment_width; - S32 segment_height; - segmentp->getDimensionsF32(line_seg_offset, cursor_seg_offset - line_seg_offset, segment_width, segment_height); - doc_left_precise += segment_width; - - break; - } - else - { - // add remainder of current text segment to cursor position - F32 segment_width; - S32 segment_height; - segmentp->getDimensionsF32(line_seg_offset, (segmentp->getEnd() - segmentp->getStart()) - line_seg_offset, segment_width, segment_height); - doc_left_precise += segment_width; - // offset will be 0 for all segments after the first - line_seg_offset = 0; - // go to next text segment on this line - ++line_seg_iter; - } - } - - LLRect doc_rect; - doc_rect.mLeft = doc_left_precise; - doc_rect.mBottom = line_iter->mRect.mBottom; - doc_rect.mTop = line_iter->mRect.mTop; - - // set rect to 0 width - doc_rect.mRight = doc_rect.mLeft; - - return doc_rect; -} - -LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const -{ - LLRect content_window_rect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); - if (mBorderVisible) - { - content_window_rect.stretch(-1); - } - - LLRect local_rect; - - if (mLineInfoList.empty()) - { - // return default height rect in upper left - local_rect = content_window_rect; - local_rect.mBottom = local_rect.mTop - mFont->getLineHeight(); - return local_rect; - } - - // get the rect in document coordinates - LLRect doc_rect = getDocRectFromDocIndex(pos); - - // compensate for scrolled, inset view of doc - LLRect scrolled_view_rect = getVisibleDocumentRect(); - local_rect = doc_rect; - local_rect.translate(content_window_rect.mLeft - scrolled_view_rect.mLeft, - content_window_rect.mBottom - scrolled_view_rect.mBottom); - - return local_rect; -} - -void LLTextBase::updateCursorXPos() -{ - // reset desired x cursor position - mDesiredXPixel = getLocalRectFromDocIndex(mCursorPos).mLeft; -} - - -void LLTextBase::startOfLine() -{ - S32 offset = getLineOffsetFromDocIndex(mCursorPos); - setCursorPos(mCursorPos - offset); -} - -void LLTextBase::endOfLine() -{ - S32 line = getLineNumFromDocIndex(mCursorPos); - S32 num_lines = getLineCount(); - if (line + 1 >= num_lines) - { - setCursorPos(getLength()); - } - else - { - setCursorPos( getLineStart(line + 1) - 1 ); - } -} - -void LLTextBase::startOfDoc() -{ - setCursorPos(0); - if (mScroller) - { - mScroller->goToTop(); - } -} - -void LLTextBase::endOfDoc() -{ - setCursorPos(getLength()); - if (mScroller) - { - mScroller->goToBottom(); - } -} - -void LLTextBase::changePage( S32 delta ) -{ - const S32 PIXEL_OVERLAP_ON_PAGE_CHANGE = 10; - if (delta == 0 || !mScroller) return; - - LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); - - if( delta == -1 ) - { - mScroller->pageUp(PIXEL_OVERLAP_ON_PAGE_CHANGE); - } - else - if( delta == 1 ) - { - mScroller->pageDown(PIXEL_OVERLAP_ON_PAGE_CHANGE); - } - - if (getLocalRectFromDocIndex(mCursorPos) == cursor_rect) - { - // cursor didn't change apparent position, so move to top or bottom of document, respectively - if (delta < 0) - { - startOfDoc(); - } - else - { - endOfDoc(); - } - } - else - { - setCursorAtLocalPos(cursor_rect.getCenterX(), cursor_rect.getCenterY(), true, false); - } -} - -// Picks a new cursor position based on the screen size of text being drawn. -void LLTextBase::setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool keep_cursor_offset ) -{ - setCursorPos(getDocIndexFromLocalCoord(local_x, local_y, round), keep_cursor_offset); -} - - -void LLTextBase::changeLine( S32 delta ) -{ - S32 line = getLineNumFromDocIndex(mCursorPos); - S32 max_line_nb = getLineCount() - 1; - max_line_nb = (max_line_nb < 0 ? 0 : max_line_nb); - - S32 new_line = llclamp(line + delta, 0, max_line_nb); - - if (new_line != line) - { - LLRect visible_region = getVisibleDocumentRect(); - S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, - mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, true); - S32 actual_line = getLineNumFromDocIndex(new_cursor_pos); - if (actual_line != new_line) - { - // line edge, correcting position by 1 to move onto proper line - new_cursor_pos += new_line - actual_line; - } - setCursorPos(new_cursor_pos, true); - } -} - -bool LLTextBase::scrolledToStart() -{ - return mScroller->isAtTop(); -} - -bool LLTextBase::scrolledToEnd() -{ - return mScroller->isAtBottom(); -} - -bool LLTextBase::setCursor(S32 row, S32 column) -{ - if (row < 0 || column < 0) return false; - - S32 n_lines = mLineInfoList.size(); - for (S32 line = row; line < n_lines; ++line) - { - const line_info& li = mLineInfoList[line]; - - if (li.mLineNum < row) - { - continue; - } - else if (li.mLineNum > row) - { - break; // invalid column specified - } - - // Found the given row. - S32 line_length = li.mDocIndexEnd - li.mDocIndexStart;; - if (column >= line_length) - { - column -= line_length; - continue; - } - - // Found the given column. - updateCursorXPos(); - S32 doc_pos = li.mDocIndexStart + column; - return setCursorPos(doc_pos); - } - - return false; // invalid row or column specified -} - - -bool LLTextBase::setCursorPos(S32 cursor_pos, bool keep_cursor_offset) -{ - S32 new_cursor_pos = cursor_pos; - if (new_cursor_pos != mCursorPos) - { - new_cursor_pos = getEditableIndex(new_cursor_pos, new_cursor_pos >= mCursorPos); - } - - mCursorPos = llclamp(new_cursor_pos, 0, (S32)getLength()); - needsScroll(); - if (!keep_cursor_offset) - updateCursorXPos(); - // did we get requested position? - return new_cursor_pos == cursor_pos; -} - -// constraint cursor to editable segments of document -S32 LLTextBase::getEditableIndex(S32 index, bool increasing_direction) -{ - segment_set_t::iterator segment_iter; - S32 offset; - getSegmentAndOffset(index, &segment_iter, &offset); - if (segment_iter == mSegments.end()) - { - return 0; - } - - LLTextSegmentPtr segmentp = *segment_iter; - - if (segmentp->canEdit()) - { - return segmentp->getStart() + offset; - } - else if (segmentp->getStart() < index && index < segmentp->getEnd()) - { - // bias towards document end - if (increasing_direction) - { - return segmentp->getEnd(); - } - // bias towards document start - else - { - return segmentp->getStart(); - } - } - else - { - return index; - } -} - -void LLTextBase::updateRects() -{ - LLRect old_text_rect = mVisibleTextRect; - mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); - - if (mLineInfoList.empty()) - { - mTextBoundingRect = LLRect(0, mVPad, mHPad, 0); - } - else - { - mTextBoundingRect = mLineInfoList.begin()->mRect; - for (line_list_t::const_iterator line_iter = ++mLineInfoList.begin(); - line_iter != mLineInfoList.end(); - ++line_iter) - { - mTextBoundingRect.unionWith(line_iter->mRect); - } - - mTextBoundingRect.mTop += mVPad; - - S32 delta_pos = 0; - - switch(mVAlign) - { - case LLFontGL::TOP: - delta_pos = llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom); - break; - case LLFontGL::VCENTER: - delta_pos = (llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom) + (mVisibleTextRect.mBottom - mTextBoundingRect.mBottom)) / 2; - break; - case LLFontGL::BOTTOM: - delta_pos = mVisibleTextRect.mBottom - mTextBoundingRect.mBottom; - break; - case LLFontGL::BASELINE: - // do nothing - break; - } - // move line segments to fit new document rect - for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it) - { - it->mRect.translate(0, delta_pos); - } - mTextBoundingRect.translate(0, delta_pos); - } - - // update document container dimensions according to text contents - LLRect doc_rect; - // use old mVisibleTextRect constraint document to width of viewable region - doc_rect.mBottom = llmin(mVisibleTextRect.mBottom, mTextBoundingRect.mBottom); - doc_rect.mLeft = 0; - - // allow horizontal scrolling? - // if so, use entire width of text contents - // otherwise, stop at width of mVisibleTextRect - //FIXME: consider use of getWordWrap() instead - doc_rect.mRight = mScroller - ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) - : mVisibleTextRect.getWidth(); - doc_rect.mTop = llmax(mVisibleTextRect.mTop, mTextBoundingRect.mTop); - - if (!mScroller) - { - // push doc rect to top of text widget - switch(mVAlign) - { - case LLFontGL::TOP: - doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop); - break; - case LLFontGL::VCENTER: - doc_rect.translate(0, (mVisibleTextRect.getHeight() - doc_rect.mTop) / 2); - case LLFontGL::BOTTOM: - default: - break; - } - } - - mDocumentView->setShape(doc_rect); - - //update mVisibleTextRect *after* mDocumentView has been resized - // so that scrollbars are added if document needs to scroll - // since mVisibleTextRect does not include scrollbars - mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); - //FIXME: replace border with image? - if (mBorderVisible) - { - mVisibleTextRect.stretch(-1); - } - if (mVisibleTextRect != old_text_rect) - { - needsReflow(); - } - - // update mTextBoundingRect after mVisibleTextRect took scrolls into account - if (!mLineInfoList.empty() && mScroller) - { - S32 delta_pos = 0; - - switch(mVAlign) - { - case LLFontGL::TOP: - delta_pos = llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom); - break; - case LLFontGL::VCENTER: - delta_pos = (llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom) + (mVisibleTextRect.mBottom - mTextBoundingRect.mBottom)) / 2; - break; - case LLFontGL::BOTTOM: - delta_pos = mVisibleTextRect.mBottom - mTextBoundingRect.mBottom; - break; - case LLFontGL::BASELINE: - // do nothing - break; - } - // move line segments to fit new visible rect - if (delta_pos != 0) - { - for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it) - { - it->mRect.translate(0, delta_pos); - } - mTextBoundingRect.translate(0, delta_pos); - } - } - - // update document container again, using new mVisibleTextRect (that has scrollbars enabled as needed) - doc_rect.mBottom = llmin(mVisibleTextRect.mBottom, mTextBoundingRect.mBottom); - doc_rect.mLeft = 0; - doc_rect.mRight = mScroller - ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) - : mVisibleTextRect.getWidth(); - doc_rect.mTop = llmax(mVisibleTextRect.getHeight(), mTextBoundingRect.getHeight()) + doc_rect.mBottom; - if (!mScroller) - { - // push doc rect to top of text widget - switch(mVAlign) - { - case LLFontGL::TOP: - doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop); - break; - case LLFontGL::VCENTER: - doc_rect.translate(0, (mVisibleTextRect.getHeight() - doc_rect.mTop) / 2); - case LLFontGL::BOTTOM: - default: - break; - } - } - mDocumentView->setShape(doc_rect); -} - - -void LLTextBase::startSelection() -{ - if( !mIsSelecting ) - { - mIsSelecting = true; - mSelectionStart = mCursorPos; - mSelectionEnd = mCursorPos; - } -} - -void LLTextBase::endSelection() -{ - if( mIsSelecting ) - { - mIsSelecting = false; - mSelectionEnd = mCursorPos; - } -} - -// get portion of document that is visible in text editor -LLRect LLTextBase::getVisibleDocumentRect() const -{ - if (mScroller) - { - return mScroller->getVisibleContentRect(); - } - else if (mClip) - { - LLRect visible_text_rect = getVisibleTextRect(); - LLRect doc_rect = mDocumentView->getRect(); - visible_text_rect.translate(-doc_rect.mLeft, -doc_rect.mBottom); - - // reject partially visible lines - LLRect visible_lines_rect; - for (line_list_t::const_iterator it = mLineInfoList.begin(), end_it = mLineInfoList.end(); - it != end_it; - ++it) - { - bool line_visible = mClipPartial ? visible_text_rect.contains(it->mRect) : visible_text_rect.overlaps(it->mRect); - if (line_visible) - { - if (visible_lines_rect.isEmpty()) - { - visible_lines_rect = it->mRect; - } - else - { - visible_lines_rect.unionWith(it->mRect); - } - } - } - return visible_lines_rect; - } - else - { // entire document rect is visible - // but offset according to height of widget - - LLRect doc_rect = mDocumentView->getLocalRect(); - doc_rect.mLeft -= mDocumentView->getRect().mLeft; - // adjust for height of text above widget baseline - doc_rect.mBottom = doc_rect.getHeight() - mVisibleTextRect.getHeight(); - return doc_rect; - } -} - -boost::signals2::connection LLTextBase::setURLClickedCallback(const commit_signal_t::slot_type& cb) -{ - if (!mURLClickSignal) - { - mURLClickSignal = new commit_signal_t(); - } - return mURLClickSignal->connect(cb); -} - -boost::signals2::connection LLTextBase::setIsFriendCallback(const is_friend_signal_t::slot_type& cb) -{ - if (!mIsFriendSignal) - { - mIsFriendSignal = new is_friend_signal_t(); - } - return mIsFriendSignal->connect(cb); -} - -boost::signals2::connection LLTextBase::setIsObjectBlockedCallback(const is_blocked_signal_t::slot_type& cb) -{ - if (!mIsObjectBlockedSignal) - { - mIsObjectBlockedSignal = new is_blocked_signal_t(); - } - return mIsObjectBlockedSignal->connect(cb); -} - -// -// LLTextSegment -// - -LLTextSegment::~LLTextSegment() -{} - -bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; height = 0; return false; } -bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const -{ - F32 fwidth = 0; - bool result = getDimensionsF32(first_char, num_chars, fwidth, height); - width = ll_round(fwidth); - return result; -} - -S32 LLTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { return 0; } -S32 LLTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const { return 0; } -void LLTextSegment::updateLayout(const LLTextBase& editor) {} -F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) { return draw_rect.mLeft; } -bool LLTextSegment::canEdit() const { return false; } -void LLTextSegment::unlinkFromDocument(LLTextBase*) {} -void LLTextSegment::linkToDocument(LLTextBase*) {} -const LLColor4& LLTextSegment::getColor() const { return LLColor4::white; } -//void LLTextSegment::setColor(const LLColor4 &color) {} -LLStyleConstSP LLTextSegment::getStyle() const {static LLStyleConstSP sp(new LLStyle()); return sp; } -void LLTextSegment::setStyle(LLStyleConstSP style) {} -void LLTextSegment::setToken( LLKeywordToken* token ) {} -LLKeywordToken* LLTextSegment::getToken() const { return NULL; } -void LLTextSegment::setToolTip( const std::string &msg ) {} -void LLTextSegment::dump() const {} -bool LLTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) { return false; } -bool LLTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) { return false; } -bool LLTextSegment::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { return false; } -bool LLTextSegment::handleMiddleMouseUp(S32 x, S32 y, MASK mask) { return false; } -bool LLTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) { return false; } -bool LLTextSegment::handleRightMouseUp(S32 x, S32 y, MASK mask) { return false; } -bool LLTextSegment::handleDoubleClick(S32 x, S32 y, MASK mask) { return false; } -bool LLTextSegment::handleHover(S32 x, S32 y, MASK mask) { return false; } -bool LLTextSegment::handleScrollWheel(S32 x, S32 y, S32 clicks) { return false; } -bool LLTextSegment::handleScrollHWheel(S32 x, S32 y, S32 clicks) { return false; } -bool LLTextSegment::handleToolTip(S32 x, S32 y, MASK mask) { return false; } -const std::string& LLTextSegment::getName() const -{ - return LLStringUtil::null; -} -void LLTextSegment::onMouseCaptureLost() {} -void LLTextSegment::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {} -void LLTextSegment::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {} -bool LLTextSegment::hasMouseCapture() { return false; } - -// -// LLNormalTextSegment -// - -LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) -: LLTextSegment(start, end), - mStyle( style ), - mToken(NULL), - mEditor(editor) -{ - mFontHeight = mStyle->getFont()->getLineHeight(); - - LLUIImagePtr image = mStyle->getImage(); - if (image.notNull()) - { - mImageLoadedConnection = image->addLoadedCallback(boost::bind(&LLTextBase::needsReflow, &mEditor, start)); - } -} - -LLNormalTextSegment::LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible) -: LLTextSegment(start, end), - mToken(NULL), - mEditor(editor) -{ - mStyle = new LLStyle(LLStyle::Params().visible(is_visible).color(color)); - - mFontHeight = mStyle->getFont()->getLineHeight(); -} - -LLNormalTextSegment::~LLNormalTextSegment() -{ - mImageLoadedConnection.disconnect(); -} - - -F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) -{ - if( end - start > 0 ) - { - return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect); - } - return draw_rect.mLeft; -} - -// Draws a single text segment, reversing the color for selection if needed. -F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, LLRectf rect) -{ - F32 alpha = LLViewDrawContext::getCurrentContext().mAlpha; - - const LLWString &text = getWText(); - - F32 right_x = rect.mLeft; - if (!mStyle->isVisible()) - { - return right_x; - } - - const LLFontGL* font = mStyle->getFont(); - - LLColor4 color = (mEditor.getReadOnly() ? mStyle->getReadOnlyColor() : mStyle->getColor()) % alpha; - - if( selection_start > seg_start ) - { - // Draw normally - S32 start = seg_start; - S32 end = llmin( selection_start, seg_end ); - S32 length = end - start; - font->render(text, start, - rect, - color, - LLFontGL::LEFT, mEditor.mTextVAlign, - LLFontGL::NORMAL, - mStyle->getShadowType(), - length, - &right_x, - mEditor.getUseEllipses(), - mEditor.getUseColor()); - } - rect.mLeft = right_x; - - if( (selection_start < seg_end) && (selection_end > seg_start) ) - { - // Draw reversed - S32 start = llmax( selection_start, seg_start ); - S32 end = llmin( selection_end, seg_end ); - S32 length = end - start; - - font->render(text, start, - rect, - mStyle->getSelectedColor().get(), - LLFontGL::LEFT, mEditor.mTextVAlign, - LLFontGL::NORMAL, - LLFontGL::NO_SHADOW, - length, - &right_x, - mEditor.getUseEllipses(), - mEditor.getUseColor()); - } - rect.mLeft = right_x; - if( selection_end < seg_end ) - { - // Draw normally - S32 start = llmax( selection_end, seg_start ); - S32 end = seg_end; - S32 length = end - start; - font->render(text, start, - rect, - color, - LLFontGL::LEFT, mEditor.mTextVAlign, - LLFontGL::NORMAL, - mStyle->getShadowType(), - length, - &right_x, - mEditor.getUseEllipses(), - mEditor.getUseColor()); - } - return right_x; -} - -bool LLNormalTextSegment::handleHover(S32 x, S32 y, MASK mask) -{ - if (getStyle() && getStyle()->isLink()) - { - // Only process the click if it's actually in this segment, not to the right of the end-of-line. - if(mEditor.getSegmentAtLocalPos(x, y, false) == this) - { - LLUI::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND); - return true; - } - } - return false; -} - -bool LLNormalTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - if (getStyle() && getStyle()->isLink()) - { - // Only process the click if it's actually in this segment, not to the right of the end-of-line. - if(mEditor.getSegmentAtLocalPos(x, y, false) == this) - { - mEditor.createUrlContextMenu(x, y, getStyle()->getLinkHREF()); - return true; - } - } - return false; -} - -bool LLNormalTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (getStyle() && getStyle()->isLink()) - { - // Only process the click if it's actually in this segment, not to the right of the end-of-line. - if(mEditor.getSegmentAtLocalPos(x, y, false) == this) - { - // eat mouse down event on hyperlinks, so we get the mouse up - return true; - } - } - - return false; -} - -bool LLNormalTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (getStyle() && getStyle()->isLink()) - { - // Only process the click if it's actually in this segment, not to the right of the end-of-line. - if(mEditor.getSegmentAtLocalPos(x, y, false) == this) - { - std::string url = getStyle()->getLinkHREF(); - if (!mEditor.mForceUrlsExternal) - { - LLUrlAction::clickAction(url, mEditor.isContentTrusted()); - } - else if (!LLUrlAction::executeSLURL(url, mEditor.isContentTrusted())) - { - LLUrlAction::openURLExternal(url); - } - return true; - } - } - - return false; -} - -bool LLNormalTextSegment::handleToolTip(S32 x, S32 y, MASK mask) -{ - std::string msg; - // do we have a tooltip for a loaded keyword (for script editor)? - if (mToken && !mToken->getToolTip().empty()) - { - const LLWString& wmsg = mToken->getToolTip(); - LLToolTipMgr::instance().show(wstring_to_utf8str(wmsg), (mToken->getType() == LLKeywordToken::TT_FUNCTION)); - return true; - } - // or do we have an explicitly set tooltip (e.g., for Urls) - if (!mTooltip.empty()) - { - LLToolTipMgr::instance().show(mTooltip); - return true; - } - - return false; -} - -void LLNormalTextSegment::setToolTip(const std::string& tooltip) -{ - // we cannot replace a keyword tooltip that's loaded from a file - if (mToken) - { - LL_WARNS() << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << LL_ENDL; - return; - } - mTooltip = tooltip; -} - -bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const -{ - height = 0; - width = 0; - if (num_chars > 0) - { - height = mFontHeight; - const LLWString &text = getWText(); - // if last character is a newline, then return true, forcing line break - width = mStyle->getFont()->getWidthF32(text.c_str(), mStart + first_char, num_chars, true); - } - return false; -} - -S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const -{ - const LLWString &text = getWText(); - return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset, - (F32)segment_local_x_coord, - F32_MAX, - num_chars, - round); -} - -S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const -{ - const LLWString &text = getWText(); - - LLUIImagePtr image = mStyle->getImage(); - if( image.notNull()) - { - num_pixels = llmax(0, num_pixels - image->getWidth()); - } - - S32 last_char = mEnd; - - // set max characters to length of segment, or to first newline - max_chars = llmin(max_chars, last_char - (mStart + segment_offset)); - - // if no character yet displayed on this line, don't require word wrapping since - // we can just move to the next line, otherwise insist on it so we make forward progress - LLFontGL::EWordWrapStyle word_wrap_style = (line_offset == 0) - ? LLFontGL::WORD_BOUNDARY_IF_POSSIBLE - : LLFontGL::ONLY_WORD_BOUNDARIES; - - - S32 offsetLength = text.length() - (segment_offset + mStart); - - if(getLength() < segment_offset + mStart) - { - LL_INFOS() << "getLength() < segment_offset + mStart\t getLength()\t" << getLength() << "\tsegment_offset:\t" - << segment_offset << "\tmStart:\t" << mStart << "\tsegments\t" << mEditor.mSegments.size() << "\tmax_chars\t" << max_chars << LL_ENDL; - } - - if( (offsetLength + 1) < max_chars) - { - LL_INFOS() << "offsetString.length() + 1 < max_chars\t max_chars:\t" << max_chars << "\toffsetString.length():\t" << offsetLength << " getLength() : " - << getLength() << "\tsegment_offset:\t" << segment_offset << "\tmStart:\t" << mStart << "\tsegments\t" << mEditor.mSegments.size() << LL_ENDL; - } - - S32 num_chars = mStyle->getFont()->maxDrawableChars( text.c_str() + (segment_offset + mStart), - (F32)num_pixels, - max_chars, - word_wrap_style); - - if (num_chars == 0 - && line_offset == 0 - && max_chars > 0) - { - // If at the beginning of a line, and a single character won't fit, draw it anyway - num_chars = 1; - } - - // include *either* the EOF or newline character in this run of text - // but not both - S32 last_char_in_run = mStart + segment_offset + num_chars; - // check length first to avoid indexing off end of string - if (last_char_in_run < mEnd - && (last_char_in_run >= getLength())) - { - num_chars++; - } - return num_chars; -} - -void LLNormalTextSegment::dump() const -{ - LL_INFOS() << "Segment [" << -// mColor.mV[VX] << ", " << -// mColor.mV[VY] << ", " << -// mColor.mV[VZ] << "]\t[" << - mStart << ", " << - getEnd() << "]" << - LL_ENDL; -} - -/*virtual*/ -const LLWString& LLNormalTextSegment::getWText() const -{ - return mEditor.getWText(); -} - -/*virtual*/ -const S32 LLNormalTextSegment::getLength() const -{ - return mEditor.getLength(); -} - -LLLabelTextSegment::LLLabelTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) -: LLNormalTextSegment(style, start, end, editor) -{ -} - -LLLabelTextSegment::LLLabelTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible) -: LLNormalTextSegment(color, start, end, editor, is_visible) -{ -} - -/*virtual*/ -const LLWString& LLLabelTextSegment::getWText() const -{ - return mEditor.getWlabel(); -} -/*virtual*/ -const S32 LLLabelTextSegment::getLength() const -{ - return mEditor.getWlabel().length(); -} - -// -// LLEmojiTextSegment -// -LLEmojiTextSegment::LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor) - : LLNormalTextSegment(style, start, end, editor) -{ -} - -LLEmojiTextSegment::LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible) - : LLNormalTextSegment(color, start, end, editor, is_visible) -{ -} - -bool LLEmojiTextSegment::handleToolTip(S32 x, S32 y, MASK mask) -{ - if (mTooltip.empty()) - { - LLWString emoji = getWText().substr(getStart(), getEnd() - getStart()); - if (!emoji.empty()) - { - mTooltip = LLEmojiHelper::instance().getToolTip(emoji[0]); - } - } - - return LLNormalTextSegment::handleToolTip(x, y, mask); -} - -// -// LLOnHoverChangeableTextSegment -// - -LLOnHoverChangeableTextSegment::LLOnHoverChangeableTextSegment( LLStyleConstSP style, LLStyleConstSP normal_style, S32 start, S32 end, LLTextBase& editor ): - LLNormalTextSegment(normal_style, start, end, editor), - mHoveredStyle(style), - mNormalStyle(normal_style){} - -/*virtual*/ -F32 LLOnHoverChangeableTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) -{ - F32 result = LLNormalTextSegment::draw(start, end, selection_start, selection_end, draw_rect); - if (end == mEnd - mStart) - { - mStyle = mNormalStyle; - } - return result; -} - -/*virtual*/ -bool LLOnHoverChangeableTextSegment::handleHover(S32 x, S32 y, MASK mask) -{ - mStyle = mEditor.getSkipLinkUnderline() ? mNormalStyle : mHoveredStyle; - return LLNormalTextSegment::handleHover(x, y, mask); -} - - -// -// LLInlineViewSegment -// - -LLInlineViewSegment::LLInlineViewSegment(const Params& p, S32 start, S32 end) -: LLTextSegment(start, end), - mView(p.view), - mForceNewLine(p.force_newline), - mLeftPad(p.left_pad), - mRightPad(p.right_pad), - mTopPad(p.top_pad), - mBottomPad(p.bottom_pad) -{ -} - -LLInlineViewSegment::~LLInlineViewSegment() -{ - mView->die(); -} - -bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const -{ - if (first_char == 0 && num_chars == 0) - { - // We didn't fit on a line or were forced to new string - // the widget will fall on the next line, so width here is 0 - width = 0; - - if (mForceNewLine) - { - // Chat, string can't be smaller then font height even if it is empty - LLStyleSP s(new LLStyle(LLStyle::Params().visible(true))); - height = s->getFont()->getLineHeight(); - - return true; // new line - } - else - { - // height from previous segment in same string will be used, word-wrap - height = 0; - } - - } - else - { - width = mLeftPad + mRightPad + mView->getRect().getWidth(); - height = mBottomPad + mTopPad + mView->getRect().getHeight(); - } - - return false; -} - -S32 LLInlineViewSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const -{ - // if putting a widget anywhere but at the beginning of a line - // and the widget doesn't fit or mForceNewLine is true - // then return 0 chars for that line, and all characters for the next - if (mForceNewLine && line_ind == 0) - { - return 0; - } - else if (line_offset != 0 && num_pixels < mView->getRect().getWidth()) - { - return 0; - } - else - { - return mEnd - mStart; - } -} - -void LLInlineViewSegment::updateLayout(const LLTextBase& editor) -{ - LLRect start_rect = editor.getDocRectFromDocIndex(mStart); - mView->setOrigin(start_rect.mLeft + mLeftPad, start_rect.mBottom + mBottomPad); -} - -F32 LLInlineViewSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) -{ - // return padded width of widget - // widget is actually drawn during mDocumentView's draw() - return (F32)(draw_rect.mLeft + mView->getRect().getWidth() + mLeftPad + mRightPad); -} - -void LLInlineViewSegment::unlinkFromDocument(LLTextBase* editor) -{ - editor->removeDocumentChild(mView); -} - -void LLInlineViewSegment::linkToDocument(LLTextBase* editor) -{ - editor->addDocumentChild(mView); -} - -LLLineBreakTextSegment::LLLineBreakTextSegment(S32 pos):LLTextSegment(pos,pos+1) -{ - LLStyleSP s( new LLStyle(LLStyle::Params().visible(true))); - - mFontHeight = s->getFont()->getLineHeight(); -} -LLLineBreakTextSegment::LLLineBreakTextSegment(LLStyleConstSP style,S32 pos):LLTextSegment(pos,pos+1) -{ - mFontHeight = style->getFont()->getLineHeight(); -} -LLLineBreakTextSegment::~LLLineBreakTextSegment() -{ -} -bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const -{ - width = 0; - height = mFontHeight; - - return true; -} -S32 LLLineBreakTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const -{ - return 1; -} -F32 LLLineBreakTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) -{ - return draw_rect.mLeft; -} - -LLImageTextSegment::LLImageTextSegment(LLStyleConstSP style,S32 pos,class LLTextBase& editor) -: LLTextSegment(pos,pos+1), - mStyle( style ), - mEditor(editor) -{ -} - -LLImageTextSegment::~LLImageTextSegment() -{ -} - -static const S32 IMAGE_HPAD = 3; - -bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const -{ - width = 0; - height = mStyle->getFont()->getLineHeight(); - - LLUIImagePtr image = mStyle->getImage(); - if( num_chars>0 && image.notNull()) - { - width += image->getWidth() + IMAGE_HPAD; - height = llmax(height, image->getHeight() + IMAGE_HPAD ); - } - return false; -} - -S32 LLImageTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const -{ - LLUIImagePtr image = mStyle->getImage(); - - if (image.isNull()) - { - return 1; - } - - S32 image_width = image->getWidth(); - if(line_offset == 0 || num_pixels>image_width + IMAGE_HPAD) - { - return 1; - } - - return 0; -} - -bool LLImageTextSegment::handleToolTip(S32 x, S32 y, MASK mask) -{ - if (!mTooltip.empty()) - { - LLToolTipMgr::instance().show(mTooltip); - return true; - } - - return false; -} - -void LLImageTextSegment::setToolTip(const std::string& tooltip) -{ - mTooltip = tooltip; -} - -F32 LLImageTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) -{ - if ( (start >= 0) && (end <= mEnd - mStart)) - { - LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha; - LLUIImagePtr image = mStyle->getImage(); - if (image.notNull()) - { - S32 style_image_height = image->getHeight(); - S32 style_image_width = image->getWidth(); - // Text is drawn from the top of the draw_rect downward - - S32 text_center = draw_rect.mTop - (draw_rect.getHeight() / 2); - // Align image to center of draw rect - S32 image_bottom = text_center - (style_image_height / 2); - image->draw(draw_rect.mLeft, image_bottom, - style_image_width, style_image_height, color); - - const S32 IMAGE_HPAD = 3; - return draw_rect.mLeft + style_image_width + IMAGE_HPAD; - } - } - return 0.0; -} - -void LLTextBase::setWordWrap(bool wrap) -{ - mWordWrap = wrap; -} +/** + * @file lltextbase.cpp + * @author Martin Reddy + * @brief The base class of text box/editor, providing Url handling support + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2009-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 "lltextbase.h" + +#include "llemojidictionary.h" +#include "llemojihelper.h" +#include "lllocalcliprect.h" +#include "llmenugl.h" +#include "llscrollcontainer.h" +#include "llspellcheck.h" +#include "llstl.h" +#include "lltextparser.h" +#include "lltextutil.h" +#include "lltooltip.h" +#include "lltrans.h" +#include "lluictrl.h" +#include "llurlaction.h" +#include "llurlregistry.h" +#include "llview.h" +#include "llwindow.h" +#include + +const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds +const S32 CURSOR_THICKNESS = 2; +const F32 TRIPLE_CLICK_INTERVAL = 0.3f; // delay between double and triple click. + +LLTextBase::line_info::line_info(S32 index_start, S32 index_end, LLRect rect, S32 line_num) +: mDocIndexStart(index_start), + mDocIndexEnd(index_end), + mRect(rect), + mLineNum(line_num) +{} + +bool LLTextBase::compare_segment_end::operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const +{ + // sort empty spans (e.g. 11-11) after previous non-empty spans (e.g. 5-11) + if (a->getEnd() == b->getEnd()) + { + return a->getStart() < b->getStart(); + } + else + { + return a->getEnd() < b->getEnd(); + } +} + + +// helper functors +bool LLTextBase::compare_bottom::operator()(const S32& a, const LLTextBase::line_info& b) const +{ + return a > b.mRect.mBottom; // bottom of a is higher than bottom of b +} + +bool LLTextBase::compare_bottom::operator()(const LLTextBase::line_info& a, const S32& b) const +{ + return a.mRect.mBottom > b; // bottom of a is higher than bottom of b +} + +bool LLTextBase::compare_bottom::operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const +{ + return a.mRect.mBottom > b.mRect.mBottom; // bottom of a is higher than bottom of b +} + +// helper functors +bool LLTextBase::compare_top::operator()(const S32& a, const LLTextBase::line_info& b) const +{ + return a > b.mRect.mTop; // top of a is higher than top of b +} + +bool LLTextBase::compare_top::operator()(const LLTextBase::line_info& a, const S32& b) const +{ + return a.mRect.mTop > b; // top of a is higher than top of b +} + +bool LLTextBase::compare_top::operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const +{ + return a.mRect.mTop > b.mRect.mTop; // top of a is higher than top of b +} + +struct LLTextBase::line_end_compare +{ + bool operator()(const S32& pos, const LLTextBase::line_info& info) const + { + return (pos < info.mDocIndexEnd); + } + + bool operator()(const LLTextBase::line_info& info, const S32& pos) const + { + return (info.mDocIndexEnd < pos); + } + + bool operator()(const LLTextBase::line_info& a, const LLTextBase::line_info& b) const + { + return (a.mDocIndexEnd < b.mDocIndexEnd); + } + +}; + +////////////////////////////////////////////////////////////////////////// +// +// LLTextBase +// + +// register LLTextBase::Params under name "textbase" +static LLWidgetNameRegistry::StaticRegistrar sRegisterTextBaseParams(&typeid(LLTextBase::Params), "textbase"); + +LLTextBase::LineSpacingParams::LineSpacingParams() +: multiple("multiple", 1.f), + pixels("pixels", 0) +{ +} + + +LLTextBase::Params::Params() +: cursor_color("cursor_color"), + text_color("text_color"), + text_readonly_color("text_readonly_color"), + text_tentative_color("text_tentative_color"), + bg_visible("bg_visible", false), + border_visible("border_visible", false), + bg_readonly_color("bg_readonly_color"), + bg_writeable_color("bg_writeable_color"), + bg_focus_color("bg_focus_color"), + text_selected_color("text_selected_color"), + bg_selected_color("bg_selected_color"), + allow_scroll("allow_scroll", true), + plain_text("plain_text",false), + track_end("track_end", false), + read_only("read_only", false), + skip_link_underline("skip_link_underline", false), + spellcheck("spellcheck", false), + v_pad("v_pad", 0), + h_pad("h_pad", 0), + clip("clip", true), + clip_partial("clip_partial", true), + line_spacing("line_spacing"), + max_text_length("max_length", 255), + font_shadow("font_shadow"), + text_valign("text_valign"), + wrap("wrap"), + trusted_content("trusted_content", true), + always_show_icons("always_show_icons", false), + use_ellipses("use_ellipses", false), + use_emoji("use_emoji", true), + use_color("use_color", true), + parse_urls("parse_urls", false), + force_urls_external("force_urls_external", false), + parse_highlights("parse_highlights", false) +{ + addSynonym(track_end, "track_bottom"); + addSynonym(wrap, "word_wrap"); + addSynonym(parse_urls, "allow_html"); +} + + +LLTextBase::LLTextBase(const LLTextBase::Params &p) +: LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)), + mURLClickSignal(NULL), + mIsFriendSignal(NULL), + mIsObjectBlockedSignal(NULL), + mMaxTextByteLength( p.max_text_length ), + mFont(p.font), + mFontShadow(p.font_shadow), + mPopupMenuHandle(), + mReadOnly(p.read_only), + mSkipTripleClick(false), + mSkipLinkUnderline(p.skip_link_underline), + mSpellCheck(p.spellcheck), + mSpellCheckStart(-1), + mSpellCheckEnd(-1), + mCursorColor(p.cursor_color), + mFgColor(p.text_color), + mBorderVisible( p.border_visible ), + mReadOnlyFgColor(p.text_readonly_color), + mTentativeFgColor(p.text_tentative_color()), + mWriteableBgColor(p.bg_writeable_color), + mReadOnlyBgColor(p.bg_readonly_color), + mFocusBgColor(p.bg_focus_color), + mTextSelectedColor(p.text_selected_color), + mSelectedBGColor(p.bg_selected_color), + mReflowIndex(S32_MAX), + mCursorPos( 0 ), + mScrollNeeded(false), + mDesiredXPixel(-1), + mHPad(p.h_pad), + mVPad(p.v_pad), + mHAlign(p.font_halign), + mVAlign(p.font_valign), + mTextVAlign(p.text_valign.isProvided() ? p.text_valign.getValue() : p.font_valign.getValue()), + mLineSpacingMult(p.line_spacing.multiple), + mLineSpacingPixels(p.line_spacing.pixels), + mClip(p.clip), + mClipPartial(p.clip_partial && !p.allow_scroll), + mTrustedContent(p.trusted_content), + mAlwaysShowIcons(p.always_show_icons), + mTrackEnd( p.track_end ), + mScrollIndex(-1), + mSelectionStart( 0 ), + mSelectionEnd( 0 ), + mIsSelecting( false ), + mPlainText ( p.plain_text ), + mWordWrap(p.wrap), + mUseEllipses( p.use_ellipses ), + mUseEmoji(p.use_emoji), + mUseColor(p.use_color), + mParseHTML(p.parse_urls), + mForceUrlsExternal(p.force_urls_external), + mParseHighlights(p.parse_highlights), + mBGVisible(p.bg_visible), + mScroller(NULL), + mStyleDirty(true) +{ + if(p.allow_scroll) + { + LLScrollContainer::Params scroll_params; + scroll_params.name = "text scroller"; + scroll_params.rect = getLocalRect(); + scroll_params.follows.flags = FOLLOWS_ALL; + scroll_params.is_opaque = false; + scroll_params.mouse_opaque = false; + scroll_params.min_auto_scroll_rate = 200; + scroll_params.max_auto_scroll_rate = 800; + scroll_params.border_visible = p.border_visible; + mScroller = LLUICtrlFactory::create(scroll_params); + addChild(mScroller); + } + + LLView::Params view_params; + view_params.name = "text_contents"; + view_params.rect = LLRect(0, 500, 500, 0); + view_params.mouse_opaque = false; + + mDocumentView = LLUICtrlFactory::create(view_params); + if (mScroller) + { + mScroller->addChild(mDocumentView); + } + else + { + addChild(mDocumentView); + } + + if (mSpellCheck) + { + LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLTextBase::onSpellCheckSettingsChange, this)); + } + mSpellCheckTimer.reset(); + + createDefaultSegment(); + + updateRects(); +} + +LLTextBase::~LLTextBase() +{ + mSegments.clear(); + LLContextMenu* menu = static_cast(mPopupMenuHandle.get()); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } + delete mURLClickSignal; + delete mIsFriendSignal; + delete mIsObjectBlockedSignal; +} + +void LLTextBase::initFromParams(const LLTextBase::Params& p) +{ + LLUICtrl::initFromParams(p); + resetDirty(); // Update saved text state + updateSegments(); + + // HACK: work around enabled == readonly design bug -- RN + // setEnabled will modify our read only status, so do this after + // LLTextBase::initFromParams + if (p.read_only.isProvided()) + { + mReadOnly = p.read_only; + } +} + +bool LLTextBase::truncate() +{ + bool did_truncate = false; + + // First rough check - if we're less than 1/4th the size, we're OK + if (getLength() >= S32(mMaxTextByteLength / 4)) + { + // Have to check actual byte size + S32 utf8_byte_size = 0; + LLSD value = getViewModel()->getValue(); + if (value.type() == LLSD::TypeString) + { + // save a copy for strings. + utf8_byte_size = value.size(); + } + else + { + // non string LLSDs need explicit conversion to string + utf8_byte_size = value.asString().size(); + } + + if ( utf8_byte_size > mMaxTextByteLength ) + { + // Truncate safely in UTF-8 + std::string temp_utf8_text = value.asString(); + temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength ); + LLWString text = utf8str_to_wstring( temp_utf8_text ); + // remove extra bit of current string, to preserve formatting, etc. + removeStringNoUndo(text.size(), getWText().size() - text.size()); + did_truncate = true; + } + } + + return did_truncate; +} + +const LLStyle::Params& LLTextBase::getStyleParams() +{ + //FIXME: convert mDefaultStyle to a flyweight http://www.boost.org/doc/libs/1_40_0/libs/flyweight/doc/index.html + //and eliminate color member values + if (mStyleDirty) + { + mStyle + .color(LLUIColor(&mFgColor)) // pass linked color instead of copy of mFGColor + .readonly_color(LLUIColor(&mReadOnlyFgColor)) + .selected_color(LLUIColor(&mTextSelectedColor)) + .font(mFont) + .drop_shadow(mFontShadow); + mStyleDirty = false; + } + return mStyle; +} + +void LLTextBase::beforeValueChange() +{ + +} + +void LLTextBase::onValueChange(S32 start, S32 end) +{ +} + +std::vector LLTextBase::getSelectionRects() +{ + // Nor supposed to be called without selection + llassert(hasSelection()); + llassert(!mLineInfoList.empty()); + + std::vector selection_rects; + + S32 selection_left = llmin(mSelectionStart, mSelectionEnd); + S32 selection_right = llmax(mSelectionStart, mSelectionEnd); + + // Skip through the lines we aren't drawing. + LLRect content_display_rect = getVisibleDocumentRect(); + + // binary search for line that starts before top of visible buffer + line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mTop, compare_bottom()); + line_list_t::const_iterator end_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), content_display_rect.mBottom, compare_top()); + + bool done = false; + + // Find the coordinates of the selected area + for (; line_iter != end_iter && !done; ++line_iter) + { + // is selection visible on this line? + if (line_iter->mDocIndexEnd > selection_left && line_iter->mDocIndexStart < selection_right) + { + segment_set_t::iterator segment_iter; + S32 segment_offset; + getSegmentAndOffset(line_iter->mDocIndexStart, &segment_iter, &segment_offset); + + // Use F32 otherwise a string of multiple segments + // will accumulate a large error + F32 left_precise = line_iter->mRect.mLeft; + F32 right_precise = line_iter->mRect.mLeft; + + for (; segment_iter != mSegments.end(); ++segment_iter, segment_offset = 0) + { + LLTextSegmentPtr segmentp = *segment_iter; + + S32 segment_line_start = segmentp->getStart() + segment_offset; + S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd); + + if (segment_line_start > segment_line_end) break; + + F32 segment_width = 0; + S32 segment_height = 0; + + // if selection after beginning of segment + if (selection_left >= segment_line_start) + { + S32 num_chars = llmin(selection_left, segment_line_end) - segment_line_start; + segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height); + left_precise += segment_width; + } + + // if selection_right == segment_line_end then that means we are the first character of the next segment + // or first character of the next line, in either case we want to add the length of the current segment + // to the selection rectangle and continue. + // if selection right > segment_line_end then selection spans end of current segment... + if (selection_right >= segment_line_end) + { + // extend selection slightly beyond end of line + // to indicate selection of newline character (use "n" character to determine width) + S32 num_chars = segment_line_end - segment_line_start; + segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height); + right_precise += segment_width; + } + // else if selection ends on current segment... + else + { + S32 num_chars = selection_right - segment_line_start; + segmentp->getDimensionsF32(segment_offset, num_chars, segment_width, segment_height); + right_precise += segment_width; + + break; + } + } + + LLRect selection_rect; + selection_rect.mLeft = left_precise; + selection_rect.mRight = right_precise; + selection_rect.mBottom = line_iter->mRect.mBottom; + selection_rect.mTop = line_iter->mRect.mTop; + + selection_rects.push_back(selection_rect); + } + } + + return selection_rects; +} + +// Draws the black box behind the selected text +void LLTextBase::drawSelectionBackground() +{ + // Draw selection even if we don't have keyboard focus for search/replace + if (hasSelection() && !mLineInfoList.empty()) + { + std::vector selection_rects = getSelectionRects(); + + // Draw the selection box (we're using a box instead of reversing the colors on the selected text). + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + const LLColor4& color = mSelectedBGColor; + F32 alpha = hasFocus() ? 0.7f : 0.3f; + alpha *= getDrawContext().mAlpha; + + LLColor4 selection_color(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], alpha); + LLRect content_display_rect = getVisibleDocumentRect(); + + for (std::vector::iterator rect_it = selection_rects.begin(); + rect_it != selection_rects.end(); + ++rect_it) + { + LLRect selection_rect = *rect_it; + if (mScroller) + { + // If scroller is On content_display_rect has correct rect and safe to use as is + // Note: we might need to account for border + selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom); + } + else + { + // If scroller is Off content_display_rect will have rect from document, adjusted to text width, heigh and position + // and we have to acount for offset depending on position + S32 v_delta = 0; + S32 h_delta = 0; + switch (mVAlign) + { + case LLFontGL::TOP: + v_delta = mVisibleTextRect.mTop - content_display_rect.mTop - mVPad; + break; + case LLFontGL::VCENTER: + v_delta = (llmax(mVisibleTextRect.getHeight() - content_display_rect.mTop, -content_display_rect.mBottom) + (mVisibleTextRect.mBottom - content_display_rect.mBottom)) / 2; + break; + case LLFontGL::BOTTOM: + v_delta = mVisibleTextRect.mBottom - content_display_rect.mBottom; + break; + default: + break; + } + switch (mHAlign) + { + case LLFontGL::LEFT: + h_delta = mVisibleTextRect.mLeft - content_display_rect.mLeft + mHPad; + break; + case LLFontGL::HCENTER: + h_delta = (llmax(mVisibleTextRect.getWidth() - content_display_rect.mLeft, -content_display_rect.mRight) + (mVisibleTextRect.mRight - content_display_rect.mRight)) / 2; + break; + case LLFontGL::RIGHT: + h_delta = mVisibleTextRect.mRight - content_display_rect.mRight; + break; + default: + break; + } + selection_rect.translate(h_delta, v_delta); + } + gl_rect_2d(selection_rect, selection_color); + } + } +} + +void LLTextBase::drawCursor() +{ + F32 alpha = getDrawContext().mAlpha; + + if( hasFocus() + && gFocusMgr.getAppHasFocus() + && !mReadOnly) + { + const LLWString &wtext = getWText(); + const llwchar* text = wtext.c_str(); + + LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); + cursor_rect.translate(-1, 0); + segment_set_t::iterator seg_it = getSegIterContaining(mCursorPos); + + // take style from last segment + LLTextSegmentPtr segmentp; + + if (seg_it != mSegments.end()) + { + segmentp = *seg_it; + } + else + { + return; + } + + // Draw the cursor + // (Flash the cursor every half second starting a fixed time after the last keystroke) + F32 elapsed = mCursorBlinkTimer.getElapsedTimeF32(); + if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) + { + + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) + { + S32 segment_width = 0; + S32 segment_height = 0; + segmentp->getDimensions(mCursorPos - segmentp->getStart(), 1, segment_width, segment_height); + S32 width = llmax(CURSOR_THICKNESS, segment_width); + cursor_rect.mRight = cursor_rect.mLeft + width; + } + else + { + cursor_rect.mRight = cursor_rect.mLeft + CURSOR_THICKNESS; + } + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLColor4 cursor_color = mCursorColor.get() % alpha; + gGL.color4fv( cursor_color.mV ); + + gl_rect_2d(cursor_rect); + + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n') + { + LLColor4 text_color; + const LLFontGL* fontp; + text_color = segmentp->getColor(); + fontp = segmentp->getStyle()->getFont(); + fontp->render(text, mCursorPos, cursor_rect, + LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha), + LLFontGL::LEFT, mTextVAlign, + LLFontGL::NORMAL, + LLFontGL::NO_SHADOW, + 1); + } + + // Make sure the IME is in the right place + LLRect screen_pos = calcScreenRect(); + LLCoordGL ime_pos( screen_pos.mLeft + llfloor(cursor_rect.mLeft), screen_pos.mBottom + llfloor(cursor_rect.mTop) ); + + ime_pos.mX = (S32) (ime_pos.mX * LLUI::getScaleFactor().mV[VX]); + ime_pos.mY = (S32) (ime_pos.mY * LLUI::getScaleFactor().mV[VY]); + getWindow()->setLanguageTextInput( ime_pos ); + } + } +} + +void LLTextBase::drawText() +{ + S32 text_len = getLength(); + + if (text_len <= 0 && mLabel.empty()) + { + return; + } + else if (useLabel()) + { + text_len = mLabel.getWString().length(); + } + + S32 selection_left = -1; + S32 selection_right = -1; + // Draw selection even if we don't have keyboard focus for search/replace + if( hasSelection()) + { + selection_left = llmin( mSelectionStart, mSelectionEnd ); + selection_right = llmax( mSelectionStart, mSelectionEnd ); + } + + std::pair line_range = getVisibleLines(mClipPartial); + S32 first_line = line_range.first; + S32 last_line = line_range.second; + if (first_line >= last_line) + { + return; + } + + S32 line_start = getLineStart(first_line); + // find first text segment that spans top of visible portion of text buffer + segment_set_t::iterator seg_iter = getSegIterContaining(line_start); + if (seg_iter == mSegments.end()) + { + return; + } + + // Perform spell check if needed + if ( (getSpellCheck()) && (getWText().length() > 2) ) + { + // Calculate start and end indices for the spell checking range + S32 start = line_start; + S32 end = getLineEnd(last_line); + + if ( (mSpellCheckStart != start) || (mSpellCheckEnd != end) ) + { + const LLWString& wstrText = getWText(); + mMisspellRanges.clear(); + + segment_set_t::const_iterator seg_it = getSegIterContaining(start); + while (mSegments.end() != seg_it) + { + LLTextSegmentPtr text_segment = *seg_it; + if ( (text_segment.isNull()) || (text_segment->getStart() >= end) ) + { + break; + } + + if (!text_segment->canEdit()) + { + ++seg_it; + continue; + } + + // Combine adjoining text segments into one + U32 seg_start = text_segment->getStart(), seg_end = llmin(text_segment->getEnd(), end); + while (mSegments.end() != ++seg_it) + { + text_segment = *seg_it; + if ( (text_segment.isNull()) || (!text_segment->canEdit()) || (text_segment->getStart() >= end) ) + { + break; + } + seg_end = llmin(text_segment->getEnd(), end); + } + + // Find the start of the first word + U32 word_start = seg_start, word_end = -1; + U32 text_length = wstrText.length(); + while ( (word_start < text_length) && (!LLStringOps::isAlpha(wstrText[word_start])) ) + { + word_start++; + } + + // Iterate over all words in the text block and check them one by one + while (word_start < seg_end) + { + // Find the end of the current word (special case handling for "'" when it's used as a contraction) + word_end = word_start + 1; + while ( (word_end < seg_end) && + ((LLWStringUtil::isPartOfWord(wstrText[word_end])) || + ((L'\'' == wstrText[word_end]) && + (LLStringOps::isAlnum(wstrText[word_end - 1])) && (LLStringOps::isAlnum(wstrText[word_end + 1])))) ) + { + word_end++; + } + if (word_end > seg_end) + { + break; + } + + if (word_start < text_length && word_end <= text_length && word_end > word_start) + { + std::string word = wstring_to_utf8str(wstrText.substr(word_start, word_end - word_start)); + + // Don't process words shorter than 3 characters + if ( (word.length() >= 3) && (!LLSpellChecker::instance().checkSpelling(word)) ) + { + mMisspellRanges.push_back(std::pair(word_start, word_end)); + } + } + + // Find the start of the next word + word_start = word_end + 1; + while ( (word_start < seg_end) && (!LLWStringUtil::isPartOfWord(wstrText[word_start])) ) + { + word_start++; + } + } + } + + mSpellCheckStart = start; + mSpellCheckEnd = end; + } + } + else + { + mMisspellRanges.clear(); + } + + LLTextSegmentPtr cur_segment = *seg_iter; + + std::list >::const_iterator misspell_it = std::lower_bound(mMisspellRanges.begin(), mMisspellRanges.end(), std::pair(line_start, 0)); + for (S32 cur_line = first_line; cur_line < last_line; cur_line++) + { + S32 next_line = cur_line + 1; + line_info& line = mLineInfoList[cur_line]; + + S32 next_start = -1; + S32 line_end = text_len; + + if (next_line < getLineCount()) + { + next_start = getLineStart(next_line); + line_end = next_start; + } + + LLRectf text_rect(line.mRect.mLeft, line.mRect.mTop, line.mRect.mRight, line.mRect.mBottom); + text_rect.mRight = mDocumentView->getRect().getWidth(); // clamp right edge to document extents + text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom); // adjust by scroll position + + // draw a single line of text + S32 seg_start = line_start; + while( seg_start < line_end ) + { + while( cur_segment->getEnd() <= seg_start ) + { + seg_iter++; + if (seg_iter == mSegments.end()) + { + LL_WARNS() << "Ran off the segmentation end!" << LL_ENDL; + + return; + } + cur_segment = *seg_iter; + } + + S32 seg_end = llmin(line_end, cur_segment->getEnd()); + S32 clipped_end = seg_end - cur_segment->getStart(); + + if (mUseEllipses // using ellipses + && clipped_end == line_end // last segment on line + && next_line == last_line // this is the last visible line + && last_line < (S32)mLineInfoList.size()) // and there is more text to display + { + // more lines of text to go, but we can't fit them + // so shrink text rect to force ellipses + text_rect.mRight -= 2; + } + + // Draw squiggly lines under any visible misspelled words + while ( (mMisspellRanges.end() != misspell_it) && (misspell_it->first < seg_end) && (misspell_it->second > seg_start) ) + { + // Skip the current word if the user is still busy editing it + if ( (!mSpellCheckTimer.hasExpired()) && (misspell_it->first <= (U32)mCursorPos) && (misspell_it->second >= (U32)mCursorPos) ) + { + ++misspell_it; + continue; + } + + U32 misspell_start = llmax(misspell_it->first, seg_start), misspell_end = llmin(misspell_it->second, seg_end); + S32 squiggle_start = 0, squiggle_end = 0, pony = 0; + cur_segment->getDimensions(seg_start - cur_segment->getStart(), misspell_start - seg_start, squiggle_start, pony); + cur_segment->getDimensions(misspell_start - cur_segment->getStart(), misspell_end - misspell_start, squiggle_end, pony); + squiggle_start += text_rect.mLeft; + + pony = (squiggle_end + 3) / 6; + squiggle_start += squiggle_end / 2 - pony * 3; + squiggle_end = squiggle_start + pony * 6; + + S32 squiggle_bottom = text_rect.mBottom + (S32)cur_segment->getStyle()->getFont()->getDescenderHeight(); + + gGL.color4ub(255, 0, 0, 200); + while (squiggle_start + 1 < squiggle_end) + { + gl_line_2d(squiggle_start, squiggle_bottom, squiggle_start + 2, squiggle_bottom - 2); + if (squiggle_start + 3 < squiggle_end) + { + gl_line_2d(squiggle_start + 2, squiggle_bottom - 3, squiggle_start + 4, squiggle_bottom - 1); + } + squiggle_start += 4; + } + + if (misspell_it->second > seg_end) + { + break; + } + ++misspell_it; + } + + text_rect.mLeft = cur_segment->draw(seg_start - cur_segment->getStart(), clipped_end, selection_left, selection_right, text_rect); + + seg_start = clipped_end + cur_segment->getStart(); + } + + line_start = next_start; + } +} + +/////////////////////////////////////////////////////////////////// +// Returns change in number of characters in mWText + +S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::segment_vec_t* segments ) +{ + beforeValueChange(); + + S32 old_len = getLength(); // length() returns character length + S32 insert_len = wstr.length(); + + pos = getEditableIndex(pos, true); + if (pos > old_len) + { + pos = old_len; + // Should not happen, + // if you encounter this, check where wrong position comes from + llassert(false); + } + + segment_set_t::iterator seg_iter = getEditableSegIterContaining(pos); + + LLTextSegmentPtr default_segment; + + LLTextSegmentPtr segmentp; + if (seg_iter != mSegments.end()) + { + segmentp = *seg_iter; + } + else + { + //segmentp = mSegments.back(); + return pos; + } + + if (segmentp->canEdit()) + { + segmentp->setEnd(segmentp->getEnd() + insert_len); + if (seg_iter != mSegments.end()) + { + ++seg_iter; + } + } + else + { + // create default editable segment to hold new text + LLStyleConstSP sp(new LLStyle(getStyleParams())); + default_segment = new LLNormalTextSegment( sp, pos, pos + insert_len, *this); + } + + // shift remaining segments to right + for(;seg_iter != mSegments.end(); ++seg_iter) + { + LLTextSegmentPtr segmentp = *seg_iter; + segmentp->setStart(segmentp->getStart() + insert_len); + segmentp->setEnd(segmentp->getEnd() + insert_len); + } + + // insert new segments + if (segments) + { + if (default_segment.notNull()) + { + // potentially overwritten by segments passed in + insertSegment(default_segment); + } + for (segment_vec_t::iterator seg_iter = segments->begin(); + seg_iter != segments->end(); + ++seg_iter) + { + LLTextSegment* segmentp = *seg_iter; + insertSegment(segmentp); + } + } + + // Insert special segments where necessary (insertSegment takes care of splitting normal text segments around them for us) + if (mUseEmoji) + { + LLStyleSP emoji_style; + LLEmojiDictionary* ed = LLEmojiDictionary::instanceExists() ? LLEmojiDictionary::getInstance() : NULL; + for (S32 text_kitty = 0, text_len = wstr.size(); text_kitty < text_len; text_kitty++) + { + llwchar code = wstr[text_kitty]; + bool isEmoji = ed ? ed->isEmoji(code) : LLStringOps::isEmoji(code); + if (isEmoji) + { + if (!emoji_style) + { + emoji_style = new LLStyle(getStyleParams()); + emoji_style->setFont(LLFontGL::getFontEmojiLarge()); + } + + S32 new_seg_start = pos + text_kitty; + insertSegment(new LLEmojiTextSegment(emoji_style, new_seg_start, new_seg_start + 1, *this)); + } + } + } + + getViewModel()->getEditableDisplay().insert(pos, wstr); + + if ( truncate() ) + { + insert_len = getLength() - old_len; + } + + onValueChange(pos, pos + insert_len); + needsReflow(pos); + + return insert_len; +} + +S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) +{ + + beforeValueChange(); + segment_set_t::iterator seg_iter = getSegIterContaining(pos); + while(seg_iter != mSegments.end()) + { + LLTextSegmentPtr segmentp = *seg_iter; + S32 end = pos + length; + if (segmentp->getStart() < pos) + { + // deleting from middle of segment + if (segmentp->getEnd() > end) + { + segmentp->setEnd(segmentp->getEnd() - length); + } + // truncating segment + else + { + segmentp->setEnd(pos); + } + } + else if (segmentp->getStart() < end) + { + // deleting entire segment + if (segmentp->getEnd() <= end) + { + // remove segment + segmentp->unlinkFromDocument(this); + segment_set_t::iterator seg_to_erase(seg_iter++); + mSegments.erase(seg_to_erase); + continue; + } + // deleting head of segment + else + { + segmentp->setStart(pos); + segmentp->setEnd(segmentp->getEnd() - length); + } + } + else + { + // shifting segments backward to fill deleted portion + segmentp->setStart(segmentp->getStart() - length); + segmentp->setEnd(segmentp->getEnd() - length); + } + ++seg_iter; + } + + getViewModel()->getEditableDisplay().erase(pos, length); + + // recreate default segment in case we erased everything + createDefaultSegment(); + + onValueChange(pos, pos); + needsReflow(pos); + + return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length +} + +S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) +{ + beforeValueChange(); + + if (pos > (S32)getLength()) + { + return 0; + } + getViewModel()->getEditableDisplay()[pos] = wc; + + onValueChange(pos, pos + 1); + needsReflow(pos); + + return 1; +} + + +void LLTextBase::createDefaultSegment() +{ + // ensures that there is always at least one segment + if (mSegments.empty()) + { + LLStyleConstSP sp(new LLStyle(getStyleParams())); + LLTextSegmentPtr default_segment = new LLNormalTextSegment( sp, 0, getLength() + 1, *this); + mSegments.insert(default_segment); + default_segment->linkToDocument(this); + } +} + +void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) +{ + if (segment_to_insert.isNull()) + { + return; + } + + segment_set_t::iterator cur_seg_iter = getSegIterContaining(segment_to_insert->getStart()); + S32 reflow_start_index = 0; + + if (cur_seg_iter == mSegments.end()) + { + mSegments.insert(segment_to_insert); + segment_to_insert->linkToDocument(this); + reflow_start_index = segment_to_insert->getStart(); + } + else + { + LLTextSegmentPtr cur_segmentp = *cur_seg_iter; + reflow_start_index = cur_segmentp->getStart(); + if (cur_segmentp->getStart() < segment_to_insert->getStart()) + { + S32 old_segment_end = cur_segmentp->getEnd(); + // split old at start point for new segment + cur_segmentp->setEnd(segment_to_insert->getStart()); + // advance to next segment + // insert remainder of old segment + LLStyleConstSP sp = cur_segmentp->getStyle(); + LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( sp, segment_to_insert->getStart(), old_segment_end, *this); + mSegments.insert(cur_seg_iter, remainder_segment); + remainder_segment->linkToDocument(this); + // insert new segment before remainder of old segment + mSegments.insert(cur_seg_iter, segment_to_insert); + + segment_to_insert->linkToDocument(this); + // at this point, there will be two overlapping segments owning the text + // associated with the incoming segment + } + else + { + mSegments.insert(cur_seg_iter, segment_to_insert); + segment_to_insert->linkToDocument(this); + } + + // now delete/truncate remaining segments as necessary + // cur_seg_iter points to segment before incoming segment + while(cur_seg_iter != mSegments.end()) + { + cur_segmentp = *cur_seg_iter; + if (cur_segmentp == segment_to_insert) + { + ++cur_seg_iter; + continue; + } + + if (cur_segmentp->getStart() >= segment_to_insert->getStart()) + { + if(cur_segmentp->getEnd() <= segment_to_insert->getEnd()) + { + cur_segmentp->unlinkFromDocument(this); + // grab copy of iterator to erase, and bump it + segment_set_t::iterator seg_to_erase(cur_seg_iter++); + mSegments.erase(seg_to_erase); + continue; + } + else + { + // last overlapping segment, clip to end of incoming segment + // and stop traversal + cur_segmentp->setStart(segment_to_insert->getEnd()); + break; + } + } + ++cur_seg_iter; + } + } + + // layout potentially changed + needsReflow(reflow_start_index); +} + +//virtual +bool LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // handle triple click + if (!mTripleClickTimer.hasExpired()) + { + if (mSkipTripleClick) + { + return true; + } + + S32 real_line = getLineNumFromDocIndex(mCursorPos, false); + S32 line_start = -1; + S32 line_end = -1; + for (line_list_t::const_iterator it = mLineInfoList.begin(), end_it = mLineInfoList.end(); + it != end_it; + ++it) + { + if (it->mLineNum < real_line) + { + continue; + } + if (it->mLineNum > real_line) + { + break; + } + if (line_start == -1) + { + line_start = it->mDocIndexStart; + } + line_end = it->mDocIndexEnd; + line_end = llclamp(line_end, 0, getLength()); + } + + if (line_start == -1) + { + return true; + } + + mSelectionEnd = line_start; + mSelectionStart = line_end; + setCursorPos(line_start); + + return true; + } + + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleMouseDown(x, y, mask)) + { + return true; + } + + return LLUICtrl::handleMouseDown(x, y, mask); +} + +//virtual +bool LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (hasMouseCapture() && cur_segment && cur_segment->handleMouseUp(x, y, mask)) + { + // Did we just click on a link? + if (mURLClickSignal + && cur_segment->getStyle() + && cur_segment->getStyle()->isLink()) + { + // *TODO: send URL here? + (*mURLClickSignal)(this, LLSD() ); + } + return true; + } + + return LLUICtrl::handleMouseUp(x, y, mask); +} + +//virtual +bool LLTextBase::handleMiddleMouseDown(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleMiddleMouseDown(x, y, mask)) + { + return true; + } + + return LLUICtrl::handleMiddleMouseDown(x, y, mask); +} + +//virtual +bool LLTextBase::handleMiddleMouseUp(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleMiddleMouseUp(x, y, mask)) + { + return true; + } + + return LLUICtrl::handleMiddleMouseUp(x, y, mask); +} + +//virtual +bool LLTextBase::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleRightMouseDown(x, y, mask)) + { + return true; + } + + return LLUICtrl::handleRightMouseDown(x, y, mask); +} + +//virtual +bool LLTextBase::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleRightMouseUp(x, y, mask)) + { + return true; + } + + return LLUICtrl::handleRightMouseUp(x, y, mask); +} + +//virtual +bool LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + //Don't start triple click timer if user have clicked on scrollbar + mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + if (x >= mVisibleTextRect.mLeft && x <= mVisibleTextRect.mRight + && y >= mVisibleTextRect.mBottom && y <= mVisibleTextRect.mTop) + { + mTripleClickTimer.setTimerExpirySec(TRIPLE_CLICK_INTERVAL); + } + + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleDoubleClick(x, y, mask)) + { + return true; + } + + return LLUICtrl::handleDoubleClick(x, y, mask); +} + +//virtual +bool LLTextBase::handleHover(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleHover(x, y, mask)) + { + return true; + } + + return LLUICtrl::handleHover(x, y, mask); +} + +//virtual +bool LLTextBase::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleScrollWheel(x, y, clicks)) + { + return true; + } + + return LLUICtrl::handleScrollWheel(x, y, clicks); +} + +//virtual +bool LLTextBase::handleToolTip(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleToolTip(x, y, mask)) + { + return true; + } + + return LLUICtrl::handleToolTip(x, y, mask); +} + +//virtual +void LLTextBase::reshape(S32 width, S32 height, bool called_from_parent) +{ + if (width != getRect().getWidth() || height != getRect().getHeight() || LLView::sForceReshape) + { + bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false; + + LLUICtrl::reshape( width, height, called_from_parent ); + + if (mScroller && scrolled_to_bottom && mTrackEnd) + { + // keep bottom of text buffer visible + // do this here as well as in reflow to handle case + // where shrinking from top, which causes buffer to temporarily + // not be scrolled to the bottom, since the scroll index + // specified the _top_ of the visible document region + mScroller->goToBottom(); + } + + // do this first after reshape, because other things depend on + // up-to-date mVisibleTextRect + updateRects(); + + needsReflow(); + } +} + +//virtual +void LLTextBase::draw() +{ + // reflow if needed, on demand + reflow(); + + // then update scroll position, as cursor may have moved + if (!mReadOnly) + { + updateScrollFromCursor(); + } + + LLRect text_rect; + if (mScroller) + { + mScroller->localRectToOtherView(mScroller->getContentWindowRect(), &text_rect, this); + } + else + { + LLRect visible_lines_rect; + std::pair line_range = getVisibleLines(mClipPartial); + for (S32 i = line_range.first; i < line_range.second; i++) + { + if (visible_lines_rect.isEmpty()) + { + visible_lines_rect = mLineInfoList[i].mRect; + } + else + { + visible_lines_rect.unionWith(mLineInfoList[i].mRect); + } + } + text_rect = visible_lines_rect; + text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom); + } + + if (mBGVisible) + { + F32 alpha = getCurrentTransparency(); + // clip background rect against extents, if we support scrolling + LLRect bg_rect = mVisibleTextRect; + if (mScroller) + { + bg_rect.intersectWith(text_rect); + } + LLColor4 bg_color = mReadOnly + ? mReadOnlyBgColor.get() + : hasFocus() + ? mFocusBgColor.get() + : mWriteableBgColor.get(); + gl_rect_2d(text_rect, bg_color % alpha, true); + } + + // Draw highlighted if needed + if( ll::ui::SearchableControl::getHighlighted() ) + { + LLColor4 bg_color = ll::ui::SearchableControl::getHighlightColor(); + LLRect bg_rect = mVisibleTextRect; + if( mScroller ) + bg_rect.intersectWith( text_rect ); + + gl_rect_2d( text_rect, bg_color, true ); + } + + bool should_clip = mClip || mScroller != NULL; + { LLLocalClipRect clip(text_rect, should_clip); + + // draw document view + if (mScroller) + { + drawChild(mScroller); + } + else + { + drawChild(mDocumentView); + } + + drawSelectionBackground(); + drawText(); + drawCursor(); + } + + mDocumentView->setVisibleDirect(false); + LLUICtrl::draw(); + mDocumentView->setVisibleDirect(true); +} + + +//virtual +void LLTextBase::setColor( const LLColor4& c ) +{ + mFgColor = c; + mStyleDirty = true; +} + +//virtual +void LLTextBase::setReadOnlyColor(const LLColor4 &c) +{ + mReadOnlyFgColor = c; + mStyleDirty = true; +} + +//virtual +void LLTextBase::onVisibilityChange( bool new_visibility ) +{ + LLContextMenu* menu = static_cast(mPopupMenuHandle.get()); + if(!new_visibility && menu) + { + menu->hide(); + } + LLUICtrl::onVisibilityChange(new_visibility); +} + +//virtual +void LLTextBase::setValue(const LLSD& value ) +{ + setText(value.asString()); +} + +//virtual +bool LLTextBase::canDeselect() const +{ + return hasSelection(); +} + + +//virtual +void LLTextBase::deselect() +{ + mSelectionStart = 0; + mSelectionEnd = 0; + mIsSelecting = false; +} + +bool LLTextBase::getSpellCheck() const +{ + return (LLSpellChecker::getUseSpellCheck()) && (!mReadOnly) && (mSpellCheck); +} + +const std::string& LLTextBase::getSuggestion(U32 index) const +{ + return (index < mSuggestionList.size()) ? mSuggestionList[index] : LLStringUtil::null; +} + +U32 LLTextBase::getSuggestionCount() const +{ + return mSuggestionList.size(); +} + +void LLTextBase::replaceWithSuggestion(U32 index) +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= (U32)mCursorPos) && (it->second >= (U32)mCursorPos) ) + { + deselect(); + // Insert the suggestion in its place + LLWString suggestion = utf8str_to_wstring(mSuggestionList[index]); + insertStringNoUndo(it->first, utf8str_to_wstring(mSuggestionList[index])); + + // Delete the misspelled word + removeStringNoUndo(it->first + (S32)suggestion.length(), it->second - it->first); + + + setCursorPos(it->first + (S32)suggestion.length()); + onSpellCheckPerformed(); + + break; + } + } + mSpellCheckStart = mSpellCheckEnd = -1; +} + +void LLTextBase::addToDictionary() +{ + if (canAddToDictionary()) + { + LLSpellChecker::instance().addToCustomDictionary(getMisspelledWord(mCursorPos)); + } +} + +bool LLTextBase::canAddToDictionary() const +{ + return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +void LLTextBase::addToIgnore() +{ + if (canAddToIgnore()) + { + LLSpellChecker::instance().addToIgnoreList(getMisspelledWord(mCursorPos)); + } +} + +bool LLTextBase::canAddToIgnore() const +{ + return (getSpellCheck()) && (isMisspelledWord(mCursorPos)); +} + +std::string LLTextBase::getMisspelledWord(U32 pos) const +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= pos) && (it->second >= pos) ) + { + return wstring_to_utf8str(getWText().substr(it->first, it->second - it->first)); + } + } + return LLStringUtil::null; +} + +bool LLTextBase::isMisspelledWord(U32 pos) const +{ + for (std::list >::const_iterator it = mMisspellRanges.begin(); it != mMisspellRanges.end(); ++it) + { + if ( (it->first <= pos) && (it->second >= pos) ) + { + return true; + } + } + return false; +} + +void LLTextBase::onSpellCheckSettingsChange() +{ + // Recheck the spelling on every change + mMisspellRanges.clear(); + mSpellCheckStart = mSpellCheckEnd = -1; +} + +void LLTextBase::onFocusReceived() +{ + LLUICtrl::onFocusReceived(); + if (!getLength() && !mLabel.empty()) + { + // delete label which is LLLabelTextSegment + clearSegments(); + } +} + +void LLTextBase::onFocusLost() +{ + LLUICtrl::onFocusLost(); + if (!getLength() && !mLabel.empty()) + { + resetLabel(); + } +} + +// Sets the scrollbar from the cursor position +void LLTextBase::updateScrollFromCursor() +{ + // Update scroll position even in read-only mode (when there's no cursor displayed) + // because startOfDoc()/endOfDoc() modify cursor position. See EXT-736. + + if (!mScrollNeeded || !mScroller) + { + return; + } + mScrollNeeded = false; + + // scroll so that the cursor is at the top of the page + LLRect scroller_doc_window = getVisibleDocumentRect(); + LLRect cursor_rect_doc = getDocRectFromDocIndex(mCursorPos); + mScroller->scrollToShowRect(cursor_rect_doc, LLRect(0, scroller_doc_window.getHeight() - 5, scroller_doc_window.getWidth(), 5)); +} + +S32 LLTextBase::getLeftOffset(S32 width) +{ + switch (mHAlign) + { + case LLFontGL::LEFT: + return mHPad; + case LLFontGL::HCENTER: + return mHPad + llmax(0, (mVisibleTextRect.getWidth() - width - mHPad) / 2); + case LLFontGL::RIGHT: + { + // Font's rendering rounds string size, if value gets rounded + // down last symbol might not have enough space to render, + // compensate by adding an extra pixel as padding + const S32 right_padding = 1; + return llmax(mHPad, mVisibleTextRect.getWidth() - width - right_padding); + } + default: + return mHPad; + } +} + +void LLTextBase::reflow() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + + updateSegments(); + + if (mReflowIndex == S32_MAX) + { + return; + } + + bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false; + + LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); + bool follow_selection = getLocalRect().overlaps(cursor_rect); // cursor is (potentially) visible + + // store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing + cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop; + cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom; + + S32 first_line = getFirstVisibleLine(); + + // if scroll anchor not on first line, update it to first character of first line + if ((first_line < mLineInfoList.size()) + && (mScrollIndex < mLineInfoList[first_line].mDocIndexStart + || mScrollIndex >= mLineInfoList[first_line].mDocIndexEnd)) + { + mScrollIndex = mLineInfoList[first_line].mDocIndexStart; + } + LLRect first_char_rect = getLocalRectFromDocIndex(mScrollIndex); + // store in top-left relative coordinates to avoid issues with horizontal scrollbar appearing and disappearing + first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop; + first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom; + + S32 reflow_count = 0; + while(mReflowIndex < S32_MAX) + { + // we can get into an infinite loop if the document height does not monotonically increase + // with decreasing width (embedded ui elements with alternate layouts). In that case, + // we want to stop reflowing after 2 iterations. We use 2, since we need to handle the case + // of introducing a vertical scrollbar causing a reflow with less width. We should also always + // use an even number of iterations to avoid user visible oscillation of the layout + if(++reflow_count > 2) + { + LL_DEBUGS() << "Breaking out of reflow due to possible infinite loop in " << getName() << LL_ENDL; + break; + } + + S32 start_index = mReflowIndex; + mReflowIndex = S32_MAX; + + // shrink document to minimum size (visible portion of text widget) + // to force inlined widgets with follows set to shrink + if (mWordWrap) + { + mDocumentView->reshape(mVisibleTextRect.getWidth(), mDocumentView->getRect().getHeight()); + } + + S32 cur_top = 0; + + segment_set_t::iterator seg_iter = mSegments.begin(); + S32 seg_offset = 0; + S32 line_start_index = 0; + const F32 text_available_width = mVisibleTextRect.getWidth() - mHPad; // reserve room for margin + F32 remaining_pixels = text_available_width; + S32 line_count = 0; + + // find and erase line info structs starting at start_index and going to end of document + if (!mLineInfoList.empty()) + { + // find first element whose end comes after start_index + line_list_t::iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), start_index, line_end_compare()); + if (iter != mLineInfoList.end()) + { + line_start_index = iter->mDocIndexStart; + line_count = iter->mLineNum; + cur_top = iter->mRect.mTop; + getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset); + mLineInfoList.erase(iter, mLineInfoList.end()); + } + } + + S32 line_height = 0; + S32 seg_line_offset = line_count + 1; + + while(seg_iter != mSegments.end()) + { + LLTextSegmentPtr segment = *seg_iter; + + // track maximum height of any segment on this line + S32 cur_index = segment->getStart() + seg_offset; + + // ask segment how many character fit in remaining space + S32 character_count = segment->getNumChars(getWordWrap() ? llmax(0, ll_round(remaining_pixels)) : S32_MAX, + seg_offset, + cur_index - line_start_index, + S32_MAX, + line_count - seg_line_offset); + + F32 segment_width; + S32 segment_height; + bool force_newline = segment->getDimensionsF32(seg_offset, character_count, segment_width, segment_height); + // grow line height as necessary based on reported height of this segment + line_height = llmax(line_height, segment_height); + remaining_pixels -= segment_width; + + seg_offset += character_count; + + S32 last_segment_char_on_line = segment->getStart() + seg_offset; + + // Note: make sure text will fit in width - use ceil, but also make sure + // ceil is used only once per line + S32 text_actual_width = llceil(text_available_width - remaining_pixels); + S32 text_left = getLeftOffset(text_actual_width); + LLRect line_rect(text_left, + cur_top, + text_left + text_actual_width, + cur_top - line_height); + + // if we didn't finish the current segment... + if (last_segment_char_on_line < segment->getEnd()) + { + // add line info and keep going + mLineInfoList.push_back(line_info( + line_start_index, + last_segment_char_on_line, + line_rect, + line_count)); + + line_start_index = segment->getStart() + seg_offset; + cur_top -= ll_round((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; + remaining_pixels = text_available_width; + line_height = 0; + } + // ...just consumed last segment.. + else if (++segment_set_t::iterator(seg_iter) == mSegments.end()) + { + mLineInfoList.push_back(line_info( + line_start_index, + last_segment_char_on_line, + line_rect, + line_count)); + cur_top -= ll_round((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; + break; + } + // ...or finished a segment and there are segments remaining on this line + else + { + // subtract pixels used and increment segment + if (force_newline) + { + mLineInfoList.push_back(line_info( + line_start_index, + last_segment_char_on_line, + line_rect, + line_count)); + line_start_index = segment->getStart() + seg_offset; + cur_top -= ll_round((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; + line_height = 0; + remaining_pixels = text_available_width; + } + ++seg_iter; + seg_offset = 0; + seg_line_offset = force_newline ? line_count + 1 : line_count; + } + if (force_newline) + { + line_count++; + } + } + + // calculate visible region for diplaying text + updateRects(); + + for (segment_set_t::iterator segment_it = mSegments.begin(); + segment_it != mSegments.end(); + ++segment_it) + { + LLTextSegmentPtr segmentp = *segment_it; + segmentp->updateLayout(*this); + + } + } + + // apply scroll constraints after reflowing text + if (!hasMouseCapture() && mScroller) + { + if (scrolled_to_bottom && mTrackEnd) + { + // keep bottom of text buffer visible + endOfDoc(); + } + else if (hasSelection() && follow_selection) + { + // keep cursor in same vertical position on screen when selecting text + LLRect new_cursor_rect_doc = getDocRectFromDocIndex(mCursorPos); + LLRect old_cursor_rect = cursor_rect; + old_cursor_rect.mTop = mVisibleTextRect.mTop - cursor_rect.mTop; + old_cursor_rect.mBottom = mVisibleTextRect.mTop - cursor_rect.mBottom; + + mScroller->scrollToShowRect(new_cursor_rect_doc, old_cursor_rect); + } + else + { + // keep first line of text visible + LLRect new_first_char_rect = getDocRectFromDocIndex(mScrollIndex); + + // pass in desired rect in the coordinate frame of the document viewport + LLRect old_first_char_rect = first_char_rect; + old_first_char_rect.mTop = mVisibleTextRect.mTop - first_char_rect.mTop; + old_first_char_rect.mBottom = mVisibleTextRect.mTop - first_char_rect.mBottom; + + mScroller->scrollToShowRect(new_first_char_rect, old_first_char_rect); + } + } + + // reset desired x cursor position + updateCursorXPos(); +} + +LLRect LLTextBase::getTextBoundingRect() +{ + reflow(); + return mTextBoundingRect; +} + + +void LLTextBase::clearSegments() +{ + mSegments.clear(); + createDefaultSegment(); +} + +S32 LLTextBase::getLineStart( S32 line ) const +{ + S32 num_lines = getLineCount(); + if (num_lines == 0) + { + return 0; + } + + line = llclamp(line, 0, num_lines-1); + return mLineInfoList[line].mDocIndexStart; +} + +S32 LLTextBase::getLineEnd( S32 line ) const +{ + S32 num_lines = getLineCount(); + if (num_lines == 0) + { + return 0; + } + + line = llclamp(line, 0, num_lines-1); + return mLineInfoList[line].mDocIndexEnd; +} + + + +S32 LLTextBase::getLineNumFromDocIndex( S32 doc_index, bool include_wordwrap) const +{ + if (mLineInfoList.empty()) + { + return 0; + } + else + { + line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_index, line_end_compare()); + if (include_wordwrap) + { + return iter - mLineInfoList.begin(); + } + else + { + if (iter == mLineInfoList.end()) + { + return mLineInfoList.back().mLineNum; + } + else + { + return iter->mLineNum; + } + } + } +} + +// Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line. +S32 LLTextBase::getLineOffsetFromDocIndex( S32 startpos, bool include_wordwrap) const +{ + if (mLineInfoList.empty()) + { + return startpos; + } + else + { + line_list_t::const_iterator iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), startpos, line_end_compare()); + return startpos - iter->mDocIndexStart; + } +} + +S32 LLTextBase::getFirstVisibleLine() const +{ + LLRect visible_region = getVisibleDocumentRect(); + + // binary search for line that starts before top of visible buffer + line_list_t::const_iterator iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom()); + + return iter - mLineInfoList.begin(); +} + +std::pair LLTextBase::getVisibleLines(bool require_fully_visible) +{ + LLRect visible_region = getVisibleDocumentRect(); + line_list_t::const_iterator first_iter; + line_list_t::const_iterator last_iter; + + // make sure we have an up-to-date mLineInfoList + reflow(); + + if (require_fully_visible) + { + first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_top()); + last_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom()); + } + else + { + first_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_bottom()); + last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_top()); + } + return std::pair(first_iter - mLineInfoList.begin(), last_iter - mLineInfoList.begin()); +} + + + +LLTextViewModel* LLTextBase::getViewModel() const +{ + return (LLTextViewModel*)mViewModel.get(); +} + +void LLTextBase::addDocumentChild(LLView* view) +{ + mDocumentView->addChild(view); +} + +void LLTextBase::removeDocumentChild(LLView* view) +{ + mDocumentView->removeChild(view); +} + + +void LLTextBase::updateSegments() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + createDefaultSegment(); +} + +void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const +{ + *seg_iter = getSegIterContaining(startpos); + if (*seg_iter == mSegments.end()) + { + *offsetp = 0; + } + else + { + *offsetp = startpos - (**seg_iter)->getStart(); + } +} + +void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp ) +{ + *seg_iter = getSegIterContaining(startpos); + if (*seg_iter == mSegments.end()) + { + *offsetp = 0; + } + else + { + *offsetp = startpos - (**seg_iter)->getStart(); + } +} + +LLTextBase::segment_set_t::iterator LLTextBase::getEditableSegIterContaining(S32 index) +{ + segment_set_t::iterator it = getSegIterContaining(index); + segment_set_t::iterator orig_it = it; + + if (it == mSegments.end()) return it; + + if (!(*it)->canEdit() + && index == (*it)->getStart() + && it != mSegments.begin()) + { + it--; + if ((*it)->canEdit()) + { + return it; + } + } + return orig_it; +} + +LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaining(S32 index) const +{ + segment_set_t::const_iterator it = getSegIterContaining(index); + segment_set_t::const_iterator orig_it = it; + if (it == mSegments.end()) return it; + + if (!(*it)->canEdit() + && index == (*it)->getStart() + && it != mSegments.begin()) + { + it--; + if ((*it)->canEdit()) + { + return it; + } + } + return orig_it; +} + +LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index) +{ + static LLPointer index_segment = new LLIndexSegment(); + + // when there are no segments, we return the end iterator, which must be checked by caller + if (mSegments.size() <= 1) { return mSegments.begin(); } + + index_segment->setStart(index); + index_segment->setEnd(index); + segment_set_t::iterator it = mSegments.upper_bound(index_segment); + return it; +} + +LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 index) const +{ + static LLPointer index_segment = new LLIndexSegment(); + + // when there are no segments, we return the end iterator, which must be checked by caller + if (mSegments.size() <= 1) { return mSegments.begin(); } + + index_segment->setStart(index); + index_segment->setEnd(index); + LLTextBase::segment_set_t::const_iterator it = mSegments.upper_bound(index_segment); + return it; +} + +// Finds the text segment (if any) at the give local screen position +LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line) +{ + // Find the cursor position at the requested local screen position + S32 offset = getDocIndexFromLocalCoord( x, y, false, hit_past_end_of_line); + segment_set_t::iterator seg_iter = getSegIterContaining(offset); + if (seg_iter != mSegments.end()) + { + return *seg_iter; + } + else + { + return LLTextSegmentPtr(); + } +} + +void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) +{ + // work out the XUI menu file to use for this url + LLUrlMatch match; + std::string url = in_url; + if (! LLUrlRegistry::instance().findUrl(url, match)) + { + return; + } + + std::string xui_file = match.getMenuName(); + if (xui_file.empty()) + { + return; + } + + // set up the callbacks for all of the potential menu items, N.B. we + // don't use const ref strings in callbacks in case url goes out of scope + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + registrar.add("Url.Open", boost::bind(&LLUrlAction::openURL, url)); + registrar.add("Url.OpenInternal", boost::bind(&LLUrlAction::openURLInternal, url)); + registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url)); + registrar.add("Url.Execute", boost::bind(&LLUrlAction::executeSLURL, url, true)); + registrar.add("Url.Block", boost::bind(&LLUrlAction::blockObject, url)); + registrar.add("Url.Unblock", boost::bind(&LLUrlAction::unblockObject, url)); + registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url)); + registrar.add("Url.ShowProfile", boost::bind(&LLUrlAction::showProfile, url)); + registrar.add("Url.AddFriend", boost::bind(&LLUrlAction::addFriend, url)); + registrar.add("Url.RemoveFriend", boost::bind(&LLUrlAction::removeFriend, url)); + registrar.add("Url.ReportAbuse", boost::bind(&LLUrlAction::reportAbuse, url)); + registrar.add("Url.SendIM", boost::bind(&LLUrlAction::sendIM, url)); + registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url)); + registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url)); + registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url)); + + // create and return the context menu from the XUI file + + LLContextMenu* menu = static_cast(mPopupMenuHandle.get()); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } + llassert(LLMenuGL::sMenuContainer != NULL); + menu = LLUICtrlFactory::getInstance()->createFromFile(xui_file, LLMenuGL::sMenuContainer, + LLMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mPopupMenuHandle = menu->getHandle(); + + if (mIsFriendSignal) + { + bool isFriend = *(*mIsFriendSignal)(LLUUID(LLUrlAction::getUserID(url))); + LLView* addFriendButton = menu->getChild("add_friend"); + LLView* removeFriendButton = menu->getChild("remove_friend"); + + if (addFriendButton && removeFriendButton) + { + addFriendButton->setEnabled(!isFriend); + removeFriendButton->setEnabled(isFriend); + } + } + + if (mIsObjectBlockedSignal) + { + bool is_blocked = *(*mIsObjectBlockedSignal)(LLUUID(LLUrlAction::getObjectId(url)), LLUrlAction::getObjectName(url)); + LLView* blockButton = menu->getChild("block_object"); + LLView* unblockButton = menu->getChild("unblock_object"); + + if (blockButton && unblockButton) + { + blockButton->setVisible(!is_blocked); + unblockButton->setVisible(is_blocked); + } + } + menu->show(x, y); + LLMenuGL::showPopup(this, menu, x, y); + } +} + +void LLTextBase::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) +{ + // clear out the existing text and segments + getViewModel()->setDisplay(LLWStringUtil::null); + + clearSegments(); +// createDefaultSegment(); + + deselect(); + + // append the new text (supports Url linking) + std::string text(utf8str); + LLStringUtil::removeCRLF(text); + + // appendText modifies mCursorPos... + appendText(text, false, input_params); + // ...so move cursor to top after appending text + if (!mTrackEnd) + { + startOfDoc(); + } + + onValueChange(0, getLength()); +} + +// virtual +const std::string& LLTextBase::getText() const +{ + return getViewModel()->getStringValue(); +} + +// IDEVO - icons can be UI image names or UUID sent from +// server with avatar display name +static LLUIImagePtr image_from_icon_name(const std::string& icon_name) +{ + if (LLUUID::validate(icon_name)) + { + return LLUI::getUIImageByID( LLUUID(icon_name) ); + } + else + { + return LLUI::getUIImage(icon_name); + } +} + + +void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + LLStyle::Params style_params(input_params); + style_params.fillFrom(getStyleParams()); + + S32 part = (S32)LLTextParser::WHOLE; + if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). + { + S32 start=0,end=0; + LLUrlMatch match; + std::string text = new_text; + while (LLUrlRegistry::instance().findUrl(text, match, + boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons)) + { + start = match.getStart(); + end = match.getEnd()+1; + + LLStyle::Params link_params(style_params); + link_params.overwriteFrom(match.getStyle()); + + // output the text before the Url + if (start > 0) + { + if (part == (S32)LLTextParser::WHOLE || + part == (S32)LLTextParser::START) + { + part = (S32)LLTextParser::START; + } + else + { + part = (S32)LLTextParser::MIDDLE; + } + std::string subtext=text.substr(0,start); + appendAndHighlightText(subtext, part, style_params); + } + + // add icon before url if need + LLTextUtil::processUrlMatch(&match, this, isContentTrusted() || match.isTrusted() || mAlwaysShowIcons); + if ((isContentTrusted() || match.isTrusted()) && !match.getIcon().empty() ) + { + setLastSegmentToolTip(LLTrans::getString("TooltipSLIcon")); + } + + // output the styled Url + appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly()); + bool tooltip_required = !match.getTooltip().empty(); + + // set the tooltip for the Url label + if (tooltip_required) + { + setLastSegmentToolTip(match.getTooltip()); + } + + // show query part of url with gray color only for LLUrlEntryHTTP url entries + std::string label = match.getQuery(); + if (label.size()) + { + link_params.color = LLColor4::grey; + link_params.readonly_color = LLColor4::grey; + appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly()); + + // set the tooltip for the query part of url + if (tooltip_required) + { + setLastSegmentToolTip(match.getTooltip()); + } + } + + // move on to the rest of the text after the Url + if (end < (S32)text.length()) + { + text = text.substr(end,text.length() - end); + end=0; + part=(S32)LLTextParser::END; + } + else + { + break; + } + } + if (part != (S32)LLTextParser::WHOLE) + part=(S32)LLTextParser::END; + if (end < (S32)text.length()) + appendAndHighlightText(text, part, style_params); + } + else + { + appendAndHighlightText(new_text, part, style_params); + } +} + +void LLTextBase::setLastSegmentToolTip(const std::string &tooltip) +{ + segment_set_t::iterator it = getSegIterContaining(getLength()-1); + if (it != mSegments.end()) + { + LLTextSegmentPtr segment = *it; + segment->setToolTip(tooltip); + } +} + +void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + if (new_text.empty()) + return; + + if(prepend_newline) + appendLineBreakSegment(input_params); + appendTextImpl(new_text,input_params); +} + +void LLTextBase::setLabel(const LLStringExplicit& label) +{ + mLabel = label; + resetLabel(); +} + +bool LLTextBase::setLabelArg(const std::string& key, const LLStringExplicit& text ) +{ + mLabel.setArg(key, text); + return true; +} + +void LLTextBase::resetLabel() +{ + if (useLabel()) + { + clearSegments(); + + LLStyle* style = new LLStyle(getStyleParams()); + style->setColor(mTentativeFgColor); + LLStyleConstSP sp(style); + + LLTextSegmentPtr label = new LLLabelTextSegment(sp, 0, mLabel.getWString().length() + 1, *this); + insertSegment(label); + } +} + +bool LLTextBase::useLabel() const +{ + return !getLength() && !mLabel.empty() && !hasFocus(); +} + +void LLTextBase::setFont(const LLFontGL* font) +{ + mFont = font; + mStyleDirty = true; +} + +void LLTextBase::needsReflow(S32 index) +{ + LL_DEBUGS() << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << LL_ENDL; + mReflowIndex = llmin(mReflowIndex, index); +} + +S32 LLTextBase::removeFirstLine() +{ + if (!mLineInfoList.empty()) + { + S32 length = getLineEnd(0); + deselect(); + removeStringNoUndo(0, length); + return length; + } + return 0; +} + +void LLTextBase::appendLineBreakSegment(const LLStyle::Params& style_params) +{ + segment_vec_t segments; + LLStyleConstSP sp(new LLStyle(style_params)); + segments.push_back(new LLLineBreakTextSegment(sp, getLength())); + + insertStringNoUndo(getLength(), utf8str_to_wstring("\n"), &segments); +} + +void LLTextBase::appendImageSegment(const LLStyle::Params& style_params) +{ + if(getPlainText()) + { + return; + } + segment_vec_t segments; + LLStyleConstSP sp(new LLStyle(style_params)); + segments.push_back(new LLImageTextSegment(sp, getLength(),*this)); + + insertStringNoUndo(getLength(), utf8str_to_wstring(" "), &segments); +} + +void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo) +{ + segment_vec_t segments; + LLWString widget_wide_text = utf8str_to_wstring(text); + segments.push_back(new LLInlineViewSegment(params, getLength(), getLength() + widget_wide_text.size())); + + insertStringNoUndo(getLength(), widget_wide_text, &segments); +} + +void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) +{ + // Save old state + S32 selection_start = mSelectionStart; + S32 selection_end = mSelectionEnd; + bool was_selecting = mIsSelecting; + S32 cursor_pos = mCursorPos; + S32 old_length = getLength(); + bool cursor_was_at_end = (mCursorPos == old_length); + + deselect(); + + setCursorPos(old_length); + + if (mParseHighlights) + { + LLStyle::Params highlight_params(style_params); + + LLSD pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part); + for (S32 i = 0; i < pieces.size(); i++) + { + LLSD color_llsd = pieces[i]["color"]; + LLColor4 lcolor; + lcolor.setValue(color_llsd); + highlight_params.color = lcolor; + + LLWString wide_text; + wide_text = utf8str_to_wstring(pieces[i]["text"].asString()); + + S32 cur_length = getLength(); + LLStyleConstSP sp(new LLStyle(highlight_params)); + LLTextSegmentPtr segmentp; + if (underline_on_hover_only || mSkipLinkUnderline) + { + highlight_params.font.style("NORMAL"); + LLStyleConstSP normal_sp(new LLStyle(highlight_params)); + segmentp = new LLOnHoverChangeableTextSegment(sp, normal_sp, cur_length, cur_length + wide_text.size(), *this); + } + else + { + segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + wide_text.size(), *this); + } + segment_vec_t segments; + segments.push_back(segmentp); + insertStringNoUndo(cur_length, wide_text, &segments); + } + } + else + { + LLWString wide_text; + wide_text = utf8str_to_wstring(new_text); + + segment_vec_t segments; + S32 segment_start = old_length; + S32 segment_end = old_length + wide_text.size(); + LLStyleConstSP sp(new LLStyle(style_params)); + if (underline_on_hover_only || mSkipLinkUnderline) + { + LLStyle::Params normal_style_params(style_params); + normal_style_params.font.style("NORMAL"); + LLStyleConstSP normal_sp(new LLStyle(normal_style_params)); + segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this)); + } + else + { + segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this)); + } + + insertStringNoUndo(getLength(), wide_text, &segments); + } + + // Set the cursor and scroll position + if (selection_start != selection_end) + { + mSelectionStart = selection_start; + mSelectionEnd = selection_end; + + mIsSelecting = was_selecting; + setCursorPos(cursor_pos); + } + else if (cursor_was_at_end) + { + setCursorPos(getLength()); + } + else + { + setCursorPos(cursor_pos); + } +} + +void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) +{ + if (new_text.empty()) + { + return; + } + + std::string::size_type start = 0; + std::string::size_type pos = new_text.find("\n", start); + + while (pos != std::string::npos) + { + if (pos != start) + { + std::string str = std::string(new_text,start,pos-start); + appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); + } + appendLineBreakSegment(style_params); + start = pos+1; + pos = new_text.find("\n", start); + } + + std::string str = std::string(new_text, start, new_text.length() - start); + appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); +} + + +void LLTextBase::replaceUrl(const std::string &url, + const std::string &label, + const std::string &icon) +{ + // get the full (wide) text for the editor so we can change it + LLWString text = getWText(); + LLWString wlabel = utf8str_to_wstring(label); + bool modified = false; + S32 seg_start = 0; + + // iterate through each segment looking for ones styled as links + segment_set_t::iterator it; + for (it = mSegments.begin(); it != mSegments.end(); ++it) + { + LLTextSegment *seg = *it; + LLStyleConstSP style = seg->getStyle(); + + // update segment start/end length in case we replaced text earlier + S32 seg_length = seg->getEnd() - seg->getStart(); + seg->setStart(seg_start); + seg->setEnd(seg_start + seg_length); + + // if we find a link with our Url, then replace the label + if (style->getLinkHREF() == url) + { + S32 start = seg->getStart(); + S32 end = seg->getEnd(); + text = text.substr(0, start) + wlabel + text.substr(end, text.size() - end + 1); + seg->setEnd(start + wlabel.size()); + modified = true; + } + + // Icon might be updated when more avatar or group info + // becomes available + if (style->isImage() && style->getLinkHREF() == url) + { + LLUIImagePtr image = image_from_icon_name( icon ); + if (image) + { + LLStyle::Params icon_params; + icon_params.image = image; + LLStyleConstSP new_style(new LLStyle(icon_params)); + seg->setStyle(new_style); + modified = true; + } + } + + // work out the character offset for the next segment + seg_start = seg->getEnd(); + } + + // update the editor with the new (wide) text string + if (modified) + { + getViewModel()->setDisplay(text); + deselect(); + setCursorPos(mCursorPos); + needsReflow(); + } +} + + +void LLTextBase::setWText(const LLWString& text) +{ + setText(wstring_to_utf8str(text)); +} + +const LLWString& LLTextBase::getWText() const +{ + return getViewModel()->getDisplay(); +} + +// If round is true, if the position is on the right half of a character, the cursor +// will be put to its right. If round is false, the cursor will always be put to the +// character's left. + +S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, bool round, bool hit_past_end_of_line) const +{ + // Figure out which line we're nearest to. + LLRect doc_rect = mDocumentView->getRect(); + S32 doc_y = local_y - doc_rect.mBottom; + + // binary search for line that starts before local_y + line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), doc_y, compare_bottom()); + + if (!mLineInfoList.size() || line_iter == mLineInfoList.end()) + { + return getLength(); // past the end + } + + S32 pos = getLength(); + F32 start_x = line_iter->mRect.mLeft + doc_rect.mLeft; + + segment_set_t::iterator line_seg_iter; + S32 line_seg_offset; + for(getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset); + line_seg_iter != mSegments.end(); + ++line_seg_iter, line_seg_offset = 0) + { + const LLTextSegmentPtr segmentp = *line_seg_iter; + + S32 segment_line_start = segmentp->getStart() + line_seg_offset; + S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start; + F32 text_width; + S32 text_height; + bool newline = segmentp->getDimensionsF32(line_seg_offset, segment_line_length, text_width, text_height); + + if(newline) + { + pos = segment_line_start + segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round); + break; + } + + // if we've reached a line of text *below* the mouse cursor, doc index is first character on that line + if (hit_past_end_of_line && doc_y > line_iter->mRect.mTop) + { + pos = segment_line_start; + break; + } + if (local_x < start_x + text_width) // cursor to left of right edge of text + { + // Figure out which character we're nearest to. + S32 offset; + if (!segmentp->canEdit()) + { + F32 segment_width; + S32 segment_height; + segmentp->getDimensionsF32(0, segmentp->getEnd() - segmentp->getStart(), segment_width, segment_height); + if (round && local_x - start_x > segment_width / 2) + { + offset = segment_line_length; + } + else + { + offset = 0; + } + } + else + { + offset = segmentp->getOffset(local_x - start_x, line_seg_offset, segment_line_length, round); + } + pos = segment_line_start + offset; + break; + } + else if (hit_past_end_of_line && segmentp->getEnd() >= line_iter->mDocIndexEnd) + { + if (getLineNumFromDocIndex(line_iter->mDocIndexEnd - 1) == line_iter->mLineNum) + { + // if segment wraps to the next line we should step one char back + // to compensate for the space char between words + // which is removed due to wrapping + pos = llclamp(line_iter->mDocIndexEnd - 1, 0, getLength()); + } + else + { + pos = llclamp(line_iter->mDocIndexEnd, 0, getLength()); + } + break; + } + start_x += text_width; + } + + return pos; +} + +// returns rectangle of insertion caret +// in document coordinate frame from given index into text +LLRect LLTextBase::getDocRectFromDocIndex(S32 pos) const +{ + if (mLineInfoList.empty()) + { + return LLRect(); + } + + // clamp pos to valid values + pos = llclamp(pos, 0, mLineInfoList.back().mDocIndexEnd - 1); + + line_list_t::const_iterator line_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), pos, line_end_compare()); + + segment_set_t::iterator line_seg_iter; + S32 line_seg_offset; + segment_set_t::iterator cursor_seg_iter; + S32 cursor_seg_offset; + getSegmentAndOffset(line_iter->mDocIndexStart, &line_seg_iter, &line_seg_offset); + getSegmentAndOffset(pos, &cursor_seg_iter, &cursor_seg_offset); + + F32 doc_left_precise = line_iter->mRect.mLeft; + + while(line_seg_iter != mSegments.end()) + { + const LLTextSegmentPtr segmentp = *line_seg_iter; + + if (line_seg_iter == cursor_seg_iter) + { + // cursor advanced to right based on difference in offset of cursor to start of line + F32 segment_width; + S32 segment_height; + segmentp->getDimensionsF32(line_seg_offset, cursor_seg_offset - line_seg_offset, segment_width, segment_height); + doc_left_precise += segment_width; + + break; + } + else + { + // add remainder of current text segment to cursor position + F32 segment_width; + S32 segment_height; + segmentp->getDimensionsF32(line_seg_offset, (segmentp->getEnd() - segmentp->getStart()) - line_seg_offset, segment_width, segment_height); + doc_left_precise += segment_width; + // offset will be 0 for all segments after the first + line_seg_offset = 0; + // go to next text segment on this line + ++line_seg_iter; + } + } + + LLRect doc_rect; + doc_rect.mLeft = doc_left_precise; + doc_rect.mBottom = line_iter->mRect.mBottom; + doc_rect.mTop = line_iter->mRect.mTop; + + // set rect to 0 width + doc_rect.mRight = doc_rect.mLeft; + + return doc_rect; +} + +LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const +{ + LLRect content_window_rect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + if (mBorderVisible) + { + content_window_rect.stretch(-1); + } + + LLRect local_rect; + + if (mLineInfoList.empty()) + { + // return default height rect in upper left + local_rect = content_window_rect; + local_rect.mBottom = local_rect.mTop - mFont->getLineHeight(); + return local_rect; + } + + // get the rect in document coordinates + LLRect doc_rect = getDocRectFromDocIndex(pos); + + // compensate for scrolled, inset view of doc + LLRect scrolled_view_rect = getVisibleDocumentRect(); + local_rect = doc_rect; + local_rect.translate(content_window_rect.mLeft - scrolled_view_rect.mLeft, + content_window_rect.mBottom - scrolled_view_rect.mBottom); + + return local_rect; +} + +void LLTextBase::updateCursorXPos() +{ + // reset desired x cursor position + mDesiredXPixel = getLocalRectFromDocIndex(mCursorPos).mLeft; +} + + +void LLTextBase::startOfLine() +{ + S32 offset = getLineOffsetFromDocIndex(mCursorPos); + setCursorPos(mCursorPos - offset); +} + +void LLTextBase::endOfLine() +{ + S32 line = getLineNumFromDocIndex(mCursorPos); + S32 num_lines = getLineCount(); + if (line + 1 >= num_lines) + { + setCursorPos(getLength()); + } + else + { + setCursorPos( getLineStart(line + 1) - 1 ); + } +} + +void LLTextBase::startOfDoc() +{ + setCursorPos(0); + if (mScroller) + { + mScroller->goToTop(); + } +} + +void LLTextBase::endOfDoc() +{ + setCursorPos(getLength()); + if (mScroller) + { + mScroller->goToBottom(); + } +} + +void LLTextBase::changePage( S32 delta ) +{ + const S32 PIXEL_OVERLAP_ON_PAGE_CHANGE = 10; + if (delta == 0 || !mScroller) return; + + LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); + + if( delta == -1 ) + { + mScroller->pageUp(PIXEL_OVERLAP_ON_PAGE_CHANGE); + } + else + if( delta == 1 ) + { + mScroller->pageDown(PIXEL_OVERLAP_ON_PAGE_CHANGE); + } + + if (getLocalRectFromDocIndex(mCursorPos) == cursor_rect) + { + // cursor didn't change apparent position, so move to top or bottom of document, respectively + if (delta < 0) + { + startOfDoc(); + } + else + { + endOfDoc(); + } + } + else + { + setCursorAtLocalPos(cursor_rect.getCenterX(), cursor_rect.getCenterY(), true, false); + } +} + +// Picks a new cursor position based on the screen size of text being drawn. +void LLTextBase::setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool keep_cursor_offset ) +{ + setCursorPos(getDocIndexFromLocalCoord(local_x, local_y, round), keep_cursor_offset); +} + + +void LLTextBase::changeLine( S32 delta ) +{ + S32 line = getLineNumFromDocIndex(mCursorPos); + S32 max_line_nb = getLineCount() - 1; + max_line_nb = (max_line_nb < 0 ? 0 : max_line_nb); + + S32 new_line = llclamp(line + delta, 0, max_line_nb); + + if (new_line != line) + { + LLRect visible_region = getVisibleDocumentRect(); + S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, + mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, true); + S32 actual_line = getLineNumFromDocIndex(new_cursor_pos); + if (actual_line != new_line) + { + // line edge, correcting position by 1 to move onto proper line + new_cursor_pos += new_line - actual_line; + } + setCursorPos(new_cursor_pos, true); + } +} + +bool LLTextBase::scrolledToStart() +{ + return mScroller->isAtTop(); +} + +bool LLTextBase::scrolledToEnd() +{ + return mScroller->isAtBottom(); +} + +bool LLTextBase::setCursor(S32 row, S32 column) +{ + if (row < 0 || column < 0) return false; + + S32 n_lines = mLineInfoList.size(); + for (S32 line = row; line < n_lines; ++line) + { + const line_info& li = mLineInfoList[line]; + + if (li.mLineNum < row) + { + continue; + } + else if (li.mLineNum > row) + { + break; // invalid column specified + } + + // Found the given row. + S32 line_length = li.mDocIndexEnd - li.mDocIndexStart;; + if (column >= line_length) + { + column -= line_length; + continue; + } + + // Found the given column. + updateCursorXPos(); + S32 doc_pos = li.mDocIndexStart + column; + return setCursorPos(doc_pos); + } + + return false; // invalid row or column specified +} + + +bool LLTextBase::setCursorPos(S32 cursor_pos, bool keep_cursor_offset) +{ + S32 new_cursor_pos = cursor_pos; + if (new_cursor_pos != mCursorPos) + { + new_cursor_pos = getEditableIndex(new_cursor_pos, new_cursor_pos >= mCursorPos); + } + + mCursorPos = llclamp(new_cursor_pos, 0, (S32)getLength()); + needsScroll(); + if (!keep_cursor_offset) + updateCursorXPos(); + // did we get requested position? + return new_cursor_pos == cursor_pos; +} + +// constraint cursor to editable segments of document +S32 LLTextBase::getEditableIndex(S32 index, bool increasing_direction) +{ + segment_set_t::iterator segment_iter; + S32 offset; + getSegmentAndOffset(index, &segment_iter, &offset); + if (segment_iter == mSegments.end()) + { + return 0; + } + + LLTextSegmentPtr segmentp = *segment_iter; + + if (segmentp->canEdit()) + { + return segmentp->getStart() + offset; + } + else if (segmentp->getStart() < index && index < segmentp->getEnd()) + { + // bias towards document end + if (increasing_direction) + { + return segmentp->getEnd(); + } + // bias towards document start + else + { + return segmentp->getStart(); + } + } + else + { + return index; + } +} + +void LLTextBase::updateRects() +{ + LLRect old_text_rect = mVisibleTextRect; + mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + + if (mLineInfoList.empty()) + { + mTextBoundingRect = LLRect(0, mVPad, mHPad, 0); + } + else + { + mTextBoundingRect = mLineInfoList.begin()->mRect; + for (line_list_t::const_iterator line_iter = ++mLineInfoList.begin(); + line_iter != mLineInfoList.end(); + ++line_iter) + { + mTextBoundingRect.unionWith(line_iter->mRect); + } + + mTextBoundingRect.mTop += mVPad; + + S32 delta_pos = 0; + + switch(mVAlign) + { + case LLFontGL::TOP: + delta_pos = llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom); + break; + case LLFontGL::VCENTER: + delta_pos = (llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom) + (mVisibleTextRect.mBottom - mTextBoundingRect.mBottom)) / 2; + break; + case LLFontGL::BOTTOM: + delta_pos = mVisibleTextRect.mBottom - mTextBoundingRect.mBottom; + break; + case LLFontGL::BASELINE: + // do nothing + break; + } + // move line segments to fit new document rect + for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it) + { + it->mRect.translate(0, delta_pos); + } + mTextBoundingRect.translate(0, delta_pos); + } + + // update document container dimensions according to text contents + LLRect doc_rect; + // use old mVisibleTextRect constraint document to width of viewable region + doc_rect.mBottom = llmin(mVisibleTextRect.mBottom, mTextBoundingRect.mBottom); + doc_rect.mLeft = 0; + + // allow horizontal scrolling? + // if so, use entire width of text contents + // otherwise, stop at width of mVisibleTextRect + //FIXME: consider use of getWordWrap() instead + doc_rect.mRight = mScroller + ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) + : mVisibleTextRect.getWidth(); + doc_rect.mTop = llmax(mVisibleTextRect.mTop, mTextBoundingRect.mTop); + + if (!mScroller) + { + // push doc rect to top of text widget + switch(mVAlign) + { + case LLFontGL::TOP: + doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop); + break; + case LLFontGL::VCENTER: + doc_rect.translate(0, (mVisibleTextRect.getHeight() - doc_rect.mTop) / 2); + case LLFontGL::BOTTOM: + default: + break; + } + } + + mDocumentView->setShape(doc_rect); + + //update mVisibleTextRect *after* mDocumentView has been resized + // so that scrollbars are added if document needs to scroll + // since mVisibleTextRect does not include scrollbars + mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + //FIXME: replace border with image? + if (mBorderVisible) + { + mVisibleTextRect.stretch(-1); + } + if (mVisibleTextRect != old_text_rect) + { + needsReflow(); + } + + // update mTextBoundingRect after mVisibleTextRect took scrolls into account + if (!mLineInfoList.empty() && mScroller) + { + S32 delta_pos = 0; + + switch(mVAlign) + { + case LLFontGL::TOP: + delta_pos = llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom); + break; + case LLFontGL::VCENTER: + delta_pos = (llmax(mVisibleTextRect.getHeight() - mTextBoundingRect.mTop, -mTextBoundingRect.mBottom) + (mVisibleTextRect.mBottom - mTextBoundingRect.mBottom)) / 2; + break; + case LLFontGL::BOTTOM: + delta_pos = mVisibleTextRect.mBottom - mTextBoundingRect.mBottom; + break; + case LLFontGL::BASELINE: + // do nothing + break; + } + // move line segments to fit new visible rect + if (delta_pos != 0) + { + for (line_list_t::iterator it = mLineInfoList.begin(); it != mLineInfoList.end(); ++it) + { + it->mRect.translate(0, delta_pos); + } + mTextBoundingRect.translate(0, delta_pos); + } + } + + // update document container again, using new mVisibleTextRect (that has scrollbars enabled as needed) + doc_rect.mBottom = llmin(mVisibleTextRect.mBottom, mTextBoundingRect.mBottom); + doc_rect.mLeft = 0; + doc_rect.mRight = mScroller + ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) + : mVisibleTextRect.getWidth(); + doc_rect.mTop = llmax(mVisibleTextRect.getHeight(), mTextBoundingRect.getHeight()) + doc_rect.mBottom; + if (!mScroller) + { + // push doc rect to top of text widget + switch(mVAlign) + { + case LLFontGL::TOP: + doc_rect.translate(0, mVisibleTextRect.getHeight() - doc_rect.mTop); + break; + case LLFontGL::VCENTER: + doc_rect.translate(0, (mVisibleTextRect.getHeight() - doc_rect.mTop) / 2); + case LLFontGL::BOTTOM: + default: + break; + } + } + mDocumentView->setShape(doc_rect); +} + + +void LLTextBase::startSelection() +{ + if( !mIsSelecting ) + { + mIsSelecting = true; + mSelectionStart = mCursorPos; + mSelectionEnd = mCursorPos; + } +} + +void LLTextBase::endSelection() +{ + if( mIsSelecting ) + { + mIsSelecting = false; + mSelectionEnd = mCursorPos; + } +} + +// get portion of document that is visible in text editor +LLRect LLTextBase::getVisibleDocumentRect() const +{ + if (mScroller) + { + return mScroller->getVisibleContentRect(); + } + else if (mClip) + { + LLRect visible_text_rect = getVisibleTextRect(); + LLRect doc_rect = mDocumentView->getRect(); + visible_text_rect.translate(-doc_rect.mLeft, -doc_rect.mBottom); + + // reject partially visible lines + LLRect visible_lines_rect; + for (line_list_t::const_iterator it = mLineInfoList.begin(), end_it = mLineInfoList.end(); + it != end_it; + ++it) + { + bool line_visible = mClipPartial ? visible_text_rect.contains(it->mRect) : visible_text_rect.overlaps(it->mRect); + if (line_visible) + { + if (visible_lines_rect.isEmpty()) + { + visible_lines_rect = it->mRect; + } + else + { + visible_lines_rect.unionWith(it->mRect); + } + } + } + return visible_lines_rect; + } + else + { // entire document rect is visible + // but offset according to height of widget + + LLRect doc_rect = mDocumentView->getLocalRect(); + doc_rect.mLeft -= mDocumentView->getRect().mLeft; + // adjust for height of text above widget baseline + doc_rect.mBottom = doc_rect.getHeight() - mVisibleTextRect.getHeight(); + return doc_rect; + } +} + +boost::signals2::connection LLTextBase::setURLClickedCallback(const commit_signal_t::slot_type& cb) +{ + if (!mURLClickSignal) + { + mURLClickSignal = new commit_signal_t(); + } + return mURLClickSignal->connect(cb); +} + +boost::signals2::connection LLTextBase::setIsFriendCallback(const is_friend_signal_t::slot_type& cb) +{ + if (!mIsFriendSignal) + { + mIsFriendSignal = new is_friend_signal_t(); + } + return mIsFriendSignal->connect(cb); +} + +boost::signals2::connection LLTextBase::setIsObjectBlockedCallback(const is_blocked_signal_t::slot_type& cb) +{ + if (!mIsObjectBlockedSignal) + { + mIsObjectBlockedSignal = new is_blocked_signal_t(); + } + return mIsObjectBlockedSignal->connect(cb); +} + +// +// LLTextSegment +// + +LLTextSegment::~LLTextSegment() +{} + +bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; height = 0; return false; } +bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +{ + F32 fwidth = 0; + bool result = getDimensionsF32(first_char, num_chars, fwidth, height); + width = ll_round(fwidth); + return result; +} + +S32 LLTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { return 0; } +S32 LLTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const { return 0; } +void LLTextSegment::updateLayout(const LLTextBase& editor) {} +F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) { return draw_rect.mLeft; } +bool LLTextSegment::canEdit() const { return false; } +void LLTextSegment::unlinkFromDocument(LLTextBase*) {} +void LLTextSegment::linkToDocument(LLTextBase*) {} +const LLColor4& LLTextSegment::getColor() const { return LLColor4::white; } +//void LLTextSegment::setColor(const LLColor4 &color) {} +LLStyleConstSP LLTextSegment::getStyle() const {static LLStyleConstSP sp(new LLStyle()); return sp; } +void LLTextSegment::setStyle(LLStyleConstSP style) {} +void LLTextSegment::setToken( LLKeywordToken* token ) {} +LLKeywordToken* LLTextSegment::getToken() const { return NULL; } +void LLTextSegment::setToolTip( const std::string &msg ) {} +void LLTextSegment::dump() const {} +bool LLTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleMiddleMouseUp(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleRightMouseUp(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleDoubleClick(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleHover(S32 x, S32 y, MASK mask) { return false; } +bool LLTextSegment::handleScrollWheel(S32 x, S32 y, S32 clicks) { return false; } +bool LLTextSegment::handleScrollHWheel(S32 x, S32 y, S32 clicks) { return false; } +bool LLTextSegment::handleToolTip(S32 x, S32 y, MASK mask) { return false; } +const std::string& LLTextSegment::getName() const +{ + return LLStringUtil::null; +} +void LLTextSegment::onMouseCaptureLost() {} +void LLTextSegment::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {} +void LLTextSegment::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {} +bool LLTextSegment::hasMouseCapture() { return false; } + +// +// LLNormalTextSegment +// + +LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) +: LLTextSegment(start, end), + mStyle( style ), + mToken(NULL), + mEditor(editor) +{ + mFontHeight = mStyle->getFont()->getLineHeight(); + + LLUIImagePtr image = mStyle->getImage(); + if (image.notNull()) + { + mImageLoadedConnection = image->addLoadedCallback(boost::bind(&LLTextBase::needsReflow, &mEditor, start)); + } +} + +LLNormalTextSegment::LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible) +: LLTextSegment(start, end), + mToken(NULL), + mEditor(editor) +{ + mStyle = new LLStyle(LLStyle::Params().visible(is_visible).color(color)); + + mFontHeight = mStyle->getFont()->getLineHeight(); +} + +LLNormalTextSegment::~LLNormalTextSegment() +{ + mImageLoadedConnection.disconnect(); +} + + +F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) +{ + if( end - start > 0 ) + { + return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect); + } + return draw_rect.mLeft; +} + +// Draws a single text segment, reversing the color for selection if needed. +F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, LLRectf rect) +{ + F32 alpha = LLViewDrawContext::getCurrentContext().mAlpha; + + const LLWString &text = getWText(); + + F32 right_x = rect.mLeft; + if (!mStyle->isVisible()) + { + return right_x; + } + + const LLFontGL* font = mStyle->getFont(); + + LLColor4 color = (mEditor.getReadOnly() ? mStyle->getReadOnlyColor() : mStyle->getColor()) % alpha; + + if( selection_start > seg_start ) + { + // Draw normally + S32 start = seg_start; + S32 end = llmin( selection_start, seg_end ); + S32 length = end - start; + font->render(text, start, + rect, + color, + LLFontGL::LEFT, mEditor.mTextVAlign, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, + &right_x, + mEditor.getUseEllipses(), + mEditor.getUseColor()); + } + rect.mLeft = right_x; + + if( (selection_start < seg_end) && (selection_end > seg_start) ) + { + // Draw reversed + S32 start = llmax( selection_start, seg_start ); + S32 end = llmin( selection_end, seg_end ); + S32 length = end - start; + + font->render(text, start, + rect, + mStyle->getSelectedColor().get(), + LLFontGL::LEFT, mEditor.mTextVAlign, + LLFontGL::NORMAL, + LLFontGL::NO_SHADOW, + length, + &right_x, + mEditor.getUseEllipses(), + mEditor.getUseColor()); + } + rect.mLeft = right_x; + if( selection_end < seg_end ) + { + // Draw normally + S32 start = llmax( selection_end, seg_start ); + S32 end = seg_end; + S32 length = end - start; + font->render(text, start, + rect, + color, + LLFontGL::LEFT, mEditor.mTextVAlign, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, + &right_x, + mEditor.getUseEllipses(), + mEditor.getUseColor()); + } + return right_x; +} + +bool LLNormalTextSegment::handleHover(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + LLUI::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND); + return true; + } + } + return false; +} + +bool LLNormalTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + mEditor.createUrlContextMenu(x, y, getStyle()->getLinkHREF()); + return true; + } + } + return false; +} + +bool LLNormalTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + // eat mouse down event on hyperlinks, so we get the mouse up + return true; + } + } + + return false; +} + +bool LLNormalTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + std::string url = getStyle()->getLinkHREF(); + if (!mEditor.mForceUrlsExternal) + { + LLUrlAction::clickAction(url, mEditor.isContentTrusted()); + } + else if (!LLUrlAction::executeSLURL(url, mEditor.isContentTrusted())) + { + LLUrlAction::openURLExternal(url); + } + return true; + } + } + + return false; +} + +bool LLNormalTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + std::string msg; + // do we have a tooltip for a loaded keyword (for script editor)? + if (mToken && !mToken->getToolTip().empty()) + { + const LLWString& wmsg = mToken->getToolTip(); + LLToolTipMgr::instance().show(wstring_to_utf8str(wmsg), (mToken->getType() == LLKeywordToken::TT_FUNCTION)); + return true; + } + // or do we have an explicitly set tooltip (e.g., for Urls) + if (!mTooltip.empty()) + { + LLToolTipMgr::instance().show(mTooltip); + return true; + } + + return false; +} + +void LLNormalTextSegment::setToolTip(const std::string& tooltip) +{ + // we cannot replace a keyword tooltip that's loaded from a file + if (mToken) + { + LL_WARNS() << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << LL_ENDL; + return; + } + mTooltip = tooltip; +} + +bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +{ + height = 0; + width = 0; + if (num_chars > 0) + { + height = mFontHeight; + const LLWString &text = getWText(); + // if last character is a newline, then return true, forcing line break + width = mStyle->getFont()->getWidthF32(text.c_str(), mStart + first_char, num_chars, true); + } + return false; +} + +S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const +{ + const LLWString &text = getWText(); + return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset, + (F32)segment_local_x_coord, + F32_MAX, + num_chars, + round); +} + +S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const +{ + const LLWString &text = getWText(); + + LLUIImagePtr image = mStyle->getImage(); + if( image.notNull()) + { + num_pixels = llmax(0, num_pixels - image->getWidth()); + } + + S32 last_char = mEnd; + + // set max characters to length of segment, or to first newline + max_chars = llmin(max_chars, last_char - (mStart + segment_offset)); + + // if no character yet displayed on this line, don't require word wrapping since + // we can just move to the next line, otherwise insist on it so we make forward progress + LLFontGL::EWordWrapStyle word_wrap_style = (line_offset == 0) + ? LLFontGL::WORD_BOUNDARY_IF_POSSIBLE + : LLFontGL::ONLY_WORD_BOUNDARIES; + + + S32 offsetLength = text.length() - (segment_offset + mStart); + + if(getLength() < segment_offset + mStart) + { + LL_INFOS() << "getLength() < segment_offset + mStart\t getLength()\t" << getLength() << "\tsegment_offset:\t" + << segment_offset << "\tmStart:\t" << mStart << "\tsegments\t" << mEditor.mSegments.size() << "\tmax_chars\t" << max_chars << LL_ENDL; + } + + if( (offsetLength + 1) < max_chars) + { + LL_INFOS() << "offsetString.length() + 1 < max_chars\t max_chars:\t" << max_chars << "\toffsetString.length():\t" << offsetLength << " getLength() : " + << getLength() << "\tsegment_offset:\t" << segment_offset << "\tmStart:\t" << mStart << "\tsegments\t" << mEditor.mSegments.size() << LL_ENDL; + } + + S32 num_chars = mStyle->getFont()->maxDrawableChars( text.c_str() + (segment_offset + mStart), + (F32)num_pixels, + max_chars, + word_wrap_style); + + if (num_chars == 0 + && line_offset == 0 + && max_chars > 0) + { + // If at the beginning of a line, and a single character won't fit, draw it anyway + num_chars = 1; + } + + // include *either* the EOF or newline character in this run of text + // but not both + S32 last_char_in_run = mStart + segment_offset + num_chars; + // check length first to avoid indexing off end of string + if (last_char_in_run < mEnd + && (last_char_in_run >= getLength())) + { + num_chars++; + } + return num_chars; +} + +void LLNormalTextSegment::dump() const +{ + LL_INFOS() << "Segment [" << +// mColor.mV[VX] << ", " << +// mColor.mV[VY] << ", " << +// mColor.mV[VZ] << "]\t[" << + mStart << ", " << + getEnd() << "]" << + LL_ENDL; +} + +/*virtual*/ +const LLWString& LLNormalTextSegment::getWText() const +{ + return mEditor.getWText(); +} + +/*virtual*/ +const S32 LLNormalTextSegment::getLength() const +{ + return mEditor.getLength(); +} + +LLLabelTextSegment::LLLabelTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) +: LLNormalTextSegment(style, start, end, editor) +{ +} + +LLLabelTextSegment::LLLabelTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible) +: LLNormalTextSegment(color, start, end, editor, is_visible) +{ +} + +/*virtual*/ +const LLWString& LLLabelTextSegment::getWText() const +{ + return mEditor.getWlabel(); +} +/*virtual*/ +const S32 LLLabelTextSegment::getLength() const +{ + return mEditor.getWlabel().length(); +} + +// +// LLEmojiTextSegment +// +LLEmojiTextSegment::LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor) + : LLNormalTextSegment(style, start, end, editor) +{ +} + +LLEmojiTextSegment::LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible) + : LLNormalTextSegment(color, start, end, editor, is_visible) +{ +} + +bool LLEmojiTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (mTooltip.empty()) + { + LLWString emoji = getWText().substr(getStart(), getEnd() - getStart()); + if (!emoji.empty()) + { + mTooltip = LLEmojiHelper::instance().getToolTip(emoji[0]); + } + } + + return LLNormalTextSegment::handleToolTip(x, y, mask); +} + +// +// LLOnHoverChangeableTextSegment +// + +LLOnHoverChangeableTextSegment::LLOnHoverChangeableTextSegment( LLStyleConstSP style, LLStyleConstSP normal_style, S32 start, S32 end, LLTextBase& editor ): + LLNormalTextSegment(normal_style, start, end, editor), + mHoveredStyle(style), + mNormalStyle(normal_style){} + +/*virtual*/ +F32 LLOnHoverChangeableTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) +{ + F32 result = LLNormalTextSegment::draw(start, end, selection_start, selection_end, draw_rect); + if (end == mEnd - mStart) + { + mStyle = mNormalStyle; + } + return result; +} + +/*virtual*/ +bool LLOnHoverChangeableTextSegment::handleHover(S32 x, S32 y, MASK mask) +{ + mStyle = mEditor.getSkipLinkUnderline() ? mNormalStyle : mHoveredStyle; + return LLNormalTextSegment::handleHover(x, y, mask); +} + + +// +// LLInlineViewSegment +// + +LLInlineViewSegment::LLInlineViewSegment(const Params& p, S32 start, S32 end) +: LLTextSegment(start, end), + mView(p.view), + mForceNewLine(p.force_newline), + mLeftPad(p.left_pad), + mRightPad(p.right_pad), + mTopPad(p.top_pad), + mBottomPad(p.bottom_pad) +{ +} + +LLInlineViewSegment::~LLInlineViewSegment() +{ + mView->die(); +} + +bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +{ + if (first_char == 0 && num_chars == 0) + { + // We didn't fit on a line or were forced to new string + // the widget will fall on the next line, so width here is 0 + width = 0; + + if (mForceNewLine) + { + // Chat, string can't be smaller then font height even if it is empty + LLStyleSP s(new LLStyle(LLStyle::Params().visible(true))); + height = s->getFont()->getLineHeight(); + + return true; // new line + } + else + { + // height from previous segment in same string will be used, word-wrap + height = 0; + } + + } + else + { + width = mLeftPad + mRightPad + mView->getRect().getWidth(); + height = mBottomPad + mTopPad + mView->getRect().getHeight(); + } + + return false; +} + +S32 LLInlineViewSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const +{ + // if putting a widget anywhere but at the beginning of a line + // and the widget doesn't fit or mForceNewLine is true + // then return 0 chars for that line, and all characters for the next + if (mForceNewLine && line_ind == 0) + { + return 0; + } + else if (line_offset != 0 && num_pixels < mView->getRect().getWidth()) + { + return 0; + } + else + { + return mEnd - mStart; + } +} + +void LLInlineViewSegment::updateLayout(const LLTextBase& editor) +{ + LLRect start_rect = editor.getDocRectFromDocIndex(mStart); + mView->setOrigin(start_rect.mLeft + mLeftPad, start_rect.mBottom + mBottomPad); +} + +F32 LLInlineViewSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) +{ + // return padded width of widget + // widget is actually drawn during mDocumentView's draw() + return (F32)(draw_rect.mLeft + mView->getRect().getWidth() + mLeftPad + mRightPad); +} + +void LLInlineViewSegment::unlinkFromDocument(LLTextBase* editor) +{ + editor->removeDocumentChild(mView); +} + +void LLInlineViewSegment::linkToDocument(LLTextBase* editor) +{ + editor->addDocumentChild(mView); +} + +LLLineBreakTextSegment::LLLineBreakTextSegment(S32 pos):LLTextSegment(pos,pos+1) +{ + LLStyleSP s( new LLStyle(LLStyle::Params().visible(true))); + + mFontHeight = s->getFont()->getLineHeight(); +} +LLLineBreakTextSegment::LLLineBreakTextSegment(LLStyleConstSP style,S32 pos):LLTextSegment(pos,pos+1) +{ + mFontHeight = style->getFont()->getLineHeight(); +} +LLLineBreakTextSegment::~LLLineBreakTextSegment() +{ +} +bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +{ + width = 0; + height = mFontHeight; + + return true; +} +S32 LLLineBreakTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const +{ + return 1; +} +F32 LLLineBreakTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) +{ + return draw_rect.mLeft; +} + +LLImageTextSegment::LLImageTextSegment(LLStyleConstSP style,S32 pos,class LLTextBase& editor) +: LLTextSegment(pos,pos+1), + mStyle( style ), + mEditor(editor) +{ +} + +LLImageTextSegment::~LLImageTextSegment() +{ +} + +static const S32 IMAGE_HPAD = 3; + +bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const +{ + width = 0; + height = mStyle->getFont()->getLineHeight(); + + LLUIImagePtr image = mStyle->getImage(); + if( num_chars>0 && image.notNull()) + { + width += image->getWidth() + IMAGE_HPAD; + height = llmax(height, image->getHeight() + IMAGE_HPAD ); + } + return false; +} + +S32 LLImageTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const +{ + LLUIImagePtr image = mStyle->getImage(); + + if (image.isNull()) + { + return 1; + } + + S32 image_width = image->getWidth(); + if(line_offset == 0 || num_pixels>image_width + IMAGE_HPAD) + { + return 1; + } + + return 0; +} + +bool LLImageTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (!mTooltip.empty()) + { + LLToolTipMgr::instance().show(mTooltip); + return true; + } + + return false; +} + +void LLImageTextSegment::setToolTip(const std::string& tooltip) +{ + mTooltip = tooltip; +} + +F32 LLImageTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) +{ + if ( (start >= 0) && (end <= mEnd - mStart)) + { + LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha; + LLUIImagePtr image = mStyle->getImage(); + if (image.notNull()) + { + S32 style_image_height = image->getHeight(); + S32 style_image_width = image->getWidth(); + // Text is drawn from the top of the draw_rect downward + + S32 text_center = draw_rect.mTop - (draw_rect.getHeight() / 2); + // Align image to center of draw rect + S32 image_bottom = text_center - (style_image_height / 2); + image->draw(draw_rect.mLeft, image_bottom, + style_image_width, style_image_height, color); + + const S32 IMAGE_HPAD = 3; + return draw_rect.mLeft + style_image_width + IMAGE_HPAD; + } + } + return 0.0; +} + +void LLTextBase::setWordWrap(bool wrap) +{ + mWordWrap = wrap; +} diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index d8e9027bae..f6c7ce6e81 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -1,758 +1,758 @@ -/** - * @file lltextbase.h - * @author Martin Reddy - * @brief The base class of text box/editor, providing Url handling support - * - * $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$ - */ - -#ifndef LL_LLTEXTBASE_H -#define LL_LLTEXTBASE_H - -#include "v4color.h" -#include "lleditmenuhandler.h" -#include "llspellcheckmenuhandler.h" -#include "llstyle.h" -#include "llkeywords.h" -#include "llpanel.h" - -#include -#include -#include - -#include - -class LLScrollContainer; -class LLContextMenu; -class LLUrlMatch; - -/// -/// A text segment is used to specify a subsection of a text string -/// that should be formatted differently, such as a hyperlink. It -/// includes a start/end offset from the start of the string, a -/// style to render with, an optional tooltip, etc. -/// -class LLTextSegment -: public LLRefCount, - public LLMouseHandler -{ -public: - LLTextSegment(S32 start, S32 end) - : mStart(start), - mEnd(end) - {} - virtual ~LLTextSegment(); - bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const; - - virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; - virtual S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; - - /** - * Get number of chars that fit into free part of current line. - * - * @param num_pixels - maximum width of rect - * @param segment_offset - symbol in segment we start processing line from - * @param line_offset - symbol in line after which segment starts - * @param max_chars - limit of symbols that will fit in current line - * @param line_ind - index of not word-wrapped string inside segment for multi-line segments. - * Two string separated by word-wrap will have same index. - * @return number of chars that will fit into current line - */ - virtual S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; - virtual void updateLayout(const class LLTextBase& editor); - virtual F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); - virtual bool canEdit() const; - virtual void unlinkFromDocument(class LLTextBase* editor); - virtual void linkToDocument(class LLTextBase* editor); - - virtual const LLColor4& getColor() const; - //virtual void setColor(const LLColor4 &color); - virtual LLStyleConstSP getStyle() const; - virtual void setStyle(LLStyleConstSP style); - virtual void setToken( LLKeywordToken* token ); - virtual LLKeywordToken* getToken() const; - virtual void setToolTip(const std::string& tooltip); - virtual void dump() const; - - // LLMouseHandler interface - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMiddleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ bool handleScrollHWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - /*virtual*/ const std::string& getName() const; - /*virtual*/ void onMouseCaptureLost(); - /*virtual*/ void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const; - /*virtual*/ void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const; - /*virtual*/ bool hasMouseCapture(); - - S32 getStart() const { return mStart; } - void setStart(S32 start) { mStart = start; } - S32 getEnd() const { return mEnd; } - void setEnd( S32 end ) { mEnd = end; } - -protected: - S32 mStart; - S32 mEnd; -}; - -class LLNormalTextSegment : public LLTextSegment -{ -public: - LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ); - LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible = true); - virtual ~LLNormalTextSegment(); - - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; - /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; - /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; - /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); - /*virtual*/ bool canEdit() const { return true; } - /*virtual*/ const LLColor4& getColor() const { return mStyle->getColor(); } - /*virtual*/ LLStyleConstSP getStyle() const { return mStyle; } - /*virtual*/ void setStyle(LLStyleConstSP style) { mStyle = style; } - /*virtual*/ void setToken( LLKeywordToken* token ) { mToken = token; } - /*virtual*/ LLKeywordToken* getToken() const { return mToken; } - /*virtual*/ bool getToolTip( std::string& msg ) const; - /*virtual*/ void setToolTip(const std::string& tooltip); - /*virtual*/ void dump() const; - - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - -protected: - F32 drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, LLRectf rect); - - virtual const LLWString& getWText() const; - virtual const S32 getLength() const; - -protected: - class LLTextBase& mEditor; - LLStyleConstSP mStyle; - S32 mFontHeight; - LLKeywordToken* mToken; - std::string mTooltip; - boost::signals2::connection mImageLoadedConnection; -}; - -// This text segment is the same as LLNormalTextSegment, the only difference -// is that LLNormalTextSegment draws value of LLTextBase (LLTextBase::getWText()), -// but LLLabelTextSegment draws label of the LLTextBase (LLTextBase::mLabel) -class LLLabelTextSegment : public LLNormalTextSegment -{ -public: - LLLabelTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ); - LLLabelTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible = true); - -protected: - - /*virtual*/ const LLWString& getWText() const; - /*virtual*/ const S32 getLength() const; -}; - -// Text segment that represents a single emoji character that has a different style (=font size) than the rest of -// the document it belongs to -class LLEmojiTextSegment : public LLNormalTextSegment -{ -public: - LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor); - LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible = true); - - bool canEdit() const override { return false; } - bool handleToolTip(S32 x, S32 y, MASK mask) override; -}; - -// Text segment that changes it's style depending of mouse pointer position ( is it inside or outside segment) -class LLOnHoverChangeableTextSegment : public LLNormalTextSegment -{ -public: - LLOnHoverChangeableTextSegment( LLStyleConstSP style, LLStyleConstSP normal_style, S32 start, S32 end, LLTextBase& editor ); - /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); -protected: - // Style used for text when mouse pointer is over segment - LLStyleConstSP mHoveredStyle; - // Style used for text when mouse pointer is outside segment - LLStyleConstSP mNormalStyle; - -}; - -class LLIndexSegment : public LLTextSegment -{ -public: - LLIndexSegment() : LLTextSegment(0, 0) {} -}; - -class LLInlineViewSegment : public LLTextSegment -{ -public: - struct Params : public LLInitParam::Block - { - Mandatory view; - Optional force_newline; - Optional left_pad, - right_pad, - bottom_pad, - top_pad; - }; - - LLInlineViewSegment(const Params& p, S32 start, S32 end); - ~LLInlineViewSegment(); - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; - /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; - /*virtual*/ void updateLayout(const class LLTextBase& editor); - /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); - /*virtual*/ bool canEdit() const { return false; } - /*virtual*/ void unlinkFromDocument(class LLTextBase* editor); - /*virtual*/ void linkToDocument(class LLTextBase* editor); - -private: - S32 mLeftPad; - S32 mRightPad; - S32 mTopPad; - S32 mBottomPad; - LLView* mView; - bool mForceNewLine; -}; - -class LLLineBreakTextSegment : public LLTextSegment -{ -public: - - LLLineBreakTextSegment(LLStyleConstSP style,S32 pos); - LLLineBreakTextSegment(S32 pos); - ~LLLineBreakTextSegment(); - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; - S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; - F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); - -private: - S32 mFontHeight; -}; - -class LLImageTextSegment : public LLTextSegment -{ -public: - LLImageTextSegment(LLStyleConstSP style,S32 pos,class LLTextBase& editor); - ~LLImageTextSegment(); - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; - S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 char_offset, S32 max_chars, S32 line_ind) const; - F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); - - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - /*virtual*/ void setToolTip(const std::string& tooltip); - -private: - class LLTextBase& mEditor; - LLStyleConstSP mStyle; - -protected: - std::string mTooltip; -}; - -typedef LLPointer LLTextSegmentPtr; - -/// -/// The LLTextBase class provides a base class for all text fields, such -/// as LLTextEditor and LLTextBox. It implements shared functionality -/// such as Url highlighting and opening. -/// -class LLTextBase -: public LLUICtrl, - protected LLEditMenuHandler, - public LLSpellCheckMenuHandler, - public ll::ui::SearchableControl -{ -public: - friend class LLTextSegment; - friend class LLNormalTextSegment; - friend class LLUICtrlFactory; - - typedef boost::signals2::signal is_friend_signal_t; - typedef boost::signals2::signal is_blocked_signal_t; - - struct LineSpacingParams : public LLInitParam::ChoiceBlock - { - Alternative multiple; - Alternative pixels; - LineSpacingParams(); - }; - - struct Params : public LLInitParam::Block - { - Optional cursor_color, - text_color, - text_readonly_color, - text_tentative_color, - bg_readonly_color, - bg_writeable_color, - bg_focus_color, - text_selected_color, - bg_selected_color; - - Optional bg_visible, - border_visible, - track_end, - read_only, - skip_link_underline, - spellcheck, - allow_scroll, - plain_text, - wrap, - use_ellipses, - use_emoji, - use_color, - parse_urls, - force_urls_external, - parse_highlights, - clip, - clip_partial, - trusted_content, - always_show_icons; - - Optional v_pad, - h_pad; - - - Optional - line_spacing; - - Optional max_text_length; - - Optional font_shadow; - - Optional text_valign; - - Params(); - }; - - // LLMouseHandler interface - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleMiddleMouseUp(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks) override; - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask) override; - - // LLView interface - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true) override; - /*virtual*/ void draw() override; - - // LLUICtrl interface - /*virtual*/ bool acceptsTextInput() const override { return !mReadOnly; } - /*virtual*/ void setColor(const LLColor4& c) override; - virtual void setReadOnlyColor(const LLColor4 &c); - /*virtual*/ void onVisibilityChange(bool new_visibility) override; - - /*virtual*/ void setValue(const LLSD& value) override; - /*virtual*/ LLTextViewModel* getViewModel() const override; - - // LLEditMenuHandler interface - /*virtual*/ bool canDeselect() const override; - /*virtual*/ void deselect() override; - - virtual void onFocusReceived() override; - virtual void onFocusLost() override; - - void setParseHTML(bool parse_html) { mParseHTML = parse_html; } - - // LLSpellCheckMenuHandler overrides - /*virtual*/ bool getSpellCheck() const override; - - /*virtual*/ const std::string& getSuggestion(U32 index) const override; - /*virtual*/ U32 getSuggestionCount() const override; - /*virtual*/ void replaceWithSuggestion(U32 index) override; - - /*virtual*/ void addToDictionary() override; - /*virtual*/ bool canAddToDictionary() const override; - - /*virtual*/ void addToIgnore() override; - /*virtual*/ bool canAddToIgnore() const override; - - // Spell checking helper functions - std::string getMisspelledWord(U32 pos) const; - bool isMisspelledWord(U32 pos) const; - void onSpellCheckSettingsChange(); - virtual void onSpellCheckPerformed(){} - - // used by LLTextSegment layout code - bool getWordWrap() const { return mWordWrap; } - bool getUseEllipses() const { return mUseEllipses; } - bool getUseEmoji() const { return mUseEmoji; } - void setUseEmoji(bool value) { mUseEmoji = value; } - bool getUseColor() const { return mUseColor; } - void setUseColor(bool value) { mUseColor = value; } - bool truncate(); // returns true of truncation occurred - - bool isContentTrusted() const { return mTrustedContent; } - void setContentTrusted(bool trusted_content) { mTrustedContent = trusted_content; } - - // TODO: move into LLTextSegment? - void createUrlContextMenu(S32 x, S32 y, const std::string &url); // create a popup context menu for the given Url - - // Text accessors - // TODO: add optional style parameter - virtual void setText(const LLStringExplicit &utf8str , const LLStyle::Params& input_params = LLStyle::Params()); // uses default style - /*virtual*/ const std::string& getText() const override; - void setMaxTextLength(S32 length) { mMaxTextByteLength = length; } - S32 getMaxTextLength() { return mMaxTextByteLength; } - - // wide-char versions - void setWText(const LLWString& text); - const LLWString& getWText() const; - - void appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params = LLStyle::Params()); - - void setLabel(const LLStringExplicit& label); - /*virtual*/ bool setLabelArg(const std::string& key, const LLStringExplicit& text) override; - - const std::string& getLabel() { return mLabel.getString(); } - const LLWString& getWlabel() { return mLabel.getWString();} - - void setLastSegmentToolTip(const std::string &tooltip); - - /** - * If label is set, draws text label (which is LLLabelTextSegment) - * that is visible when no user text provided - */ - void resetLabel(); - - void setFont(const LLFontGL* font); - - // force reflow of text - void needsReflow(S32 index = 0); - - S32 getLength() const { return getWText().length(); } - S32 getLineCount() const { return mLineInfoList.size(); } - S32 removeFirstLine(); // returns removed length - - void addDocumentChild(LLView* view); - void removeDocumentChild(LLView* view); - const LLView* getDocumentView() const { return mDocumentView; } - LLRect getVisibleTextRect() const { return mVisibleTextRect; } - LLRect getTextBoundingRect(); - LLRect getVisibleDocumentRect() const; - - S32 getVPad() { return mVPad; } - S32 getHPad() { return mHPad; } - F32 getLineSpacingMult() { return mLineSpacingMult; } - S32 getLineSpacingPixels() { return mLineSpacingPixels; } // only for multiline - - S32 getDocIndexFromLocalCoord( S32 local_x, S32 local_y, bool round, bool hit_past_end_of_line = true) const; - LLRect getLocalRectFromDocIndex(S32 pos) const; - LLRect getDocRectFromDocIndex(S32 pos) const; - - void setReadOnly(bool read_only) { mReadOnly = read_only; } - bool getReadOnly() { return mReadOnly; } - - void setSkipLinkUnderline(bool skip_link_underline) { mSkipLinkUnderline = skip_link_underline; } - bool getSkipLinkUnderline() { return mSkipLinkUnderline; } - - void setParseURLs(bool parse_urls) { mParseHTML = parse_urls; } - - void setPlainText(bool value) { mPlainText = value;} - bool getPlainText() const { return mPlainText; } - - // cursor manipulation - bool setCursor(S32 row, S32 column); - bool setCursorPos(S32 cursor_pos, bool keep_cursor_offset = false); - void startOfLine(); - void endOfLine(); - void startOfDoc(); - void endOfDoc(); - void changePage( S32 delta ); - void changeLine( S32 delta ); - - bool scrolledToStart(); - bool scrolledToEnd(); - - const LLFontGL* getFont() const override { return mFont; } - - virtual void appendLineBreakSegment(const LLStyle::Params& style_params); - virtual void appendImageSegment(const LLStyle::Params& style_params); - virtual void appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo); - boost::signals2::connection setURLClickedCallback(const commit_signal_t::slot_type& cb); - boost::signals2::connection setIsFriendCallback(const is_friend_signal_t::slot_type& cb); - boost::signals2::connection setIsObjectBlockedCallback(const is_blocked_signal_t::slot_type& cb); - - void setWordWrap(bool wrap); - LLScrollContainer* getScrollContainer() const { return mScroller; } - -protected: - // protected member variables - // List of offsets and segment index of the start of each line. Always has at least one node (0). - struct line_info - { - line_info(S32 index_start, S32 index_end, LLRect rect, S32 line_num); - S32 mDocIndexStart; - S32 mDocIndexEnd; - LLRect mRect; - S32 mLineNum; // actual line count (ignoring soft newlines due to word wrap) - }; - typedef std::vector line_list_t; - - // helper structs - struct compare_bottom - { - bool operator()(const S32& a, const line_info& b) const; - bool operator()(const line_info& a, const S32& b) const; - bool operator()(const line_info& a, const line_info& b) const; - }; - struct compare_top - { - bool operator()(const S32& a, const line_info& b) const; - bool operator()(const line_info& a, const S32& b) const; - bool operator()(const line_info& a, const line_info& b) const; - }; - struct line_end_compare; - typedef std::vector segment_vec_t; - - // Abstract inner base class representing an undoable editor command. - // Concrete sub-classes can be defined for operations such as insert, remove, etc. - // Used as arguments to the execute() method below. - class TextCmd - { - public: - TextCmd( S32 pos, bool group_with_next, LLTextSegmentPtr segment = LLTextSegmentPtr() ) - : mPos(pos), - mGroupWithNext(group_with_next) - { - if (segment.notNull()) - { - mSegments.push_back(segment); - } - } - virtual ~TextCmd() {} - virtual bool execute(LLTextBase* editor, S32* delta) = 0; - virtual S32 undo(LLTextBase* editor) = 0; - virtual S32 redo(LLTextBase* editor) = 0; - virtual bool canExtend(S32 pos) const { return false; } - virtual void blockExtensions() {} - virtual bool extendAndExecute( LLTextBase* editor, S32 pos, llwchar c, S32* delta ) { llassert(0); return 0; } - virtual bool hasExtCharValue( llwchar value ) const { return false; } - - // Defined here so they can access protected LLTextEditor editing methods - S32 insert(LLTextBase* editor, S32 pos, const LLWString &wstr) { return editor->insertStringNoUndo( pos, wstr, &mSegments ); } - S32 remove(LLTextBase* editor, S32 pos, S32 length) { return editor->removeStringNoUndo( pos, length ); } - S32 overwrite(LLTextBase* editor, S32 pos, llwchar wc) { return editor->overwriteCharNoUndo(pos, wc); } - - S32 getPosition() const { return mPos; } - bool groupWithNext() const { return mGroupWithNext; } - - protected: - const S32 mPos; - bool mGroupWithNext; - segment_vec_t mSegments; - }; - - struct compare_segment_end - { - bool operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const; - }; - typedef std::multiset segment_set_t; - - // member functions - LLTextBase(const Params &p); - virtual ~LLTextBase(); - void initFromParams(const Params& p); - virtual void beforeValueChange(); - virtual void onValueChange(S32 start, S32 end); - virtual bool useLabel() const; - - // draw methods - virtual void drawSelectionBackground(); // draws the black box behind the selected text - void drawCursor(); - void drawText(); - - // modify contents - S32 insertStringNoUndo(S32 pos, const LLWString &wstr, segment_vec_t* segments = NULL); // returns num of chars actually inserted - S32 removeStringNoUndo(S32 pos, S32 length); - S32 overwriteCharNoUndo(S32 pos, llwchar wc); - void appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& stylep, bool underline_on_hover_only = false); - - - // manage segments - void getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const; - void getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp ); - LLTextSegmentPtr getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line = true); - segment_set_t::iterator getEditableSegIterContaining(S32 index); - segment_set_t::const_iterator getEditableSegIterContaining(S32 index) const; - segment_set_t::iterator getSegIterContaining(S32 index); - segment_set_t::const_iterator getSegIterContaining(S32 index) const; - void clearSegments(); - void createDefaultSegment(); - virtual void updateSegments(); - void insertSegment(LLTextSegmentPtr segment_to_insert); - const LLStyle::Params& getStyleParams(); - - // manage lines - S32 getLineStart( S32 line ) const; - S32 getLineEnd( S32 line ) const; - S32 getLineNumFromDocIndex( S32 doc_index, bool include_wordwrap = true) const; - S32 getLineOffsetFromDocIndex( S32 doc_index, bool include_wordwrap = true) const; - S32 getFirstVisibleLine() const; - std::pair getVisibleLines(bool fully_visible = false); - S32 getLeftOffset(S32 width); - void reflow(); - - // cursor - void updateCursorXPos(); - void setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool keep_cursor_offset=false ); - S32 getEditableIndex(S32 index, bool increasing_direction); // constraint cursor to editable segments of document - void resetCursorBlink() { mCursorBlinkTimer.reset(); } - void updateScrollFromCursor(); - - // text selection - bool hasSelection() const { return (mSelectionStart !=mSelectionEnd); } - void startSelection(); - void endSelection(); - - // misc - void updateRects(); - void needsScroll() { mScrollNeeded = true; } - - struct URLLabelCallback; - // Replace a URL with a new icon and label, for example, when - // avatar names are looked up. - void replaceUrl(const std::string &url, const std::string &label, const std::string& icon); - - void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params()); - void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only = false); - S32 normalizeUri(std::string& uri); - -protected: - // virtual - std::string _getSearchText() const override - { - return mLabel.getString() + getToolTip(); - } - - std::vector getSelectionRects(); - -protected: - // text segmentation and flow - segment_set_t mSegments; - line_list_t mLineInfoList; - LLRect mVisibleTextRect; // The rect in which text is drawn. Excludes borders. - LLRect mTextBoundingRect; - - // default text style - LLStyle::Params mStyle; - bool mStyleDirty; - const LLFontGL* mFont; - const LLFontGL::ShadowType mFontShadow; - - // colors - LLUIColor mCursorColor; - LLUIColor mFgColor; - LLUIColor mReadOnlyFgColor; - LLUIColor mTentativeFgColor; - LLUIColor mWriteableBgColor; - LLUIColor mReadOnlyBgColor; - LLUIColor mFocusBgColor; - LLUIColor mTextSelectedColor; - LLUIColor mSelectedBGColor; - - // cursor - S32 mCursorPos; // I-beam is just after the mCursorPos-th character. - S32 mDesiredXPixel; // X pixel position where the user wants the cursor to be - LLFrameTimer mCursorBlinkTimer; // timer that controls cursor blinking - - // selection - S32 mSelectionStart; - S32 mSelectionEnd; - LLTimer mTripleClickTimer; - - bool mIsSelecting; // Are we in the middle of a drag-select? - - // spell checking - bool mSpellCheck; - S32 mSpellCheckStart; - S32 mSpellCheckEnd; - LLTimer mSpellCheckTimer; - std::list > mMisspellRanges; - std::vector mSuggestionList; - - // configuration - S32 mHPad; // padding on left of text - S32 mVPad; // padding above text - LLFontGL::HAlign mHAlign; // horizontal alignment of the document in its entirety - LLFontGL::VAlign mVAlign; // vertical alignment of the document in its entirety - LLFontGL::VAlign mTextVAlign; // vertical alignment of a text segment within a single line of text - F32 mLineSpacingMult; // multiple of line height used as space for a single line of text (e.g. 1.5 to get 50% padding) - S32 mLineSpacingPixels; // padding between lines - bool mBorderVisible; - bool mParseHTML; // make URLs interactive - bool mForceUrlsExternal; // URLs from this textbox will be opened in external browser - bool mParseHighlights; // highlight user-defined keywords - bool mWordWrap; - bool mUseEllipses; - bool mUseEmoji; - bool mUseColor; - bool mTrackEnd; // if true, keeps scroll position at end of document during resize - bool mReadOnly; - bool mBGVisible; // render background? - bool mClip; // clip text to widget rect - bool mClipPartial; // false if we show lines that are partially inside bounding rect - bool mTrustedContent; // if false, does not allow to execute SURL links from this editor - bool mPlainText; // didn't use Image or Icon segments - bool mAutoIndent; - S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes - bool mSkipTripleClick; - bool mAlwaysShowIcons; - - bool mSkipLinkUnderline; - - // support widgets - LLHandle mPopupMenuHandle; - LLView* mDocumentView; - LLScrollContainer* mScroller; - - // transient state - S32 mReflowIndex; // index at which to start reflow. S32_MAX indicates no reflow needed. - bool mScrollNeeded; // need to change scroll region because of change to cursor position - S32 mScrollIndex; // index of first character to keep visible in scroll region - - // Fired when a URL link is clicked - commit_signal_t* mURLClickSignal; - - // Used to check if user with given ID is avatar's friend - is_friend_signal_t* mIsFriendSignal; - is_blocked_signal_t* mIsObjectBlockedSignal; - - LLUIString mLabel; // text label that is visible when no user text provided -}; - -#endif +/** + * @file lltextbase.h + * @author Martin Reddy + * @brief The base class of text box/editor, providing Url handling support + * + * $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$ + */ + +#ifndef LL_LLTEXTBASE_H +#define LL_LLTEXTBASE_H + +#include "v4color.h" +#include "lleditmenuhandler.h" +#include "llspellcheckmenuhandler.h" +#include "llstyle.h" +#include "llkeywords.h" +#include "llpanel.h" + +#include +#include +#include + +#include + +class LLScrollContainer; +class LLContextMenu; +class LLUrlMatch; + +/// +/// A text segment is used to specify a subsection of a text string +/// that should be formatted differently, such as a hyperlink. It +/// includes a start/end offset from the start of the string, a +/// style to render with, an optional tooltip, etc. +/// +class LLTextSegment +: public LLRefCount, + public LLMouseHandler +{ +public: + LLTextSegment(S32 start, S32 end) + : mStart(start), + mEnd(end) + {} + virtual ~LLTextSegment(); + bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const; + + virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + virtual S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; + + /** + * Get number of chars that fit into free part of current line. + * + * @param num_pixels - maximum width of rect + * @param segment_offset - symbol in segment we start processing line from + * @param line_offset - symbol in line after which segment starts + * @param max_chars - limit of symbols that will fit in current line + * @param line_ind - index of not word-wrapped string inside segment for multi-line segments. + * Two string separated by word-wrap will have same index. + * @return number of chars that will fit into current line + */ + virtual S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; + virtual void updateLayout(const class LLTextBase& editor); + virtual F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); + virtual bool canEdit() const; + virtual void unlinkFromDocument(class LLTextBase* editor); + virtual void linkToDocument(class LLTextBase* editor); + + virtual const LLColor4& getColor() const; + //virtual void setColor(const LLColor4 &color); + virtual LLStyleConstSP getStyle() const; + virtual void setStyle(LLStyleConstSP style); + virtual void setToken( LLKeywordToken* token ); + virtual LLKeywordToken* getToken() const; + virtual void setToolTip(const std::string& tooltip); + virtual void dump() const; + + // LLMouseHandler interface + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMiddleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ bool handleScrollHWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + /*virtual*/ const std::string& getName() const; + /*virtual*/ void onMouseCaptureLost(); + /*virtual*/ void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const; + /*virtual*/ void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const; + /*virtual*/ bool hasMouseCapture(); + + S32 getStart() const { return mStart; } + void setStart(S32 start) { mStart = start; } + S32 getEnd() const { return mEnd; } + void setEnd( S32 end ) { mEnd = end; } + +protected: + S32 mStart; + S32 mEnd; +}; + +class LLNormalTextSegment : public LLTextSegment +{ +public: + LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ); + LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible = true); + virtual ~LLNormalTextSegment(); + + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; + /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; + /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); + /*virtual*/ bool canEdit() const { return true; } + /*virtual*/ const LLColor4& getColor() const { return mStyle->getColor(); } + /*virtual*/ LLStyleConstSP getStyle() const { return mStyle; } + /*virtual*/ void setStyle(LLStyleConstSP style) { mStyle = style; } + /*virtual*/ void setToken( LLKeywordToken* token ) { mToken = token; } + /*virtual*/ LLKeywordToken* getToken() const { return mToken; } + /*virtual*/ bool getToolTip( std::string& msg ) const; + /*virtual*/ void setToolTip(const std::string& tooltip); + /*virtual*/ void dump() const; + + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + +protected: + F32 drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, LLRectf rect); + + virtual const LLWString& getWText() const; + virtual const S32 getLength() const; + +protected: + class LLTextBase& mEditor; + LLStyleConstSP mStyle; + S32 mFontHeight; + LLKeywordToken* mToken; + std::string mTooltip; + boost::signals2::connection mImageLoadedConnection; +}; + +// This text segment is the same as LLNormalTextSegment, the only difference +// is that LLNormalTextSegment draws value of LLTextBase (LLTextBase::getWText()), +// but LLLabelTextSegment draws label of the LLTextBase (LLTextBase::mLabel) +class LLLabelTextSegment : public LLNormalTextSegment +{ +public: + LLLabelTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ); + LLLabelTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible = true); + +protected: + + /*virtual*/ const LLWString& getWText() const; + /*virtual*/ const S32 getLength() const; +}; + +// Text segment that represents a single emoji character that has a different style (=font size) than the rest of +// the document it belongs to +class LLEmojiTextSegment : public LLNormalTextSegment +{ +public: + LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor); + LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, bool is_visible = true); + + bool canEdit() const override { return false; } + bool handleToolTip(S32 x, S32 y, MASK mask) override; +}; + +// Text segment that changes it's style depending of mouse pointer position ( is it inside or outside segment) +class LLOnHoverChangeableTextSegment : public LLNormalTextSegment +{ +public: + LLOnHoverChangeableTextSegment( LLStyleConstSP style, LLStyleConstSP normal_style, S32 start, S32 end, LLTextBase& editor ); + /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); +protected: + // Style used for text when mouse pointer is over segment + LLStyleConstSP mHoveredStyle; + // Style used for text when mouse pointer is outside segment + LLStyleConstSP mNormalStyle; + +}; + +class LLIndexSegment : public LLTextSegment +{ +public: + LLIndexSegment() : LLTextSegment(0, 0) {} +}; + +class LLInlineViewSegment : public LLTextSegment +{ +public: + struct Params : public LLInitParam::Block + { + Mandatory view; + Optional force_newline; + Optional left_pad, + right_pad, + bottom_pad, + top_pad; + }; + + LLInlineViewSegment(const Params& p, S32 start, S32 end); + ~LLInlineViewSegment(); + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; + /*virtual*/ void updateLayout(const class LLTextBase& editor); + /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); + /*virtual*/ bool canEdit() const { return false; } + /*virtual*/ void unlinkFromDocument(class LLTextBase* editor); + /*virtual*/ void linkToDocument(class LLTextBase* editor); + +private: + S32 mLeftPad; + S32 mRightPad; + S32 mTopPad; + S32 mBottomPad; + LLView* mView; + bool mForceNewLine; +}; + +class LLLineBreakTextSegment : public LLTextSegment +{ +public: + + LLLineBreakTextSegment(LLStyleConstSP style,S32 pos); + LLLineBreakTextSegment(S32 pos); + ~LLLineBreakTextSegment(); + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; + F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); + +private: + S32 mFontHeight; +}; + +class LLImageTextSegment : public LLTextSegment +{ +public: + LLImageTextSegment(LLStyleConstSP style,S32 pos,class LLTextBase& editor); + ~LLImageTextSegment(); + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 char_offset, S32 max_chars, S32 line_ind) const; + F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); + + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + /*virtual*/ void setToolTip(const std::string& tooltip); + +private: + class LLTextBase& mEditor; + LLStyleConstSP mStyle; + +protected: + std::string mTooltip; +}; + +typedef LLPointer LLTextSegmentPtr; + +/// +/// The LLTextBase class provides a base class for all text fields, such +/// as LLTextEditor and LLTextBox. It implements shared functionality +/// such as Url highlighting and opening. +/// +class LLTextBase +: public LLUICtrl, + protected LLEditMenuHandler, + public LLSpellCheckMenuHandler, + public ll::ui::SearchableControl +{ +public: + friend class LLTextSegment; + friend class LLNormalTextSegment; + friend class LLUICtrlFactory; + + typedef boost::signals2::signal is_friend_signal_t; + typedef boost::signals2::signal is_blocked_signal_t; + + struct LineSpacingParams : public LLInitParam::ChoiceBlock + { + Alternative multiple; + Alternative pixels; + LineSpacingParams(); + }; + + struct Params : public LLInitParam::Block + { + Optional cursor_color, + text_color, + text_readonly_color, + text_tentative_color, + bg_readonly_color, + bg_writeable_color, + bg_focus_color, + text_selected_color, + bg_selected_color; + + Optional bg_visible, + border_visible, + track_end, + read_only, + skip_link_underline, + spellcheck, + allow_scroll, + plain_text, + wrap, + use_ellipses, + use_emoji, + use_color, + parse_urls, + force_urls_external, + parse_highlights, + clip, + clip_partial, + trusted_content, + always_show_icons; + + Optional v_pad, + h_pad; + + + Optional + line_spacing; + + Optional max_text_length; + + Optional font_shadow; + + Optional text_valign; + + Params(); + }; + + // LLMouseHandler interface + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleMiddleMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks) override; + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask) override; + + // LLView interface + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true) override; + /*virtual*/ void draw() override; + + // LLUICtrl interface + /*virtual*/ bool acceptsTextInput() const override { return !mReadOnly; } + /*virtual*/ void setColor(const LLColor4& c) override; + virtual void setReadOnlyColor(const LLColor4 &c); + /*virtual*/ void onVisibilityChange(bool new_visibility) override; + + /*virtual*/ void setValue(const LLSD& value) override; + /*virtual*/ LLTextViewModel* getViewModel() const override; + + // LLEditMenuHandler interface + /*virtual*/ bool canDeselect() const override; + /*virtual*/ void deselect() override; + + virtual void onFocusReceived() override; + virtual void onFocusLost() override; + + void setParseHTML(bool parse_html) { mParseHTML = parse_html; } + + // LLSpellCheckMenuHandler overrides + /*virtual*/ bool getSpellCheck() const override; + + /*virtual*/ const std::string& getSuggestion(U32 index) const override; + /*virtual*/ U32 getSuggestionCount() const override; + /*virtual*/ void replaceWithSuggestion(U32 index) override; + + /*virtual*/ void addToDictionary() override; + /*virtual*/ bool canAddToDictionary() const override; + + /*virtual*/ void addToIgnore() override; + /*virtual*/ bool canAddToIgnore() const override; + + // Spell checking helper functions + std::string getMisspelledWord(U32 pos) const; + bool isMisspelledWord(U32 pos) const; + void onSpellCheckSettingsChange(); + virtual void onSpellCheckPerformed(){} + + // used by LLTextSegment layout code + bool getWordWrap() const { return mWordWrap; } + bool getUseEllipses() const { return mUseEllipses; } + bool getUseEmoji() const { return mUseEmoji; } + void setUseEmoji(bool value) { mUseEmoji = value; } + bool getUseColor() const { return mUseColor; } + void setUseColor(bool value) { mUseColor = value; } + bool truncate(); // returns true of truncation occurred + + bool isContentTrusted() const { return mTrustedContent; } + void setContentTrusted(bool trusted_content) { mTrustedContent = trusted_content; } + + // TODO: move into LLTextSegment? + void createUrlContextMenu(S32 x, S32 y, const std::string &url); // create a popup context menu for the given Url + + // Text accessors + // TODO: add optional style parameter + virtual void setText(const LLStringExplicit &utf8str , const LLStyle::Params& input_params = LLStyle::Params()); // uses default style + /*virtual*/ const std::string& getText() const override; + void setMaxTextLength(S32 length) { mMaxTextByteLength = length; } + S32 getMaxTextLength() { return mMaxTextByteLength; } + + // wide-char versions + void setWText(const LLWString& text); + const LLWString& getWText() const; + + void appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params = LLStyle::Params()); + + void setLabel(const LLStringExplicit& label); + /*virtual*/ bool setLabelArg(const std::string& key, const LLStringExplicit& text) override; + + const std::string& getLabel() { return mLabel.getString(); } + const LLWString& getWlabel() { return mLabel.getWString();} + + void setLastSegmentToolTip(const std::string &tooltip); + + /** + * If label is set, draws text label (which is LLLabelTextSegment) + * that is visible when no user text provided + */ + void resetLabel(); + + void setFont(const LLFontGL* font); + + // force reflow of text + void needsReflow(S32 index = 0); + + S32 getLength() const { return getWText().length(); } + S32 getLineCount() const { return mLineInfoList.size(); } + S32 removeFirstLine(); // returns removed length + + void addDocumentChild(LLView* view); + void removeDocumentChild(LLView* view); + const LLView* getDocumentView() const { return mDocumentView; } + LLRect getVisibleTextRect() const { return mVisibleTextRect; } + LLRect getTextBoundingRect(); + LLRect getVisibleDocumentRect() const; + + S32 getVPad() { return mVPad; } + S32 getHPad() { return mHPad; } + F32 getLineSpacingMult() { return mLineSpacingMult; } + S32 getLineSpacingPixels() { return mLineSpacingPixels; } // only for multiline + + S32 getDocIndexFromLocalCoord( S32 local_x, S32 local_y, bool round, bool hit_past_end_of_line = true) const; + LLRect getLocalRectFromDocIndex(S32 pos) const; + LLRect getDocRectFromDocIndex(S32 pos) const; + + void setReadOnly(bool read_only) { mReadOnly = read_only; } + bool getReadOnly() { return mReadOnly; } + + void setSkipLinkUnderline(bool skip_link_underline) { mSkipLinkUnderline = skip_link_underline; } + bool getSkipLinkUnderline() { return mSkipLinkUnderline; } + + void setParseURLs(bool parse_urls) { mParseHTML = parse_urls; } + + void setPlainText(bool value) { mPlainText = value;} + bool getPlainText() const { return mPlainText; } + + // cursor manipulation + bool setCursor(S32 row, S32 column); + bool setCursorPos(S32 cursor_pos, bool keep_cursor_offset = false); + void startOfLine(); + void endOfLine(); + void startOfDoc(); + void endOfDoc(); + void changePage( S32 delta ); + void changeLine( S32 delta ); + + bool scrolledToStart(); + bool scrolledToEnd(); + + const LLFontGL* getFont() const override { return mFont; } + + virtual void appendLineBreakSegment(const LLStyle::Params& style_params); + virtual void appendImageSegment(const LLStyle::Params& style_params); + virtual void appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo); + boost::signals2::connection setURLClickedCallback(const commit_signal_t::slot_type& cb); + boost::signals2::connection setIsFriendCallback(const is_friend_signal_t::slot_type& cb); + boost::signals2::connection setIsObjectBlockedCallback(const is_blocked_signal_t::slot_type& cb); + + void setWordWrap(bool wrap); + LLScrollContainer* getScrollContainer() const { return mScroller; } + +protected: + // protected member variables + // List of offsets and segment index of the start of each line. Always has at least one node (0). + struct line_info + { + line_info(S32 index_start, S32 index_end, LLRect rect, S32 line_num); + S32 mDocIndexStart; + S32 mDocIndexEnd; + LLRect mRect; + S32 mLineNum; // actual line count (ignoring soft newlines due to word wrap) + }; + typedef std::vector line_list_t; + + // helper structs + struct compare_bottom + { + bool operator()(const S32& a, const line_info& b) const; + bool operator()(const line_info& a, const S32& b) const; + bool operator()(const line_info& a, const line_info& b) const; + }; + struct compare_top + { + bool operator()(const S32& a, const line_info& b) const; + bool operator()(const line_info& a, const S32& b) const; + bool operator()(const line_info& a, const line_info& b) const; + }; + struct line_end_compare; + typedef std::vector segment_vec_t; + + // Abstract inner base class representing an undoable editor command. + // Concrete sub-classes can be defined for operations such as insert, remove, etc. + // Used as arguments to the execute() method below. + class TextCmd + { + public: + TextCmd( S32 pos, bool group_with_next, LLTextSegmentPtr segment = LLTextSegmentPtr() ) + : mPos(pos), + mGroupWithNext(group_with_next) + { + if (segment.notNull()) + { + mSegments.push_back(segment); + } + } + virtual ~TextCmd() {} + virtual bool execute(LLTextBase* editor, S32* delta) = 0; + virtual S32 undo(LLTextBase* editor) = 0; + virtual S32 redo(LLTextBase* editor) = 0; + virtual bool canExtend(S32 pos) const { return false; } + virtual void blockExtensions() {} + virtual bool extendAndExecute( LLTextBase* editor, S32 pos, llwchar c, S32* delta ) { llassert(0); return 0; } + virtual bool hasExtCharValue( llwchar value ) const { return false; } + + // Defined here so they can access protected LLTextEditor editing methods + S32 insert(LLTextBase* editor, S32 pos, const LLWString &wstr) { return editor->insertStringNoUndo( pos, wstr, &mSegments ); } + S32 remove(LLTextBase* editor, S32 pos, S32 length) { return editor->removeStringNoUndo( pos, length ); } + S32 overwrite(LLTextBase* editor, S32 pos, llwchar wc) { return editor->overwriteCharNoUndo(pos, wc); } + + S32 getPosition() const { return mPos; } + bool groupWithNext() const { return mGroupWithNext; } + + protected: + const S32 mPos; + bool mGroupWithNext; + segment_vec_t mSegments; + }; + + struct compare_segment_end + { + bool operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const; + }; + typedef std::multiset segment_set_t; + + // member functions + LLTextBase(const Params &p); + virtual ~LLTextBase(); + void initFromParams(const Params& p); + virtual void beforeValueChange(); + virtual void onValueChange(S32 start, S32 end); + virtual bool useLabel() const; + + // draw methods + virtual void drawSelectionBackground(); // draws the black box behind the selected text + void drawCursor(); + void drawText(); + + // modify contents + S32 insertStringNoUndo(S32 pos, const LLWString &wstr, segment_vec_t* segments = NULL); // returns num of chars actually inserted + S32 removeStringNoUndo(S32 pos, S32 length); + S32 overwriteCharNoUndo(S32 pos, llwchar wc); + void appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& stylep, bool underline_on_hover_only = false); + + + // manage segments + void getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const; + void getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp ); + LLTextSegmentPtr getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line = true); + segment_set_t::iterator getEditableSegIterContaining(S32 index); + segment_set_t::const_iterator getEditableSegIterContaining(S32 index) const; + segment_set_t::iterator getSegIterContaining(S32 index); + segment_set_t::const_iterator getSegIterContaining(S32 index) const; + void clearSegments(); + void createDefaultSegment(); + virtual void updateSegments(); + void insertSegment(LLTextSegmentPtr segment_to_insert); + const LLStyle::Params& getStyleParams(); + + // manage lines + S32 getLineStart( S32 line ) const; + S32 getLineEnd( S32 line ) const; + S32 getLineNumFromDocIndex( S32 doc_index, bool include_wordwrap = true) const; + S32 getLineOffsetFromDocIndex( S32 doc_index, bool include_wordwrap = true) const; + S32 getFirstVisibleLine() const; + std::pair getVisibleLines(bool fully_visible = false); + S32 getLeftOffset(S32 width); + void reflow(); + + // cursor + void updateCursorXPos(); + void setCursorAtLocalPos( S32 local_x, S32 local_y, bool round, bool keep_cursor_offset=false ); + S32 getEditableIndex(S32 index, bool increasing_direction); // constraint cursor to editable segments of document + void resetCursorBlink() { mCursorBlinkTimer.reset(); } + void updateScrollFromCursor(); + + // text selection + bool hasSelection() const { return (mSelectionStart !=mSelectionEnd); } + void startSelection(); + void endSelection(); + + // misc + void updateRects(); + void needsScroll() { mScrollNeeded = true; } + + struct URLLabelCallback; + // Replace a URL with a new icon and label, for example, when + // avatar names are looked up. + void replaceUrl(const std::string &url, const std::string &label, const std::string& icon); + + void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params()); + void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only = false); + S32 normalizeUri(std::string& uri); + +protected: + // virtual + std::string _getSearchText() const override + { + return mLabel.getString() + getToolTip(); + } + + std::vector getSelectionRects(); + +protected: + // text segmentation and flow + segment_set_t mSegments; + line_list_t mLineInfoList; + LLRect mVisibleTextRect; // The rect in which text is drawn. Excludes borders. + LLRect mTextBoundingRect; + + // default text style + LLStyle::Params mStyle; + bool mStyleDirty; + const LLFontGL* mFont; + const LLFontGL::ShadowType mFontShadow; + + // colors + LLUIColor mCursorColor; + LLUIColor mFgColor; + LLUIColor mReadOnlyFgColor; + LLUIColor mTentativeFgColor; + LLUIColor mWriteableBgColor; + LLUIColor mReadOnlyBgColor; + LLUIColor mFocusBgColor; + LLUIColor mTextSelectedColor; + LLUIColor mSelectedBGColor; + + // cursor + S32 mCursorPos; // I-beam is just after the mCursorPos-th character. + S32 mDesiredXPixel; // X pixel position where the user wants the cursor to be + LLFrameTimer mCursorBlinkTimer; // timer that controls cursor blinking + + // selection + S32 mSelectionStart; + S32 mSelectionEnd; + LLTimer mTripleClickTimer; + + bool mIsSelecting; // Are we in the middle of a drag-select? + + // spell checking + bool mSpellCheck; + S32 mSpellCheckStart; + S32 mSpellCheckEnd; + LLTimer mSpellCheckTimer; + std::list > mMisspellRanges; + std::vector mSuggestionList; + + // configuration + S32 mHPad; // padding on left of text + S32 mVPad; // padding above text + LLFontGL::HAlign mHAlign; // horizontal alignment of the document in its entirety + LLFontGL::VAlign mVAlign; // vertical alignment of the document in its entirety + LLFontGL::VAlign mTextVAlign; // vertical alignment of a text segment within a single line of text + F32 mLineSpacingMult; // multiple of line height used as space for a single line of text (e.g. 1.5 to get 50% padding) + S32 mLineSpacingPixels; // padding between lines + bool mBorderVisible; + bool mParseHTML; // make URLs interactive + bool mForceUrlsExternal; // URLs from this textbox will be opened in external browser + bool mParseHighlights; // highlight user-defined keywords + bool mWordWrap; + bool mUseEllipses; + bool mUseEmoji; + bool mUseColor; + bool mTrackEnd; // if true, keeps scroll position at end of document during resize + bool mReadOnly; + bool mBGVisible; // render background? + bool mClip; // clip text to widget rect + bool mClipPartial; // false if we show lines that are partially inside bounding rect + bool mTrustedContent; // if false, does not allow to execute SURL links from this editor + bool mPlainText; // didn't use Image or Icon segments + bool mAutoIndent; + S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes + bool mSkipTripleClick; + bool mAlwaysShowIcons; + + bool mSkipLinkUnderline; + + // support widgets + LLHandle mPopupMenuHandle; + LLView* mDocumentView; + LLScrollContainer* mScroller; + + // transient state + S32 mReflowIndex; // index at which to start reflow. S32_MAX indicates no reflow needed. + bool mScrollNeeded; // need to change scroll region because of change to cursor position + S32 mScrollIndex; // index of first character to keep visible in scroll region + + // Fired when a URL link is clicked + commit_signal_t* mURLClickSignal; + + // Used to check if user with given ID is avatar's friend + is_friend_signal_t* mIsFriendSignal; + is_blocked_signal_t* mIsObjectBlockedSignal; + + LLUIString mLabel; // text label that is visible when no user text provided +}; + +#endif diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp index b3b97c00b4..92551b682c 100644 --- a/indra/llui/lltextbox.cpp +++ b/indra/llui/lltextbox.cpp @@ -1,183 +1,183 @@ -/** - * @file lltextbox.cpp - * @brief A text display widget - * - * $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" - -#define LLTEXTBOX_CPP -#include "lltextbox.h" - -#include "lluictrlfactory.h" -#include "llfocusmgr.h" -#include "llwindow.h" -#include "llurlregistry.h" -#include "llstyle.h" - -static LLDefaultChildRegistry::Register r("text"); - -// Compiler optimization, generate extern template -template class LLTextBox* LLView::getChild( - const std::string& name, bool recurse) const; - -LLTextBox::LLTextBox(const LLTextBox::Params& p) -: LLTextBase(p), - mClickedCallback(NULL), - mShowCursorHand(true) -{ - mSkipTripleClick = true; -} - -LLTextBox::~LLTextBox() -{} - -bool LLTextBox::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = LLTextBase::handleMouseDown(x, y, mask); - - if (getSoundFlags() & MOUSE_DOWN) - { - make_ui_sound("UISndClick"); - } - - if (!handled && mClickedCallback) - { - handled = true; - } - - if (handled) - { - // Route future Mouse messages here preemptively. (Release on mouse up.) - gFocusMgr.setMouseCapture( this ); - } - - return handled; -} - -bool LLTextBox::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = LLTextBase::handleMouseUp(x, y, mask); - - if (getSoundFlags() & MOUSE_UP) - { - make_ui_sound("UISndClickRelease"); - } - - // We only handle the click if the click both started and ended within us - if (hasMouseCapture()) - { - // Release the mouse - gFocusMgr.setMouseCapture( NULL ); - - // DO THIS AT THE VERY END to allow the button to be destroyed - // as a result of being clicked. If mouseup in the widget, - // it's been clicked - if (mClickedCallback && !handled) - { - mClickedCallback(); - handled = true; - } - } - - return handled; -} - -bool LLTextBox::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = LLTextBase::handleHover(x, y, mask); - if (!handled && mClickedCallback && mShowCursorHand) - { - // Clickable text boxes change the cursor to a hand - LLUI::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND); - return true; - } - return handled; -} - -void LLTextBox::setEnabled(bool enabled) -{ - // just treat enabled as read-only flag - bool read_only = !enabled; - if (read_only != mReadOnly) - { - LLTextBase::setReadOnly(read_only); - updateSegments(); - } - LLTextBase::setEnabled(enabled); -} - -void LLTextBox::setText(const LLStringExplicit& text , const LLStyle::Params& input_params ) -{ - // does string argument insertion - mText.assign(text); - - LLTextBase::setText(mText.getString(), input_params ); -} - -void LLTextBox::setClickedCallback( boost::function cb, void* userdata /*= NULL */ ) -{ - mClickedCallback = boost::bind(cb, userdata); -} - -S32 LLTextBox::getTextPixelWidth() -{ - return getTextBoundingRect().getWidth(); -} - -S32 LLTextBox::getTextPixelHeight() -{ - return getTextBoundingRect().getHeight(); -} - - -LLSD LLTextBox::getValue() const -{ - return getViewModel()->getValue(); -} - -bool LLTextBox::setTextArg( const std::string& key, const LLStringExplicit& text ) -{ - mText.setArg(key, text); - LLTextBase::setText(mText.getString()); - - return true; -} - - -void LLTextBox::reshapeToFitText(bool called_from_parent) -{ - reflow(); - - S32 width = getTextPixelWidth(); - S32 height = getTextPixelHeight(); - //consider investigating reflow() to find missing width pixel (see SL-17045 changes) - reshape( width + 2 * mHPad + 1, height + 2 * mVPad, called_from_parent ); -} - - -void LLTextBox::onUrlLabelUpdated(const std::string &url, const std::string &label) -{ - needsReflow(); -} - +/** + * @file lltextbox.cpp + * @brief A text display widget + * + * $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" + +#define LLTEXTBOX_CPP +#include "lltextbox.h" + +#include "lluictrlfactory.h" +#include "llfocusmgr.h" +#include "llwindow.h" +#include "llurlregistry.h" +#include "llstyle.h" + +static LLDefaultChildRegistry::Register r("text"); + +// Compiler optimization, generate extern template +template class LLTextBox* LLView::getChild( + const std::string& name, bool recurse) const; + +LLTextBox::LLTextBox(const LLTextBox::Params& p) +: LLTextBase(p), + mClickedCallback(NULL), + mShowCursorHand(true) +{ + mSkipTripleClick = true; +} + +LLTextBox::~LLTextBox() +{} + +bool LLTextBox::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLTextBase::handleMouseDown(x, y, mask); + + if (getSoundFlags() & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } + + if (!handled && mClickedCallback) + { + handled = true; + } + + if (handled) + { + // Route future Mouse messages here preemptively. (Release on mouse up.) + gFocusMgr.setMouseCapture( this ); + } + + return handled; +} + +bool LLTextBox::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = LLTextBase::handleMouseUp(x, y, mask); + + if (getSoundFlags() & MOUSE_UP) + { + make_ui_sound("UISndClickRelease"); + } + + // We only handle the click if the click both started and ended within us + if (hasMouseCapture()) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL ); + + // DO THIS AT THE VERY END to allow the button to be destroyed + // as a result of being clicked. If mouseup in the widget, + // it's been clicked + if (mClickedCallback && !handled) + { + mClickedCallback(); + handled = true; + } + } + + return handled; +} + +bool LLTextBox::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = LLTextBase::handleHover(x, y, mask); + if (!handled && mClickedCallback && mShowCursorHand) + { + // Clickable text boxes change the cursor to a hand + LLUI::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND); + return true; + } + return handled; +} + +void LLTextBox::setEnabled(bool enabled) +{ + // just treat enabled as read-only flag + bool read_only = !enabled; + if (read_only != mReadOnly) + { + LLTextBase::setReadOnly(read_only); + updateSegments(); + } + LLTextBase::setEnabled(enabled); +} + +void LLTextBox::setText(const LLStringExplicit& text , const LLStyle::Params& input_params ) +{ + // does string argument insertion + mText.assign(text); + + LLTextBase::setText(mText.getString(), input_params ); +} + +void LLTextBox::setClickedCallback( boost::function cb, void* userdata /*= NULL */ ) +{ + mClickedCallback = boost::bind(cb, userdata); +} + +S32 LLTextBox::getTextPixelWidth() +{ + return getTextBoundingRect().getWidth(); +} + +S32 LLTextBox::getTextPixelHeight() +{ + return getTextBoundingRect().getHeight(); +} + + +LLSD LLTextBox::getValue() const +{ + return getViewModel()->getValue(); +} + +bool LLTextBox::setTextArg( const std::string& key, const LLStringExplicit& text ) +{ + mText.setArg(key, text); + LLTextBase::setText(mText.getString()); + + return true; +} + + +void LLTextBox::reshapeToFitText(bool called_from_parent) +{ + reflow(); + + S32 width = getTextPixelWidth(); + S32 height = getTextPixelHeight(); + //consider investigating reflow() to find missing width pixel (see SL-17045 changes) + reshape( width + 2 * mHPad + 1, height + 2 * mVPad, called_from_parent ); +} + + +void LLTextBox::onUrlLabelUpdated(const std::string &url, const std::string &label) +{ + needsReflow(); +} + diff --git a/indra/llui/lltextbox.h b/indra/llui/lltextbox.h index 1e58fe954c..c1f829c2b9 100644 --- a/indra/llui/lltextbox.h +++ b/indra/llui/lltextbox.h @@ -1,87 +1,87 @@ -/** - * @file lltextbox.h - * @brief A single text item display - * - * $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$ - */ - -#ifndef LL_LLTEXTBOX_H -#define LL_LLTEXTBOX_H - -#include "lluistring.h" -#include "lltextbase.h" - -class LLTextBox : - public LLTextBase -{ -public: - - // *TODO: Add callback to Params - typedef boost::function callback_t; - - struct Params : public LLInitParam::Block - {}; - -protected: - LLTextBox(const Params&); - friend class LLUICtrlFactory; - -public: - virtual ~LLTextBox(); - - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - - /*virtual*/ void setEnabled(bool enabled); - - /*virtual*/ void setText( const LLStringExplicit& text, const LLStyle::Params& input_params = LLStyle::Params() ); - - void setRightAlign() { mHAlign = LLFontGL::RIGHT; } - void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; } - void setClickedCallback( boost::function cb, void* userdata = NULL ); - - void reshapeToFitText(bool called_from_parent = false); - - S32 getTextPixelWidth(); - S32 getTextPixelHeight(); - - /*virtual*/ LLSD getValue() const; - /*virtual*/ bool setTextArg( const std::string& key, const LLStringExplicit& text ); - - void setShowCursorHand(bool show_cursor) { mShowCursorHand = show_cursor; } - -protected: - void onUrlLabelUpdated(const std::string &url, const std::string &label); - - LLUIString mText; - callback_t mClickedCallback; - bool mShowCursorHand; -}; - -// Build time optimization, generate once in .cpp file -#ifndef LLTEXTBOX_CPP -extern template class LLTextBox* LLView::getChild( - const std::string& name, bool recurse) const; -#endif - -#endif +/** + * @file lltextbox.h + * @brief A single text item display + * + * $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$ + */ + +#ifndef LL_LLTEXTBOX_H +#define LL_LLTEXTBOX_H + +#include "lluistring.h" +#include "lltextbase.h" + +class LLTextBox : + public LLTextBase +{ +public: + + // *TODO: Add callback to Params + typedef boost::function callback_t; + + struct Params : public LLInitParam::Block + {}; + +protected: + LLTextBox(const Params&); + friend class LLUICtrlFactory; + +public: + virtual ~LLTextBox(); + + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + + /*virtual*/ void setEnabled(bool enabled); + + /*virtual*/ void setText( const LLStringExplicit& text, const LLStyle::Params& input_params = LLStyle::Params() ); + + void setRightAlign() { mHAlign = LLFontGL::RIGHT; } + void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; } + void setClickedCallback( boost::function cb, void* userdata = NULL ); + + void reshapeToFitText(bool called_from_parent = false); + + S32 getTextPixelWidth(); + S32 getTextPixelHeight(); + + /*virtual*/ LLSD getValue() const; + /*virtual*/ bool setTextArg( const std::string& key, const LLStringExplicit& text ); + + void setShowCursorHand(bool show_cursor) { mShowCursorHand = show_cursor; } + +protected: + void onUrlLabelUpdated(const std::string &url, const std::string &label); + + LLUIString mText; + callback_t mClickedCallback; + bool mShowCursorHand; +}; + +// Build time optimization, generate once in .cpp file +#ifndef LLTEXTBOX_CPP +extern template class LLTextBox* LLView::getChild( + const std::string& name, bool recurse) const; +#endif + +#endif diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index daffd58dfe..b4254524ad 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -1,3070 +1,3070 @@ -/** - * @file lltexteditor.cpp - * - * $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$ - */ - -// Text editor widget to let users enter a a multi-line ASCII document. - -#include "linden_common.h" - -#define LLTEXTEDITOR_CPP -#include "lltexteditor.h" - -#include "llfontfreetype.h" // for LLFontFreetype::FIRST_CHAR -#include "llfontgl.h" -#include "llgl.h" // LLGLSUIDefault() -#include "lllocalcliprect.h" -#include "llrender.h" -#include "llui.h" -#include "lluictrlfactory.h" -#include "llrect.h" -#include "llfocusmgr.h" -#include "lltimer.h" -#include "llmath.h" - -#include "llclipboard.h" -#include "llemojihelper.h" -#include "llscrollbar.h" -#include "llstl.h" -#include "llstring.h" -#include "llkeyboard.h" -#include "llkeywords.h" -#include "llundo.h" -#include "llviewborder.h" -#include "llcontrol.h" -#include "llwindow.h" -#include "lltextparser.h" -#include "llscrollcontainer.h" -#include "llspellcheck.h" -#include "llpanel.h" -#include "llurlregistry.h" -#include "lltooltip.h" -#include "llmenugl.h" - -#include -#include "llcombobox.h" - -// -// Globals -// -static LLDefaultChildRegistry::Register r("simple_text_editor"); - -// Compiler optimization, generate extern template -template class LLTextEditor* LLView::getChild( - const std::string& name, bool recurse) const; - -// -// Constants -// -const S32 SPACES_PER_TAB = 4; -const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and spell checking the word the cursor is on - -/////////////////////////////////////////////////////////////////// - -class LLTextEditor::TextCmdInsert : public LLTextBase::TextCmd -{ -public: - TextCmdInsert(S32 pos, bool group_with_next, const LLWString &ws, LLTextSegmentPtr segment) - : TextCmd(pos, group_with_next, segment), mWString(ws) - { - } - virtual ~TextCmdInsert() {} - virtual bool execute( LLTextBase* editor, S32* delta ) - { - *delta = insert(editor, getPosition(), mWString ); - LLWStringUtil::truncate(mWString, *delta); - //mWString = wstring_truncate(mWString, *delta); - return (*delta != 0); - } - virtual S32 undo( LLTextBase* editor ) - { - remove(editor, getPosition(), mWString.length() ); - return getPosition(); - } - virtual S32 redo( LLTextBase* editor ) - { - insert(editor, getPosition(), mWString ); - return getPosition() + mWString.length(); - } - -private: - LLWString mWString; -}; - -/////////////////////////////////////////////////////////////////// -class LLTextEditor::TextCmdAddChar : public LLTextBase::TextCmd -{ -public: - TextCmdAddChar( S32 pos, bool group_with_next, llwchar wc, LLTextSegmentPtr segment) - : TextCmd(pos, group_with_next, segment), mWString(1, wc), mBlockExtensions(false) - { - } - virtual void blockExtensions() - { - mBlockExtensions = true; - } - virtual bool canExtend(S32 pos) const - { - // cannot extend text with custom segments - if (!mSegments.empty()) return false; - - return !mBlockExtensions && (pos == getPosition() + (S32)mWString.length()); - } - virtual bool execute( LLTextBase* editor, S32* delta ) - { - *delta = insert(editor, getPosition(), mWString); - LLWStringUtil::truncate(mWString, *delta); - //mWString = wstring_truncate(mWString, *delta); - return (*delta != 0); - } - virtual bool extendAndExecute( LLTextBase* editor, S32 pos, llwchar wc, S32* delta ) - { - LLWString ws; - ws += wc; - - *delta = insert(editor, pos, ws); - if( *delta > 0 ) - { - mWString += wc; - } - return (*delta != 0); - } - virtual S32 undo( LLTextBase* editor ) - { - remove(editor, getPosition(), mWString.length() ); - return getPosition(); - } - virtual S32 redo( LLTextBase* editor ) - { - insert(editor, getPosition(), mWString ); - return getPosition() + mWString.length(); - } - -private: - LLWString mWString; - bool mBlockExtensions; - -}; - -/////////////////////////////////////////////////////////////////// - -class LLTextEditor::TextCmdOverwriteChar : public LLTextBase::TextCmd -{ -public: - TextCmdOverwriteChar( S32 pos, bool group_with_next, llwchar wc) - : TextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {} - - virtual bool execute( LLTextBase* editor, S32* delta ) - { - mOldChar = editor->getWText()[getPosition()]; - overwrite(editor, getPosition(), mChar); - *delta = 0; - return true; - } - virtual S32 undo( LLTextBase* editor ) - { - overwrite(editor, getPosition(), mOldChar); - return getPosition(); - } - virtual S32 redo( LLTextBase* editor ) - { - overwrite(editor, getPosition(), mChar); - return getPosition()+1; - } - -private: - llwchar mChar; - llwchar mOldChar; -}; - -/////////////////////////////////////////////////////////////////// - -class LLTextEditor::TextCmdRemove : public LLTextBase::TextCmd -{ -public: - TextCmdRemove( S32 pos, bool group_with_next, S32 len, segment_vec_t& segments ) : - TextCmd(pos, group_with_next), mLen(len) - { - std::swap(mSegments, segments); - } - virtual bool execute( LLTextBase* editor, S32* delta ) - { - mWString = editor->getWText().substr(getPosition(), mLen); - *delta = remove(editor, getPosition(), mLen ); - return (*delta != 0); - } - virtual S32 undo( LLTextBase* editor ) - { - insert(editor, getPosition(), mWString); - return getPosition() + mWString.length(); - } - virtual S32 redo( LLTextBase* editor ) - { - remove(editor, getPosition(), mLen ); - return getPosition(); - } -private: - LLWString mWString; - S32 mLen; -}; - - -/////////////////////////////////////////////////////////////////// -LLTextEditor::Params::Params() -: default_text("default_text"), - prevalidator("prevalidator"), - embedded_items("embedded_items", false), - ignore_tab("ignore_tab", true), - auto_indent("auto_indent", true), - default_color("default_color"), - commit_on_focus_lost("commit_on_focus_lost", false), - show_context_menu("show_context_menu"), - show_emoji_helper("show_emoji_helper"), - enable_tooltip_paste("enable_tooltip_paste") -{ - addSynonym(prevalidator, "prevalidate_callback"); - addSynonym(prevalidator, "text_type"); -} - -LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : - LLTextBase(p), - mAutoreplaceCallback(), - mBaseDocIsPristine(true), - mPristineCmd( NULL ), - mLastCmd( NULL ), - mDefaultColor( p.default_color() ), - mAutoIndent(p.auto_indent), - mParseOnTheFly(false), - mCommitOnFocusLost( p.commit_on_focus_lost), - mAllowEmbeddedItems( p.embedded_items ), - mMouseDownX(0), - mMouseDownY(0), - mTabsToNextField(p.ignore_tab), - mPrevalidator(p.prevalidator()), - mShowContextMenu(p.show_context_menu), - mShowEmojiHelper(p.show_emoji_helper), - mEnableTooltipPaste(p.enable_tooltip_paste), - mPassDelete(false), - mKeepSelectionOnReturn(false) -{ - mSourceID.generate(); - - //FIXME: use image? - LLViewBorder::Params params; - params.name = "text ed border"; - params.rect = getLocalRect(); - params.bevel_style = LLViewBorder::BEVEL_IN; - params.border_thickness = 1; - params.visible = p.border_visible; - mBorder = LLUICtrlFactory::create (params); - addChild( mBorder ); - setText(p.default_text()); - - mParseOnTheFly = true; -} - -void LLTextEditor::initFromParams( const LLTextEditor::Params& p) -{ - LLTextBase::initFromParams(p); - - // HACK: text editors always need to be enabled so that we can scroll - LLView::setEnabled(true); - - if (p.commit_on_focus_lost.isProvided()) - { - mCommitOnFocusLost = p.commit_on_focus_lost; - } - - updateAllowingLanguageInput(); -} - -LLTextEditor::~LLTextEditor() -{ - gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() while LLTextEditor still valid - - // Scrollbar is deleted by LLView - std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); - mUndoStack.clear(); - // Mark the menu as dead or its retained in memory till shutdown. - LLContextMenu* menu = static_cast(mContextMenuHandle.get()); - if(menu) - { - menu->die(); - mContextMenuHandle.markDead(); - } -} - -//////////////////////////////////////////////////////////// -// LLTextEditor -// Public methods - -void LLTextEditor::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) -{ - // validate incoming text if necessary - if (mPrevalidator) - { - if (!mPrevalidator.validate(utf8str)) - { - LLUI::getInstance()->reportBadKeystroke(); - mPrevalidator.showLastErrorUsingTimeout(); - - // not valid text, nothing to do - return; - } - } - - blockUndo(); - deselect(); - - mParseOnTheFly = false; - LLTextBase::setText(utf8str, input_params); - mParseOnTheFly = true; - - resetDirty(); -} - -void LLTextEditor::selectNext(const std::string& search_text_in, bool case_insensitive, bool wrap) -{ - if (search_text_in.empty()) - { - return; - } - - LLWString text = getWText(); - LLWString search_text = utf8str_to_wstring(search_text_in); - if (case_insensitive) - { - LLWStringUtil::toLower(text); - LLWStringUtil::toLower(search_text); - } - - if (mIsSelecting) - { - LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd); - - if (selected_text == search_text) - { - // We already have this word selected, we are searching for the next. - setCursorPos(mCursorPos + search_text.size()); - } - } - - S32 loc = text.find(search_text,mCursorPos); - - // If Maybe we wrapped, search again - if (wrap && (-1 == loc)) - { - loc = text.find(search_text); - } - - // If still -1, then search_text just isn't found. - if (-1 == loc) - { - mIsSelecting = false; - mSelectionEnd = 0; - mSelectionStart = 0; - return; - } - - setCursorPos(loc); - - mIsSelecting = true; - mSelectionEnd = mCursorPos; - mSelectionStart = llmin((S32)getLength(), (S32)(mCursorPos + search_text.size())); -} - -bool LLTextEditor::replaceText(const std::string& search_text_in, const std::string& replace_text, - bool case_insensitive, bool wrap) -{ - bool replaced = false; - - if (search_text_in.empty()) - { - return replaced; - } - - LLWString search_text = utf8str_to_wstring(search_text_in); - if (mIsSelecting) - { - LLWString text = getWText(); - LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd); - - if (case_insensitive) - { - LLWStringUtil::toLower(selected_text); - LLWStringUtil::toLower(search_text); - } - - if (selected_text == search_text) - { - insertText(replace_text); - replaced = true; - } - } - - selectNext(search_text_in, case_insensitive, wrap); - return replaced; -} - -void LLTextEditor::replaceTextAll(const std::string& search_text, const std::string& replace_text, bool case_insensitive) -{ - startOfDoc(); - selectNext(search_text, case_insensitive, false); - - bool replaced = true; - while ( replaced ) - { - replaced = replaceText(search_text,replace_text, case_insensitive, false); - } -} - -S32 LLTextEditor::prevWordPos(S32 cursorPos) const -{ - LLWString wtext(getWText()); - while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') ) - { - cursorPos--; - } - while( (cursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[cursorPos-1] ) ) - { - cursorPos--; - } - return cursorPos; -} - -S32 LLTextEditor::nextWordPos(S32 cursorPos) const -{ - LLWString wtext(getWText()); - while( (cursorPos < getLength()) && LLWStringUtil::isPartOfWord( wtext[cursorPos] ) ) - { - cursorPos++; - } - while( (cursorPos < getLength()) && (wtext[cursorPos] == ' ') ) - { - cursorPos++; - } - return cursorPos; -} - -const LLTextSegmentPtr LLTextEditor::getPreviousSegment() const -{ - static LLPointer index_segment = new LLIndexSegment; - - index_segment->setStart(mCursorPos); - index_segment->setEnd(mCursorPos); - - // find segment index at character to left of cursor (or rightmost edge of selection) - segment_set_t::const_iterator it = mSegments.lower_bound(index_segment); - - if (it != mSegments.end()) - { - return *it; - } - else - { - return LLTextSegmentPtr(); - } -} - -void LLTextEditor::getSelectedSegments(LLTextEditor::segment_vec_t& segments) const -{ - S32 left = hasSelection() ? llmin(mSelectionStart, mSelectionEnd) : mCursorPos; - S32 right = hasSelection() ? llmax(mSelectionStart, mSelectionEnd) : mCursorPos; - - return getSegmentsInRange(segments, left, right, true); -} - -void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out, S32 start, S32 end, bool include_partial) const -{ - segment_set_t::const_iterator first_it = getSegIterContaining(start); - segment_set_t::const_iterator end_it = getSegIterContaining(end - 1); - if (end_it != mSegments.end()) ++end_it; - - for (segment_set_t::const_iterator it = first_it; it != end_it; ++it) - { - LLTextSegmentPtr segment = *it; - if (include_partial - || (segment->getStart() >= start - && segment->getEnd() <= end)) - { - segments_out.push_back(segment); - } - } -} - -void LLTextEditor::setShowEmojiHelper(bool show) -{ - if (!mShowEmojiHelper) - { - LLEmojiHelper::instance().hideHelper(this); - } - - mShowEmojiHelper = show; -} - -bool LLTextEditor::selectionContainsLineBreaks() -{ - if (hasSelection()) - { - S32 left = llmin(mSelectionStart, mSelectionEnd); - S32 right = left + llabs(mSelectionStart - mSelectionEnd); - - LLWString wtext = getWText(); - for( S32 i = left; i < right; i++ ) - { - if (wtext[i] == '\n') - { - return true; - } - } - } - return false; -} - - -S32 LLTextEditor::indentLine( S32 pos, S32 spaces ) -{ - // Assumes that pos is at the start of the line - // spaces may be positive (indent) or negative (unindent). - // Returns the actual number of characters added or removed. - - llassert(pos >= 0); - llassert(pos <= getLength() ); - - S32 delta_spaces = 0; - - if (spaces >= 0) - { - // Indent - for(S32 i=0; i < spaces; i++) - { - delta_spaces += addChar(pos, ' '); - } - } - else - { - // Unindent - for(S32 i=0; i < -spaces; i++) - { - LLWString wtext = getWText(); - if (wtext[pos] == ' ') - { - delta_spaces += remove( pos, 1, false ); - } - } - } - - return delta_spaces; -} - -void LLTextEditor::indentSelectedLines( S32 spaces ) -{ - if( hasSelection() ) - { - LLWString text = getWText(); - S32 left = llmin( mSelectionStart, mSelectionEnd ); - S32 right = left + llabs( mSelectionStart - mSelectionEnd ); - bool cursor_on_right = (mSelectionEnd > mSelectionStart); - S32 cur = left; - - // Expand left to start of line - while( (cur > 0) && (text[cur] != '\n') ) - { - cur--; - } - left = cur; - if( cur > 0 ) - { - left++; - } - - // Expand right to end of line - if( text[right - 1] == '\n' ) - { - right--; - } - else - { - while( (text[right] != '\n') && (right <= getLength() ) ) - { - right++; - } - } - - // Disabling parsing on the fly to avoid updating text segments - // until all indentation commands are executed. - mParseOnTheFly = false; - - // Find each start-of-line and indent it - do - { - if( text[cur] == '\n' ) - { - cur++; - } - - S32 delta_spaces = indentLine( cur, spaces ); - if( delta_spaces > 0 ) - { - cur += delta_spaces; - } - right += delta_spaces; - - text = getWText(); - - // Find the next new line - while( (cur < right) && (text[cur] != '\n') ) - { - cur++; - } - } - while( cur < right ); - - mParseOnTheFly = true; - - if( (right < getLength()) && (text[right] == '\n') ) - { - right++; - } - - // Set the selection and cursor - if( cursor_on_right ) - { - mSelectionStart = left; - mSelectionEnd = right; - } - else - { - mSelectionStart = right; - mSelectionEnd = left; - } - setCursorPos(mSelectionEnd); - } -} - -//virtual -bool LLTextEditor::canSelectAll() const -{ - return true; -} - -// virtual -void LLTextEditor::selectAll() -{ - mSelectionStart = getLength(); - mSelectionEnd = 0; - setCursorPos(mSelectionEnd); - updatePrimary(); -} - -void LLTextEditor::selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_pos) -{ - setCursorPos(prev_cursor_pos); - startSelection(); - setCursorPos(next_cursor_pos); - endSelection(); -} - -void LLTextEditor::insertEmoji(llwchar emoji) -{ - LL_INFOS() << "LLTextEditor::insertEmoji(" << wchar_utf8_preview(emoji) << ")" << LL_ENDL; - auto styleParams = LLStyle::Params(); - styleParams.font = LLFontGL::getFontEmojiLarge(); - auto segment = new LLEmojiTextSegment(new LLStyle(styleParams), mCursorPos, mCursorPos + 1, *this); - insert(mCursorPos, LLWString(1, emoji), false, segment); - setCursorPos(mCursorPos + 1); -} - -void LLTextEditor::handleEmojiCommit(llwchar emoji) -{ - S32 shortCodePos; - if (LLEmojiHelper::isCursorInEmojiCode(getWText(), mCursorPos, &shortCodePos)) - { - remove(shortCodePos, mCursorPos - shortCodePos, true); - setCursorPos(shortCodePos); - - insertEmoji(emoji); - } -} - -bool LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // set focus first, in case click callbacks want to change it - // RN: do we really need to have a tab stop? - if (hasTabStop()) - { - setFocus( true ); - } - - // Let scrollbar have first dibs - handled = LLTextBase::handleMouseDown(x, y, mask); - - if( !handled ) - { - if (!(mask & MASK_SHIFT)) - { - deselect(); - } - - bool start_select = true; - if( start_select ) - { - // If we're not scrolling (handled by child), then we're selecting - if (mask & MASK_SHIFT) - { - S32 old_cursor_pos = mCursorPos; - setCursorAtLocalPos( x, y, true ); - - if (hasSelection()) - { - mSelectionEnd = mCursorPos; - } - else - { - mSelectionStart = old_cursor_pos; - mSelectionEnd = mCursorPos; - } - // assume we're starting a drag select - mIsSelecting = true; - } - else - { - setCursorAtLocalPos( x, y, true ); - startSelection(); - } - } - - handled = true; - } - - // Delay cursor flashing - resetCursorBlink(); - - if (handled && !gFocusMgr.getMouseCapture()) - { - gFocusMgr.setMouseCapture( this ); - } - return handled; -} - -bool LLTextEditor::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - if (hasTabStop()) - { - setFocus(true); - } - - bool show_menu = false; - - // Prefer editor menu if it has selection. See EXT-6806. - if (hasSelection()) - { - S32 click_pos = getDocIndexFromLocalCoord(x, y, false); - if (click_pos > mSelectionStart && click_pos < mSelectionEnd) - { - show_menu = true; - } - } - - // Let segments handle the click, if nothing does, show editor menu - if (!show_menu && !LLTextBase::handleRightMouseDown(x, y, mask)) - { - show_menu = true; - } - - if (show_menu && getShowContextMenu()) - { - showContextMenu(x, y); - } - - return true; -} - - - -bool LLTextEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask) -{ - if (hasTabStop()) - { - setFocus(true); - } - - if (!LLTextBase::handleMouseDown(x, y, mask)) - { - if( canPastePrimary() ) - { - setCursorAtLocalPos( x, y, true ); - // does not rely on focus being set - pastePrimary(); - } - } - return true; -} - - -bool LLTextEditor::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - if(hasMouseCapture() ) - { - if( mIsSelecting ) - { - if(mScroller) - { - mScroller->autoScroll(x, y); - } - S32 clamped_x = llclamp(x, mVisibleTextRect.mLeft, mVisibleTextRect.mRight); - S32 clamped_y = llclamp(y, mVisibleTextRect.mBottom, mVisibleTextRect.mTop); - setCursorAtLocalPos( clamped_x, clamped_y, true ); - mSelectionEnd = mCursorPos; - } - LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; - getWindow()->setCursor(UI_CURSOR_IBEAM); - handled = true; - } - - if( !handled ) - { - // Pass to children - handled = LLTextBase::handleHover(x, y, mask); - } - - if( handled ) - { - // Delay cursor flashing - resetCursorBlink(); - } - - if( !handled ) - { - getWindow()->setCursor(UI_CURSOR_IBEAM); - handled = true; - } - - return handled; -} - - -bool LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // if I'm not currently selecting text - if (!(mIsSelecting && hasMouseCapture())) - { - // let text segments handle mouse event - handled = LLTextBase::handleMouseUp(x, y, mask); - } - - if( !handled ) - { - if( mIsSelecting ) - { - if(mScroller) - { - mScroller->autoScroll(x, y); - } - S32 clamped_x = llclamp(x, mVisibleTextRect.mLeft, mVisibleTextRect.mRight); - S32 clamped_y = llclamp(y, mVisibleTextRect.mBottom, mVisibleTextRect.mTop); - setCursorAtLocalPos( clamped_x, clamped_y, true ); - endSelection(); - } - - // take selection to 'primary' clipboard - updatePrimary(); - - handled = true; - } - - // Delay cursor flashing - resetCursorBlink(); - - if( hasMouseCapture() ) - { - gFocusMgr.setMouseCapture( NULL ); - - handled = true; - } - - return handled; -} - - -bool LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // let scrollbar and text segments have first dibs - handled = LLTextBase::handleDoubleClick(x, y, mask); - - if( !handled ) - { - setCursorAtLocalPos( x, y, false ); - deselect(); - - LLWString text = getWText(); - - if( LLWStringUtil::isPartOfWord( text[mCursorPos] ) ) - { - // Select word the cursor is over - while ((mCursorPos > 0) && LLWStringUtil::isPartOfWord(text[mCursorPos-1])) - { - if (!setCursorPos(mCursorPos - 1)) break; - } - startSelection(); - - while ((mCursorPos < (S32)text.length()) && LLWStringUtil::isPartOfWord( text[mCursorPos] ) ) - { - if (!setCursorPos(mCursorPos + 1)) break; - } - - mSelectionEnd = mCursorPos; - } - else if ((mCursorPos < (S32)text.length()) && !iswspace( text[mCursorPos]) ) - { - // Select the character the cursor is over - startSelection(); - setCursorPos(mCursorPos + 1); - mSelectionEnd = mCursorPos; - } - - // We don't want handleMouseUp() to "finish" the selection (and thereby - // set mSelectionEnd to where the mouse is), so we finish the selection here. - mIsSelecting = false; - - // delay cursor flashing - resetCursorBlink(); - - // take selection to 'primary' clipboard - updatePrimary(); - - handled = true; - } - - return handled; -} - - -//---------------------------------------------------------------------------- -// Returns change in number of characters in mText - -S32 LLTextEditor::execute( TextCmd* cmd ) -{ - if (!mReadOnly && mShowEmojiHelper) - { - // Any change to our contents should always hide the helper - LLEmojiHelper::instance().hideHelper(this); - } - - S32 delta = 0; - if( cmd->execute(this, &delta) ) - { - // Delete top of undo stack - undo_stack_t::iterator enditer = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); - std::for_each(mUndoStack.begin(), enditer, DeletePointer()); - mUndoStack.erase(mUndoStack.begin(), enditer); - // Push the new command is now on the top (front) of the undo stack. - mUndoStack.push_front(cmd); - mLastCmd = cmd; - - bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(getViewModel()->getDisplay()); - if (need_to_rollback) - { - LLUI::getInstance()->reportBadKeystroke(); - mPrevalidator.showLastErrorUsingTimeout(); - - // get rid of this last command and clean up undo stack - undo(); - - // remove any evidence of this command from redo history - mUndoStack.pop_front(); - delete cmd; - - // failure, nothing changed - delta = 0; - } - } - else - { - // Operation failed, so don't put it on the undo stack. - delete cmd; - } - - return delta; -} - -S32 LLTextEditor::insert(S32 pos, const LLWString &wstr, bool group_with_next_op, LLTextSegmentPtr segment) -{ - return execute( new TextCmdInsert( pos, group_with_next_op, wstr, segment ) ); -} - -S32 LLTextEditor::remove(S32 pos, S32 length, bool group_with_next_op) -{ - S32 end_pos = getEditableIndex(pos + length, true); - bool removedChar = false; - - segment_vec_t segments_to_remove; - // store text segments - getSegmentsInRange(segments_to_remove, pos, pos + length, false); - - if (pos <= end_pos) - { - removedChar = execute( new TextCmdRemove( pos, group_with_next_op, end_pos - pos, segments_to_remove ) ); - } - - return removedChar; -} - -S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc) -{ - if ((S32)getLength() == pos) - { - return addChar(pos, wc); - } - else - { - return execute(new TextCmdOverwriteChar(pos, false, wc)); - } -} - -// Remove a single character from the text. Tries to remove -// a pseudo-tab (up to for spaces in a row) -void LLTextEditor::removeCharOrTab() -{ - if (!getEnabled()) - { - return; - } - - if (mCursorPos > 0) - { - S32 chars_to_remove = 1; - - LLWString text = getWText(); - if (text[mCursorPos - 1] == ' ') - { - // Try to remove a "tab" - S32 offset = getLineOffsetFromDocIndex(mCursorPos); - if (offset > 0) - { - chars_to_remove = offset % SPACES_PER_TAB; - if (chars_to_remove == 0) - { - chars_to_remove = SPACES_PER_TAB; - } - - for (S32 i = 0; i < chars_to_remove; i++) - { - if (text[mCursorPos - i - 1] != ' ') - { - // Fewer than a full tab's worth of spaces, so - // just delete a single character. - chars_to_remove = 1; - break; - } - } - } - } - - for (S32 i = 0; i < chars_to_remove; i++) - { - setCursorPos(mCursorPos - 1); - remove(mCursorPos, 1, false); - } - - tryToShowEmojiHelper(); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } -} - -// Remove a single character from the text -S32 LLTextEditor::removeChar(S32 pos) -{ - return remove(pos, 1, false); -} - -void LLTextEditor::removeChar() -{ - if (!getEnabled()) - { - return; - } - - if (mCursorPos > 0) - { - setCursorPos(mCursorPos - 1); - removeChar(mCursorPos); - tryToShowEmojiHelper(); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } -} - -// Add a single character to the text -S32 LLTextEditor::addChar(S32 pos, llwchar wc) -{ - if ((wstring_utf8_length(getWText()) + wchar_utf8_length(wc)) > mMaxTextByteLength) - { - LLUI::getInstance()->reportBadKeystroke(); - return 0; - } - - if (mLastCmd && mLastCmd->canExtend(pos)) - { - if (mPrevalidator) - { - // get a copy of current text contents - LLWString test_string(getViewModel()->getDisplay()); - - // modify text contents as if this addChar succeeded - llassert(pos <= (S32)test_string.size()); - test_string.insert(pos, 1, wc); - if (!mPrevalidator.validate(test_string)) - { - LLUI::getInstance()->reportBadKeystroke(); - mPrevalidator.showLastErrorUsingTimeout(); - return 0; - } - } - - S32 delta = 0; - mLastCmd->extendAndExecute(this, pos, wc, &delta); - - return delta; - } - - return execute(new TextCmdAddChar(pos, false, wc, LLTextSegmentPtr())); -} - -void LLTextEditor::addChar(llwchar wc) -{ - if (!getEnabled()) - { - return; - } - - if (hasSelection()) - { - deleteSelection(true); - } - else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) - { - removeChar(mCursorPos); - } - - setCursorPos(mCursorPos + addChar( mCursorPos, wc )); - tryToShowEmojiHelper(); - - if (!mReadOnly && mAutoreplaceCallback != NULL) - { - // autoreplace the text, if necessary - S32 replacement_start; - S32 replacement_length; - LLWString replacement_string; - S32 new_cursor_pos = mCursorPos; - mAutoreplaceCallback(replacement_start, replacement_length, replacement_string, new_cursor_pos, getWText()); - - if (replacement_length > 0 || !replacement_string.empty()) - { - remove(replacement_start, replacement_length, true); - insert(replacement_start, replacement_string, false, LLTextSegmentPtr()); - setCursorPos(new_cursor_pos); - } - } -} - -void LLTextEditor::showEmojiHelper() -{ - if (mReadOnly || !mShowEmojiHelper) - return; - - const LLRect cursorRect(getLocalRectFromDocIndex(mCursorPos)); - auto cb = [this](llwchar emoji) { insertEmoji(emoji); }; - LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, LLStringUtil::null, cb); -} - -void LLTextEditor::tryToShowEmojiHelper() -{ - if (mReadOnly || !mShowEmojiHelper) - return; - - S32 shortCodePos; - LLWString wtext(getWText()); - if (LLEmojiHelper::isCursorInEmojiCode(wtext, mCursorPos, &shortCodePos)) - { - const LLRect cursorRect(getLocalRectFromDocIndex(shortCodePos)); - const LLWString wpart(wtext.substr(shortCodePos, mCursorPos - shortCodePos)); - const std::string part(wstring_to_utf8str(wpart)); - auto cb = [this](llwchar emoji) { handleEmojiCommit(emoji); }; - LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, part, cb); - } - else - { - LLEmojiHelper::instance().hideHelper(); - } -} - -void LLTextEditor::addLineBreakChar(bool group_together) -{ - if( !getEnabled() ) - { - return; - } - if( hasSelection() ) - { - deleteSelection(true); - } - else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) - { - removeChar(mCursorPos); - } - - LLStyleConstSP sp(new LLStyle(LLStyle::Params())); - LLTextSegmentPtr segment = new LLLineBreakTextSegment(sp, mCursorPos); - - S32 pos = execute(new TextCmdAddChar(mCursorPos, group_together, '\n', segment)); - - setCursorPos(mCursorPos + pos); -} - - -bool LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) -{ - bool handled = false; - - if( mask & MASK_SHIFT ) - { - handled = true; - - switch( key ) - { - case KEY_LEFT: - if( 0 < mCursorPos ) - { - startSelection(); - setCursorPos(mCursorPos - 1); - if( mask & MASK_CONTROL ) - { - setCursorPos(prevWordPos(mCursorPos)); - } - mSelectionEnd = mCursorPos; - } - break; - - case KEY_RIGHT: - if( mCursorPos < getLength() ) - { - startSelection(); - setCursorPos(mCursorPos + 1); - if( mask & MASK_CONTROL ) - { - setCursorPos(nextWordPos(mCursorPos)); - } - mSelectionEnd = mCursorPos; - } - break; - - case KEY_UP: - startSelection(); - changeLine( -1 ); - mSelectionEnd = mCursorPos; - break; - - case KEY_PAGE_UP: - startSelection(); - changePage( -1 ); - mSelectionEnd = mCursorPos; - break; - - case KEY_HOME: - startSelection(); - if( mask & MASK_CONTROL ) - { - setCursorPos(0); - } - else - { - startOfLine(); - } - mSelectionEnd = mCursorPos; - break; - - case KEY_DOWN: - startSelection(); - changeLine( 1 ); - mSelectionEnd = mCursorPos; - break; - - case KEY_PAGE_DOWN: - startSelection(); - changePage( 1 ); - mSelectionEnd = mCursorPos; - break; - - case KEY_END: - startSelection(); - if( mask & MASK_CONTROL ) - { - setCursorPos(getLength()); - } - else - { - endOfLine(); - } - mSelectionEnd = mCursorPos; - break; - - default: - handled = false; - break; - } - } - - if( handled ) - { - // take selection to 'primary' clipboard - updatePrimary(); - } - - return handled; -} - -bool LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) -{ - bool handled = false; - - // Ignore capslock key - if( MASK_NONE == mask ) - { - handled = true; - switch( key ) - { - case KEY_UP: - changeLine( -1 ); - break; - - case KEY_PAGE_UP: - changePage( -1 ); - break; - - case KEY_HOME: - startOfLine(); - break; - - case KEY_DOWN: - changeLine( 1 ); - deselect(); - break; - - case KEY_PAGE_DOWN: - changePage( 1 ); - break; - - case KEY_END: - endOfLine(); - break; - - case KEY_LEFT: - if( hasSelection() ) - { - setCursorPos(llmin( mSelectionStart, mSelectionEnd )); - } - else - { - if( 0 < mCursorPos ) - { - setCursorPos(mCursorPos - 1); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - } - break; - - case KEY_RIGHT: - if( hasSelection() ) - { - setCursorPos(llmax( mSelectionStart, mSelectionEnd )); - } - else - { - if( mCursorPos < getLength() ) - { - setCursorPos(mCursorPos + 1); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - } - break; - - default: - handled = false; - break; - } - } - - if (handled) - { - deselect(); - } - - return handled; -} - -void LLTextEditor::deleteSelection(bool group_with_next_op ) -{ - if( getEnabled() && hasSelection() ) - { - S32 pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = llabs( mSelectionStart - mSelectionEnd ); - - remove( pos, length, group_with_next_op ); - - deselect(); - setCursorPos(pos); - } -} - -// virtual -bool LLTextEditor::canCut() const -{ - return !mReadOnly && hasSelection(); -} - -// cut selection to clipboard -void LLTextEditor::cut() -{ - if( !canCut() ) - { - return; - } - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = llabs( mSelectionStart - mSelectionEnd ); - LLClipboard::instance().copyToClipboard( getWText(), left_pos, length); - deleteSelection( false ); - - onKeyStroke(); -} - -bool LLTextEditor::canCopy() const -{ - return hasSelection(); -} - -// copy selection to clipboard -void LLTextEditor::copy() -{ - if( !canCopy() ) - { - return; - } - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = llabs( mSelectionStart - mSelectionEnd ); - LLClipboard::instance().copyToClipboard(getWText(), left_pos, length); -} - -bool LLTextEditor::canPaste() const -{ - return !mReadOnly && LLClipboard::instance().isTextAvailable(); -} - -// paste from clipboard -void LLTextEditor::paste() -{ - bool is_primary = false; - pasteHelper(is_primary); -} - -// paste from primary -void LLTextEditor::pastePrimary() -{ - bool is_primary = true; - pasteHelper(is_primary); -} - -// paste from primary (itsprimary==true) or clipboard (itsprimary==false) -void LLTextEditor::pasteHelper(bool is_primary) -{ - struct BoolReset - { - BoolReset(bool& value) : mValuePtr(&value) { *mValuePtr = false; } - ~BoolReset() { *mValuePtr = true; } - bool* mValuePtr; - } reset(mParseOnTheFly); - - bool can_paste_it; - if (is_primary) - { - can_paste_it = canPastePrimary(); - } - else - { - can_paste_it = canPaste(); - } - - if (!can_paste_it) - { - return; - } - - LLWString paste; - LLClipboard::instance().pasteFromClipboard(paste, is_primary); - - if (paste.empty()) - { - return; - } - - // Delete any selected characters (the paste replaces them) - if( (!is_primary) && hasSelection() ) - { - deleteSelection(true); - } - - // Clean up string (replace tabs and remove characters that our fonts don't support). - LLWString clean_string(paste); - cleanStringForPaste(clean_string); - - // Insert the new text into the existing text. - - //paste text with linebreaks. - pasteTextWithLinebreaks(clean_string); - - deselect(); - - onKeyStroke(); -} - - -// Clean up string (replace tabs and remove characters that our fonts don't support). -void LLTextEditor::cleanStringForPaste(LLWString & clean_string) -{ - std::string clean_string_utf = wstring_to_utf8str(clean_string); - std::replace( clean_string_utf.begin(), clean_string_utf.end(), '\r', '\n'); - clean_string = utf8str_to_wstring(clean_string_utf); - - LLWStringUtil::replaceTabsWithSpaces(clean_string, SPACES_PER_TAB); - if( mAllowEmbeddedItems ) - { - const llwchar LF = 10; - S32 len = clean_string.length(); - for( S32 i = 0; i < len; i++ ) - { - llwchar wc = clean_string[i]; - if( (wc < LLFontFreetype::FIRST_CHAR) && (wc != LF) ) - { - clean_string[i] = LL_UNKNOWN_CHAR; - } - else if (wc >= FIRST_EMBEDDED_CHAR && wc <= LAST_EMBEDDED_CHAR) - { - clean_string[i] = pasteEmbeddedItem(wc); - } - } - } -} - - -void LLTextEditor::pasteTextWithLinebreaks(LLWString & clean_string) -{ - std::basic_string::size_type start = 0; - std::basic_string::size_type pos = clean_string.find('\n',start); - - while((pos != -1) && (pos != clean_string.length() -1)) - { - if(pos!=start) - { - std::basic_string str = std::basic_string(clean_string,start,pos-start); - setCursorPos(mCursorPos + insert(mCursorPos, str, true, LLTextSegmentPtr())); - } - addLineBreakChar(true); // Add a line break and group with the next addition. - - start = pos+1; - pos = clean_string.find('\n',start); - } - - if (pos != start) - { - std::basic_string str = std::basic_string(clean_string,start,clean_string.length()-start); - setCursorPos(mCursorPos + insert(mCursorPos, str, false, LLTextSegmentPtr())); - } - else - { - addLineBreakChar(false); // Add a line break and end the grouping. - } -} - -// copy selection to primary -void LLTextEditor::copyPrimary() -{ - if( !canCopy() ) - { - return; - } - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - S32 length = llabs( mSelectionStart - mSelectionEnd ); - LLClipboard::instance().copyToClipboard(getWText(), left_pos, length, true); -} - -bool LLTextEditor::canPastePrimary() const -{ - return !mReadOnly && LLClipboard::instance().isTextAvailable(true); -} - -void LLTextEditor::updatePrimary() -{ - if (canCopy()) - { - copyPrimary(); - } -} - -bool LLTextEditor::handleControlKey(const KEY key, const MASK mask) -{ - bool handled = false; - - if( mask & MASK_CONTROL ) - { - handled = true; - - switch( key ) - { - case KEY_HOME: - if( mask & MASK_SHIFT ) - { - startSelection(); - setCursorPos(0); - mSelectionEnd = mCursorPos; - } - else - { - // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down - // all move the cursor as if clicking, so should deselect. - deselect(); - startOfDoc(); - } - break; - - case KEY_END: - { - if( mask & MASK_SHIFT ) - { - startSelection(); - } - else - { - // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down - // all move the cursor as if clicking, so should deselect. - deselect(); - } - endOfDoc(); - if( mask & MASK_SHIFT ) - { - mSelectionEnd = mCursorPos; - } - break; - } - - case KEY_RIGHT: - if( mCursorPos < getLength() ) - { - // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down - // all move the cursor as if clicking, so should deselect. - deselect(); - - setCursorPos(nextWordPos(mCursorPos + 1)); - } - break; - - - case KEY_LEFT: - if( mCursorPos > 0 ) - { - // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down - // all move the cursor as if clicking, so should deselect. - deselect(); - - setCursorPos(prevWordPos(mCursorPos - 1)); - } - break; - - default: - handled = false; - break; - } - } - - if (handled && !gFocusMgr.getMouseCapture()) - { - updatePrimary(); - } - - return handled; -} - - -bool LLTextEditor::handleSpecialKey(const KEY key, const MASK mask) - { - bool handled = true; - - if (mReadOnly) return false; - - switch( key ) - { - case KEY_INSERT: - if (mask == MASK_NONE) - { - gKeyboard->toggleInsertMode(); - } - break; - - case KEY_BACKSPACE: - if( hasSelection() ) - { - deleteSelection(false); - } - else - if( 0 < mCursorPos ) - { - removeCharOrTab(); - } - else - { - LLUI::getInstance()->reportBadKeystroke(); - } - break; - - - case KEY_RETURN: - if (mask == MASK_NONE) - { - if( hasSelection() && !mKeepSelectionOnReturn ) - { - deleteSelection(false); - } - if (mAutoIndent) - { - autoIndent(); - } - } - else - { - handled = false; - break; - } - break; - - case KEY_TAB: - if (mask & MASK_CONTROL) - { - handled = false; - break; - } - if( hasSelection() && selectionContainsLineBreaks() ) - { - indentSelectedLines( (mask & MASK_SHIFT) ? -SPACES_PER_TAB : SPACES_PER_TAB ); - } - else - { - if( hasSelection() ) - { - deleteSelection(false); - } - - S32 offset = getLineOffsetFromDocIndex(mCursorPos); - - S32 spaces_needed = SPACES_PER_TAB - (offset % SPACES_PER_TAB); - for( S32 i=0; i < spaces_needed; i++ ) - { - addChar( ' ' ); - } - } - break; - - default: - handled = false; - break; - } - - if (handled) - { - onKeyStroke(); - } - return handled; -} - - -void LLTextEditor::unindentLineBeforeCloseBrace() -{ - if( mCursorPos >= 1 ) - { - LLWString text = getWText(); - if( ' ' == text[ mCursorPos - 1 ] ) - { - S32 line = getLineNumFromDocIndex(mCursorPos, false); - S32 line_start = getLineStart(line); - - // Jump over spaces in the current line - while ((' ' == text[line_start]) && (line_start < mCursorPos)) - { - line_start++; - } - - // Make sure there is nothing but ' ' before the Brace we are unindenting - if (line_start == mCursorPos) - { - removeCharOrTab(); - } - } - } -} - - -bool LLTextEditor::handleKeyHere(KEY key, MASK mask ) -{ - bool handled = false; - - // Special case for TAB. If want to move to next field, report - // not handled and let the parent take care of field movement. - if (KEY_TAB == key && mTabsToNextField) - { - return false; - } - - if (mReadOnly && mScroller) - { - handled = (mScroller && mScroller->handleKeyHere( key, mask )) - || handleSelectionKey(key, mask) - || handleControlKey(key, mask); - } - else - { - if (!mReadOnly && mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) - { - return true; - } - - if (mEnableTooltipPaste && - LLToolTipMgr::instance().toolTipVisible() && - LLToolTipMgr::instance().isTooltipPastable() && - KEY_TAB == key) - { // Paste the first line of a tooltip into the editor - std::string message; - LLToolTipMgr::instance().getToolTipMessage(message); - LLWString tool_tip_text(utf8str_to_wstring(message)); - - if (tool_tip_text.size() > 0) - { - // Delete any selected characters (the tooltip text replaces them) - if(hasSelection()) - { - deleteSelection(true); - } - - std::basic_string::size_type pos = tool_tip_text.find('\n',0); - if (pos != -1) - { // Extract the first line of the tooltip - tool_tip_text = std::basic_string(tool_tip_text, 0, pos); - } - - // Add the text - cleanStringForPaste(tool_tip_text); - pasteTextWithLinebreaks(tool_tip_text); - handled = true; - } - } - else - { // Normal key handling - handled = handleNavigationKey( key, mask ) - || handleSelectionKey(key, mask) - || handleControlKey(key, mask) - || handleSpecialKey(key, mask); - } - } - - if( handled ) - { - resetCursorBlink(); - needsScroll(); - - if (mShowEmojiHelper) - { - // Dismiss the helper whenever we handled a key that it didn't - LLEmojiHelper::instance().hideHelper(this); - } - } - - return handled; -} - - -bool LLTextEditor::handleUnicodeCharHere(llwchar uni_char) -{ - if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL - { - return false; - } - - bool handled = false; - - // Handle most keys only if the text editor is writeable. - if( !mReadOnly ) - { - if (mShowEmojiHelper && uni_char < 0x80 && LLEmojiHelper::instance().handleKey(this, (KEY)uni_char, MASK_NONE)) - { - return true; - } - - if( mAutoIndent && '}' == uni_char ) - { - unindentLineBeforeCloseBrace(); - } - - // TODO: KLW Add auto show of tool tip on ( - addChar( uni_char ); - - // Keys that add characters temporarily hide the cursor - getWindow()->hideCursorUntilMouseMove(); - - handled = true; - } - - if( handled ) - { - resetCursorBlink(); - - // Most keystrokes will make the selection box go away, but not all will. - deselect(); - - onKeyStroke(); - } - - return handled; -} - - -// virtual -bool LLTextEditor::canDoDelete() const -{ - return !mReadOnly && ( !mPassDelete || ( hasSelection() || (mCursorPos < getLength())) ); -} - -void LLTextEditor::doDelete() -{ - if( !canDoDelete() ) - { - return; - } - if( hasSelection() ) - { - deleteSelection(false); - } - else - if( mCursorPos < getLength() ) - { - S32 i; - S32 chars_to_remove = 1; - LLWString text = getWText(); - if( (text[ mCursorPos ] == ' ') && (mCursorPos + SPACES_PER_TAB < getLength()) ) - { - // Try to remove a full tab's worth of spaces - S32 offset = getLineOffsetFromDocIndex(mCursorPos); - chars_to_remove = SPACES_PER_TAB - (offset % SPACES_PER_TAB); - if( chars_to_remove == 0 ) - { - chars_to_remove = SPACES_PER_TAB; - } - - for( i = 0; i < chars_to_remove; i++ ) - { - if( text[mCursorPos + i] != ' ' ) - { - chars_to_remove = 1; - break; - } - } - } - - for( i = 0; i < chars_to_remove; i++ ) - { - setCursorPos(mCursorPos + 1); - removeChar(); - } - - } - - onKeyStroke(); -} - -//---------------------------------------------------------------------------- - - -void LLTextEditor::blockUndo() -{ - mBaseDocIsPristine = false; - mLastCmd = NULL; - std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); - mUndoStack.clear(); -} - -// virtual -bool LLTextEditor::canUndo() const -{ - return !mReadOnly && mLastCmd != NULL; -} - -void LLTextEditor::undo() -{ - if( !canUndo() ) - { - return; - } - deselect(); - S32 pos = 0; - do - { - pos = mLastCmd->undo(this); - undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); - if (iter != mUndoStack.end()) - ++iter; - if (iter != mUndoStack.end()) - mLastCmd = *iter; - else - mLastCmd = NULL; - - } while( mLastCmd && mLastCmd->groupWithNext() ); - - setCursorPos(pos); - - onKeyStroke(); -} - -bool LLTextEditor::canRedo() const -{ - return !mReadOnly && (mUndoStack.size() > 0) && (mLastCmd != mUndoStack.front()); -} - -void LLTextEditor::redo() -{ - if( !canRedo() ) - { - return; - } - deselect(); - S32 pos = 0; - do - { - if( !mLastCmd ) - { - mLastCmd = mUndoStack.back(); - } - else - { - undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); - if (iter != mUndoStack.begin()) - mLastCmd = *(--iter); - else - mLastCmd = NULL; - } - - if( mLastCmd ) - { - pos = mLastCmd->redo(this); - } - } while( - mLastCmd && - mLastCmd->groupWithNext() && - (mLastCmd != mUndoStack.front()) ); - - setCursorPos(pos); - - onKeyStroke(); -} - -void LLTextEditor::onFocusReceived() -{ - LLTextBase::onFocusReceived(); - updateAllowingLanguageInput(); -} - -void LLTextEditor::focusLostHelper() -{ - updateAllowingLanguageInput(); - - // Route menu back to the default - if( gEditMenuHandler == this ) - { - gEditMenuHandler = NULL; - } - - if (mCommitOnFocusLost) - { - onCommit(); - } - - // Make sure cursor is shown again - getWindow()->showCursorFromMouseMove(); -} - -void LLTextEditor::onFocusLost() -{ - focusLostHelper(); - LLTextBase::onFocusLost(); -} - -void LLTextEditor::onCommit() -{ - setControlValue(getValue()); - LLTextBase::onCommit(); -} - -void LLTextEditor::setEnabled(bool enabled) -{ - // just treat enabled as read-only flag - bool read_only = !enabled; - if (read_only != mReadOnly) - { - //mReadOnly = read_only; - LLTextBase::setReadOnly(read_only); - updateSegments(); - updateAllowingLanguageInput(); - } -} - -void LLTextEditor::showContextMenu(S32 x, S32 y) -{ - LLContextMenu* menu = static_cast(mContextMenuHandle.get()); - if (!menu) - { - llassert(LLMenuGL::sMenuContainer != NULL); - menu = LLUICtrlFactory::createFromFile("menu_text_editor.xml", - LLMenuGL::sMenuContainer, - LLMenuHolderGL::child_registry_t::instance()); - if(!menu) - { - LL_WARNS() << "Failed to create menu for LLTextEditor: " << getName() << LL_ENDL; - return; - } - mContextMenuHandle = menu->getHandle(); - } - - // Route menu to this class - // previously this was done in ::handleRightMoseDown: - //if(hasTabStop()) - // setFocus(true) - why? weird... - // and then inside setFocus - // .... - // gEditMenuHandler = this; - // .... - // but this didn't work in all cases and just weird... - //why not here? - // (all this was done for EXT-4443) - - gEditMenuHandler = this; - - S32 screen_x, screen_y; - localPointToScreen(x, y, &screen_x, &screen_y); - - setCursorAtLocalPos(x, y, false); - if (hasSelection()) - { - if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) ) - { - deselect(); - } - else - { - setCursorPos(llmax(mSelectionStart, mSelectionEnd)); - } - } - - bool use_spellcheck = getSpellCheck(), is_misspelled = false; - if (use_spellcheck) - { - mSuggestionList.clear(); - - // If the cursor is on a misspelled word, retrieve suggestions for it - std::string misspelled_word = getMisspelledWord(mCursorPos); - if ((is_misspelled = !misspelled_word.empty())) - { - LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList); - } - } - - menu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty())); - menu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled)); - menu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled)); - menu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled)); - menu->show(screen_x, screen_y, this); -} - - -void LLTextEditor::drawPreeditMarker() -{ - static LLUICachedControl preedit_marker_brightness ("UIPreeditMarkerBrightness", 0); - static LLUICachedControl preedit_marker_gap ("UIPreeditMarkerGap", 0); - static LLUICachedControl preedit_marker_position ("UIPreeditMarkerPosition", 0); - static LLUICachedControl preedit_marker_thickness ("UIPreeditMarkerThickness", 0); - static LLUICachedControl preedit_standout_brightness ("UIPreeditStandoutBrightness", 0); - static LLUICachedControl preedit_standout_gap ("UIPreeditStandoutGap", 0); - static LLUICachedControl preedit_standout_position ("UIPreeditStandoutPosition", 0); - static LLUICachedControl preedit_standout_thickness ("UIPreeditStandoutThickness", 0); - - if (!hasPreeditString()) - { - return; - } - - const LLWString textString(getWText()); - const llwchar *text = textString.c_str(); - const S32 text_len = getLength(); - const S32 num_lines = getLineCount(); - - S32 cur_line = getFirstVisibleLine(); - if (cur_line >= num_lines) - { - return; - } - - const S32 line_height = mFont->getLineHeight(); - - S32 line_start = getLineStart(cur_line); - S32 line_y = mVisibleTextRect.mTop - line_height; - while((mVisibleTextRect.mBottom <= line_y) && (num_lines > cur_line)) - { - S32 next_start = -1; - S32 line_end = text_len; - - if ((cur_line + 1) < num_lines) - { - next_start = getLineStart(cur_line + 1); - line_end = next_start; - } - if ( text[line_end-1] == '\n' ) - { - --line_end; - } - - // Does this line contain preedits? - if (line_start >= mPreeditPositions.back()) - { - // We have passed the preedits. - break; - } - if (line_end > mPreeditPositions.front()) - { - for (U32 i = 0; i < mPreeditStandouts.size(); i++) - { - S32 left = mPreeditPositions[i]; - S32 right = mPreeditPositions[i + 1]; - if (right <= line_start || left >= line_end) - { - continue; - } - - line_info& line = mLineInfoList[cur_line]; - LLRect text_rect(line.mRect); - text_rect.mRight = mDocumentView->getRect().getWidth(); // clamp right edge to document extents - text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom); // adjust by scroll position - - S32 preedit_left = text_rect.mLeft; - if (left > line_start) - { - preedit_left += mFont->getWidth(text, line_start, left - line_start); - } - S32 preedit_right = text_rect.mLeft; - if (right < line_end) - { - preedit_right += mFont->getWidth(text, line_start, right - line_start); - } - else - { - preedit_right += mFont->getWidth(text, line_start, line_end - line_start); - } - - if (mPreeditStandouts[i]) - { - gl_rect_2d(preedit_left + preedit_standout_gap, - text_rect.mBottom + mFont->getDescenderHeight() - 1, - preedit_right - preedit_standout_gap - 1, - text_rect.mBottom + mFont->getDescenderHeight() - 1 - preedit_standout_thickness, - (mCursorColor.get() * preedit_standout_brightness + mWriteableBgColor.get() * (1 - preedit_standout_brightness)).setAlpha(1.0f)); - } - else - { - gl_rect_2d(preedit_left + preedit_marker_gap, - text_rect.mBottom + mFont->getDescenderHeight() - 1, - preedit_right - preedit_marker_gap - 1, - text_rect.mBottom + mFont->getDescenderHeight() - 1 - preedit_marker_thickness, - (mCursorColor.get() * preedit_marker_brightness + mWriteableBgColor.get() * (1 - preedit_marker_brightness)).setAlpha(1.0f)); - } - } - } - - // move down one line - line_y -= line_height; - line_start = next_start; - cur_line++; - } -} - -void LLTextEditor::draw() -{ - { - // pad clipping rectangle so that cursor can draw at full width - // when at left edge of mVisibleTextRect - LLRect clip_rect(mVisibleTextRect); - clip_rect.stretch(1); - LLLocalClipRect clip(clip_rect); - } - - LLTextBase::draw(); - - drawPreeditMarker(); - - //RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret - // when in readonly mode - mBorder->setKeyboardFocusHighlight( hasFocus() );// && !mReadOnly); -} - -// Start or stop the editor from accepting text-editing keystrokes -// see also LLLineEditor -void LLTextEditor::setFocus( bool new_state ) -{ - bool old_state = hasFocus(); - - // Don't change anything if the focus state didn't change - if (new_state == old_state) return; - - // Notify early if we are losing focus. - if (!new_state) - { - getWindow()->allowLanguageTextInput(this, false); - } - - LLTextBase::setFocus( new_state ); - - if( new_state ) - { - // Route menu to this class - gEditMenuHandler = this; - - // Don't start the cursor flashing right away - resetCursorBlink(); - } - else - { - // Route menu back to the default - if( gEditMenuHandler == this ) - { - gEditMenuHandler = NULL; - } - - endSelection(); - } -} - -// public -void LLTextEditor::setCursorAndScrollToEnd() -{ - deselect(); - endOfDoc(); -} - -void LLTextEditor::getCurrentLineAndColumn( S32* line, S32* col, bool include_wordwrap ) -{ - *line = getLineNumFromDocIndex(mCursorPos, include_wordwrap); - *col = getLineOffsetFromDocIndex(mCursorPos, include_wordwrap); -} - -void LLTextEditor::autoIndent() -{ - // Count the number of spaces in the current line - S32 line = getLineNumFromDocIndex(mCursorPos, false); - S32 line_start = getLineStart(line); - S32 space_count = 0; - S32 i; - - LLWString text = getWText(); - S32 offset = getLineOffsetFromDocIndex(mCursorPos); - while(( ' ' == text[line_start] ) && (space_count < offset)) - { - space_count++; - line_start++; - } - - // If we're starting a braced section, indent one level. - if( (mCursorPos > 0) && (text[mCursorPos -1] == '{') ) - { - space_count += SPACES_PER_TAB; - } - - // Insert that number of spaces on the new line - - //appendLineBreakSegment(LLStyle::Params());//addChar( '\n' ); - addLineBreakChar(); - - for( i = 0; i < space_count; i++ ) - { - addChar( ' ' ); - } -} - -// Inserts new text at the cursor position -void LLTextEditor::insertText(const std::string &new_text) -{ - bool enabled = getEnabled(); - setEnabled( true ); - - // Delete any selected characters (the insertion replaces them) - if( hasSelection() ) - { - deleteSelection(true); - } - - setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), false, LLTextSegmentPtr() )); - - setEnabled( enabled ); -} - -void LLTextEditor::insertText(LLWString &new_text) -{ - bool enabled = getEnabled(); - setEnabled( true ); - - // Delete any selected characters (the insertion replaces them) - if( hasSelection() ) - { - deleteSelection(true); - } - - setCursorPos(mCursorPos + insert( mCursorPos, new_text, false, LLTextSegmentPtr() )); - - setEnabled( enabled ); -} - -void LLTextEditor::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo) -{ - // Save old state - S32 selection_start = mSelectionStart; - S32 selection_end = mSelectionEnd; - bool was_selecting = mIsSelecting; - S32 cursor_pos = mCursorPos; - S32 old_length = getLength(); - bool cursor_was_at_end = (mCursorPos == old_length); - - deselect(); - - setCursorPos(old_length); - - LLWString widget_wide_text = utf8str_to_wstring(text); - - LLTextSegmentPtr segment = new LLInlineViewSegment(params, old_length, old_length + widget_wide_text.size()); - insert(getLength(), widget_wide_text, false, segment); - - // Set the cursor and scroll position - if( selection_start != selection_end ) - { - mSelectionStart = selection_start; - mSelectionEnd = selection_end; - - mIsSelecting = was_selecting; - setCursorPos(cursor_pos); - } - else if( cursor_was_at_end ) - { - setCursorPos(getLength()); - } - else - { - setCursorPos(cursor_pos); - } - - if (!allow_undo) - { - blockUndo(); - } -} - -void LLTextEditor::removeTextFromEnd(S32 num_chars) -{ - if (num_chars <= 0) return; - - remove(getLength() - num_chars, num_chars, false); - - S32 len = getLength(); - setCursorPos (llclamp(mCursorPos, 0, len)); - mSelectionStart = llclamp(mSelectionStart, 0, len); - mSelectionEnd = llclamp(mSelectionEnd, 0, len); - - needsScroll(); -} - -//---------------------------------------------------------------------------- - -void LLTextEditor::onSpellCheckPerformed() -{ - if (isPristine()) - { - mBaseDocIsPristine = false; - } -} - -void LLTextEditor::makePristine() -{ - mPristineCmd = mLastCmd; - mBaseDocIsPristine = !mLastCmd; - - // Create a clean partition in the undo stack. We don't want a single command to extend from - // the "pre-pristine" state to the "post-pristine" state. - if( mLastCmd ) - { - mLastCmd->blockExtensions(); - } -} - -bool LLTextEditor::isPristine() const -{ - if( mPristineCmd ) - { - return (mPristineCmd == mLastCmd); - } - else - { - // No undo stack, so check if the version before and commands were done was the original version - return !mLastCmd && mBaseDocIsPristine; - } -} - -bool LLTextEditor::tryToRevertToPristineState() -{ - if( !isPristine() ) - { - deselect(); - S32 i = 0; - while( !isPristine() && canUndo() ) - { - undo(); - i--; - } - - while( !isPristine() && canRedo() ) - { - redo(); - i++; - } - - if( !isPristine() ) - { - // failed, so go back to where we started - while( i > 0 ) - { - undo(); - i--; - } - } - } - - return isPristine(); // true => success -} - -void LLTextEditor::updateLinkSegments() -{ - LLWString wtext = getWText(); - - // update any segments that contain a link - for (segment_set_t::iterator it = mSegments.begin(); it != mSegments.end(); ++it) - { - LLTextSegment *segment = *it; - if (segment && segment->getStyle() && segment->getStyle()->isLink()) - { - LLStyleConstSP style = segment->getStyle(); - LLStyleSP new_style(new LLStyle(*style)); - LLWString url_label = wtext.substr(segment->getStart(), segment->getEnd()-segment->getStart()); - - segment_set_t::const_iterator next_it = mSegments.upper_bound(segment); - LLTextSegment *next_segment = *next_it; - if (next_segment) - { - LLWString next_url_label = wtext.substr(next_segment->getStart(), next_segment->getEnd()-next_segment->getStart()); - std::string link_check = wstring_to_utf8str(url_label) + wstring_to_utf8str(next_url_label); - LLUrlMatch match; - - if ( LLUrlRegistry::instance().findUrl(link_check, match)) - { - if(match.getQuery() == wstring_to_utf8str(next_url_label)) - { - continue; - } - } - } - - // if the link's label (what the user can edit) is a valid Url, - // then update the link's HREF to be the same as the label text. - // This lets users edit Urls in-place. - if (acceptsTextInput() && LLUrlRegistry::instance().hasUrl(url_label)) - { - std::string new_url = wstring_to_utf8str(url_label); - LLStringUtil::trim(new_url); - new_style->setLinkHREF(new_url); - LLStyleConstSP sp(new_style); - segment->setStyle(sp); - } - } - } -} - - - -void LLTextEditor::onMouseCaptureLost() -{ - endSelection(); -} - -/////////////////////////////////////////////////////////////////// -// Hack for Notecards - -bool LLTextEditor::importBuffer(const char* buffer, S32 length ) -{ - std::istringstream instream(buffer); - - // Version 1 format: - // Linden text version 1\n - // {\n - // - // Text length \n - // (text may contain ext_char_values) - // }\n - - char tbuf[MAX_STRING]; /* Flawfinder: ignore */ - - S32 version = 0; - instream.getline(tbuf, MAX_STRING); - if( 1 != sscanf(tbuf, "Linden text version %d", &version) ) - { - LL_WARNS() << "Invalid Linden text file header " << LL_ENDL; - return false; - } - - if( 1 != version ) - { - LL_WARNS() << "Invalid Linden text file version: " << version << LL_ENDL; - return false; - } - - instream.getline(tbuf, MAX_STRING); - if( 0 != sscanf(tbuf, "{") ) - { - LL_WARNS() << "Invalid Linden text file format" << LL_ENDL; - return false; - } - - S32 text_len = 0; - instream.getline(tbuf, MAX_STRING); - if( 1 != sscanf(tbuf, "Text length %d", &text_len) ) - { - LL_WARNS() << "Invalid Linden text length field" << LL_ENDL; - return false; - } - - if( text_len > mMaxTextByteLength ) - { - LL_WARNS() << "Invalid Linden text length: " << text_len << LL_ENDL; - return false; - } - - bool success = true; - - char* text = new char[ text_len + 1]; - if (text == NULL) - { - LLError::LLUserWarningMsg::showOutOfMemory(); - LL_ERRS() << "Memory allocation failure." << LL_ENDL; - return false; - } - instream.get(text, text_len + 1, '\0'); - text[text_len] = '\0'; - if( text_len != (S32)strlen(text) )/* Flawfinder: ignore */ - { - LL_WARNS() << llformat("Invalid text length: %d != %d ",strlen(text),text_len) << LL_ENDL;/* Flawfinder: ignore */ - success = false; - } - - instream.getline(tbuf, MAX_STRING); - if( success && (0 != sscanf(tbuf, "}")) ) - { - LL_WARNS() << "Invalid Linden text file format: missing terminal }" << LL_ENDL; - success = false; - } - - if( success ) - { - // Actually set the text - setText( LLStringExplicit(text) ); - } - - delete[] text; - - startOfDoc(); - deselect(); - - return success; -} - -bool LLTextEditor::exportBuffer(std::string &buffer ) -{ - std::ostringstream outstream(buffer); - - outstream << "Linden text version 1\n"; - outstream << "{\n"; - - outstream << llformat("Text length %d\n", getLength() ); - outstream << getText(); - outstream << "}\n"; - - return true; -} - -void LLTextEditor::updateAllowingLanguageInput() -{ - LLWindow* window = getWindow(); - if (!window) - { - // test app, no window available - return; - } - if (hasFocus() && !mReadOnly) - { - window->allowLanguageTextInput(this, true); - } - else - { - window->allowLanguageTextInput(this, false); - } -} - -// Preedit is managed off the undo/redo command stack. - -bool LLTextEditor::hasPreeditString() const -{ - return (mPreeditPositions.size() > 1); -} - -void LLTextEditor::resetPreedit() -{ - if (hasSelection()) - { - if (hasPreeditString()) - { - LL_WARNS() << "Preedit and selection!" << LL_ENDL; - deselect(); - } - else - { - deleteSelection(true); - } - } - if (hasPreeditString()) - { - if (hasSelection()) - { - LL_WARNS() << "Preedit and selection!" << LL_ENDL; - deselect(); - } - - setCursorPos(mPreeditPositions.front()); - removeStringNoUndo(mCursorPos, mPreeditPositions.back() - mCursorPos); - insertStringNoUndo(mCursorPos, mPreeditOverwrittenWString); - - mPreeditWString.clear(); - mPreeditOverwrittenWString.clear(); - mPreeditPositions.clear(); - - // A call to updatePreedit should soon follow under a - // normal course of operation, so we don't need to - // maintain internal variables such as line start - // positions now. - } -} - -void LLTextEditor::updatePreedit(const LLWString &preedit_string, - const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) -{ - // Just in case. - if (mReadOnly) - { - return; - } - - getWindow()->hideCursorUntilMouseMove(); - - S32 insert_preedit_at = mCursorPos; - - mPreeditWString = preedit_string; - mPreeditPositions.resize(preedit_segment_lengths.size() + 1); - S32 position = insert_preedit_at; - for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++) - { - mPreeditPositions[i] = position; - position += preedit_segment_lengths[i]; - } - mPreeditPositions.back() = position; - - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) - { - mPreeditOverwrittenWString = getWText().substr(insert_preedit_at, mPreeditWString.length()); - removeStringNoUndo(insert_preedit_at, mPreeditWString.length()); - } - else - { - mPreeditOverwrittenWString.clear(); - } - - segment_vec_t segments; - //pass empty segments to let "insertStringNoUndo" make new LLNormalTextSegment and insert it, if needed. - insertStringNoUndo(insert_preedit_at, mPreeditWString, &segments); - - mPreeditStandouts = preedit_standouts; - - setCursorPos(insert_preedit_at + caret_position); - - // Update of the preedit should be caused by some key strokes. - resetCursorBlink(); - - onKeyStroke(); -} - -bool LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const -{ - if (control) - { - LLRect control_rect_screen; - localRectToScreen(mVisibleTextRect, &control_rect_screen); - LLUI::getInstance()->screenRectToGL(control_rect_screen, control); - } - - S32 preedit_left_position, preedit_right_position; - if (hasPreeditString()) - { - preedit_left_position = mPreeditPositions.front(); - preedit_right_position = mPreeditPositions.back(); - } - else - { - preedit_left_position = preedit_right_position = mCursorPos; - } - - const S32 query = (query_offset >= 0 ? preedit_left_position + query_offset : mCursorPos); - if (query < preedit_left_position || query > preedit_right_position) - { - return false; - } - - const S32 first_visible_line = getFirstVisibleLine(); - if (query < getLineStart(first_visible_line)) - { - return false; - } - - S32 current_line = first_visible_line; - S32 current_line_start, current_line_end; - for (;;) - { - current_line_start = getLineStart(current_line); - current_line_end = getLineStart(current_line + 1); - if (query >= current_line_start && query < current_line_end) - { - break; - } - if (current_line_start == current_line_end) - { - // We have reached on the last line. The query position must be here. - break; - } - current_line++; - } - - const LLWString textString(getWText()); - const llwchar * const text = textString.c_str(); - const S32 line_height = mFont->getLineHeight(); - - if (coord) - { - const S32 query_x = mVisibleTextRect.mLeft + mFont->getWidth(text, current_line_start, query - current_line_start); - const S32 query_y = mVisibleTextRect.mTop - (current_line - first_visible_line) * line_height - line_height / 2; - S32 query_screen_x, query_screen_y; - localPointToScreen(query_x, query_y, &query_screen_x, &query_screen_y); - LLUI::getInstance()->screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); - } - - if (bounds) - { - S32 preedit_left = mVisibleTextRect.mLeft; - if (preedit_left_position > current_line_start) - { - preedit_left += mFont->getWidth(text, current_line_start, preedit_left_position - current_line_start); - } - - S32 preedit_right = mVisibleTextRect.mLeft; - if (preedit_right_position < current_line_end) - { - preedit_right += mFont->getWidth(text, current_line_start, preedit_right_position - current_line_start); - } - else - { - preedit_right += mFont->getWidth(text, current_line_start, current_line_end - current_line_start); - } - - const S32 preedit_top = mVisibleTextRect.mTop - (current_line - first_visible_line) * line_height; - const S32 preedit_bottom = preedit_top - line_height; - - const LLRect preedit_rect_local(preedit_left, preedit_top, preedit_right, preedit_bottom); - LLRect preedit_rect_screen; - localRectToScreen(preedit_rect_local, &preedit_rect_screen); - LLUI::getInstance()->screenRectToGL(preedit_rect_screen, bounds); - } - - return true; -} - -void LLTextEditor::getSelectionRange(S32 *position, S32 *length) const -{ - if (hasSelection()) - { - *position = llmin(mSelectionStart, mSelectionEnd); - *length = llabs(mSelectionStart - mSelectionEnd); - } - else - { - *position = mCursorPos; - *length = 0; - } -} - -void LLTextEditor::getPreeditRange(S32 *position, S32 *length) const -{ - if (hasPreeditString()) - { - *position = mPreeditPositions.front(); - *length = mPreeditPositions.back() - mPreeditPositions.front(); - } - else - { - *position = mCursorPos; - *length = 0; - } -} - -void LLTextEditor::markAsPreedit(S32 position, S32 length) -{ - deselect(); - setCursorPos(position); - if (hasPreeditString()) - { - LL_WARNS() << "markAsPreedit invoked when hasPreeditString is true." << LL_ENDL; - } - mPreeditWString = LLWString( getWText(), position, length ); - if (length > 0) - { - mPreeditPositions.resize(2); - mPreeditPositions[0] = position; - mPreeditPositions[1] = position + length; - mPreeditStandouts.resize(1); - mPreeditStandouts[0] = false; - } - else - { - mPreeditPositions.clear(); - mPreeditStandouts.clear(); - } - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) - { - mPreeditOverwrittenWString = mPreeditWString; - } - else - { - mPreeditOverwrittenWString.clear(); - } -} - -S32 LLTextEditor::getPreeditFontSize() const -{ - return ll_round((F32)mFont->getLineHeight() * LLUI::getScaleFactor().mV[VY]); -} - -bool LLTextEditor::isDirty() const -{ - if(mReadOnly) - { - return false; - } - - if( mPristineCmd ) - { - return ( mPristineCmd == mLastCmd ); - } - else - { - return ( NULL != mLastCmd ); - } -} - -void LLTextEditor::setKeystrokeCallback(const keystroke_signal_t::slot_type& callback) -{ - mKeystrokeSignal.connect(callback); -} - -void LLTextEditor::onKeyStroke() -{ - mKeystrokeSignal(this); - - mSpellCheckStart = mSpellCheckEnd = -1; - mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); -} - -//virtual -void LLTextEditor::clear() -{ - getViewModel()->setDisplay(LLWStringUtil::null); - clearSegments(); -} - -bool LLTextEditor::canLoadOrSaveToFile() -{ - return !mReadOnly; -} - -S32 LLTextEditor::spacesPerTab() -{ - return SPACES_PER_TAB; -} +/** + * @file lltexteditor.cpp + * + * $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$ + */ + +// Text editor widget to let users enter a a multi-line ASCII document. + +#include "linden_common.h" + +#define LLTEXTEDITOR_CPP +#include "lltexteditor.h" + +#include "llfontfreetype.h" // for LLFontFreetype::FIRST_CHAR +#include "llfontgl.h" +#include "llgl.h" // LLGLSUIDefault() +#include "lllocalcliprect.h" +#include "llrender.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llrect.h" +#include "llfocusmgr.h" +#include "lltimer.h" +#include "llmath.h" + +#include "llclipboard.h" +#include "llemojihelper.h" +#include "llscrollbar.h" +#include "llstl.h" +#include "llstring.h" +#include "llkeyboard.h" +#include "llkeywords.h" +#include "llundo.h" +#include "llviewborder.h" +#include "llcontrol.h" +#include "llwindow.h" +#include "lltextparser.h" +#include "llscrollcontainer.h" +#include "llspellcheck.h" +#include "llpanel.h" +#include "llurlregistry.h" +#include "lltooltip.h" +#include "llmenugl.h" + +#include +#include "llcombobox.h" + +// +// Globals +// +static LLDefaultChildRegistry::Register r("simple_text_editor"); + +// Compiler optimization, generate extern template +template class LLTextEditor* LLView::getChild( + const std::string& name, bool recurse) const; + +// +// Constants +// +const S32 SPACES_PER_TAB = 4; +const F32 SPELLCHECK_DELAY = 0.5f; // delay between the last keypress and spell checking the word the cursor is on + +/////////////////////////////////////////////////////////////////// + +class LLTextEditor::TextCmdInsert : public LLTextBase::TextCmd +{ +public: + TextCmdInsert(S32 pos, bool group_with_next, const LLWString &ws, LLTextSegmentPtr segment) + : TextCmd(pos, group_with_next, segment), mWString(ws) + { + } + virtual ~TextCmdInsert() {} + virtual bool execute( LLTextBase* editor, S32* delta ) + { + *delta = insert(editor, getPosition(), mWString ); + LLWStringUtil::truncate(mWString, *delta); + //mWString = wstring_truncate(mWString, *delta); + return (*delta != 0); + } + virtual S32 undo( LLTextBase* editor ) + { + remove(editor, getPosition(), mWString.length() ); + return getPosition(); + } + virtual S32 redo( LLTextBase* editor ) + { + insert(editor, getPosition(), mWString ); + return getPosition() + mWString.length(); + } + +private: + LLWString mWString; +}; + +/////////////////////////////////////////////////////////////////// +class LLTextEditor::TextCmdAddChar : public LLTextBase::TextCmd +{ +public: + TextCmdAddChar( S32 pos, bool group_with_next, llwchar wc, LLTextSegmentPtr segment) + : TextCmd(pos, group_with_next, segment), mWString(1, wc), mBlockExtensions(false) + { + } + virtual void blockExtensions() + { + mBlockExtensions = true; + } + virtual bool canExtend(S32 pos) const + { + // cannot extend text with custom segments + if (!mSegments.empty()) return false; + + return !mBlockExtensions && (pos == getPosition() + (S32)mWString.length()); + } + virtual bool execute( LLTextBase* editor, S32* delta ) + { + *delta = insert(editor, getPosition(), mWString); + LLWStringUtil::truncate(mWString, *delta); + //mWString = wstring_truncate(mWString, *delta); + return (*delta != 0); + } + virtual bool extendAndExecute( LLTextBase* editor, S32 pos, llwchar wc, S32* delta ) + { + LLWString ws; + ws += wc; + + *delta = insert(editor, pos, ws); + if( *delta > 0 ) + { + mWString += wc; + } + return (*delta != 0); + } + virtual S32 undo( LLTextBase* editor ) + { + remove(editor, getPosition(), mWString.length() ); + return getPosition(); + } + virtual S32 redo( LLTextBase* editor ) + { + insert(editor, getPosition(), mWString ); + return getPosition() + mWString.length(); + } + +private: + LLWString mWString; + bool mBlockExtensions; + +}; + +/////////////////////////////////////////////////////////////////// + +class LLTextEditor::TextCmdOverwriteChar : public LLTextBase::TextCmd +{ +public: + TextCmdOverwriteChar( S32 pos, bool group_with_next, llwchar wc) + : TextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {} + + virtual bool execute( LLTextBase* editor, S32* delta ) + { + mOldChar = editor->getWText()[getPosition()]; + overwrite(editor, getPosition(), mChar); + *delta = 0; + return true; + } + virtual S32 undo( LLTextBase* editor ) + { + overwrite(editor, getPosition(), mOldChar); + return getPosition(); + } + virtual S32 redo( LLTextBase* editor ) + { + overwrite(editor, getPosition(), mChar); + return getPosition()+1; + } + +private: + llwchar mChar; + llwchar mOldChar; +}; + +/////////////////////////////////////////////////////////////////// + +class LLTextEditor::TextCmdRemove : public LLTextBase::TextCmd +{ +public: + TextCmdRemove( S32 pos, bool group_with_next, S32 len, segment_vec_t& segments ) : + TextCmd(pos, group_with_next), mLen(len) + { + std::swap(mSegments, segments); + } + virtual bool execute( LLTextBase* editor, S32* delta ) + { + mWString = editor->getWText().substr(getPosition(), mLen); + *delta = remove(editor, getPosition(), mLen ); + return (*delta != 0); + } + virtual S32 undo( LLTextBase* editor ) + { + insert(editor, getPosition(), mWString); + return getPosition() + mWString.length(); + } + virtual S32 redo( LLTextBase* editor ) + { + remove(editor, getPosition(), mLen ); + return getPosition(); + } +private: + LLWString mWString; + S32 mLen; +}; + + +/////////////////////////////////////////////////////////////////// +LLTextEditor::Params::Params() +: default_text("default_text"), + prevalidator("prevalidator"), + embedded_items("embedded_items", false), + ignore_tab("ignore_tab", true), + auto_indent("auto_indent", true), + default_color("default_color"), + commit_on_focus_lost("commit_on_focus_lost", false), + show_context_menu("show_context_menu"), + show_emoji_helper("show_emoji_helper"), + enable_tooltip_paste("enable_tooltip_paste") +{ + addSynonym(prevalidator, "prevalidate_callback"); + addSynonym(prevalidator, "text_type"); +} + +LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : + LLTextBase(p), + mAutoreplaceCallback(), + mBaseDocIsPristine(true), + mPristineCmd( NULL ), + mLastCmd( NULL ), + mDefaultColor( p.default_color() ), + mAutoIndent(p.auto_indent), + mParseOnTheFly(false), + mCommitOnFocusLost( p.commit_on_focus_lost), + mAllowEmbeddedItems( p.embedded_items ), + mMouseDownX(0), + mMouseDownY(0), + mTabsToNextField(p.ignore_tab), + mPrevalidator(p.prevalidator()), + mShowContextMenu(p.show_context_menu), + mShowEmojiHelper(p.show_emoji_helper), + mEnableTooltipPaste(p.enable_tooltip_paste), + mPassDelete(false), + mKeepSelectionOnReturn(false) +{ + mSourceID.generate(); + + //FIXME: use image? + LLViewBorder::Params params; + params.name = "text ed border"; + params.rect = getLocalRect(); + params.bevel_style = LLViewBorder::BEVEL_IN; + params.border_thickness = 1; + params.visible = p.border_visible; + mBorder = LLUICtrlFactory::create (params); + addChild( mBorder ); + setText(p.default_text()); + + mParseOnTheFly = true; +} + +void LLTextEditor::initFromParams( const LLTextEditor::Params& p) +{ + LLTextBase::initFromParams(p); + + // HACK: text editors always need to be enabled so that we can scroll + LLView::setEnabled(true); + + if (p.commit_on_focus_lost.isProvided()) + { + mCommitOnFocusLost = p.commit_on_focus_lost; + } + + updateAllowingLanguageInput(); +} + +LLTextEditor::~LLTextEditor() +{ + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() while LLTextEditor still valid + + // Scrollbar is deleted by LLView + std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); + mUndoStack.clear(); + // Mark the menu as dead or its retained in memory till shutdown. + LLContextMenu* menu = static_cast(mContextMenuHandle.get()); + if(menu) + { + menu->die(); + mContextMenuHandle.markDead(); + } +} + +//////////////////////////////////////////////////////////// +// LLTextEditor +// Public methods + +void LLTextEditor::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) +{ + // validate incoming text if necessary + if (mPrevalidator) + { + if (!mPrevalidator.validate(utf8str)) + { + LLUI::getInstance()->reportBadKeystroke(); + mPrevalidator.showLastErrorUsingTimeout(); + + // not valid text, nothing to do + return; + } + } + + blockUndo(); + deselect(); + + mParseOnTheFly = false; + LLTextBase::setText(utf8str, input_params); + mParseOnTheFly = true; + + resetDirty(); +} + +void LLTextEditor::selectNext(const std::string& search_text_in, bool case_insensitive, bool wrap) +{ + if (search_text_in.empty()) + { + return; + } + + LLWString text = getWText(); + LLWString search_text = utf8str_to_wstring(search_text_in); + if (case_insensitive) + { + LLWStringUtil::toLower(text); + LLWStringUtil::toLower(search_text); + } + + if (mIsSelecting) + { + LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd); + + if (selected_text == search_text) + { + // We already have this word selected, we are searching for the next. + setCursorPos(mCursorPos + search_text.size()); + } + } + + S32 loc = text.find(search_text,mCursorPos); + + // If Maybe we wrapped, search again + if (wrap && (-1 == loc)) + { + loc = text.find(search_text); + } + + // If still -1, then search_text just isn't found. + if (-1 == loc) + { + mIsSelecting = false; + mSelectionEnd = 0; + mSelectionStart = 0; + return; + } + + setCursorPos(loc); + + mIsSelecting = true; + mSelectionEnd = mCursorPos; + mSelectionStart = llmin((S32)getLength(), (S32)(mCursorPos + search_text.size())); +} + +bool LLTextEditor::replaceText(const std::string& search_text_in, const std::string& replace_text, + bool case_insensitive, bool wrap) +{ + bool replaced = false; + + if (search_text_in.empty()) + { + return replaced; + } + + LLWString search_text = utf8str_to_wstring(search_text_in); + if (mIsSelecting) + { + LLWString text = getWText(); + LLWString selected_text = text.substr(mSelectionEnd, mSelectionStart - mSelectionEnd); + + if (case_insensitive) + { + LLWStringUtil::toLower(selected_text); + LLWStringUtil::toLower(search_text); + } + + if (selected_text == search_text) + { + insertText(replace_text); + replaced = true; + } + } + + selectNext(search_text_in, case_insensitive, wrap); + return replaced; +} + +void LLTextEditor::replaceTextAll(const std::string& search_text, const std::string& replace_text, bool case_insensitive) +{ + startOfDoc(); + selectNext(search_text, case_insensitive, false); + + bool replaced = true; + while ( replaced ) + { + replaced = replaceText(search_text,replace_text, case_insensitive, false); + } +} + +S32 LLTextEditor::prevWordPos(S32 cursorPos) const +{ + LLWString wtext(getWText()); + while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') ) + { + cursorPos--; + } + while( (cursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[cursorPos-1] ) ) + { + cursorPos--; + } + return cursorPos; +} + +S32 LLTextEditor::nextWordPos(S32 cursorPos) const +{ + LLWString wtext(getWText()); + while( (cursorPos < getLength()) && LLWStringUtil::isPartOfWord( wtext[cursorPos] ) ) + { + cursorPos++; + } + while( (cursorPos < getLength()) && (wtext[cursorPos] == ' ') ) + { + cursorPos++; + } + return cursorPos; +} + +const LLTextSegmentPtr LLTextEditor::getPreviousSegment() const +{ + static LLPointer index_segment = new LLIndexSegment; + + index_segment->setStart(mCursorPos); + index_segment->setEnd(mCursorPos); + + // find segment index at character to left of cursor (or rightmost edge of selection) + segment_set_t::const_iterator it = mSegments.lower_bound(index_segment); + + if (it != mSegments.end()) + { + return *it; + } + else + { + return LLTextSegmentPtr(); + } +} + +void LLTextEditor::getSelectedSegments(LLTextEditor::segment_vec_t& segments) const +{ + S32 left = hasSelection() ? llmin(mSelectionStart, mSelectionEnd) : mCursorPos; + S32 right = hasSelection() ? llmax(mSelectionStart, mSelectionEnd) : mCursorPos; + + return getSegmentsInRange(segments, left, right, true); +} + +void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out, S32 start, S32 end, bool include_partial) const +{ + segment_set_t::const_iterator first_it = getSegIterContaining(start); + segment_set_t::const_iterator end_it = getSegIterContaining(end - 1); + if (end_it != mSegments.end()) ++end_it; + + for (segment_set_t::const_iterator it = first_it; it != end_it; ++it) + { + LLTextSegmentPtr segment = *it; + if (include_partial + || (segment->getStart() >= start + && segment->getEnd() <= end)) + { + segments_out.push_back(segment); + } + } +} + +void LLTextEditor::setShowEmojiHelper(bool show) +{ + if (!mShowEmojiHelper) + { + LLEmojiHelper::instance().hideHelper(this); + } + + mShowEmojiHelper = show; +} + +bool LLTextEditor::selectionContainsLineBreaks() +{ + if (hasSelection()) + { + S32 left = llmin(mSelectionStart, mSelectionEnd); + S32 right = left + llabs(mSelectionStart - mSelectionEnd); + + LLWString wtext = getWText(); + for( S32 i = left; i < right; i++ ) + { + if (wtext[i] == '\n') + { + return true; + } + } + } + return false; +} + + +S32 LLTextEditor::indentLine( S32 pos, S32 spaces ) +{ + // Assumes that pos is at the start of the line + // spaces may be positive (indent) or negative (unindent). + // Returns the actual number of characters added or removed. + + llassert(pos >= 0); + llassert(pos <= getLength() ); + + S32 delta_spaces = 0; + + if (spaces >= 0) + { + // Indent + for(S32 i=0; i < spaces; i++) + { + delta_spaces += addChar(pos, ' '); + } + } + else + { + // Unindent + for(S32 i=0; i < -spaces; i++) + { + LLWString wtext = getWText(); + if (wtext[pos] == ' ') + { + delta_spaces += remove( pos, 1, false ); + } + } + } + + return delta_spaces; +} + +void LLTextEditor::indentSelectedLines( S32 spaces ) +{ + if( hasSelection() ) + { + LLWString text = getWText(); + S32 left = llmin( mSelectionStart, mSelectionEnd ); + S32 right = left + llabs( mSelectionStart - mSelectionEnd ); + bool cursor_on_right = (mSelectionEnd > mSelectionStart); + S32 cur = left; + + // Expand left to start of line + while( (cur > 0) && (text[cur] != '\n') ) + { + cur--; + } + left = cur; + if( cur > 0 ) + { + left++; + } + + // Expand right to end of line + if( text[right - 1] == '\n' ) + { + right--; + } + else + { + while( (text[right] != '\n') && (right <= getLength() ) ) + { + right++; + } + } + + // Disabling parsing on the fly to avoid updating text segments + // until all indentation commands are executed. + mParseOnTheFly = false; + + // Find each start-of-line and indent it + do + { + if( text[cur] == '\n' ) + { + cur++; + } + + S32 delta_spaces = indentLine( cur, spaces ); + if( delta_spaces > 0 ) + { + cur += delta_spaces; + } + right += delta_spaces; + + text = getWText(); + + // Find the next new line + while( (cur < right) && (text[cur] != '\n') ) + { + cur++; + } + } + while( cur < right ); + + mParseOnTheFly = true; + + if( (right < getLength()) && (text[right] == '\n') ) + { + right++; + } + + // Set the selection and cursor + if( cursor_on_right ) + { + mSelectionStart = left; + mSelectionEnd = right; + } + else + { + mSelectionStart = right; + mSelectionEnd = left; + } + setCursorPos(mSelectionEnd); + } +} + +//virtual +bool LLTextEditor::canSelectAll() const +{ + return true; +} + +// virtual +void LLTextEditor::selectAll() +{ + mSelectionStart = getLength(); + mSelectionEnd = 0; + setCursorPos(mSelectionEnd); + updatePrimary(); +} + +void LLTextEditor::selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_pos) +{ + setCursorPos(prev_cursor_pos); + startSelection(); + setCursorPos(next_cursor_pos); + endSelection(); +} + +void LLTextEditor::insertEmoji(llwchar emoji) +{ + LL_INFOS() << "LLTextEditor::insertEmoji(" << wchar_utf8_preview(emoji) << ")" << LL_ENDL; + auto styleParams = LLStyle::Params(); + styleParams.font = LLFontGL::getFontEmojiLarge(); + auto segment = new LLEmojiTextSegment(new LLStyle(styleParams), mCursorPos, mCursorPos + 1, *this); + insert(mCursorPos, LLWString(1, emoji), false, segment); + setCursorPos(mCursorPos + 1); +} + +void LLTextEditor::handleEmojiCommit(llwchar emoji) +{ + S32 shortCodePos; + if (LLEmojiHelper::isCursorInEmojiCode(getWText(), mCursorPos, &shortCodePos)) + { + remove(shortCodePos, mCursorPos - shortCodePos, true); + setCursorPos(shortCodePos); + + insertEmoji(emoji); + } +} + +bool LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // set focus first, in case click callbacks want to change it + // RN: do we really need to have a tab stop? + if (hasTabStop()) + { + setFocus( true ); + } + + // Let scrollbar have first dibs + handled = LLTextBase::handleMouseDown(x, y, mask); + + if( !handled ) + { + if (!(mask & MASK_SHIFT)) + { + deselect(); + } + + bool start_select = true; + if( start_select ) + { + // If we're not scrolling (handled by child), then we're selecting + if (mask & MASK_SHIFT) + { + S32 old_cursor_pos = mCursorPos; + setCursorAtLocalPos( x, y, true ); + + if (hasSelection()) + { + mSelectionEnd = mCursorPos; + } + else + { + mSelectionStart = old_cursor_pos; + mSelectionEnd = mCursorPos; + } + // assume we're starting a drag select + mIsSelecting = true; + } + else + { + setCursorAtLocalPos( x, y, true ); + startSelection(); + } + } + + handled = true; + } + + // Delay cursor flashing + resetCursorBlink(); + + if (handled && !gFocusMgr.getMouseCapture()) + { + gFocusMgr.setMouseCapture( this ); + } + return handled; +} + +bool LLTextEditor::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (hasTabStop()) + { + setFocus(true); + } + + bool show_menu = false; + + // Prefer editor menu if it has selection. See EXT-6806. + if (hasSelection()) + { + S32 click_pos = getDocIndexFromLocalCoord(x, y, false); + if (click_pos > mSelectionStart && click_pos < mSelectionEnd) + { + show_menu = true; + } + } + + // Let segments handle the click, if nothing does, show editor menu + if (!show_menu && !LLTextBase::handleRightMouseDown(x, y, mask)) + { + show_menu = true; + } + + if (show_menu && getShowContextMenu()) + { + showContextMenu(x, y); + } + + return true; +} + + + +bool LLTextEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask) +{ + if (hasTabStop()) + { + setFocus(true); + } + + if (!LLTextBase::handleMouseDown(x, y, mask)) + { + if( canPastePrimary() ) + { + setCursorAtLocalPos( x, y, true ); + // does not rely on focus being set + pastePrimary(); + } + } + return true; +} + + +bool LLTextEditor::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + if(hasMouseCapture() ) + { + if( mIsSelecting ) + { + if(mScroller) + { + mScroller->autoScroll(x, y); + } + S32 clamped_x = llclamp(x, mVisibleTextRect.mLeft, mVisibleTextRect.mRight); + S32 clamped_y = llclamp(y, mVisibleTextRect.mBottom, mVisibleTextRect.mTop); + setCursorAtLocalPos( clamped_x, clamped_y, true ); + mSelectionEnd = mCursorPos; + } + LL_DEBUGS("UserInput") << "hover handled by " << getName() << " (active)" << LL_ENDL; + getWindow()->setCursor(UI_CURSOR_IBEAM); + handled = true; + } + + if( !handled ) + { + // Pass to children + handled = LLTextBase::handleHover(x, y, mask); + } + + if( handled ) + { + // Delay cursor flashing + resetCursorBlink(); + } + + if( !handled ) + { + getWindow()->setCursor(UI_CURSOR_IBEAM); + handled = true; + } + + return handled; +} + + +bool LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // if I'm not currently selecting text + if (!(mIsSelecting && hasMouseCapture())) + { + // let text segments handle mouse event + handled = LLTextBase::handleMouseUp(x, y, mask); + } + + if( !handled ) + { + if( mIsSelecting ) + { + if(mScroller) + { + mScroller->autoScroll(x, y); + } + S32 clamped_x = llclamp(x, mVisibleTextRect.mLeft, mVisibleTextRect.mRight); + S32 clamped_y = llclamp(y, mVisibleTextRect.mBottom, mVisibleTextRect.mTop); + setCursorAtLocalPos( clamped_x, clamped_y, true ); + endSelection(); + } + + // take selection to 'primary' clipboard + updatePrimary(); + + handled = true; + } + + // Delay cursor flashing + resetCursorBlink(); + + if( hasMouseCapture() ) + { + gFocusMgr.setMouseCapture( NULL ); + + handled = true; + } + + return handled; +} + + +bool LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // let scrollbar and text segments have first dibs + handled = LLTextBase::handleDoubleClick(x, y, mask); + + if( !handled ) + { + setCursorAtLocalPos( x, y, false ); + deselect(); + + LLWString text = getWText(); + + if( LLWStringUtil::isPartOfWord( text[mCursorPos] ) ) + { + // Select word the cursor is over + while ((mCursorPos > 0) && LLWStringUtil::isPartOfWord(text[mCursorPos-1])) + { + if (!setCursorPos(mCursorPos - 1)) break; + } + startSelection(); + + while ((mCursorPos < (S32)text.length()) && LLWStringUtil::isPartOfWord( text[mCursorPos] ) ) + { + if (!setCursorPos(mCursorPos + 1)) break; + } + + mSelectionEnd = mCursorPos; + } + else if ((mCursorPos < (S32)text.length()) && !iswspace( text[mCursorPos]) ) + { + // Select the character the cursor is over + startSelection(); + setCursorPos(mCursorPos + 1); + mSelectionEnd = mCursorPos; + } + + // We don't want handleMouseUp() to "finish" the selection (and thereby + // set mSelectionEnd to where the mouse is), so we finish the selection here. + mIsSelecting = false; + + // delay cursor flashing + resetCursorBlink(); + + // take selection to 'primary' clipboard + updatePrimary(); + + handled = true; + } + + return handled; +} + + +//---------------------------------------------------------------------------- +// Returns change in number of characters in mText + +S32 LLTextEditor::execute( TextCmd* cmd ) +{ + if (!mReadOnly && mShowEmojiHelper) + { + // Any change to our contents should always hide the helper + LLEmojiHelper::instance().hideHelper(this); + } + + S32 delta = 0; + if( cmd->execute(this, &delta) ) + { + // Delete top of undo stack + undo_stack_t::iterator enditer = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); + std::for_each(mUndoStack.begin(), enditer, DeletePointer()); + mUndoStack.erase(mUndoStack.begin(), enditer); + // Push the new command is now on the top (front) of the undo stack. + mUndoStack.push_front(cmd); + mLastCmd = cmd; + + bool need_to_rollback = mPrevalidator && !mPrevalidator.validate(getViewModel()->getDisplay()); + if (need_to_rollback) + { + LLUI::getInstance()->reportBadKeystroke(); + mPrevalidator.showLastErrorUsingTimeout(); + + // get rid of this last command and clean up undo stack + undo(); + + // remove any evidence of this command from redo history + mUndoStack.pop_front(); + delete cmd; + + // failure, nothing changed + delta = 0; + } + } + else + { + // Operation failed, so don't put it on the undo stack. + delete cmd; + } + + return delta; +} + +S32 LLTextEditor::insert(S32 pos, const LLWString &wstr, bool group_with_next_op, LLTextSegmentPtr segment) +{ + return execute( new TextCmdInsert( pos, group_with_next_op, wstr, segment ) ); +} + +S32 LLTextEditor::remove(S32 pos, S32 length, bool group_with_next_op) +{ + S32 end_pos = getEditableIndex(pos + length, true); + bool removedChar = false; + + segment_vec_t segments_to_remove; + // store text segments + getSegmentsInRange(segments_to_remove, pos, pos + length, false); + + if (pos <= end_pos) + { + removedChar = execute( new TextCmdRemove( pos, group_with_next_op, end_pos - pos, segments_to_remove ) ); + } + + return removedChar; +} + +S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc) +{ + if ((S32)getLength() == pos) + { + return addChar(pos, wc); + } + else + { + return execute(new TextCmdOverwriteChar(pos, false, wc)); + } +} + +// Remove a single character from the text. Tries to remove +// a pseudo-tab (up to for spaces in a row) +void LLTextEditor::removeCharOrTab() +{ + if (!getEnabled()) + { + return; + } + + if (mCursorPos > 0) + { + S32 chars_to_remove = 1; + + LLWString text = getWText(); + if (text[mCursorPos - 1] == ' ') + { + // Try to remove a "tab" + S32 offset = getLineOffsetFromDocIndex(mCursorPos); + if (offset > 0) + { + chars_to_remove = offset % SPACES_PER_TAB; + if (chars_to_remove == 0) + { + chars_to_remove = SPACES_PER_TAB; + } + + for (S32 i = 0; i < chars_to_remove; i++) + { + if (text[mCursorPos - i - 1] != ' ') + { + // Fewer than a full tab's worth of spaces, so + // just delete a single character. + chars_to_remove = 1; + break; + } + } + } + } + + for (S32 i = 0; i < chars_to_remove; i++) + { + setCursorPos(mCursorPos - 1); + remove(mCursorPos, 1, false); + } + + tryToShowEmojiHelper(); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } +} + +// Remove a single character from the text +S32 LLTextEditor::removeChar(S32 pos) +{ + return remove(pos, 1, false); +} + +void LLTextEditor::removeChar() +{ + if (!getEnabled()) + { + return; + } + + if (mCursorPos > 0) + { + setCursorPos(mCursorPos - 1); + removeChar(mCursorPos); + tryToShowEmojiHelper(); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } +} + +// Add a single character to the text +S32 LLTextEditor::addChar(S32 pos, llwchar wc) +{ + if ((wstring_utf8_length(getWText()) + wchar_utf8_length(wc)) > mMaxTextByteLength) + { + LLUI::getInstance()->reportBadKeystroke(); + return 0; + } + + if (mLastCmd && mLastCmd->canExtend(pos)) + { + if (mPrevalidator) + { + // get a copy of current text contents + LLWString test_string(getViewModel()->getDisplay()); + + // modify text contents as if this addChar succeeded + llassert(pos <= (S32)test_string.size()); + test_string.insert(pos, 1, wc); + if (!mPrevalidator.validate(test_string)) + { + LLUI::getInstance()->reportBadKeystroke(); + mPrevalidator.showLastErrorUsingTimeout(); + return 0; + } + } + + S32 delta = 0; + mLastCmd->extendAndExecute(this, pos, wc, &delta); + + return delta; + } + + return execute(new TextCmdAddChar(pos, false, wc, LLTextSegmentPtr())); +} + +void LLTextEditor::addChar(llwchar wc) +{ + if (!getEnabled()) + { + return; + } + + if (hasSelection()) + { + deleteSelection(true); + } + else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + removeChar(mCursorPos); + } + + setCursorPos(mCursorPos + addChar( mCursorPos, wc )); + tryToShowEmojiHelper(); + + if (!mReadOnly && mAutoreplaceCallback != NULL) + { + // autoreplace the text, if necessary + S32 replacement_start; + S32 replacement_length; + LLWString replacement_string; + S32 new_cursor_pos = mCursorPos; + mAutoreplaceCallback(replacement_start, replacement_length, replacement_string, new_cursor_pos, getWText()); + + if (replacement_length > 0 || !replacement_string.empty()) + { + remove(replacement_start, replacement_length, true); + insert(replacement_start, replacement_string, false, LLTextSegmentPtr()); + setCursorPos(new_cursor_pos); + } + } +} + +void LLTextEditor::showEmojiHelper() +{ + if (mReadOnly || !mShowEmojiHelper) + return; + + const LLRect cursorRect(getLocalRectFromDocIndex(mCursorPos)); + auto cb = [this](llwchar emoji) { insertEmoji(emoji); }; + LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, LLStringUtil::null, cb); +} + +void LLTextEditor::tryToShowEmojiHelper() +{ + if (mReadOnly || !mShowEmojiHelper) + return; + + S32 shortCodePos; + LLWString wtext(getWText()); + if (LLEmojiHelper::isCursorInEmojiCode(wtext, mCursorPos, &shortCodePos)) + { + const LLRect cursorRect(getLocalRectFromDocIndex(shortCodePos)); + const LLWString wpart(wtext.substr(shortCodePos, mCursorPos - shortCodePos)); + const std::string part(wstring_to_utf8str(wpart)); + auto cb = [this](llwchar emoji) { handleEmojiCommit(emoji); }; + LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, part, cb); + } + else + { + LLEmojiHelper::instance().hideHelper(); + } +} + +void LLTextEditor::addLineBreakChar(bool group_together) +{ + if( !getEnabled() ) + { + return; + } + if( hasSelection() ) + { + deleteSelection(true); + } + else if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + removeChar(mCursorPos); + } + + LLStyleConstSP sp(new LLStyle(LLStyle::Params())); + LLTextSegmentPtr segment = new LLLineBreakTextSegment(sp, mCursorPos); + + S32 pos = execute(new TextCmdAddChar(mCursorPos, group_together, '\n', segment)); + + setCursorPos(mCursorPos + pos); +} + + +bool LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) +{ + bool handled = false; + + if( mask & MASK_SHIFT ) + { + handled = true; + + switch( key ) + { + case KEY_LEFT: + if( 0 < mCursorPos ) + { + startSelection(); + setCursorPos(mCursorPos - 1); + if( mask & MASK_CONTROL ) + { + setCursorPos(prevWordPos(mCursorPos)); + } + mSelectionEnd = mCursorPos; + } + break; + + case KEY_RIGHT: + if( mCursorPos < getLength() ) + { + startSelection(); + setCursorPos(mCursorPos + 1); + if( mask & MASK_CONTROL ) + { + setCursorPos(nextWordPos(mCursorPos)); + } + mSelectionEnd = mCursorPos; + } + break; + + case KEY_UP: + startSelection(); + changeLine( -1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_PAGE_UP: + startSelection(); + changePage( -1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_HOME: + startSelection(); + if( mask & MASK_CONTROL ) + { + setCursorPos(0); + } + else + { + startOfLine(); + } + mSelectionEnd = mCursorPos; + break; + + case KEY_DOWN: + startSelection(); + changeLine( 1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_PAGE_DOWN: + startSelection(); + changePage( 1 ); + mSelectionEnd = mCursorPos; + break; + + case KEY_END: + startSelection(); + if( mask & MASK_CONTROL ) + { + setCursorPos(getLength()); + } + else + { + endOfLine(); + } + mSelectionEnd = mCursorPos; + break; + + default: + handled = false; + break; + } + } + + if( handled ) + { + // take selection to 'primary' clipboard + updatePrimary(); + } + + return handled; +} + +bool LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) +{ + bool handled = false; + + // Ignore capslock key + if( MASK_NONE == mask ) + { + handled = true; + switch( key ) + { + case KEY_UP: + changeLine( -1 ); + break; + + case KEY_PAGE_UP: + changePage( -1 ); + break; + + case KEY_HOME: + startOfLine(); + break; + + case KEY_DOWN: + changeLine( 1 ); + deselect(); + break; + + case KEY_PAGE_DOWN: + changePage( 1 ); + break; + + case KEY_END: + endOfLine(); + break; + + case KEY_LEFT: + if( hasSelection() ) + { + setCursorPos(llmin( mSelectionStart, mSelectionEnd )); + } + else + { + if( 0 < mCursorPos ) + { + setCursorPos(mCursorPos - 1); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + } + break; + + case KEY_RIGHT: + if( hasSelection() ) + { + setCursorPos(llmax( mSelectionStart, mSelectionEnd )); + } + else + { + if( mCursorPos < getLength() ) + { + setCursorPos(mCursorPos + 1); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + } + break; + + default: + handled = false; + break; + } + } + + if (handled) + { + deselect(); + } + + return handled; +} + +void LLTextEditor::deleteSelection(bool group_with_next_op ) +{ + if( getEnabled() && hasSelection() ) + { + S32 pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); + + remove( pos, length, group_with_next_op ); + + deselect(); + setCursorPos(pos); + } +} + +// virtual +bool LLTextEditor::canCut() const +{ + return !mReadOnly && hasSelection(); +} + +// cut selection to clipboard +void LLTextEditor::cut() +{ + if( !canCut() ) + { + return; + } + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); + LLClipboard::instance().copyToClipboard( getWText(), left_pos, length); + deleteSelection( false ); + + onKeyStroke(); +} + +bool LLTextEditor::canCopy() const +{ + return hasSelection(); +} + +// copy selection to clipboard +void LLTextEditor::copy() +{ + if( !canCopy() ) + { + return; + } + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); + LLClipboard::instance().copyToClipboard(getWText(), left_pos, length); +} + +bool LLTextEditor::canPaste() const +{ + return !mReadOnly && LLClipboard::instance().isTextAvailable(); +} + +// paste from clipboard +void LLTextEditor::paste() +{ + bool is_primary = false; + pasteHelper(is_primary); +} + +// paste from primary +void LLTextEditor::pastePrimary() +{ + bool is_primary = true; + pasteHelper(is_primary); +} + +// paste from primary (itsprimary==true) or clipboard (itsprimary==false) +void LLTextEditor::pasteHelper(bool is_primary) +{ + struct BoolReset + { + BoolReset(bool& value) : mValuePtr(&value) { *mValuePtr = false; } + ~BoolReset() { *mValuePtr = true; } + bool* mValuePtr; + } reset(mParseOnTheFly); + + bool can_paste_it; + if (is_primary) + { + can_paste_it = canPastePrimary(); + } + else + { + can_paste_it = canPaste(); + } + + if (!can_paste_it) + { + return; + } + + LLWString paste; + LLClipboard::instance().pasteFromClipboard(paste, is_primary); + + if (paste.empty()) + { + return; + } + + // Delete any selected characters (the paste replaces them) + if( (!is_primary) && hasSelection() ) + { + deleteSelection(true); + } + + // Clean up string (replace tabs and remove characters that our fonts don't support). + LLWString clean_string(paste); + cleanStringForPaste(clean_string); + + // Insert the new text into the existing text. + + //paste text with linebreaks. + pasteTextWithLinebreaks(clean_string); + + deselect(); + + onKeyStroke(); +} + + +// Clean up string (replace tabs and remove characters that our fonts don't support). +void LLTextEditor::cleanStringForPaste(LLWString & clean_string) +{ + std::string clean_string_utf = wstring_to_utf8str(clean_string); + std::replace( clean_string_utf.begin(), clean_string_utf.end(), '\r', '\n'); + clean_string = utf8str_to_wstring(clean_string_utf); + + LLWStringUtil::replaceTabsWithSpaces(clean_string, SPACES_PER_TAB); + if( mAllowEmbeddedItems ) + { + const llwchar LF = 10; + S32 len = clean_string.length(); + for( S32 i = 0; i < len; i++ ) + { + llwchar wc = clean_string[i]; + if( (wc < LLFontFreetype::FIRST_CHAR) && (wc != LF) ) + { + clean_string[i] = LL_UNKNOWN_CHAR; + } + else if (wc >= FIRST_EMBEDDED_CHAR && wc <= LAST_EMBEDDED_CHAR) + { + clean_string[i] = pasteEmbeddedItem(wc); + } + } + } +} + + +void LLTextEditor::pasteTextWithLinebreaks(LLWString & clean_string) +{ + std::basic_string::size_type start = 0; + std::basic_string::size_type pos = clean_string.find('\n',start); + + while((pos != -1) && (pos != clean_string.length() -1)) + { + if(pos!=start) + { + std::basic_string str = std::basic_string(clean_string,start,pos-start); + setCursorPos(mCursorPos + insert(mCursorPos, str, true, LLTextSegmentPtr())); + } + addLineBreakChar(true); // Add a line break and group with the next addition. + + start = pos+1; + pos = clean_string.find('\n',start); + } + + if (pos != start) + { + std::basic_string str = std::basic_string(clean_string,start,clean_string.length()-start); + setCursorPos(mCursorPos + insert(mCursorPos, str, false, LLTextSegmentPtr())); + } + else + { + addLineBreakChar(false); // Add a line break and end the grouping. + } +} + +// copy selection to primary +void LLTextEditor::copyPrimary() +{ + if( !canCopy() ) + { + return; + } + S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); + S32 length = llabs( mSelectionStart - mSelectionEnd ); + LLClipboard::instance().copyToClipboard(getWText(), left_pos, length, true); +} + +bool LLTextEditor::canPastePrimary() const +{ + return !mReadOnly && LLClipboard::instance().isTextAvailable(true); +} + +void LLTextEditor::updatePrimary() +{ + if (canCopy()) + { + copyPrimary(); + } +} + +bool LLTextEditor::handleControlKey(const KEY key, const MASK mask) +{ + bool handled = false; + + if( mask & MASK_CONTROL ) + { + handled = true; + + switch( key ) + { + case KEY_HOME: + if( mask & MASK_SHIFT ) + { + startSelection(); + setCursorPos(0); + mSelectionEnd = mCursorPos; + } + else + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + startOfDoc(); + } + break; + + case KEY_END: + { + if( mask & MASK_SHIFT ) + { + startSelection(); + } + else + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + } + endOfDoc(); + if( mask & MASK_SHIFT ) + { + mSelectionEnd = mCursorPos; + } + break; + } + + case KEY_RIGHT: + if( mCursorPos < getLength() ) + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + + setCursorPos(nextWordPos(mCursorPos + 1)); + } + break; + + + case KEY_LEFT: + if( mCursorPos > 0 ) + { + // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down + // all move the cursor as if clicking, so should deselect. + deselect(); + + setCursorPos(prevWordPos(mCursorPos - 1)); + } + break; + + default: + handled = false; + break; + } + } + + if (handled && !gFocusMgr.getMouseCapture()) + { + updatePrimary(); + } + + return handled; +} + + +bool LLTextEditor::handleSpecialKey(const KEY key, const MASK mask) + { + bool handled = true; + + if (mReadOnly) return false; + + switch( key ) + { + case KEY_INSERT: + if (mask == MASK_NONE) + { + gKeyboard->toggleInsertMode(); + } + break; + + case KEY_BACKSPACE: + if( hasSelection() ) + { + deleteSelection(false); + } + else + if( 0 < mCursorPos ) + { + removeCharOrTab(); + } + else + { + LLUI::getInstance()->reportBadKeystroke(); + } + break; + + + case KEY_RETURN: + if (mask == MASK_NONE) + { + if( hasSelection() && !mKeepSelectionOnReturn ) + { + deleteSelection(false); + } + if (mAutoIndent) + { + autoIndent(); + } + } + else + { + handled = false; + break; + } + break; + + case KEY_TAB: + if (mask & MASK_CONTROL) + { + handled = false; + break; + } + if( hasSelection() && selectionContainsLineBreaks() ) + { + indentSelectedLines( (mask & MASK_SHIFT) ? -SPACES_PER_TAB : SPACES_PER_TAB ); + } + else + { + if( hasSelection() ) + { + deleteSelection(false); + } + + S32 offset = getLineOffsetFromDocIndex(mCursorPos); + + S32 spaces_needed = SPACES_PER_TAB - (offset % SPACES_PER_TAB); + for( S32 i=0; i < spaces_needed; i++ ) + { + addChar( ' ' ); + } + } + break; + + default: + handled = false; + break; + } + + if (handled) + { + onKeyStroke(); + } + return handled; +} + + +void LLTextEditor::unindentLineBeforeCloseBrace() +{ + if( mCursorPos >= 1 ) + { + LLWString text = getWText(); + if( ' ' == text[ mCursorPos - 1 ] ) + { + S32 line = getLineNumFromDocIndex(mCursorPos, false); + S32 line_start = getLineStart(line); + + // Jump over spaces in the current line + while ((' ' == text[line_start]) && (line_start < mCursorPos)) + { + line_start++; + } + + // Make sure there is nothing but ' ' before the Brace we are unindenting + if (line_start == mCursorPos) + { + removeCharOrTab(); + } + } + } +} + + +bool LLTextEditor::handleKeyHere(KEY key, MASK mask ) +{ + bool handled = false; + + // Special case for TAB. If want to move to next field, report + // not handled and let the parent take care of field movement. + if (KEY_TAB == key && mTabsToNextField) + { + return false; + } + + if (mReadOnly && mScroller) + { + handled = (mScroller && mScroller->handleKeyHere( key, mask )) + || handleSelectionKey(key, mask) + || handleControlKey(key, mask); + } + else + { + if (!mReadOnly && mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) + { + return true; + } + + if (mEnableTooltipPaste && + LLToolTipMgr::instance().toolTipVisible() && + LLToolTipMgr::instance().isTooltipPastable() && + KEY_TAB == key) + { // Paste the first line of a tooltip into the editor + std::string message; + LLToolTipMgr::instance().getToolTipMessage(message); + LLWString tool_tip_text(utf8str_to_wstring(message)); + + if (tool_tip_text.size() > 0) + { + // Delete any selected characters (the tooltip text replaces them) + if(hasSelection()) + { + deleteSelection(true); + } + + std::basic_string::size_type pos = tool_tip_text.find('\n',0); + if (pos != -1) + { // Extract the first line of the tooltip + tool_tip_text = std::basic_string(tool_tip_text, 0, pos); + } + + // Add the text + cleanStringForPaste(tool_tip_text); + pasteTextWithLinebreaks(tool_tip_text); + handled = true; + } + } + else + { // Normal key handling + handled = handleNavigationKey( key, mask ) + || handleSelectionKey(key, mask) + || handleControlKey(key, mask) + || handleSpecialKey(key, mask); + } + } + + if( handled ) + { + resetCursorBlink(); + needsScroll(); + + if (mShowEmojiHelper) + { + // Dismiss the helper whenever we handled a key that it didn't + LLEmojiHelper::instance().hideHelper(this); + } + } + + return handled; +} + + +bool LLTextEditor::handleUnicodeCharHere(llwchar uni_char) +{ + if ((uni_char < 0x20) || (uni_char == 0x7F)) // Control character or DEL + { + return false; + } + + bool handled = false; + + // Handle most keys only if the text editor is writeable. + if( !mReadOnly ) + { + if (mShowEmojiHelper && uni_char < 0x80 && LLEmojiHelper::instance().handleKey(this, (KEY)uni_char, MASK_NONE)) + { + return true; + } + + if( mAutoIndent && '}' == uni_char ) + { + unindentLineBeforeCloseBrace(); + } + + // TODO: KLW Add auto show of tool tip on ( + addChar( uni_char ); + + // Keys that add characters temporarily hide the cursor + getWindow()->hideCursorUntilMouseMove(); + + handled = true; + } + + if( handled ) + { + resetCursorBlink(); + + // Most keystrokes will make the selection box go away, but not all will. + deselect(); + + onKeyStroke(); + } + + return handled; +} + + +// virtual +bool LLTextEditor::canDoDelete() const +{ + return !mReadOnly && ( !mPassDelete || ( hasSelection() || (mCursorPos < getLength())) ); +} + +void LLTextEditor::doDelete() +{ + if( !canDoDelete() ) + { + return; + } + if( hasSelection() ) + { + deleteSelection(false); + } + else + if( mCursorPos < getLength() ) + { + S32 i; + S32 chars_to_remove = 1; + LLWString text = getWText(); + if( (text[ mCursorPos ] == ' ') && (mCursorPos + SPACES_PER_TAB < getLength()) ) + { + // Try to remove a full tab's worth of spaces + S32 offset = getLineOffsetFromDocIndex(mCursorPos); + chars_to_remove = SPACES_PER_TAB - (offset % SPACES_PER_TAB); + if( chars_to_remove == 0 ) + { + chars_to_remove = SPACES_PER_TAB; + } + + for( i = 0; i < chars_to_remove; i++ ) + { + if( text[mCursorPos + i] != ' ' ) + { + chars_to_remove = 1; + break; + } + } + } + + for( i = 0; i < chars_to_remove; i++ ) + { + setCursorPos(mCursorPos + 1); + removeChar(); + } + + } + + onKeyStroke(); +} + +//---------------------------------------------------------------------------- + + +void LLTextEditor::blockUndo() +{ + mBaseDocIsPristine = false; + mLastCmd = NULL; + std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); + mUndoStack.clear(); +} + +// virtual +bool LLTextEditor::canUndo() const +{ + return !mReadOnly && mLastCmd != NULL; +} + +void LLTextEditor::undo() +{ + if( !canUndo() ) + { + return; + } + deselect(); + S32 pos = 0; + do + { + pos = mLastCmd->undo(this); + undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); + if (iter != mUndoStack.end()) + ++iter; + if (iter != mUndoStack.end()) + mLastCmd = *iter; + else + mLastCmd = NULL; + + } while( mLastCmd && mLastCmd->groupWithNext() ); + + setCursorPos(pos); + + onKeyStroke(); +} + +bool LLTextEditor::canRedo() const +{ + return !mReadOnly && (mUndoStack.size() > 0) && (mLastCmd != mUndoStack.front()); +} + +void LLTextEditor::redo() +{ + if( !canRedo() ) + { + return; + } + deselect(); + S32 pos = 0; + do + { + if( !mLastCmd ) + { + mLastCmd = mUndoStack.back(); + } + else + { + undo_stack_t::iterator iter = std::find(mUndoStack.begin(), mUndoStack.end(), mLastCmd); + if (iter != mUndoStack.begin()) + mLastCmd = *(--iter); + else + mLastCmd = NULL; + } + + if( mLastCmd ) + { + pos = mLastCmd->redo(this); + } + } while( + mLastCmd && + mLastCmd->groupWithNext() && + (mLastCmd != mUndoStack.front()) ); + + setCursorPos(pos); + + onKeyStroke(); +} + +void LLTextEditor::onFocusReceived() +{ + LLTextBase::onFocusReceived(); + updateAllowingLanguageInput(); +} + +void LLTextEditor::focusLostHelper() +{ + updateAllowingLanguageInput(); + + // Route menu back to the default + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + if (mCommitOnFocusLost) + { + onCommit(); + } + + // Make sure cursor is shown again + getWindow()->showCursorFromMouseMove(); +} + +void LLTextEditor::onFocusLost() +{ + focusLostHelper(); + LLTextBase::onFocusLost(); +} + +void LLTextEditor::onCommit() +{ + setControlValue(getValue()); + LLTextBase::onCommit(); +} + +void LLTextEditor::setEnabled(bool enabled) +{ + // just treat enabled as read-only flag + bool read_only = !enabled; + if (read_only != mReadOnly) + { + //mReadOnly = read_only; + LLTextBase::setReadOnly(read_only); + updateSegments(); + updateAllowingLanguageInput(); + } +} + +void LLTextEditor::showContextMenu(S32 x, S32 y) +{ + LLContextMenu* menu = static_cast(mContextMenuHandle.get()); + if (!menu) + { + llassert(LLMenuGL::sMenuContainer != NULL); + menu = LLUICtrlFactory::createFromFile("menu_text_editor.xml", + LLMenuGL::sMenuContainer, + LLMenuHolderGL::child_registry_t::instance()); + if(!menu) + { + LL_WARNS() << "Failed to create menu for LLTextEditor: " << getName() << LL_ENDL; + return; + } + mContextMenuHandle = menu->getHandle(); + } + + // Route menu to this class + // previously this was done in ::handleRightMoseDown: + //if(hasTabStop()) + // setFocus(true) - why? weird... + // and then inside setFocus + // .... + // gEditMenuHandler = this; + // .... + // but this didn't work in all cases and just weird... + //why not here? + // (all this was done for EXT-4443) + + gEditMenuHandler = this; + + S32 screen_x, screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + + setCursorAtLocalPos(x, y, false); + if (hasSelection()) + { + if ( (mCursorPos < llmin(mSelectionStart, mSelectionEnd)) || (mCursorPos > llmax(mSelectionStart, mSelectionEnd)) ) + { + deselect(); + } + else + { + setCursorPos(llmax(mSelectionStart, mSelectionEnd)); + } + } + + bool use_spellcheck = getSpellCheck(), is_misspelled = false; + if (use_spellcheck) + { + mSuggestionList.clear(); + + // If the cursor is on a misspelled word, retrieve suggestions for it + std::string misspelled_word = getMisspelledWord(mCursorPos); + if ((is_misspelled = !misspelled_word.empty())) + { + LLSpellChecker::instance().getSuggestions(misspelled_word, mSuggestionList); + } + } + + menu->setItemVisible("Suggestion Separator", (use_spellcheck) && (!mSuggestionList.empty())); + menu->setItemVisible("Add to Dictionary", (use_spellcheck) && (is_misspelled)); + menu->setItemVisible("Add to Ignore", (use_spellcheck) && (is_misspelled)); + menu->setItemVisible("Spellcheck Separator", (use_spellcheck) && (is_misspelled)); + menu->show(screen_x, screen_y, this); +} + + +void LLTextEditor::drawPreeditMarker() +{ + static LLUICachedControl preedit_marker_brightness ("UIPreeditMarkerBrightness", 0); + static LLUICachedControl preedit_marker_gap ("UIPreeditMarkerGap", 0); + static LLUICachedControl preedit_marker_position ("UIPreeditMarkerPosition", 0); + static LLUICachedControl preedit_marker_thickness ("UIPreeditMarkerThickness", 0); + static LLUICachedControl preedit_standout_brightness ("UIPreeditStandoutBrightness", 0); + static LLUICachedControl preedit_standout_gap ("UIPreeditStandoutGap", 0); + static LLUICachedControl preedit_standout_position ("UIPreeditStandoutPosition", 0); + static LLUICachedControl preedit_standout_thickness ("UIPreeditStandoutThickness", 0); + + if (!hasPreeditString()) + { + return; + } + + const LLWString textString(getWText()); + const llwchar *text = textString.c_str(); + const S32 text_len = getLength(); + const S32 num_lines = getLineCount(); + + S32 cur_line = getFirstVisibleLine(); + if (cur_line >= num_lines) + { + return; + } + + const S32 line_height = mFont->getLineHeight(); + + S32 line_start = getLineStart(cur_line); + S32 line_y = mVisibleTextRect.mTop - line_height; + while((mVisibleTextRect.mBottom <= line_y) && (num_lines > cur_line)) + { + S32 next_start = -1; + S32 line_end = text_len; + + if ((cur_line + 1) < num_lines) + { + next_start = getLineStart(cur_line + 1); + line_end = next_start; + } + if ( text[line_end-1] == '\n' ) + { + --line_end; + } + + // Does this line contain preedits? + if (line_start >= mPreeditPositions.back()) + { + // We have passed the preedits. + break; + } + if (line_end > mPreeditPositions.front()) + { + for (U32 i = 0; i < mPreeditStandouts.size(); i++) + { + S32 left = mPreeditPositions[i]; + S32 right = mPreeditPositions[i + 1]; + if (right <= line_start || left >= line_end) + { + continue; + } + + line_info& line = mLineInfoList[cur_line]; + LLRect text_rect(line.mRect); + text_rect.mRight = mDocumentView->getRect().getWidth(); // clamp right edge to document extents + text_rect.translate(mDocumentView->getRect().mLeft, mDocumentView->getRect().mBottom); // adjust by scroll position + + S32 preedit_left = text_rect.mLeft; + if (left > line_start) + { + preedit_left += mFont->getWidth(text, line_start, left - line_start); + } + S32 preedit_right = text_rect.mLeft; + if (right < line_end) + { + preedit_right += mFont->getWidth(text, line_start, right - line_start); + } + else + { + preedit_right += mFont->getWidth(text, line_start, line_end - line_start); + } + + if (mPreeditStandouts[i]) + { + gl_rect_2d(preedit_left + preedit_standout_gap, + text_rect.mBottom + mFont->getDescenderHeight() - 1, + preedit_right - preedit_standout_gap - 1, + text_rect.mBottom + mFont->getDescenderHeight() - 1 - preedit_standout_thickness, + (mCursorColor.get() * preedit_standout_brightness + mWriteableBgColor.get() * (1 - preedit_standout_brightness)).setAlpha(1.0f)); + } + else + { + gl_rect_2d(preedit_left + preedit_marker_gap, + text_rect.mBottom + mFont->getDescenderHeight() - 1, + preedit_right - preedit_marker_gap - 1, + text_rect.mBottom + mFont->getDescenderHeight() - 1 - preedit_marker_thickness, + (mCursorColor.get() * preedit_marker_brightness + mWriteableBgColor.get() * (1 - preedit_marker_brightness)).setAlpha(1.0f)); + } + } + } + + // move down one line + line_y -= line_height; + line_start = next_start; + cur_line++; + } +} + +void LLTextEditor::draw() +{ + { + // pad clipping rectangle so that cursor can draw at full width + // when at left edge of mVisibleTextRect + LLRect clip_rect(mVisibleTextRect); + clip_rect.stretch(1); + LLLocalClipRect clip(clip_rect); + } + + LLTextBase::draw(); + + drawPreeditMarker(); + + //RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret + // when in readonly mode + mBorder->setKeyboardFocusHighlight( hasFocus() );// && !mReadOnly); +} + +// Start or stop the editor from accepting text-editing keystrokes +// see also LLLineEditor +void LLTextEditor::setFocus( bool new_state ) +{ + bool old_state = hasFocus(); + + // Don't change anything if the focus state didn't change + if (new_state == old_state) return; + + // Notify early if we are losing focus. + if (!new_state) + { + getWindow()->allowLanguageTextInput(this, false); + } + + LLTextBase::setFocus( new_state ); + + if( new_state ) + { + // Route menu to this class + gEditMenuHandler = this; + + // Don't start the cursor flashing right away + resetCursorBlink(); + } + else + { + // Route menu back to the default + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + + endSelection(); + } +} + +// public +void LLTextEditor::setCursorAndScrollToEnd() +{ + deselect(); + endOfDoc(); +} + +void LLTextEditor::getCurrentLineAndColumn( S32* line, S32* col, bool include_wordwrap ) +{ + *line = getLineNumFromDocIndex(mCursorPos, include_wordwrap); + *col = getLineOffsetFromDocIndex(mCursorPos, include_wordwrap); +} + +void LLTextEditor::autoIndent() +{ + // Count the number of spaces in the current line + S32 line = getLineNumFromDocIndex(mCursorPos, false); + S32 line_start = getLineStart(line); + S32 space_count = 0; + S32 i; + + LLWString text = getWText(); + S32 offset = getLineOffsetFromDocIndex(mCursorPos); + while(( ' ' == text[line_start] ) && (space_count < offset)) + { + space_count++; + line_start++; + } + + // If we're starting a braced section, indent one level. + if( (mCursorPos > 0) && (text[mCursorPos -1] == '{') ) + { + space_count += SPACES_PER_TAB; + } + + // Insert that number of spaces on the new line + + //appendLineBreakSegment(LLStyle::Params());//addChar( '\n' ); + addLineBreakChar(); + + for( i = 0; i < space_count; i++ ) + { + addChar( ' ' ); + } +} + +// Inserts new text at the cursor position +void LLTextEditor::insertText(const std::string &new_text) +{ + bool enabled = getEnabled(); + setEnabled( true ); + + // Delete any selected characters (the insertion replaces them) + if( hasSelection() ) + { + deleteSelection(true); + } + + setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), false, LLTextSegmentPtr() )); + + setEnabled( enabled ); +} + +void LLTextEditor::insertText(LLWString &new_text) +{ + bool enabled = getEnabled(); + setEnabled( true ); + + // Delete any selected characters (the insertion replaces them) + if( hasSelection() ) + { + deleteSelection(true); + } + + setCursorPos(mCursorPos + insert( mCursorPos, new_text, false, LLTextSegmentPtr() )); + + setEnabled( enabled ); +} + +void LLTextEditor::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo) +{ + // Save old state + S32 selection_start = mSelectionStart; + S32 selection_end = mSelectionEnd; + bool was_selecting = mIsSelecting; + S32 cursor_pos = mCursorPos; + S32 old_length = getLength(); + bool cursor_was_at_end = (mCursorPos == old_length); + + deselect(); + + setCursorPos(old_length); + + LLWString widget_wide_text = utf8str_to_wstring(text); + + LLTextSegmentPtr segment = new LLInlineViewSegment(params, old_length, old_length + widget_wide_text.size()); + insert(getLength(), widget_wide_text, false, segment); + + // Set the cursor and scroll position + if( selection_start != selection_end ) + { + mSelectionStart = selection_start; + mSelectionEnd = selection_end; + + mIsSelecting = was_selecting; + setCursorPos(cursor_pos); + } + else if( cursor_was_at_end ) + { + setCursorPos(getLength()); + } + else + { + setCursorPos(cursor_pos); + } + + if (!allow_undo) + { + blockUndo(); + } +} + +void LLTextEditor::removeTextFromEnd(S32 num_chars) +{ + if (num_chars <= 0) return; + + remove(getLength() - num_chars, num_chars, false); + + S32 len = getLength(); + setCursorPos (llclamp(mCursorPos, 0, len)); + mSelectionStart = llclamp(mSelectionStart, 0, len); + mSelectionEnd = llclamp(mSelectionEnd, 0, len); + + needsScroll(); +} + +//---------------------------------------------------------------------------- + +void LLTextEditor::onSpellCheckPerformed() +{ + if (isPristine()) + { + mBaseDocIsPristine = false; + } +} + +void LLTextEditor::makePristine() +{ + mPristineCmd = mLastCmd; + mBaseDocIsPristine = !mLastCmd; + + // Create a clean partition in the undo stack. We don't want a single command to extend from + // the "pre-pristine" state to the "post-pristine" state. + if( mLastCmd ) + { + mLastCmd->blockExtensions(); + } +} + +bool LLTextEditor::isPristine() const +{ + if( mPristineCmd ) + { + return (mPristineCmd == mLastCmd); + } + else + { + // No undo stack, so check if the version before and commands were done was the original version + return !mLastCmd && mBaseDocIsPristine; + } +} + +bool LLTextEditor::tryToRevertToPristineState() +{ + if( !isPristine() ) + { + deselect(); + S32 i = 0; + while( !isPristine() && canUndo() ) + { + undo(); + i--; + } + + while( !isPristine() && canRedo() ) + { + redo(); + i++; + } + + if( !isPristine() ) + { + // failed, so go back to where we started + while( i > 0 ) + { + undo(); + i--; + } + } + } + + return isPristine(); // true => success +} + +void LLTextEditor::updateLinkSegments() +{ + LLWString wtext = getWText(); + + // update any segments that contain a link + for (segment_set_t::iterator it = mSegments.begin(); it != mSegments.end(); ++it) + { + LLTextSegment *segment = *it; + if (segment && segment->getStyle() && segment->getStyle()->isLink()) + { + LLStyleConstSP style = segment->getStyle(); + LLStyleSP new_style(new LLStyle(*style)); + LLWString url_label = wtext.substr(segment->getStart(), segment->getEnd()-segment->getStart()); + + segment_set_t::const_iterator next_it = mSegments.upper_bound(segment); + LLTextSegment *next_segment = *next_it; + if (next_segment) + { + LLWString next_url_label = wtext.substr(next_segment->getStart(), next_segment->getEnd()-next_segment->getStart()); + std::string link_check = wstring_to_utf8str(url_label) + wstring_to_utf8str(next_url_label); + LLUrlMatch match; + + if ( LLUrlRegistry::instance().findUrl(link_check, match)) + { + if(match.getQuery() == wstring_to_utf8str(next_url_label)) + { + continue; + } + } + } + + // if the link's label (what the user can edit) is a valid Url, + // then update the link's HREF to be the same as the label text. + // This lets users edit Urls in-place. + if (acceptsTextInput() && LLUrlRegistry::instance().hasUrl(url_label)) + { + std::string new_url = wstring_to_utf8str(url_label); + LLStringUtil::trim(new_url); + new_style->setLinkHREF(new_url); + LLStyleConstSP sp(new_style); + segment->setStyle(sp); + } + } + } +} + + + +void LLTextEditor::onMouseCaptureLost() +{ + endSelection(); +} + +/////////////////////////////////////////////////////////////////// +// Hack for Notecards + +bool LLTextEditor::importBuffer(const char* buffer, S32 length ) +{ + std::istringstream instream(buffer); + + // Version 1 format: + // Linden text version 1\n + // {\n + // + // Text length \n + // (text may contain ext_char_values) + // }\n + + char tbuf[MAX_STRING]; /* Flawfinder: ignore */ + + S32 version = 0; + instream.getline(tbuf, MAX_STRING); + if( 1 != sscanf(tbuf, "Linden text version %d", &version) ) + { + LL_WARNS() << "Invalid Linden text file header " << LL_ENDL; + return false; + } + + if( 1 != version ) + { + LL_WARNS() << "Invalid Linden text file version: " << version << LL_ENDL; + return false; + } + + instream.getline(tbuf, MAX_STRING); + if( 0 != sscanf(tbuf, "{") ) + { + LL_WARNS() << "Invalid Linden text file format" << LL_ENDL; + return false; + } + + S32 text_len = 0; + instream.getline(tbuf, MAX_STRING); + if( 1 != sscanf(tbuf, "Text length %d", &text_len) ) + { + LL_WARNS() << "Invalid Linden text length field" << LL_ENDL; + return false; + } + + if( text_len > mMaxTextByteLength ) + { + LL_WARNS() << "Invalid Linden text length: " << text_len << LL_ENDL; + return false; + } + + bool success = true; + + char* text = new char[ text_len + 1]; + if (text == NULL) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS() << "Memory allocation failure." << LL_ENDL; + return false; + } + instream.get(text, text_len + 1, '\0'); + text[text_len] = '\0'; + if( text_len != (S32)strlen(text) )/* Flawfinder: ignore */ + { + LL_WARNS() << llformat("Invalid text length: %d != %d ",strlen(text),text_len) << LL_ENDL;/* Flawfinder: ignore */ + success = false; + } + + instream.getline(tbuf, MAX_STRING); + if( success && (0 != sscanf(tbuf, "}")) ) + { + LL_WARNS() << "Invalid Linden text file format: missing terminal }" << LL_ENDL; + success = false; + } + + if( success ) + { + // Actually set the text + setText( LLStringExplicit(text) ); + } + + delete[] text; + + startOfDoc(); + deselect(); + + return success; +} + +bool LLTextEditor::exportBuffer(std::string &buffer ) +{ + std::ostringstream outstream(buffer); + + outstream << "Linden text version 1\n"; + outstream << "{\n"; + + outstream << llformat("Text length %d\n", getLength() ); + outstream << getText(); + outstream << "}\n"; + + return true; +} + +void LLTextEditor::updateAllowingLanguageInput() +{ + LLWindow* window = getWindow(); + if (!window) + { + // test app, no window available + return; + } + if (hasFocus() && !mReadOnly) + { + window->allowLanguageTextInput(this, true); + } + else + { + window->allowLanguageTextInput(this, false); + } +} + +// Preedit is managed off the undo/redo command stack. + +bool LLTextEditor::hasPreeditString() const +{ + return (mPreeditPositions.size() > 1); +} + +void LLTextEditor::resetPreedit() +{ + if (hasSelection()) + { + if (hasPreeditString()) + { + LL_WARNS() << "Preedit and selection!" << LL_ENDL; + deselect(); + } + else + { + deleteSelection(true); + } + } + if (hasPreeditString()) + { + if (hasSelection()) + { + LL_WARNS() << "Preedit and selection!" << LL_ENDL; + deselect(); + } + + setCursorPos(mPreeditPositions.front()); + removeStringNoUndo(mCursorPos, mPreeditPositions.back() - mCursorPos); + insertStringNoUndo(mCursorPos, mPreeditOverwrittenWString); + + mPreeditWString.clear(); + mPreeditOverwrittenWString.clear(); + mPreeditPositions.clear(); + + // A call to updatePreedit should soon follow under a + // normal course of operation, so we don't need to + // maintain internal variables such as line start + // positions now. + } +} + +void LLTextEditor::updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position) +{ + // Just in case. + if (mReadOnly) + { + return; + } + + getWindow()->hideCursorUntilMouseMove(); + + S32 insert_preedit_at = mCursorPos; + + mPreeditWString = preedit_string; + mPreeditPositions.resize(preedit_segment_lengths.size() + 1); + S32 position = insert_preedit_at; + for (segment_lengths_t::size_type i = 0; i < preedit_segment_lengths.size(); i++) + { + mPreeditPositions[i] = position; + position += preedit_segment_lengths[i]; + } + mPreeditPositions.back() = position; + + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString = getWText().substr(insert_preedit_at, mPreeditWString.length()); + removeStringNoUndo(insert_preedit_at, mPreeditWString.length()); + } + else + { + mPreeditOverwrittenWString.clear(); + } + + segment_vec_t segments; + //pass empty segments to let "insertStringNoUndo" make new LLNormalTextSegment and insert it, if needed. + insertStringNoUndo(insert_preedit_at, mPreeditWString, &segments); + + mPreeditStandouts = preedit_standouts; + + setCursorPos(insert_preedit_at + caret_position); + + // Update of the preedit should be caused by some key strokes. + resetCursorBlink(); + + onKeyStroke(); +} + +bool LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const +{ + if (control) + { + LLRect control_rect_screen; + localRectToScreen(mVisibleTextRect, &control_rect_screen); + LLUI::getInstance()->screenRectToGL(control_rect_screen, control); + } + + S32 preedit_left_position, preedit_right_position; + if (hasPreeditString()) + { + preedit_left_position = mPreeditPositions.front(); + preedit_right_position = mPreeditPositions.back(); + } + else + { + preedit_left_position = preedit_right_position = mCursorPos; + } + + const S32 query = (query_offset >= 0 ? preedit_left_position + query_offset : mCursorPos); + if (query < preedit_left_position || query > preedit_right_position) + { + return false; + } + + const S32 first_visible_line = getFirstVisibleLine(); + if (query < getLineStart(first_visible_line)) + { + return false; + } + + S32 current_line = first_visible_line; + S32 current_line_start, current_line_end; + for (;;) + { + current_line_start = getLineStart(current_line); + current_line_end = getLineStart(current_line + 1); + if (query >= current_line_start && query < current_line_end) + { + break; + } + if (current_line_start == current_line_end) + { + // We have reached on the last line. The query position must be here. + break; + } + current_line++; + } + + const LLWString textString(getWText()); + const llwchar * const text = textString.c_str(); + const S32 line_height = mFont->getLineHeight(); + + if (coord) + { + const S32 query_x = mVisibleTextRect.mLeft + mFont->getWidth(text, current_line_start, query - current_line_start); + const S32 query_y = mVisibleTextRect.mTop - (current_line - first_visible_line) * line_height - line_height / 2; + S32 query_screen_x, query_screen_y; + localPointToScreen(query_x, query_y, &query_screen_x, &query_screen_y); + LLUI::getInstance()->screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); + } + + if (bounds) + { + S32 preedit_left = mVisibleTextRect.mLeft; + if (preedit_left_position > current_line_start) + { + preedit_left += mFont->getWidth(text, current_line_start, preedit_left_position - current_line_start); + } + + S32 preedit_right = mVisibleTextRect.mLeft; + if (preedit_right_position < current_line_end) + { + preedit_right += mFont->getWidth(text, current_line_start, preedit_right_position - current_line_start); + } + else + { + preedit_right += mFont->getWidth(text, current_line_start, current_line_end - current_line_start); + } + + const S32 preedit_top = mVisibleTextRect.mTop - (current_line - first_visible_line) * line_height; + const S32 preedit_bottom = preedit_top - line_height; + + const LLRect preedit_rect_local(preedit_left, preedit_top, preedit_right, preedit_bottom); + LLRect preedit_rect_screen; + localRectToScreen(preedit_rect_local, &preedit_rect_screen); + LLUI::getInstance()->screenRectToGL(preedit_rect_screen, bounds); + } + + return true; +} + +void LLTextEditor::getSelectionRange(S32 *position, S32 *length) const +{ + if (hasSelection()) + { + *position = llmin(mSelectionStart, mSelectionEnd); + *length = llabs(mSelectionStart - mSelectionEnd); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLTextEditor::getPreeditRange(S32 *position, S32 *length) const +{ + if (hasPreeditString()) + { + *position = mPreeditPositions.front(); + *length = mPreeditPositions.back() - mPreeditPositions.front(); + } + else + { + *position = mCursorPos; + *length = 0; + } +} + +void LLTextEditor::markAsPreedit(S32 position, S32 length) +{ + deselect(); + setCursorPos(position); + if (hasPreeditString()) + { + LL_WARNS() << "markAsPreedit invoked when hasPreeditString is true." << LL_ENDL; + } + mPreeditWString = LLWString( getWText(), position, length ); + if (length > 0) + { + mPreeditPositions.resize(2); + mPreeditPositions[0] = position; + mPreeditPositions[1] = position + length; + mPreeditStandouts.resize(1); + mPreeditStandouts[0] = false; + } + else + { + mPreeditPositions.clear(); + mPreeditStandouts.clear(); + } + if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) + { + mPreeditOverwrittenWString = mPreeditWString; + } + else + { + mPreeditOverwrittenWString.clear(); + } +} + +S32 LLTextEditor::getPreeditFontSize() const +{ + return ll_round((F32)mFont->getLineHeight() * LLUI::getScaleFactor().mV[VY]); +} + +bool LLTextEditor::isDirty() const +{ + if(mReadOnly) + { + return false; + } + + if( mPristineCmd ) + { + return ( mPristineCmd == mLastCmd ); + } + else + { + return ( NULL != mLastCmd ); + } +} + +void LLTextEditor::setKeystrokeCallback(const keystroke_signal_t::slot_type& callback) +{ + mKeystrokeSignal.connect(callback); +} + +void LLTextEditor::onKeyStroke() +{ + mKeystrokeSignal(this); + + mSpellCheckStart = mSpellCheckEnd = -1; + mSpellCheckTimer.setTimerExpirySec(SPELLCHECK_DELAY); +} + +//virtual +void LLTextEditor::clear() +{ + getViewModel()->setDisplay(LLWStringUtil::null); + clearSegments(); +} + +bool LLTextEditor::canLoadOrSaveToFile() +{ + return !mReadOnly; +} + +S32 LLTextEditor::spacesPerTab() +{ + return SPACES_PER_TAB; +} diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 2bffc813a5..be7f7cb256 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -1,351 +1,351 @@ -/** - * @file lltexteditor.h - * @brief LLTextEditor base 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$ - */ - -// Text editor widget to let users enter a a multi-line ASCII document// - -#ifndef LL_LLTEXTEDITOR_H -#define LL_LLTEXTEDITOR_H - -#include "llrect.h" -#include "llframetimer.h" -#include "llstyle.h" -#include "lleditmenuhandler.h" -#include "llviewborder.h" // for params -#include "lltextbase.h" -#include "lltextvalidate.h" - -#include "llpreeditor.h" -#include "llcontrol.h" - -class LLFontGL; -class LLScrollbar; -class TextCmd; -class LLUICtrlFactory; -class LLScrollContainer; - -class LLTextEditor : - public LLTextBase, - protected LLPreeditor -{ -public: - struct Params : public LLInitParam::Block - { - Optional default_text; - Optional prevalidator; - - Optional embedded_items, - ignore_tab, - commit_on_focus_lost, - show_context_menu, - show_emoji_helper, - enable_tooltip_paste, - auto_indent; - - //colors - Optional default_color; - - Params(); - }; - - void initFromParams(const Params&); -protected: - LLTextEditor(const Params&); - friend class LLUICtrlFactory; -public: - // - // Constants - // - static const llwchar FIRST_EMBEDDED_CHAR = 0x100000; - static const llwchar LAST_EMBEDDED_CHAR = 0x10ffff; - static const S32 MAX_EMBEDDED_ITEMS = LAST_EMBEDDED_CHAR - FIRST_EMBEDDED_CHAR + 1; - - virtual ~LLTextEditor(); - - typedef boost::signals2::signal keystroke_signal_t; - - void setKeystrokeCallback(const keystroke_signal_t::slot_type& callback); - - void setParseHighlights(bool parsing) {mParseHighlights=parsing;} - - static S32 spacesPerTab(); - - void insertEmoji(llwchar emoji); - void handleEmojiCommit(llwchar emoji); - - // mousehandler overrides - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask ); - virtual bool handleMiddleMouseDown(S32 x,S32 y,MASK mask); - - virtual bool handleKeyHere(KEY key, MASK mask ); - virtual bool handleUnicodeCharHere(llwchar uni_char); - - virtual void onMouseCaptureLost(); - - // view overrides - virtual void draw(); - virtual void onFocusReceived(); - virtual void onFocusLost(); - virtual void onCommit(); - virtual void setEnabled(bool enabled); - - // uictrl overrides - virtual void clear(); - virtual void setFocus( bool b ); - virtual bool isDirty() const; - - // LLEditMenuHandler interface - virtual void undo(); - virtual bool canUndo() const; - virtual void redo(); - virtual bool canRedo() const; - - virtual void cut(); - virtual bool canCut() const; - virtual void copy(); - virtual bool canCopy() const; - virtual void paste(); - virtual bool canPaste() const; - - virtual void updatePrimary(); - virtual void copyPrimary(); - virtual void pastePrimary(); - virtual bool canPastePrimary() const; - - virtual void doDelete(); - virtual bool canDoDelete() const; - virtual void selectAll(); - virtual bool canSelectAll() const; - - void selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_pos); - - virtual bool canLoadOrSaveToFile(); - - void selectNext(const std::string& search_text_in, bool case_insensitive, bool wrap = true); - bool replaceText(const std::string& search_text, const std::string& replace_text, bool case_insensitive, bool wrap = true); - void replaceTextAll(const std::string& search_text, const std::string& replace_text, bool case_insensitive); - - // Undo/redo stack - void blockUndo(); - - // Text editing - virtual void makePristine(); - bool isPristine() const; - bool allowsEmbeddedItems() const { return mAllowEmbeddedItems; } - - // Autoreplace (formerly part of LLLineEditor) - typedef boost::function autoreplace_callback_t; - autoreplace_callback_t mAutoreplaceCallback; - void setAutoreplaceCallback(autoreplace_callback_t cb) { mAutoreplaceCallback = cb; } - - /*virtual*/ void onSpellCheckPerformed(); - - // - // Text manipulation - // - - // inserts text at cursor - void insertText(const std::string &text); - void insertText(LLWString &text); - - void appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo); - // Non-undoable - void setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params = LLStyle::Params()); - - - // Removes text from the end of document - // Does not change highlight or cursor position. - void removeTextFromEnd(S32 num_chars); - - bool tryToRevertToPristineState(); - - void setCursorAndScrollToEnd(); - - void getCurrentLineAndColumn( S32* line, S32* col, bool include_wordwrap ); - - // Hacky methods to make it into a word-wrapping, potentially scrolling, - // read-only text box. - void setCommitOnFocusLost(bool b) { mCommitOnFocusLost = b; } - - // Hack to handle Notecards - virtual bool importBuffer(const char* buffer, S32 length ); - virtual bool exportBuffer(std::string& buffer ); - - const LLUUID& getSourceID() const { return mSourceID; } - - const LLTextSegmentPtr getPreviousSegment() const; - const LLTextSegmentPtr getLastSegment() const; - void getSelectedSegments(segment_vec_t& segments) const; - - void setShowContextMenu(bool show) { mShowContextMenu = show; } - bool getShowContextMenu() const { return mShowContextMenu; } - - void showEmojiHelper(); - void setShowEmojiHelper(bool show); - bool getShowEmojiHelper() const { return mShowEmojiHelper; } - - void setPassDelete(bool b) { mPassDelete = b; } - -protected: - void showContextMenu(S32 x, S32 y); - void drawPreeditMarker(); - - void assignEmbedded(const std::string &s); - - void removeCharOrTab(); - - void indentSelectedLines( S32 spaces ); - S32 indentLine( S32 pos, S32 spaces ); - void unindentLineBeforeCloseBrace(); - - virtual bool handleSpecialKey(const KEY key, const MASK mask); - bool handleNavigationKey(const KEY key, const MASK mask); - bool handleSelectionKey(const KEY key, const MASK mask); - bool handleControlKey(const KEY key, const MASK mask); - - bool selectionContainsLineBreaks(); - void deleteSelection(bool transient_operation); - - S32 prevWordPos(S32 cursorPos) const; - S32 nextWordPos(S32 cursorPos) const; - - void autoIndent(); - - void findEmbeddedItemSegments(S32 start, S32 end); - void getSegmentsInRange(segment_vec_t& segments, S32 start, S32 end, bool include_partial) const; - - virtual llwchar pasteEmbeddedItem(llwchar ext_char) { return ext_char; } - - - // Here's the method that takes and applies text commands. - S32 execute(TextCmd* cmd); - - // Undoable operations - void addChar(llwchar c); // at mCursorPos - S32 addChar(S32 pos, llwchar wc); - void addLineBreakChar(bool group_together = false); - S32 overwriteChar(S32 pos, llwchar wc); - void removeChar(); - S32 removeChar(S32 pos); - S32 insert(S32 pos, const LLWString &wstr, bool group_with_next_op, LLTextSegmentPtr segment); - S32 remove(S32 pos, S32 length, bool group_with_next_op); - - void tryToShowEmojiHelper(); - void focusLostHelper(); - void updateAllowingLanguageInput(); - bool hasPreeditString() const; - - // Overrides LLPreeditor - virtual void resetPreedit(); - virtual void updatePreedit(const LLWString &preedit_string, - const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position); - virtual void markAsPreedit(S32 position, S32 length); - virtual void getPreeditRange(S32 *position, S32 *length) const; - virtual void getSelectionRange(S32 *position, S32 *length) const; - virtual bool getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const; - virtual S32 getPreeditFontSize() const; - virtual LLWString getPreeditString() const { return getWText(); } - // - // Protected data - // - // Probably deserves serious thought to hiding as many of these - // as possible behind protected accessor methods. - // - - // Use these to determine if a click on an embedded item is a drag or not. - S32 mMouseDownX; - S32 mMouseDownY; - - LLWString mPreeditWString; - LLWString mPreeditOverwrittenWString; - std::vector mPreeditPositions; - LLPreeditor::standouts_t mPreeditStandouts; - -protected: - LLUIColor mDefaultColor; - - bool mAutoIndent; - bool mParseOnTheFly; - - void updateLinkSegments(); - void keepSelectionOnReturn(bool keep) { mKeepSelectionOnReturn = keep; } - class LLViewBorder* mBorder; - -private: - // - // Methods - // - void pasteHelper(bool is_primary); - void cleanStringForPaste(LLWString & clean_string); - void pasteTextWithLinebreaks(LLWString & clean_string); - - void onKeyStroke(); - - // Concrete TextCmd sub-classes used by the LLTextEditor base class - class TextCmdInsert; - class TextCmdAddChar; - class TextCmdOverwriteChar; - class TextCmdRemove; - - bool mBaseDocIsPristine; - TextCmd* mPristineCmd; - - TextCmd* mLastCmd; - - typedef std::deque undo_stack_t; - undo_stack_t mUndoStack; - - bool mTabsToNextField; // if true, tab moves focus to next field, else inserts spaces - bool mCommitOnFocusLost; - bool mTakesFocus; - - bool mAllowEmbeddedItems; - bool mShowContextMenu; - bool mShowEmojiHelper; - bool mEnableTooltipPaste; - bool mPassDelete; - bool mKeepSelectionOnReturn; // disabling of removing selected text after pressing of Enter - - LLUUID mSourceID; - - LLCoordGL mLastIMEPosition; // Last position of the IME editor - - keystroke_signal_t mKeystrokeSignal; - LLTextValidate::Validator mPrevalidator; - - LLHandle mContextMenuHandle; -}; // end class LLTextEditor - -// Build time optimization, generate once in .cpp file -#ifndef LLTEXTEDITOR_CPP -extern template class LLTextEditor* LLView::getChild( - const std::string& name, bool recurse) const; -#endif - -#endif // LL_TEXTEDITOR_H +/** + * @file lltexteditor.h + * @brief LLTextEditor base 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$ + */ + +// Text editor widget to let users enter a a multi-line ASCII document// + +#ifndef LL_LLTEXTEDITOR_H +#define LL_LLTEXTEDITOR_H + +#include "llrect.h" +#include "llframetimer.h" +#include "llstyle.h" +#include "lleditmenuhandler.h" +#include "llviewborder.h" // for params +#include "lltextbase.h" +#include "lltextvalidate.h" + +#include "llpreeditor.h" +#include "llcontrol.h" + +class LLFontGL; +class LLScrollbar; +class TextCmd; +class LLUICtrlFactory; +class LLScrollContainer; + +class LLTextEditor : + public LLTextBase, + protected LLPreeditor +{ +public: + struct Params : public LLInitParam::Block + { + Optional default_text; + Optional prevalidator; + + Optional embedded_items, + ignore_tab, + commit_on_focus_lost, + show_context_menu, + show_emoji_helper, + enable_tooltip_paste, + auto_indent; + + //colors + Optional default_color; + + Params(); + }; + + void initFromParams(const Params&); +protected: + LLTextEditor(const Params&); + friend class LLUICtrlFactory; +public: + // + // Constants + // + static const llwchar FIRST_EMBEDDED_CHAR = 0x100000; + static const llwchar LAST_EMBEDDED_CHAR = 0x10ffff; + static const S32 MAX_EMBEDDED_ITEMS = LAST_EMBEDDED_CHAR - FIRST_EMBEDDED_CHAR + 1; + + virtual ~LLTextEditor(); + + typedef boost::signals2::signal keystroke_signal_t; + + void setKeystrokeCallback(const keystroke_signal_t::slot_type& callback); + + void setParseHighlights(bool parsing) {mParseHighlights=parsing;} + + static S32 spacesPerTab(); + + void insertEmoji(llwchar emoji); + void handleEmojiCommit(llwchar emoji); + + // mousehandler overrides + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask ); + virtual bool handleMiddleMouseDown(S32 x,S32 y,MASK mask); + + virtual bool handleKeyHere(KEY key, MASK mask ); + virtual bool handleUnicodeCharHere(llwchar uni_char); + + virtual void onMouseCaptureLost(); + + // view overrides + virtual void draw(); + virtual void onFocusReceived(); + virtual void onFocusLost(); + virtual void onCommit(); + virtual void setEnabled(bool enabled); + + // uictrl overrides + virtual void clear(); + virtual void setFocus( bool b ); + virtual bool isDirty() const; + + // LLEditMenuHandler interface + virtual void undo(); + virtual bool canUndo() const; + virtual void redo(); + virtual bool canRedo() const; + + virtual void cut(); + virtual bool canCut() const; + virtual void copy(); + virtual bool canCopy() const; + virtual void paste(); + virtual bool canPaste() const; + + virtual void updatePrimary(); + virtual void copyPrimary(); + virtual void pastePrimary(); + virtual bool canPastePrimary() const; + + virtual void doDelete(); + virtual bool canDoDelete() const; + virtual void selectAll(); + virtual bool canSelectAll() const; + + void selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_pos); + + virtual bool canLoadOrSaveToFile(); + + void selectNext(const std::string& search_text_in, bool case_insensitive, bool wrap = true); + bool replaceText(const std::string& search_text, const std::string& replace_text, bool case_insensitive, bool wrap = true); + void replaceTextAll(const std::string& search_text, const std::string& replace_text, bool case_insensitive); + + // Undo/redo stack + void blockUndo(); + + // Text editing + virtual void makePristine(); + bool isPristine() const; + bool allowsEmbeddedItems() const { return mAllowEmbeddedItems; } + + // Autoreplace (formerly part of LLLineEditor) + typedef boost::function autoreplace_callback_t; + autoreplace_callback_t mAutoreplaceCallback; + void setAutoreplaceCallback(autoreplace_callback_t cb) { mAutoreplaceCallback = cb; } + + /*virtual*/ void onSpellCheckPerformed(); + + // + // Text manipulation + // + + // inserts text at cursor + void insertText(const std::string &text); + void insertText(LLWString &text); + + void appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo); + // Non-undoable + void setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params = LLStyle::Params()); + + + // Removes text from the end of document + // Does not change highlight or cursor position. + void removeTextFromEnd(S32 num_chars); + + bool tryToRevertToPristineState(); + + void setCursorAndScrollToEnd(); + + void getCurrentLineAndColumn( S32* line, S32* col, bool include_wordwrap ); + + // Hacky methods to make it into a word-wrapping, potentially scrolling, + // read-only text box. + void setCommitOnFocusLost(bool b) { mCommitOnFocusLost = b; } + + // Hack to handle Notecards + virtual bool importBuffer(const char* buffer, S32 length ); + virtual bool exportBuffer(std::string& buffer ); + + const LLUUID& getSourceID() const { return mSourceID; } + + const LLTextSegmentPtr getPreviousSegment() const; + const LLTextSegmentPtr getLastSegment() const; + void getSelectedSegments(segment_vec_t& segments) const; + + void setShowContextMenu(bool show) { mShowContextMenu = show; } + bool getShowContextMenu() const { return mShowContextMenu; } + + void showEmojiHelper(); + void setShowEmojiHelper(bool show); + bool getShowEmojiHelper() const { return mShowEmojiHelper; } + + void setPassDelete(bool b) { mPassDelete = b; } + +protected: + void showContextMenu(S32 x, S32 y); + void drawPreeditMarker(); + + void assignEmbedded(const std::string &s); + + void removeCharOrTab(); + + void indentSelectedLines( S32 spaces ); + S32 indentLine( S32 pos, S32 spaces ); + void unindentLineBeforeCloseBrace(); + + virtual bool handleSpecialKey(const KEY key, const MASK mask); + bool handleNavigationKey(const KEY key, const MASK mask); + bool handleSelectionKey(const KEY key, const MASK mask); + bool handleControlKey(const KEY key, const MASK mask); + + bool selectionContainsLineBreaks(); + void deleteSelection(bool transient_operation); + + S32 prevWordPos(S32 cursorPos) const; + S32 nextWordPos(S32 cursorPos) const; + + void autoIndent(); + + void findEmbeddedItemSegments(S32 start, S32 end); + void getSegmentsInRange(segment_vec_t& segments, S32 start, S32 end, bool include_partial) const; + + virtual llwchar pasteEmbeddedItem(llwchar ext_char) { return ext_char; } + + + // Here's the method that takes and applies text commands. + S32 execute(TextCmd* cmd); + + // Undoable operations + void addChar(llwchar c); // at mCursorPos + S32 addChar(S32 pos, llwchar wc); + void addLineBreakChar(bool group_together = false); + S32 overwriteChar(S32 pos, llwchar wc); + void removeChar(); + S32 removeChar(S32 pos); + S32 insert(S32 pos, const LLWString &wstr, bool group_with_next_op, LLTextSegmentPtr segment); + S32 remove(S32 pos, S32 length, bool group_with_next_op); + + void tryToShowEmojiHelper(); + void focusLostHelper(); + void updateAllowingLanguageInput(); + bool hasPreeditString() const; + + // Overrides LLPreeditor + virtual void resetPreedit(); + virtual void updatePreedit(const LLWString &preedit_string, + const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position); + virtual void markAsPreedit(S32 position, S32 length); + virtual void getPreeditRange(S32 *position, S32 *length) const; + virtual void getSelectionRange(S32 *position, S32 *length) const; + virtual bool getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const; + virtual S32 getPreeditFontSize() const; + virtual LLWString getPreeditString() const { return getWText(); } + // + // Protected data + // + // Probably deserves serious thought to hiding as many of these + // as possible behind protected accessor methods. + // + + // Use these to determine if a click on an embedded item is a drag or not. + S32 mMouseDownX; + S32 mMouseDownY; + + LLWString mPreeditWString; + LLWString mPreeditOverwrittenWString; + std::vector mPreeditPositions; + LLPreeditor::standouts_t mPreeditStandouts; + +protected: + LLUIColor mDefaultColor; + + bool mAutoIndent; + bool mParseOnTheFly; + + void updateLinkSegments(); + void keepSelectionOnReturn(bool keep) { mKeepSelectionOnReturn = keep; } + class LLViewBorder* mBorder; + +private: + // + // Methods + // + void pasteHelper(bool is_primary); + void cleanStringForPaste(LLWString & clean_string); + void pasteTextWithLinebreaks(LLWString & clean_string); + + void onKeyStroke(); + + // Concrete TextCmd sub-classes used by the LLTextEditor base class + class TextCmdInsert; + class TextCmdAddChar; + class TextCmdOverwriteChar; + class TextCmdRemove; + + bool mBaseDocIsPristine; + TextCmd* mPristineCmd; + + TextCmd* mLastCmd; + + typedef std::deque undo_stack_t; + undo_stack_t mUndoStack; + + bool mTabsToNextField; // if true, tab moves focus to next field, else inserts spaces + bool mCommitOnFocusLost; + bool mTakesFocus; + + bool mAllowEmbeddedItems; + bool mShowContextMenu; + bool mShowEmojiHelper; + bool mEnableTooltipPaste; + bool mPassDelete; + bool mKeepSelectionOnReturn; // disabling of removing selected text after pressing of Enter + + LLUUID mSourceID; + + LLCoordGL mLastIMEPosition; // Last position of the IME editor + + keystroke_signal_t mKeystrokeSignal; + LLTextValidate::Validator mPrevalidator; + + LLHandle mContextMenuHandle; +}; // end class LLTextEditor + +// Build time optimization, generate once in .cpp file +#ifndef LLTEXTEDITOR_CPP +extern template class LLTextEditor* LLView::getChild( + const std::string& name, bool recurse) const; +#endif + +#endif // LL_TEXTEDITOR_H diff --git a/indra/llui/lltextparser.cpp b/indra/llui/lltextparser.cpp index be195e1146..8e2bfe6ac2 100644 --- a/indra/llui/lltextparser.cpp +++ b/indra/llui/lltextparser.cpp @@ -1,239 +1,239 @@ -/** - * @file lltextparser.cpp - * - * $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 "lltextparser.h" - -#include "llsd.h" -#include "llsdserialize.h" -#include "llerror.h" -#include "lluuid.h" -#include "llstring.h" -#include "message.h" -#include "llmath.h" -#include "v4color.h" -#include "lldir.h" - -// -// Member Functions -// - -LLTextParser::LLTextParser() -: mLoaded(false) -{} - - -S32 LLTextParser::findPattern(const std::string &text, LLSD highlight) -{ - if (!highlight.has("pattern")) return -1; - - std::string pattern=std::string(highlight["pattern"]); - std::string ltext=text; - - if (!(bool)highlight["case_sensitive"]) - { - ltext = utf8str_tolower(text); - pattern= utf8str_tolower(pattern); - } - - size_t found=std::string::npos; - - switch ((S32)highlight["condition"]) - { - case CONTAINS: - found = ltext.find(pattern); - break; - case MATCHES: - found = (! ltext.compare(pattern) ? 0 : std::string::npos); - break; - case STARTS_WITH: - found = (! ltext.find(pattern) ? 0 : std::string::npos); - break; - case ENDS_WITH: - S32 pos = ltext.rfind(pattern); - if (pos >= 0 && (ltext.length()-pattern.length()==pos)) found = pos; - break; - } - return found; -} - -LLSD LLTextParser::parsePartialLineHighlights(const std::string &text, const LLColor4 &color, EHighlightPosition part, S32 index) -{ - loadKeywords(); - - //evil recursive string atomizer. - LLSD ret_llsd, start_llsd, middle_llsd, end_llsd; - - for (S32 i=index;i= 0 ) - { - S32 end = std::string(mHighlights[i]["pattern"]).length(); - S32 len = text.length(); - EHighlightPosition newpart; - if (start==0) - { - start_llsd[0]["text"] =text.substr(0,end); - start_llsd[0]["color"]=mHighlights[i]["color"]; - - if (end < len) - { - if (part==END || part==WHOLE) newpart=END; else newpart=MIDDLE; - end_llsd=parsePartialLineHighlights(text.substr( end ),color,newpart,i); - } - } - else - { - if (part==START || part==WHOLE) newpart=START; else newpart=MIDDLE; - - start_llsd=parsePartialLineHighlights(text.substr(0,start),color,newpart,i+1); - - if (end < len) - { - middle_llsd[0]["text"] =text.substr(start,end); - middle_llsd[0]["color"]=mHighlights[i]["color"]; - - if (part==END || part==WHOLE) newpart=END; else newpart=MIDDLE; - - end_llsd=parsePartialLineHighlights(text.substr( (start+end) ),color,newpart,i); - } - else - { - end_llsd[0]["text"] =text.substr(start,end); - end_llsd[0]["color"]=mHighlights[i]["color"]; - } - } - - S32 retcount=0; - - //FIXME These loops should be wrapped into a subroutine. - for (LLSD::array_iterator iter = start_llsd.beginArray(); - iter != start_llsd.endArray();++iter) - { - LLSD highlight = *iter; - ret_llsd[retcount++]=highlight; - } - - for (LLSD::array_iterator iter = middle_llsd.beginArray(); - iter != middle_llsd.endArray();++iter) - { - LLSD highlight = *iter; - ret_llsd[retcount++]=highlight; - } - - for (LLSD::array_iterator iter = end_llsd.beginArray(); - iter != end_llsd.endArray();++iter) - { - LLSD highlight = *iter; - ret_llsd[retcount++]=highlight; - } - - return ret_llsd; - } - } - } - } - - //No patterns found. Just send back what was passed in. - ret_llsd[0]["text"] =text; - LLSD color_sd = color.getValue(); - ret_llsd[0]["color"]=color_sd; - return ret_llsd; -} - -bool LLTextParser::parseFullLineHighlights(const std::string &text, LLColor4 *color) -{ - loadKeywords(); - - for (S32 i=0;i= 0 ) - { - LLSD color_llsd = mHighlights[i]["color"]; - color->setValue(color_llsd); - return true; - } - } - } - return false; //No matches found. -} - -std::string LLTextParser::getFileName() -{ - std::string path=gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, ""); - - if (!path.empty()) - { - path = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "highlights.xml"); - } - return path; -} - -void LLTextParser::loadKeywords() -{ - if (mLoaded) - {// keywords already loaded - return; - } - std::string filename=getFileName(); - if (!filename.empty()) - { - llifstream file; - file.open(filename.c_str()); - if (file.is_open()) - { - LLSDSerialize::fromXML(mHighlights, file); - } - file.close(); - mLoaded = true; - } -} - -bool LLTextParser::saveToDisk(LLSD highlights) -{ - mHighlights=highlights; - std::string filename=getFileName(); - if (filename.empty()) - { - LL_WARNS() << "LLTextParser::saveToDisk() no valid user directory." << LL_ENDL; - return false; - } - llofstream file; - file.open(filename.c_str()); - LLSDSerialize::toPrettyXML(mHighlights, file); - file.close(); - return true; -} +/** + * @file lltextparser.cpp + * + * $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 "lltextparser.h" + +#include "llsd.h" +#include "llsdserialize.h" +#include "llerror.h" +#include "lluuid.h" +#include "llstring.h" +#include "message.h" +#include "llmath.h" +#include "v4color.h" +#include "lldir.h" + +// +// Member Functions +// + +LLTextParser::LLTextParser() +: mLoaded(false) +{} + + +S32 LLTextParser::findPattern(const std::string &text, LLSD highlight) +{ + if (!highlight.has("pattern")) return -1; + + std::string pattern=std::string(highlight["pattern"]); + std::string ltext=text; + + if (!(bool)highlight["case_sensitive"]) + { + ltext = utf8str_tolower(text); + pattern= utf8str_tolower(pattern); + } + + size_t found=std::string::npos; + + switch ((S32)highlight["condition"]) + { + case CONTAINS: + found = ltext.find(pattern); + break; + case MATCHES: + found = (! ltext.compare(pattern) ? 0 : std::string::npos); + break; + case STARTS_WITH: + found = (! ltext.find(pattern) ? 0 : std::string::npos); + break; + case ENDS_WITH: + S32 pos = ltext.rfind(pattern); + if (pos >= 0 && (ltext.length()-pattern.length()==pos)) found = pos; + break; + } + return found; +} + +LLSD LLTextParser::parsePartialLineHighlights(const std::string &text, const LLColor4 &color, EHighlightPosition part, S32 index) +{ + loadKeywords(); + + //evil recursive string atomizer. + LLSD ret_llsd, start_llsd, middle_llsd, end_llsd; + + for (S32 i=index;i= 0 ) + { + S32 end = std::string(mHighlights[i]["pattern"]).length(); + S32 len = text.length(); + EHighlightPosition newpart; + if (start==0) + { + start_llsd[0]["text"] =text.substr(0,end); + start_llsd[0]["color"]=mHighlights[i]["color"]; + + if (end < len) + { + if (part==END || part==WHOLE) newpart=END; else newpart=MIDDLE; + end_llsd=parsePartialLineHighlights(text.substr( end ),color,newpart,i); + } + } + else + { + if (part==START || part==WHOLE) newpart=START; else newpart=MIDDLE; + + start_llsd=parsePartialLineHighlights(text.substr(0,start),color,newpart,i+1); + + if (end < len) + { + middle_llsd[0]["text"] =text.substr(start,end); + middle_llsd[0]["color"]=mHighlights[i]["color"]; + + if (part==END || part==WHOLE) newpart=END; else newpart=MIDDLE; + + end_llsd=parsePartialLineHighlights(text.substr( (start+end) ),color,newpart,i); + } + else + { + end_llsd[0]["text"] =text.substr(start,end); + end_llsd[0]["color"]=mHighlights[i]["color"]; + } + } + + S32 retcount=0; + + //FIXME These loops should be wrapped into a subroutine. + for (LLSD::array_iterator iter = start_llsd.beginArray(); + iter != start_llsd.endArray();++iter) + { + LLSD highlight = *iter; + ret_llsd[retcount++]=highlight; + } + + for (LLSD::array_iterator iter = middle_llsd.beginArray(); + iter != middle_llsd.endArray();++iter) + { + LLSD highlight = *iter; + ret_llsd[retcount++]=highlight; + } + + for (LLSD::array_iterator iter = end_llsd.beginArray(); + iter != end_llsd.endArray();++iter) + { + LLSD highlight = *iter; + ret_llsd[retcount++]=highlight; + } + + return ret_llsd; + } + } + } + } + + //No patterns found. Just send back what was passed in. + ret_llsd[0]["text"] =text; + LLSD color_sd = color.getValue(); + ret_llsd[0]["color"]=color_sd; + return ret_llsd; +} + +bool LLTextParser::parseFullLineHighlights(const std::string &text, LLColor4 *color) +{ + loadKeywords(); + + for (S32 i=0;i= 0 ) + { + LLSD color_llsd = mHighlights[i]["color"]; + color->setValue(color_llsd); + return true; + } + } + } + return false; //No matches found. +} + +std::string LLTextParser::getFileName() +{ + std::string path=gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, ""); + + if (!path.empty()) + { + path = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "highlights.xml"); + } + return path; +} + +void LLTextParser::loadKeywords() +{ + if (mLoaded) + {// keywords already loaded + return; + } + std::string filename=getFileName(); + if (!filename.empty()) + { + llifstream file; + file.open(filename.c_str()); + if (file.is_open()) + { + LLSDSerialize::fromXML(mHighlights, file); + } + file.close(); + mLoaded = true; + } +} + +bool LLTextParser::saveToDisk(LLSD highlights) +{ + mHighlights=highlights; + std::string filename=getFileName(); + if (filename.empty()) + { + LL_WARNS() << "LLTextParser::saveToDisk() no valid user directory." << LL_ENDL; + return false; + } + llofstream file; + file.open(filename.c_str()); + LLSDSerialize::toPrettyXML(mHighlights, file); + file.close(); + return true; +} diff --git a/indra/llui/lltimectrl.cpp b/indra/llui/lltimectrl.cpp index 5c298c9080..f1bf60d262 100644 --- a/indra/llui/lltimectrl.cpp +++ b/indra/llui/lltimectrl.cpp @@ -1,454 +1,454 @@ -/** - * @file lltimectrl.cpp - * @brief LLTimeCtrl base class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, 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 "lltimectrl.h" - -#include "llui.h" -#include "lluiconstants.h" - -#include "llbutton.h" -#include "llfontgl.h" -#include "lllineeditor.h" -#include "llkeyboard.h" -#include "llstring.h" -#include "lltextbox.h" -#include "lluictrlfactory.h" - -static LLDefaultChildRegistry::Register time_r("time"); - -const U32 AMPM_LEN = 3; -const U32 MINUTES_MIN = 0; -const U32 MINUTES_MAX = 59; -const U32 HOURS_MIN = 1; -const U32 HOURS_MAX = 12; -const U32 MINUTES_PER_HOUR = 60; -const U32 MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR; - -class LLTimeValidatorImpl : public LLTextValidate::ValidatorImpl -{ -public: - // virtual - bool validate(const std::string& str) override - { - std::string hours = LLTimeCtrl::getHoursString(str); - if (!LLTimeCtrl::isHoursStringValid(hours)) - return setError("ValidatorInvalidHours", LLSD().with("STR", hours)); - - std::string minutes = LLTimeCtrl::getMinutesString(str); - if (!LLTimeCtrl::isMinutesStringValid(minutes)) - return setError("ValidatorInvalidMinutes", LLSD().with("STR", minutes)); - - std::string ampm = LLTimeCtrl::getAMPMString(str); - if (!LLTimeCtrl::isPMAMStringValid(ampm)) - return setError("ValidatorInvalidAMPM", LLSD().with("STR", ampm)); - - return resetError(); - } - - // virtual - bool validate(const LLWString& wstr) override - { - std::string str = wstring_to_utf8str(wstr); - - return validate(str); - } -} validateTimeImpl; -LLTextValidate::Validator validateTime(validateTimeImpl); - -LLTimeCtrl::Params::Params() -: label_width("label_width"), - snap_to("snap_to"), - allow_text_entry("allow_text_entry", true), - text_enabled_color("text_enabled_color"), - text_disabled_color("text_disabled_color"), - up_button("up_button"), - down_button("down_button") -{} - -LLTimeCtrl::LLTimeCtrl(const LLTimeCtrl::Params& p) -: LLUICtrl(p), - mLabelBox(NULL), - mTextEnabledColor(p.text_enabled_color()), - mTextDisabledColor(p.text_disabled_color()), - mTime(0), - mSnapToMin(5) -{ - static LLUICachedControl spinctrl_spacing ("UISpinctrlSpacing", 0); - static LLUICachedControl spinctrl_btn_width ("UISpinctrlBtnWidth", 0); - static LLUICachedControl spinctrl_btn_height ("UISpinctrlBtnHeight", 0); - S32 centered_top = getRect().getHeight(); - S32 centered_bottom = getRect().getHeight() - 2 * spinctrl_btn_height; - S32 label_width = llclamp(p.label_width(), 0, llmax(0, getRect().getWidth() - 40)); - S32 editor_left = label_width + spinctrl_spacing; - - //================= Label =================// - if( !p.label().empty() ) - { - LLRect label_rect( 0, centered_top, label_width, centered_bottom ); - LLTextBox::Params params; - params.name("TimeCtrl Label"); - params.rect(label_rect); - params.initial_value(p.label()); - if (p.font.isProvided()) - { - params.font(p.font); - } - mLabelBox = LLUICtrlFactory::create (params); - addChild(mLabelBox); - - editor_left = label_rect.mRight + spinctrl_spacing; - } - - S32 editor_right = getRect().getWidth() - spinctrl_btn_width - spinctrl_spacing; - - //================= Editor ================// - LLRect editor_rect( editor_left, centered_top, editor_right, centered_bottom ); - LLLineEditor::Params params; - params.name("SpinCtrl Editor"); - params.rect(editor_rect); - if (p.font.isProvided()) - { - params.font(p.font); - } - - params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); - params.max_length.chars(8); - params.keystroke_callback(boost::bind(&LLTimeCtrl::onTextEntry, this, _1)); - mEditor = LLUICtrlFactory::create (params); - mEditor->setPrevalidateInput(LLTextValidate::validateNonNegativeS32NoSpace); - mEditor->setPrevalidate(validateTime); - mEditor->setText(LLStringExplicit("12:00 AM")); - addChild(mEditor); - - //================= Spin Buttons ==========// - LLButton::Params up_button_params(p.up_button); - up_button_params.rect = LLRect(editor_right + 1, getRect().getHeight(), editor_right + spinctrl_btn_width, getRect().getHeight() - spinctrl_btn_height); - - up_button_params.click_callback.function(boost::bind(&LLTimeCtrl::onUpBtn, this)); - up_button_params.mouse_held_callback.function(boost::bind(&LLTimeCtrl::onUpBtn, this)); - mUpBtn = LLUICtrlFactory::create(up_button_params); - addChild(mUpBtn); - - LLButton::Params down_button_params(p.down_button); - down_button_params.rect = LLRect(editor_right + 1, getRect().getHeight() - spinctrl_btn_height, editor_right + spinctrl_btn_width, getRect().getHeight() - 2 * spinctrl_btn_height); - down_button_params.click_callback.function(boost::bind(&LLTimeCtrl::onDownBtn, this)); - down_button_params.mouse_held_callback.function(boost::bind(&LLTimeCtrl::onDownBtn, this)); - mDownBtn = LLUICtrlFactory::create(down_button_params); - addChild(mDownBtn); - - setUseBoundingRect( true ); -} - -F32 LLTimeCtrl::getTime24() const -{ - // 0.0 - 23.99; - return mTime / 60.0f; -} - -U32 LLTimeCtrl::getHours24() const -{ - return (U32) getTime24(); -} - -U32 LLTimeCtrl::getMinutes() const -{ - return mTime % MINUTES_PER_HOUR; -} - -void LLTimeCtrl::setTime24(F32 time) -{ - time = llclamp(time, 0.0f, 23.99f); // fix out of range values - mTime = ll_round(time * MINUTES_PER_HOUR); // fixes values like 4.99999 - - updateText(); -} - -bool LLTimeCtrl::handleKeyHere(KEY key, MASK mask) -{ - if (mEditor->hasFocus()) - { - if(key == KEY_UP) - { - onUpBtn(); - return true; - } - if(key == KEY_DOWN) - { - onDownBtn(); - return true; - } - if (key == KEY_RETURN) - { - onCommit(); - return true; - } - } - return false; -} - -void LLTimeCtrl::onUpBtn() -{ - switch(getEditingPart()) - { - case HOURS: - increaseHours(); - break; - case MINUTES: - increaseMinutes(); - break; - case DAYPART: - switchDayPeriod(); - break; - default: - break; - } - - updateText(); - onCommit(); -} - -void LLTimeCtrl::onDownBtn() -{ - switch(getEditingPart()) - { - case HOURS: - decreaseHours(); - break; - case MINUTES: - decreaseMinutes(); - break; - case DAYPART: - switchDayPeriod(); - break; - default: - break; - } - - updateText(); - onCommit(); -} - -void LLTimeCtrl::onFocusLost() -{ - updateText(); - onCommit(); - LLUICtrl::onFocusLost(); -} - -void LLTimeCtrl::onTextEntry(LLLineEditor* line_editor) -{ - std::string time_str = line_editor->getText(); - U32 h12 = parseHours(getHoursString(time_str)); - U32 m = parseMinutes(getMinutesString(time_str)); - bool pm = parseAMPM(getAMPMString(time_str)); - - if (h12 == 12) - { - h12 = 0; - } - - U32 h24 = pm ? h12 + 12 : h12; - - mTime = h24 * MINUTES_PER_HOUR + m; -} - -void LLTimeCtrl::increaseMinutes() -{ - mTime = (mTime + mSnapToMin) % MINUTES_PER_DAY - (mTime % mSnapToMin); -} - -void LLTimeCtrl::increaseHours() -{ - mTime = (mTime + MINUTES_PER_HOUR) % MINUTES_PER_DAY; -} - -void LLTimeCtrl::decreaseMinutes() -{ - if (mTime < mSnapToMin) - { - mTime = MINUTES_PER_DAY - mTime; - } - - mTime -= (mTime % mSnapToMin) ? mTime % mSnapToMin : mSnapToMin; -} - -void LLTimeCtrl::decreaseHours() -{ - if (mTime < MINUTES_PER_HOUR) - { - mTime = 23 * MINUTES_PER_HOUR + mTime; - } - else - { - mTime -= MINUTES_PER_HOUR; - } -} - -bool LLTimeCtrl::isPM() const -{ - return mTime >= (MINUTES_PER_DAY / 2); -} - -void LLTimeCtrl::switchDayPeriod() -{ - if (isPM()) - { - mTime -= MINUTES_PER_DAY / 2; - } - else - { - mTime += MINUTES_PER_DAY / 2; - } -} - -void LLTimeCtrl::updateText() -{ - U32 h24 = getHours24(); - U32 m = getMinutes(); - U32 h12 = h24 > 12 ? h24 - 12 : h24; - - if (h12 == 0) - h12 = 12; - - mEditor->setText(llformat("%d:%02d %s", h12, m, isPM() ? "PM":"AM")); -} - -LLTimeCtrl::EEditingPart LLTimeCtrl::getEditingPart() -{ - S32 cur_pos = mEditor->getCursor(); - std::string time_str = mEditor->getText(); - - S32 colon_index = time_str.find_first_of(':'); - - if (cur_pos <= colon_index) - { - return HOURS; - } - else if (cur_pos > colon_index && cur_pos <= (S32)(time_str.length() - AMPM_LEN)) - { - return MINUTES; - } - else if (cur_pos > (S32)(time_str.length() - AMPM_LEN)) - { - return DAYPART; - } - - return NONE; -} - -// static -std::string LLTimeCtrl::getHoursString(const std::string& str) -{ - size_t colon_index = str.find_first_of(':'); - std::string hours_str = str.substr(0, colon_index); - - return hours_str; -} - -// static -std::string LLTimeCtrl::getMinutesString(const std::string& str) -{ - size_t colon_index = str.find_first_of(':'); - ++colon_index; - - int minutes_len = str.length() - colon_index - AMPM_LEN; - std::string minutes_str = str.substr(colon_index, minutes_len); - - return minutes_str; -} - -// static -std::string LLTimeCtrl::getAMPMString(const std::string& str) -{ - return str.substr(str.size() - 2, 2); // returns last two characters -} - -// static -bool LLTimeCtrl::isHoursStringValid(const std::string& str) -{ - U32 hours; - if ((!LLStringUtil::convertToU32(str, hours) || (hours <= HOURS_MAX)) && str.length() < 3) - return true; - - return false; -} - -// static -bool LLTimeCtrl::isMinutesStringValid(const std::string& str) -{ - U32 minutes; - if (!LLStringUtil::convertToU32(str, minutes) || ((minutes <= MINUTES_MAX) && str.length() < 3)) - return true; - - return false; -} - -// static -bool LLTimeCtrl::isPMAMStringValid(const std::string& str) -{ - S32 len = str.length(); - - bool valid = (str[--len] == 'M') && (str[--len] == 'P' || str[len] == 'A'); - - return valid; -} - -// static -U32 LLTimeCtrl::parseHours(const std::string& str) -{ - U32 hours; - if (LLStringUtil::convertToU32(str, hours) && (hours >= HOURS_MIN) && (hours <= HOURS_MAX)) - { - return hours; - } - else - { - return HOURS_MIN; - } -} - -// static -U32 LLTimeCtrl::parseMinutes(const std::string& str) -{ - U32 minutes; - // not sure of this fix - clang doesnt like compare minutes U32 to >= MINUTES_MIN (0) but MINUTES_MIN can change - if (LLStringUtil::convertToU32(str, minutes) && ((S32)minutes >= MINUTES_MIN) && ((S32)minutes <= MINUTES_MAX)) - { - return minutes; - } - else - { - return MINUTES_MIN; - } -} - -// static -bool LLTimeCtrl::parseAMPM(const std::string& str) -{ - return str == "PM"; -} +/** + * @file lltimectrl.cpp + * @brief LLTimeCtrl base class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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 "lltimectrl.h" + +#include "llui.h" +#include "lluiconstants.h" + +#include "llbutton.h" +#include "llfontgl.h" +#include "lllineeditor.h" +#include "llkeyboard.h" +#include "llstring.h" +#include "lltextbox.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register time_r("time"); + +const U32 AMPM_LEN = 3; +const U32 MINUTES_MIN = 0; +const U32 MINUTES_MAX = 59; +const U32 HOURS_MIN = 1; +const U32 HOURS_MAX = 12; +const U32 MINUTES_PER_HOUR = 60; +const U32 MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR; + +class LLTimeValidatorImpl : public LLTextValidate::ValidatorImpl +{ +public: + // virtual + bool validate(const std::string& str) override + { + std::string hours = LLTimeCtrl::getHoursString(str); + if (!LLTimeCtrl::isHoursStringValid(hours)) + return setError("ValidatorInvalidHours", LLSD().with("STR", hours)); + + std::string minutes = LLTimeCtrl::getMinutesString(str); + if (!LLTimeCtrl::isMinutesStringValid(minutes)) + return setError("ValidatorInvalidMinutes", LLSD().with("STR", minutes)); + + std::string ampm = LLTimeCtrl::getAMPMString(str); + if (!LLTimeCtrl::isPMAMStringValid(ampm)) + return setError("ValidatorInvalidAMPM", LLSD().with("STR", ampm)); + + return resetError(); + } + + // virtual + bool validate(const LLWString& wstr) override + { + std::string str = wstring_to_utf8str(wstr); + + return validate(str); + } +} validateTimeImpl; +LLTextValidate::Validator validateTime(validateTimeImpl); + +LLTimeCtrl::Params::Params() +: label_width("label_width"), + snap_to("snap_to"), + allow_text_entry("allow_text_entry", true), + text_enabled_color("text_enabled_color"), + text_disabled_color("text_disabled_color"), + up_button("up_button"), + down_button("down_button") +{} + +LLTimeCtrl::LLTimeCtrl(const LLTimeCtrl::Params& p) +: LLUICtrl(p), + mLabelBox(NULL), + mTextEnabledColor(p.text_enabled_color()), + mTextDisabledColor(p.text_disabled_color()), + mTime(0), + mSnapToMin(5) +{ + static LLUICachedControl spinctrl_spacing ("UISpinctrlSpacing", 0); + static LLUICachedControl spinctrl_btn_width ("UISpinctrlBtnWidth", 0); + static LLUICachedControl spinctrl_btn_height ("UISpinctrlBtnHeight", 0); + S32 centered_top = getRect().getHeight(); + S32 centered_bottom = getRect().getHeight() - 2 * spinctrl_btn_height; + S32 label_width = llclamp(p.label_width(), 0, llmax(0, getRect().getWidth() - 40)); + S32 editor_left = label_width + spinctrl_spacing; + + //================= Label =================// + if( !p.label().empty() ) + { + LLRect label_rect( 0, centered_top, label_width, centered_bottom ); + LLTextBox::Params params; + params.name("TimeCtrl Label"); + params.rect(label_rect); + params.initial_value(p.label()); + if (p.font.isProvided()) + { + params.font(p.font); + } + mLabelBox = LLUICtrlFactory::create (params); + addChild(mLabelBox); + + editor_left = label_rect.mRight + spinctrl_spacing; + } + + S32 editor_right = getRect().getWidth() - spinctrl_btn_width - spinctrl_spacing; + + //================= Editor ================// + LLRect editor_rect( editor_left, centered_top, editor_right, centered_bottom ); + LLLineEditor::Params params; + params.name("SpinCtrl Editor"); + params.rect(editor_rect); + if (p.font.isProvided()) + { + params.font(p.font); + } + + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + params.max_length.chars(8); + params.keystroke_callback(boost::bind(&LLTimeCtrl::onTextEntry, this, _1)); + mEditor = LLUICtrlFactory::create (params); + mEditor->setPrevalidateInput(LLTextValidate::validateNonNegativeS32NoSpace); + mEditor->setPrevalidate(validateTime); + mEditor->setText(LLStringExplicit("12:00 AM")); + addChild(mEditor); + + //================= Spin Buttons ==========// + LLButton::Params up_button_params(p.up_button); + up_button_params.rect = LLRect(editor_right + 1, getRect().getHeight(), editor_right + spinctrl_btn_width, getRect().getHeight() - spinctrl_btn_height); + + up_button_params.click_callback.function(boost::bind(&LLTimeCtrl::onUpBtn, this)); + up_button_params.mouse_held_callback.function(boost::bind(&LLTimeCtrl::onUpBtn, this)); + mUpBtn = LLUICtrlFactory::create(up_button_params); + addChild(mUpBtn); + + LLButton::Params down_button_params(p.down_button); + down_button_params.rect = LLRect(editor_right + 1, getRect().getHeight() - spinctrl_btn_height, editor_right + spinctrl_btn_width, getRect().getHeight() - 2 * spinctrl_btn_height); + down_button_params.click_callback.function(boost::bind(&LLTimeCtrl::onDownBtn, this)); + down_button_params.mouse_held_callback.function(boost::bind(&LLTimeCtrl::onDownBtn, this)); + mDownBtn = LLUICtrlFactory::create(down_button_params); + addChild(mDownBtn); + + setUseBoundingRect( true ); +} + +F32 LLTimeCtrl::getTime24() const +{ + // 0.0 - 23.99; + return mTime / 60.0f; +} + +U32 LLTimeCtrl::getHours24() const +{ + return (U32) getTime24(); +} + +U32 LLTimeCtrl::getMinutes() const +{ + return mTime % MINUTES_PER_HOUR; +} + +void LLTimeCtrl::setTime24(F32 time) +{ + time = llclamp(time, 0.0f, 23.99f); // fix out of range values + mTime = ll_round(time * MINUTES_PER_HOUR); // fixes values like 4.99999 + + updateText(); +} + +bool LLTimeCtrl::handleKeyHere(KEY key, MASK mask) +{ + if (mEditor->hasFocus()) + { + if(key == KEY_UP) + { + onUpBtn(); + return true; + } + if(key == KEY_DOWN) + { + onDownBtn(); + return true; + } + if (key == KEY_RETURN) + { + onCommit(); + return true; + } + } + return false; +} + +void LLTimeCtrl::onUpBtn() +{ + switch(getEditingPart()) + { + case HOURS: + increaseHours(); + break; + case MINUTES: + increaseMinutes(); + break; + case DAYPART: + switchDayPeriod(); + break; + default: + break; + } + + updateText(); + onCommit(); +} + +void LLTimeCtrl::onDownBtn() +{ + switch(getEditingPart()) + { + case HOURS: + decreaseHours(); + break; + case MINUTES: + decreaseMinutes(); + break; + case DAYPART: + switchDayPeriod(); + break; + default: + break; + } + + updateText(); + onCommit(); +} + +void LLTimeCtrl::onFocusLost() +{ + updateText(); + onCommit(); + LLUICtrl::onFocusLost(); +} + +void LLTimeCtrl::onTextEntry(LLLineEditor* line_editor) +{ + std::string time_str = line_editor->getText(); + U32 h12 = parseHours(getHoursString(time_str)); + U32 m = parseMinutes(getMinutesString(time_str)); + bool pm = parseAMPM(getAMPMString(time_str)); + + if (h12 == 12) + { + h12 = 0; + } + + U32 h24 = pm ? h12 + 12 : h12; + + mTime = h24 * MINUTES_PER_HOUR + m; +} + +void LLTimeCtrl::increaseMinutes() +{ + mTime = (mTime + mSnapToMin) % MINUTES_PER_DAY - (mTime % mSnapToMin); +} + +void LLTimeCtrl::increaseHours() +{ + mTime = (mTime + MINUTES_PER_HOUR) % MINUTES_PER_DAY; +} + +void LLTimeCtrl::decreaseMinutes() +{ + if (mTime < mSnapToMin) + { + mTime = MINUTES_PER_DAY - mTime; + } + + mTime -= (mTime % mSnapToMin) ? mTime % mSnapToMin : mSnapToMin; +} + +void LLTimeCtrl::decreaseHours() +{ + if (mTime < MINUTES_PER_HOUR) + { + mTime = 23 * MINUTES_PER_HOUR + mTime; + } + else + { + mTime -= MINUTES_PER_HOUR; + } +} + +bool LLTimeCtrl::isPM() const +{ + return mTime >= (MINUTES_PER_DAY / 2); +} + +void LLTimeCtrl::switchDayPeriod() +{ + if (isPM()) + { + mTime -= MINUTES_PER_DAY / 2; + } + else + { + mTime += MINUTES_PER_DAY / 2; + } +} + +void LLTimeCtrl::updateText() +{ + U32 h24 = getHours24(); + U32 m = getMinutes(); + U32 h12 = h24 > 12 ? h24 - 12 : h24; + + if (h12 == 0) + h12 = 12; + + mEditor->setText(llformat("%d:%02d %s", h12, m, isPM() ? "PM":"AM")); +} + +LLTimeCtrl::EEditingPart LLTimeCtrl::getEditingPart() +{ + S32 cur_pos = mEditor->getCursor(); + std::string time_str = mEditor->getText(); + + S32 colon_index = time_str.find_first_of(':'); + + if (cur_pos <= colon_index) + { + return HOURS; + } + else if (cur_pos > colon_index && cur_pos <= (S32)(time_str.length() - AMPM_LEN)) + { + return MINUTES; + } + else if (cur_pos > (S32)(time_str.length() - AMPM_LEN)) + { + return DAYPART; + } + + return NONE; +} + +// static +std::string LLTimeCtrl::getHoursString(const std::string& str) +{ + size_t colon_index = str.find_first_of(':'); + std::string hours_str = str.substr(0, colon_index); + + return hours_str; +} + +// static +std::string LLTimeCtrl::getMinutesString(const std::string& str) +{ + size_t colon_index = str.find_first_of(':'); + ++colon_index; + + int minutes_len = str.length() - colon_index - AMPM_LEN; + std::string minutes_str = str.substr(colon_index, minutes_len); + + return minutes_str; +} + +// static +std::string LLTimeCtrl::getAMPMString(const std::string& str) +{ + return str.substr(str.size() - 2, 2); // returns last two characters +} + +// static +bool LLTimeCtrl::isHoursStringValid(const std::string& str) +{ + U32 hours; + if ((!LLStringUtil::convertToU32(str, hours) || (hours <= HOURS_MAX)) && str.length() < 3) + return true; + + return false; +} + +// static +bool LLTimeCtrl::isMinutesStringValid(const std::string& str) +{ + U32 minutes; + if (!LLStringUtil::convertToU32(str, minutes) || ((minutes <= MINUTES_MAX) && str.length() < 3)) + return true; + + return false; +} + +// static +bool LLTimeCtrl::isPMAMStringValid(const std::string& str) +{ + S32 len = str.length(); + + bool valid = (str[--len] == 'M') && (str[--len] == 'P' || str[len] == 'A'); + + return valid; +} + +// static +U32 LLTimeCtrl::parseHours(const std::string& str) +{ + U32 hours; + if (LLStringUtil::convertToU32(str, hours) && (hours >= HOURS_MIN) && (hours <= HOURS_MAX)) + { + return hours; + } + else + { + return HOURS_MIN; + } +} + +// static +U32 LLTimeCtrl::parseMinutes(const std::string& str) +{ + U32 minutes; + // not sure of this fix - clang doesnt like compare minutes U32 to >= MINUTES_MIN (0) but MINUTES_MIN can change + if (LLStringUtil::convertToU32(str, minutes) && ((S32)minutes >= MINUTES_MIN) && ((S32)minutes <= MINUTES_MAX)) + { + return minutes; + } + else + { + return MINUTES_MIN; + } +} + +// static +bool LLTimeCtrl::parseAMPM(const std::string& str) +{ + return str == "PM"; +} diff --git a/indra/llui/lltimectrl.h b/indra/llui/lltimectrl.h index b5f2008f4b..6973c9a02b 100644 --- a/indra/llui/lltimectrl.h +++ b/indra/llui/lltimectrl.h @@ -1,129 +1,129 @@ -/** - * @file lltimectrl.h - * @brief Time control - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, 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$ - */ - -#ifndef LLTIMECTRL_H_ -#define LLTIMECTRL_H_ - -#include "stdtypes.h" -#include "llbutton.h" -#include "v4color.h" -#include "llrect.h" - -class LLLineEditor; - -class LLTimeCtrl -: public LLUICtrl -{ - LOG_CLASS(LLTimeCtrl); -public: - struct Params : public LLInitParam::Block - { - Optional label_width; - Optional snap_to; - Optional allow_text_entry; - - Optional text_enabled_color; - Optional text_disabled_color; - - Optional up_button; - Optional down_button; - - Params(); - }; - - F32 getTime24() const; // 0.0 - 24.0 - U32 getHours24() const; // 0 - 23 - U32 getMinutes() const; // 0 - 59 - - void setTime24(F32 time); // 0.0 - 23.98(3) - - static std::string getHoursString(const std::string& str); - static std::string getMinutesString(const std::string& str); - static std::string getAMPMString(const std::string& str); - - static bool isHoursStringValid(const std::string& str); - static bool isMinutesStringValid(const std::string& str); - static bool isPMAMStringValid(const std::string& str); - - static U32 parseHours(const std::string& str); - static U32 parseMinutes(const std::string& str); - static bool parseAMPM(const std::string& str); - -protected: - LLTimeCtrl(const Params&); - friend class LLUICtrlFactory; - -private: - - enum EDayPeriod - { - AM, - PM - }; - - enum EEditingPart - { - HOURS, - MINUTES, - DAYPART, - NONE - }; - - virtual void onFocusLost(); - virtual bool handleKeyHere(KEY key, MASK mask); - - void onUpBtn(); - void onDownBtn(); - void onTextEntry(LLLineEditor* line_editor); - - void increaseMinutes(); - void increaseHours(); - - void decreaseMinutes(); - void decreaseHours(); - - bool isPM() const; - void switchDayPeriod(); - - void updateText(); - - EEditingPart getEditingPart(); - - class LLTextBox* mLabelBox; - - class LLLineEditor* mEditor; - LLUIColor mTextEnabledColor; - LLUIColor mTextDisabledColor; - - class LLButton* mUpBtn; - class LLButton* mDownBtn; - - U32 mTime; // minutes since midnight: 0 - 1439 - U32 mSnapToMin; // interval in minutes to snap to - - bool mAllowEdit; -}; -#endif /* LLTIMECTRL_H_ */ +/** + * @file lltimectrl.h + * @brief Time control + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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$ + */ + +#ifndef LLTIMECTRL_H_ +#define LLTIMECTRL_H_ + +#include "stdtypes.h" +#include "llbutton.h" +#include "v4color.h" +#include "llrect.h" + +class LLLineEditor; + +class LLTimeCtrl +: public LLUICtrl +{ + LOG_CLASS(LLTimeCtrl); +public: + struct Params : public LLInitParam::Block + { + Optional label_width; + Optional snap_to; + Optional allow_text_entry; + + Optional text_enabled_color; + Optional text_disabled_color; + + Optional up_button; + Optional down_button; + + Params(); + }; + + F32 getTime24() const; // 0.0 - 24.0 + U32 getHours24() const; // 0 - 23 + U32 getMinutes() const; // 0 - 59 + + void setTime24(F32 time); // 0.0 - 23.98(3) + + static std::string getHoursString(const std::string& str); + static std::string getMinutesString(const std::string& str); + static std::string getAMPMString(const std::string& str); + + static bool isHoursStringValid(const std::string& str); + static bool isMinutesStringValid(const std::string& str); + static bool isPMAMStringValid(const std::string& str); + + static U32 parseHours(const std::string& str); + static U32 parseMinutes(const std::string& str); + static bool parseAMPM(const std::string& str); + +protected: + LLTimeCtrl(const Params&); + friend class LLUICtrlFactory; + +private: + + enum EDayPeriod + { + AM, + PM + }; + + enum EEditingPart + { + HOURS, + MINUTES, + DAYPART, + NONE + }; + + virtual void onFocusLost(); + virtual bool handleKeyHere(KEY key, MASK mask); + + void onUpBtn(); + void onDownBtn(); + void onTextEntry(LLLineEditor* line_editor); + + void increaseMinutes(); + void increaseHours(); + + void decreaseMinutes(); + void decreaseHours(); + + bool isPM() const; + void switchDayPeriod(); + + void updateText(); + + EEditingPart getEditingPart(); + + class LLTextBox* mLabelBox; + + class LLLineEditor* mEditor; + LLUIColor mTextEnabledColor; + LLUIColor mTextDisabledColor; + + class LLButton* mUpBtn; + class LLButton* mDownBtn; + + U32 mTime; // minutes since midnight: 0 - 1439 + U32 mSnapToMin; // interval in minutes to snap to + + bool mAllowEdit; +}; +#endif /* LLTIMECTRL_H_ */ diff --git a/indra/llui/lltoggleablemenu.cpp b/indra/llui/lltoggleablemenu.cpp index 09bd25b1a9..cfa520f693 100644 --- a/indra/llui/lltoggleablemenu.cpp +++ b/indra/llui/lltoggleablemenu.cpp @@ -1,108 +1,108 @@ -/** - * @file lltoggleablemenu.cpp - * @brief Menu toggled by a button press - * - * $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 "lltoggleablemenu.h" -#include "lluictrlfactory.h" - -static LLDefaultChildRegistry::Register r("toggleable_menu"); - -LLToggleableMenu::LLToggleableMenu(const LLToggleableMenu::Params& p) -: LLMenuGL(p), - mButtonRect(), - mVisibilityChangeSignal(NULL), - mClosedByButtonClick(false) -{ -} - -LLToggleableMenu::~LLToggleableMenu() -{ - delete mVisibilityChangeSignal; -} - -boost::signals2::connection LLToggleableMenu::setVisibilityChangeCallback(const commit_signal_t::slot_type& cb) -{ - if (!mVisibilityChangeSignal) mVisibilityChangeSignal = new commit_signal_t(); - return mVisibilityChangeSignal->connect(cb); -} - -// virtual -void LLToggleableMenu::onVisibilityChange (bool curVisibilityIn) -{ - S32 x,y; - LLUI::getInstance()->getMousePositionLocal(LLUI::getInstance()->getRootView(), &x, &y); - - // STORM-1879: also check MouseCapture to see if the button was really - // clicked (otherwise the VisibilityChange was triggered via keyboard shortcut) - if (!curVisibilityIn && mButtonRect.pointInRect(x, y) && gFocusMgr.getMouseCapture()) - { - mClosedByButtonClick = true; - } - - if (mVisibilityChangeSignal) - { - (*mVisibilityChangeSignal)(this, - LLSD().with("visibility", curVisibilityIn).with("closed_by_button_click", mClosedByButtonClick)); - } -} - -void LLToggleableMenu::setButtonRect(const LLRect& rect, LLView* current_view) -{ - LLRect screen; - current_view->localRectToScreen(rect, &screen); - mButtonRect = screen; -} - -void LLToggleableMenu::setButtonRect(LLView* current_view) -{ - LLRect rect = current_view->getLocalRect(); - setButtonRect(rect, current_view); -} - -bool LLToggleableMenu::toggleVisibility() -{ - if (mClosedByButtonClick) - { - mClosedByButtonClick = false; - return false; - } - - if (getVisible()) - { - setVisible(false); - mClosedByButtonClick = false; - return false; - } - - return true; -} - -bool LLToggleableMenu::addChild(LLView* view, S32 tab_group) -{ - return addContextChild(view, tab_group); -} +/** + * @file lltoggleablemenu.cpp + * @brief Menu toggled by a button press + * + * $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 "lltoggleablemenu.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register r("toggleable_menu"); + +LLToggleableMenu::LLToggleableMenu(const LLToggleableMenu::Params& p) +: LLMenuGL(p), + mButtonRect(), + mVisibilityChangeSignal(NULL), + mClosedByButtonClick(false) +{ +} + +LLToggleableMenu::~LLToggleableMenu() +{ + delete mVisibilityChangeSignal; +} + +boost::signals2::connection LLToggleableMenu::setVisibilityChangeCallback(const commit_signal_t::slot_type& cb) +{ + if (!mVisibilityChangeSignal) mVisibilityChangeSignal = new commit_signal_t(); + return mVisibilityChangeSignal->connect(cb); +} + +// virtual +void LLToggleableMenu::onVisibilityChange (bool curVisibilityIn) +{ + S32 x,y; + LLUI::getInstance()->getMousePositionLocal(LLUI::getInstance()->getRootView(), &x, &y); + + // STORM-1879: also check MouseCapture to see if the button was really + // clicked (otherwise the VisibilityChange was triggered via keyboard shortcut) + if (!curVisibilityIn && mButtonRect.pointInRect(x, y) && gFocusMgr.getMouseCapture()) + { + mClosedByButtonClick = true; + } + + if (mVisibilityChangeSignal) + { + (*mVisibilityChangeSignal)(this, + LLSD().with("visibility", curVisibilityIn).with("closed_by_button_click", mClosedByButtonClick)); + } +} + +void LLToggleableMenu::setButtonRect(const LLRect& rect, LLView* current_view) +{ + LLRect screen; + current_view->localRectToScreen(rect, &screen); + mButtonRect = screen; +} + +void LLToggleableMenu::setButtonRect(LLView* current_view) +{ + LLRect rect = current_view->getLocalRect(); + setButtonRect(rect, current_view); +} + +bool LLToggleableMenu::toggleVisibility() +{ + if (mClosedByButtonClick) + { + mClosedByButtonClick = false; + return false; + } + + if (getVisible()) + { + setVisible(false); + mClosedByButtonClick = false; + return false; + } + + return true; +} + +bool LLToggleableMenu::addChild(LLView* view, S32 tab_group) +{ + return addContextChild(view, tab_group); +} diff --git a/indra/llui/lltoggleablemenu.h b/indra/llui/lltoggleablemenu.h index 665d81b4fa..7cbbd417a4 100644 --- a/indra/llui/lltoggleablemenu.h +++ b/indra/llui/lltoggleablemenu.h @@ -1,71 +1,71 @@ -/** - * @file lltoggleablemenu.h - * @brief Menu toggled by a button press - * - * $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$ - */ - -#ifndef LL_LLTOGGLEABLEMENU_H -#define LL_LLTOGGLEABLEMENU_H - -#include "llmenugl.h" - -class LLToggleableMenu : public LLMenuGL -{ -public: - //adding blank params to work around registration issue - //where LLToggleableMenu was owning the LLMenuGL param - //and menu.xml was never loaded - struct Params : public LLInitParam::Block - {}; -protected: - LLToggleableMenu(const Params&); - friend class LLUICtrlFactory; -public: - ~LLToggleableMenu(); - - boost::signals2::connection setVisibilityChangeCallback( const commit_signal_t::slot_type& cb ); - - virtual void onVisibilityChange (bool curVisibilityIn); - - virtual bool addChild (LLView* view, S32 tab_group = 0); - - const LLRect& getButtonRect() const { return mButtonRect; } - - // Converts the given local button rect to a screen rect - void setButtonRect(const LLRect& rect, LLView* current_view); - void setButtonRect(LLView* current_view); - - // Returns "true" if menu was not closed by button click - // and is not still visible. If menu is visible toggles - // its visibility off. - bool toggleVisibility(); - - LLHandle getHandle() { return getDerivedHandle(); } - -protected: - bool mClosedByButtonClick; - LLRect mButtonRect; - commit_signal_t* mVisibilityChangeSignal; -}; - -#endif // LL_LLTOGGLEABLEMENU_H +/** + * @file lltoggleablemenu.h + * @brief Menu toggled by a button press + * + * $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$ + */ + +#ifndef LL_LLTOGGLEABLEMENU_H +#define LL_LLTOGGLEABLEMENU_H + +#include "llmenugl.h" + +class LLToggleableMenu : public LLMenuGL +{ +public: + //adding blank params to work around registration issue + //where LLToggleableMenu was owning the LLMenuGL param + //and menu.xml was never loaded + struct Params : public LLInitParam::Block + {}; +protected: + LLToggleableMenu(const Params&); + friend class LLUICtrlFactory; +public: + ~LLToggleableMenu(); + + boost::signals2::connection setVisibilityChangeCallback( const commit_signal_t::slot_type& cb ); + + virtual void onVisibilityChange (bool curVisibilityIn); + + virtual bool addChild (LLView* view, S32 tab_group = 0); + + const LLRect& getButtonRect() const { return mButtonRect; } + + // Converts the given local button rect to a screen rect + void setButtonRect(const LLRect& rect, LLView* current_view); + void setButtonRect(LLView* current_view); + + // Returns "true" if menu was not closed by button click + // and is not still visible. If menu is visible toggles + // its visibility off. + bool toggleVisibility(); + + LLHandle getHandle() { return getDerivedHandle(); } + +protected: + bool mClosedByButtonClick; + LLRect mButtonRect; + commit_signal_t* mVisibilityChangeSignal; +}; + +#endif // LL_LLTOGGLEABLEMENU_H diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp index 9753881dfc..aa48ae048f 100644 --- a/indra/llui/lltoolbar.cpp +++ b/indra/llui/lltoolbar.cpp @@ -1,1266 +1,1266 @@ -/** - * @file lltoolbar.cpp - * @author Richard Nelson - * @brief User customizable toolbar class - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, 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 "lltoolbar.h" - -#include "llcommandmanager.h" -#include "llmenugl.h" -#include "lltrans.h" -#include "llinventory.h" -#include "lliconctrl.h" - -// uncomment this and remove the one in llui.cpp when there is an external reference to this translation unit -// thanks, MSVC! -//static LLDefaultChildRegistry::Register r1("toolbar"); - -namespace LLToolBarEnums -{ - LLView::EOrientation getOrientation(SideType sideType) - { - LLView::EOrientation orientation = LLLayoutStack::HORIZONTAL; - - if ((sideType == SIDE_LEFT) || (sideType == SIDE_RIGHT)) - { - orientation = LLLayoutStack::VERTICAL; - } - - return orientation; - } -} - -using namespace LLToolBarEnums; - - -namespace LLInitParam -{ - void TypeValues::declareValues() - { - declare("icons_with_text", BTNTYPE_ICONS_WITH_TEXT); - declare("icons_only", BTNTYPE_ICONS_ONLY); - } - - void TypeValues::declareValues() - { - declare("bottom", SIDE_BOTTOM); - declare("left", SIDE_LEFT); - declare("right", SIDE_RIGHT); - declare("top", SIDE_TOP); - } -} - -LLToolBar::Params::Params() -: button_display_mode("button_display_mode"), - commands("command"), - side("side", SIDE_TOP), - button_icon("button_icon"), - button_icon_and_text("button_icon_and_text"), - read_only("read_only", false), - wrap("wrap", true), - pad_left("pad_left"), - pad_top("pad_top"), - pad_right("pad_right"), - pad_bottom("pad_bottom"), - pad_between("pad_between"), - min_girth("min_girth"), - button_panel("button_panel") -{} - -LLToolBar::LLToolBar(const LLToolBar::Params& p) -: LLUICtrl(p), - mReadOnly(p.read_only), - mButtonType(p.button_display_mode), - mSideType(p.side), - mWrap(p.wrap), - mNeedsLayout(false), - mModified(false), - mButtonPanel(NULL), - mCenteringStack(NULL), - mPadLeft(p.pad_left), - mPadRight(p.pad_right), - mPadTop(p.pad_top), - mPadBottom(p.pad_bottom), - mPadBetween(p.pad_between), - mMinGirth(p.min_girth), - mPopupMenuHandle(), - mRightMouseTargetButton(NULL), - mStartDragItemCallback(NULL), - mHandleDragItemCallback(NULL), - mHandleDropCallback(NULL), - mButtonAddSignal(NULL), - mButtonEnterSignal(NULL), - mButtonLeaveSignal(NULL), - mButtonRemoveSignal(NULL), - mDragAndDropTarget(false), - mCaretIcon(NULL), - mCenterPanel(NULL) -{ - mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_WITH_TEXT] = p.button_icon_and_text; - mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_ONLY] = p.button_icon; -} - -LLToolBar::~LLToolBar() -{ - auto menu = mPopupMenuHandle.get(); - if (menu) - { - menu->die(); - mPopupMenuHandle.markDead(); - } - delete mButtonAddSignal; - delete mButtonEnterSignal; - delete mButtonLeaveSignal; - delete mButtonRemoveSignal; -} - -void LLToolBar::createContextMenu() -{ - if (!mPopupMenuHandle.get()) - { - // Setup bindings specific to this instance for the context menu options - - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commit_reg; - commit_reg.add("Toolbars.EnableSetting", boost::bind(&LLToolBar::onSettingEnable, this, _2)); - commit_reg.add("Toolbars.RemoveSelectedCommand", boost::bind(&LLToolBar::onRemoveSelectedCommand, this)); - - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_reg; - enable_reg.add("Toolbars.CheckSetting", boost::bind(&LLToolBar::isSettingChecked, this, _2)); - - // Create the context menu - llassert(LLMenuGL::sMenuContainer != NULL); - LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile("menu_toolbars.xml", LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); - - if (menu) - { - menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor")); - mPopupMenuHandle = menu->getHandle(); - mRemoveButtonHandle = menu->getChild("Remove button")->getHandle(); - } - else - { - LL_WARNS() << "Unable to load toolbars context menu." << LL_ENDL; - } - } - - if (mRemoveButtonHandle.get()) - { - // Disable/Enable the "Remove button" menu item depending on whether or not a button was clicked - mRemoveButtonHandle.get()->setEnabled(mRightMouseTargetButton != NULL); - } -} - -void LLToolBar::initFromParams(const LLToolBar::Params& p) -{ - // Initialize the base object - LLUICtrl::initFromParams(p); - - LLView::EOrientation orientation = getOrientation(p.side); - - LLLayoutStack::Params centering_stack_p; - centering_stack_p.name = "centering_stack"; - centering_stack_p.rect = getLocalRect(); - centering_stack_p.follows.flags = FOLLOWS_ALL; - centering_stack_p.orientation = orientation; - centering_stack_p.mouse_opaque = false; - - mCenteringStack = LLUICtrlFactory::create(centering_stack_p); - addChild(mCenteringStack); - - LLLayoutPanel::Params border_panel_p; - border_panel_p.name = "border_panel"; - border_panel_p.rect = getLocalRect(); - border_panel_p.auto_resize = true; - border_panel_p.user_resize = false; - border_panel_p.mouse_opaque = false; - - mCenteringStack->addChild(LLUICtrlFactory::create(border_panel_p)); - - LLLayoutPanel::Params center_panel_p; - center_panel_p.name = "center_panel"; - center_panel_p.rect = getLocalRect(); - center_panel_p.auto_resize = false; - center_panel_p.user_resize = false; - center_panel_p.mouse_opaque = false; - mCenterPanel = LLUICtrlFactory::create(center_panel_p); - mCenteringStack->addChild(mCenterPanel); - - LLPanel::Params button_panel_p(p.button_panel); - button_panel_p.rect = mCenterPanel->getLocalRect(); - button_panel_p.follows.flags = FOLLOWS_BOTTOM|FOLLOWS_LEFT; - mButtonPanel = LLUICtrlFactory::create(button_panel_p); - mCenterPanel->setButtonPanel(mButtonPanel); - mCenterPanel->addChild(mButtonPanel); - - mCenteringStack->addChild(LLUICtrlFactory::create(border_panel_p)); - - for (const auto& id : p.commands) - { - addCommand(id); - } - - mNeedsLayout = true; -} - -bool LLToolBar::addCommand(const LLCommandId& commandId, int rank) -{ - LLCommand * command = LLCommandManager::instance().getCommand(commandId); - if (!command) return false; - - // Create the button and do the things that don't need ordering - LLToolBarButton* button = createButton(commandId); - mButtonPanel->addChild(button); - mButtonMap.insert(std::make_pair(commandId.uuid(), button)); - - // Insert the command and button in the right place in their respective lists - if ((rank >= mButtonCommands.size()) || (rank == RANK_NONE)) - { - // In that case, back load - mButtonCommands.push_back(command->id()); - mButtons.push_back(button); - } - else - { - // Insert in place: iterate to the right spot... - std::list::iterator it_button = mButtons.begin(); - command_id_list_t::iterator it_command = mButtonCommands.begin(); - while (rank > 0) - { - ++it_button; - ++it_command; - rank--; - } - // ...then insert - mButtonCommands.insert(it_command, command->id()); - mButtons.insert(it_button,button); - } - - mNeedsLayout = true; - - updateLayoutAsNeeded(); - - - if (mButtonAddSignal) - { - (*mButtonAddSignal)(button); - } - - return true; -} - -// Remove a command from the list -// Returns the rank of the command in the original list so that doing addCommand(id,rank) right after -// a removeCommand(id) would leave the list unchanged. -// Returns RANK_NONE if the command is not found in the list -int LLToolBar::removeCommand(const LLCommandId& commandId) -{ - if (!hasCommand(commandId)) return RANK_NONE; - - // First erase the map record - command_id_map::iterator it = mButtonMap.find(commandId.uuid()); - mButtonMap.erase(it); - - // Now iterate on the commands and buttons to identify the relevant records - int rank = 0; - std::list::iterator it_button = mButtons.begin(); - command_id_list_t::iterator it_command = mButtonCommands.begin(); - while (*it_command != commandId) - { - ++it_button; - ++it_command; - ++rank; - } - - if (mButtonRemoveSignal) - { - (*mButtonRemoveSignal)(*it_button); - } - - // Delete the button and erase the command and button records - delete (*it_button); - mButtonCommands.erase(it_command); - mButtons.erase(it_button); - - mNeedsLayout = true; - - return rank; -} - -void LLToolBar::clearCommandsList() -{ - // Clears the commands list - mButtonCommands.clear(); - // This will clear the buttons - createButtons(); -} - -bool LLToolBar::hasCommand(const LLCommandId& commandId) const -{ - if (commandId != LLCommandId::null) - { - command_id_map::const_iterator it = mButtonMap.find(commandId.uuid()); - return (it != mButtonMap.end()); - } - - return false; -} - -bool LLToolBar::enableCommand(const LLCommandId& commandId, bool enabled) -{ - LLButton * command_button = NULL; - - if (commandId != LLCommandId::null) - { - command_id_map::iterator it = mButtonMap.find(commandId.uuid()); - if (it != mButtonMap.end()) - { - command_button = it->second; - command_button->setEnabled(enabled); - } - } - - return (command_button != NULL); -} - -bool LLToolBar::stopCommandInProgress(const LLCommandId& commandId) -{ - // - // Note from Leslie: - // - // This implementation was largely put in place to handle EXP-1348 which is related to - // dragging and dropping the "speak" button. The "speak" button can be in one of two - // modes, i.e., either a toggle action or a push-to-talk action. Because of this it - // responds to mouse down and mouse up in different ways, based on which behavior the - // button is currently set to obey. This was the simplest way of getting the button - // to turn off the microphone for both behaviors without risking duplicate state. - // - - LLToolBarButton * command_button = NULL; - - if (commandId != LLCommandId::null) - { - LLCommand* command = LLCommandManager::instance().getCommand(commandId); - llassert(command); - - // If this command has an explicit function for execution stop - if (command->executeStopFunctionName().length() > 0) - { - command_id_map::iterator it = mButtonMap.find(commandId.uuid()); - if (it != mButtonMap.end()) - { - command_button = it->second; - llassert(command_button->mIsRunningSignal); - - // Check to see if it is running - if ((*command_button->mIsRunningSignal)(command_button, command->isRunningParameters())) - { - // Trigger an additional button commit, which calls mouse down, mouse up and commit - command_button->onCommit(); - } - } - } - } - - return (command_button != NULL); -} - -bool LLToolBar::flashCommand(const LLCommandId& commandId, bool flash, bool force_flashing/* = false */) -{ - LLButton * command_button = NULL; - - if (commandId != LLCommandId::null) - { - command_id_map::iterator it = mButtonMap.find(commandId.uuid()); - if (it != mButtonMap.end()) - { - command_button = it->second; - command_button->setFlashing((bool)(flash),(bool)(force_flashing)); - } - } - - return (command_button != NULL); -} - -bool LLToolBar::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - LLRect button_panel_rect; - mButtonPanel->localRectToOtherView(mButtonPanel->getLocalRect(), &button_panel_rect, this); - bool handle_it_here = !mReadOnly && button_panel_rect.pointInRect(x, y); - - if (handle_it_here) - { - // Determine which button the mouse was over during the click in case the context menu action - // is intended to affect the button. - mRightMouseTargetButton = NULL; - for (LLToolBarButton* button : mButtons) - { - LLRect button_rect; - button->localRectToOtherView(button->getLocalRect(), &button_rect, this); - - if (button_rect.pointInRect(x, y)) - { - mRightMouseTargetButton = button; - break; - } - } - - createContextMenu(); - - LLContextMenu * menu = (LLContextMenu *) mPopupMenuHandle.get(); - - if (menu) - { - menu->show(x, y); - - LLMenuGL::showPopup(this, menu, x, y); - } - } - - return handle_it_here; -} - -bool LLToolBar::isSettingChecked(const LLSD& userdata) -{ - bool retval = false; - - const std::string setting_name = userdata.asString(); - - if (setting_name == "icons_with_text") - { - retval = (mButtonType == BTNTYPE_ICONS_WITH_TEXT); - } - else if (setting_name == "icons_only") - { - retval = (mButtonType == BTNTYPE_ICONS_ONLY); - } - - return retval; -} - -void LLToolBar::onSettingEnable(const LLSD& userdata) -{ - llassert(!mReadOnly); - - const std::string setting_name = userdata.asString(); - - if (setting_name == "icons_with_text") - { - setButtonType(BTNTYPE_ICONS_WITH_TEXT); - } - else if (setting_name == "icons_only") - { - setButtonType(BTNTYPE_ICONS_ONLY); - } -} - -void LLToolBar::onRemoveSelectedCommand() -{ - llassert(!mReadOnly); - - if (mRightMouseTargetButton) - { - removeCommand(mRightMouseTargetButton->getCommandId()); - - mRightMouseTargetButton = NULL; - } -} - -void LLToolBar::setButtonType(LLToolBarEnums::ButtonType button_type) -{ - bool regenerate_buttons = (mButtonType != button_type); - - mButtonType = button_type; - - if (regenerate_buttons) - { - createButtons(); - } -} - -void LLToolBar::resizeButtonsInRow(std::vector& buttons_in_row, S32 max_row_girth) -{ - // make buttons in current row all same girth - for (LLToolBarButton* button : buttons_in_row) - { - if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL) - { - button->reshape(button->mWidthRange.clamp(button->getRect().getWidth()), max_row_girth); - } - else // VERTICAL - { - button->reshape(max_row_girth, button->getRect().getHeight()); - } - } -} - -// Returns the position of the coordinates as a rank in the button list. -// The rank is the position a tool dropped in (x,y) would assume in the button list. -// The returned value is between 0 and mButtons.size(), 0 being the first element to the left -// (or top) and mButtons.size() the last one to the right (or bottom). -// Various drag data are stored in the toolbar object though are not exposed outside (and shouldn't). -int LLToolBar::getRankFromPosition(S32 x, S32 y) -{ - if (mButtons.empty()) - { - return RANK_NONE; - } - - int rank = 0; - - // Convert the toolbar coord into button panel coords - LLView::EOrientation orientation = getOrientation(mSideType); - S32 button_panel_x = 0; - S32 button_panel_y = 0; - localPointToOtherView(x, y, &button_panel_x, &button_panel_y, mButtonPanel); - S32 dx = x - button_panel_x; - S32 dy = y - button_panel_y; - - // Simply compare the passed coord with the buttons outbound box + padding - std::list::iterator it_button = mButtons.begin(); - std::list::iterator end_button = mButtons.end(); - LLRect button_rect; - while (it_button != end_button) - { - button_rect = (*it_button)->getRect(); - S32 point_x = button_rect.mRight + mPadRight; - S32 point_y = button_rect.mBottom - mPadBottom; - - if ((button_panel_x < point_x) && (button_panel_y > point_y)) - { - break; - } - rank++; - ++it_button; - } - - // Update the passed coordinates to the hit button relevant corner - // (different depending on toolbar orientation) - if (rank < mButtons.size()) - { - if (orientation == LLLayoutStack::HORIZONTAL) - { - // Horizontal - S32 mid_point = (button_rect.mRight + button_rect.mLeft) / 2; - if (button_panel_x < mid_point) - { - mDragx = button_rect.mLeft - mPadLeft; - mDragy = button_rect.mTop + mPadTop; - } - else - { - rank++; - mDragx = button_rect.mRight + mPadRight - 1; - mDragy = button_rect.mTop + mPadTop; - } - } - else - { - // Vertical - S32 mid_point = (button_rect.mTop + button_rect.mBottom) / 2; - if (button_panel_y > mid_point) - { - mDragx = button_rect.mLeft - mPadLeft; - mDragy = button_rect.mTop + mPadTop; - } - else - { - rank++; - mDragx = button_rect.mLeft - mPadLeft; - mDragy = button_rect.mBottom - mPadBottom + 1; - } - } - } - else - { - // We hit passed the end of the list so put the insertion point at the end - if (orientation == LLLayoutStack::HORIZONTAL) - { - mDragx = button_rect.mRight + mPadRight; - mDragy = button_rect.mTop + mPadTop; - } - else - { - mDragx = button_rect.mLeft - mPadLeft; - mDragy = button_rect.mBottom - mPadBottom; - } - } - - // Update the "girth" of the caret, i.e. the width or height (depending of orientation) - if (orientation == LLLayoutStack::HORIZONTAL) - { - mDragGirth = button_rect.getHeight() + mPadBottom + mPadTop; - } - else - { - mDragGirth = button_rect.getWidth() + mPadLeft + mPadRight; - } - - // The delta account for the coord model change (i.e. convert back to toolbar coord) - mDragx += dx; - mDragy += dy; - - return rank; -} - -int LLToolBar::getRankFromPosition(const LLCommandId& id) -{ - if (!hasCommand(id)) - { - return RANK_NONE; - } - int rank = 0; - std::list::iterator it_button = mButtons.begin(); - std::list::iterator end_button = mButtons.end(); - while (it_button != end_button) - { - if ((*it_button)->mId == id) - { - break; - } - rank++; - ++it_button; - } - return rank; -} - -void LLToolBar::updateLayoutAsNeeded() -{ - if (!mNeedsLayout) return; - - LLView::EOrientation orientation = getOrientation(mSideType); - - // our terminology for orientation-agnostic layout is such that - // length refers to a distance in the direction we stack the buttons - // and girth refers to a distance in the direction buttons wrap - S32 max_row_girth = 0; - S32 max_row_length = 0; - - S32 max_length; - S32 cur_start; - S32 cur_row ; - S32 row_pad_start; - S32 row_pad_end; - S32 girth_pad_end; - S32 row_running_length; - - if (orientation == LLLayoutStack::HORIZONTAL) - { - max_length = getRect().getWidth() - mPadLeft - mPadRight; - row_pad_start = mPadLeft; - row_pad_end = mPadRight; - cur_row = mPadTop; - girth_pad_end = mPadBottom; - } - else // VERTICAL - { - max_length = getRect().getHeight() - mPadTop - mPadBottom; - row_pad_start = mPadTop; - row_pad_end = mPadBottom; - cur_row = mPadLeft; - girth_pad_end = mPadRight; - } - - row_running_length = row_pad_start; - cur_start = row_pad_start; - - - LLRect panel_rect = mButtonPanel->getLocalRect(); - - std::vector buttons_in_row; - - for (LLToolBarButton* button : mButtons) - { - button->reshape(button->mWidthRange.getMin(), button->mDesiredHeight); - button->autoResize(); - - S32 button_clamped_width = button->mWidthRange.clamp(button->getRect().getWidth()); - S32 button_length = (orientation == LLLayoutStack::HORIZONTAL) - ? button_clamped_width - : button->getRect().getHeight(); - S32 button_girth = (orientation == LLLayoutStack::HORIZONTAL) - ? button->getRect().getHeight() - : button_clamped_width; - - // wrap if needed - if (mWrap - && row_running_length + button_length > max_length // out of room... - && cur_start != row_pad_start) // ...and not first button in row - { - if (orientation == LLLayoutStack::VERTICAL) - { // row girth (width in this case) is clamped to allowable button widths - max_row_girth = button->mWidthRange.clamp(max_row_girth); - } - - // make buttons in current row all same girth - resizeButtonsInRow(buttons_in_row, max_row_girth); - buttons_in_row.clear(); - - max_row_length = llmax(max_row_length, row_running_length); - row_running_length = row_pad_start; - cur_start = row_pad_start; - cur_row += max_row_girth + mPadBetween; - max_row_girth = 0; - } - - LLRect button_rect; - if (orientation == LLLayoutStack::HORIZONTAL) - { - button_rect.setLeftTopAndSize(cur_start, panel_rect.mTop - cur_row, button_clamped_width, button->getRect().getHeight()); - } - else // VERTICAL - { - button_rect.setLeftTopAndSize(cur_row, panel_rect.mTop - cur_start, button_clamped_width, button->getRect().getHeight()); - } - button->setShape(button_rect); - - buttons_in_row.push_back(button); - - row_running_length += button_length + mPadBetween; - cur_start = row_running_length; - max_row_girth = llmax(button_girth, max_row_girth); - } - - // final resizing in "girth" direction - S32 total_girth = cur_row // current row position... - + max_row_girth // ...incremented by size of final row... - + girth_pad_end; // ...plus padding reserved on end - total_girth = llmax(total_girth,mMinGirth); - - max_row_length = llmax(max_row_length, row_running_length - mPadBetween + row_pad_end); - - resizeButtonsInRow(buttons_in_row, max_row_girth); - - // grow and optionally shift toolbar to accommodate buttons - if (orientation == LLLayoutStack::HORIZONTAL) - { - if (mSideType == SIDE_TOP) - { // shift down to maintain top edge - translate(0, getRect().getHeight() - total_girth); - } - - reshape(getRect().getWidth(), total_girth); - mButtonPanel->reshape(max_row_length, total_girth); - } - else // VERTICAL - { - if (mSideType == SIDE_RIGHT) - { // shift left to maintain right edge - translate(getRect().getWidth() - total_girth, 0); - } - - reshape(total_girth, getRect().getHeight()); - mButtonPanel->reshape(total_girth, max_row_length); - } - - // make parent fit button panel - mButtonPanel->getParent()->setShape(mButtonPanel->getLocalRect()); - - // re-center toolbar buttons - mCenteringStack->updateLayout(); - - if (!mButtons.empty()) - { - mButtonPanel->setVisible(true); - mButtonPanel->setMouseOpaque(true); - } - - // don't clear flag until after we've resized ourselves, to avoid laying out every frame - mNeedsLayout = false; -} - - -void LLToolBar::draw() -{ - if (mButtons.empty()) - { - mButtonPanel->setVisible(false); - mButtonPanel->setMouseOpaque(false); - } - else - { - mButtonPanel->setVisible(true); - mButtonPanel->setMouseOpaque(true); - } - - // Update enable/disable state and highlight state for editable toolbars - if (!mReadOnly) - { - for (toolbar_button_list::iterator btn_it = mButtons.begin(); btn_it != mButtons.end(); ++btn_it) - { - LLToolBarButton* btn = *btn_it; - LLCommand* command = LLCommandManager::instance().getCommand(btn->mId); - - if (command && btn->mIsEnabledSignal) - { - const bool button_command_enabled = (*btn->mIsEnabledSignal)(btn, command->isEnabledParameters()); - btn->setEnabled(button_command_enabled); - } - - if (command && btn->mIsRunningSignal) - { - const bool button_command_running = (*btn->mIsRunningSignal)(btn, command->isRunningParameters()); - btn->setToggleState(button_command_running); - } - } - } - - updateLayoutAsNeeded(); - // rect may have shifted during layout - LLUI::popMatrix(); - LLUI::pushMatrix(); - LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom); - - // Position the caret - if (!mCaretIcon) - { - mCaretIcon = getChild("caret"); - } - - LLIconCtrl* caret = mCaretIcon; - caret->setVisible(false); - if (mDragAndDropTarget && !mButtonCommands.empty()) - { - LLRect caret_rect = caret->getRect(); - if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL) - { - caret->setRect(LLRect(mDragx-caret_rect.getWidth()/2+1, - mDragy, - mDragx+caret_rect.getWidth()/2+1, - mDragy-mDragGirth)); - } - else - { - caret->setRect(LLRect(mDragx, - mDragy+caret_rect.getHeight()/2, - mDragx+mDragGirth, - mDragy-caret_rect.getHeight()/2)); - } - caret->setVisible(true); - } - - LLUICtrl::draw(); - caret->setVisible(false); - mDragAndDropTarget = false; -} - -void LLToolBar::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLUICtrl::reshape(width, height, called_from_parent); - mNeedsLayout = true; -} - -void LLToolBar::createButtons() -{ - std::set set_flashing; - - for (LLToolBarButton* button : mButtons) - { - if (button->getFlashTimer() && button->getFlashTimer()->isFlashingInProgress()) - { - set_flashing.insert(button->getCommandId().uuid()); - } - - if (mButtonRemoveSignal) - { - (*mButtonRemoveSignal)(button); - } - - delete button; - } - mButtons.clear(); - mButtonMap.clear(); - mRightMouseTargetButton = NULL; - - for (const LLCommandId& command_id : mButtonCommands) - { - LLToolBarButton* button = createButton(command_id); - mButtons.push_back(button); - mButtonPanel->addChild(button); - mButtonMap.insert(std::make_pair(command_id.uuid(), button)); - - if (mButtonAddSignal) - { - (*mButtonAddSignal)(button); - } - - if (set_flashing.find(button->getCommandId().uuid()) != set_flashing.end()) - { - button->setFlashing(true); - } - } - mNeedsLayout = true; -} - -void LLToolBarButton::callIfEnabled(LLUICtrl::commit_callback_t commit, LLUICtrl* ctrl, const LLSD& param ) -{ - LLCommand* command = LLCommandManager::instance().getCommand(mId); - - if (!mIsEnabledSignal || (*mIsEnabledSignal)(this, command->isEnabledParameters())) - { - commit(ctrl, param); - } -} - -LLToolBarButton* LLToolBar::createButton(const LLCommandId& id) -{ - LLCommand* commandp = LLCommandManager::instance().getCommand(id); - if (!commandp) return NULL; - - LLToolBarButton::Params button_p; - button_p.name = commandp->name(); - button_p.label = LLTrans::getString(commandp->labelRef()); - button_p.tool_tip = LLTrans::getString(commandp->tooltipRef()); - button_p.image_overlay = LLUI::getUIImage(commandp->icon()); - button_p.button_flash_enable = commandp->isFlashingAllowed(); - button_p.overwriteFrom(mButtonParams[mButtonType]); - LLToolBarButton* button = LLUICtrlFactory::create(button_p); - - if (!mReadOnly) - { - enable_callback_t isEnabledCB; - - const std::string& isEnabledFunction = commandp->isEnabledFunctionName(); - if (isEnabledFunction.length() > 0) - { - LLUICtrl::EnableCallbackParam isEnabledParam; - isEnabledParam.function_name = isEnabledFunction; - isEnabledParam.parameter = commandp->isEnabledParameters(); - isEnabledCB = initEnableCallback(isEnabledParam); - - if (NULL == button->mIsEnabledSignal) - { - button->mIsEnabledSignal = new enable_signal_t(); - } - - button->mIsEnabledSignal->connect(isEnabledCB); - } - - LLUICtrl::CommitCallbackParam executeParam; - executeParam.function_name = commandp->executeFunctionName(); - executeParam.parameter = commandp->executeParameters(); - - // If we have a "stop" function then we map the command to mouse down / mouse up otherwise commit - const std::string& executeStopFunction = commandp->executeStopFunctionName(); - if (executeStopFunction.length() > 0) - { - LLUICtrl::CommitCallbackParam executeStopParam; - executeStopParam.function_name = executeStopFunction; - executeStopParam.parameter = commandp->executeStopParameters(); - LLUICtrl::commit_callback_t execute_func = initCommitCallback(executeParam); - button->setFunctionName(commandp->executeFunctionName()); - LL_DEBUGS("UIUsage") << "button function name a -> " << commandp->executeFunctionName() << LL_ENDL; - LLUICtrl::commit_callback_t stop_func = initCommitCallback(executeStopParam); - - button->setMouseDownCallback(boost::bind(&LLToolBarButton::callIfEnabled, button, execute_func, _1, _2)); - button->setMouseUpCallback(boost::bind(&LLToolBarButton::callIfEnabled, button, stop_func, _1, _2)); - } - else - { - button->setFunctionName(commandp->executeFunctionName()); - LL_DEBUGS("UIUsage") << "button function name b -> " << commandp->executeFunctionName() << LL_ENDL; - button->setCommitCallback(executeParam); - } - - // Set up "is running" query callback - const std::string& isRunningFunction = commandp->isRunningFunctionName(); - if (isRunningFunction.length() > 0) - { - LLUICtrl::EnableCallbackParam isRunningParam; - isRunningParam.function_name = isRunningFunction; - isRunningParam.parameter = commandp->isRunningParameters(); - enable_signal_t::slot_type isRunningCB = initEnableCallback(isRunningParam); - - if (NULL == button->mIsRunningSignal) - { - button->mIsRunningSignal = new enable_signal_t(); - } - - button->mIsRunningSignal->connect(isRunningCB); - } - } - - // Drag and drop behavior must work also if provided in the Toybox and, potentially, any read-only toolbar - button->setStartDragCallback(mStartDragItemCallback); - button->setHandleDragCallback(mHandleDragItemCallback); - - button->setCommandId(id); - - return button; -} - -boost::signals2::connection connectSignal(LLToolBar::button_signal_t*& signal, const LLToolBar::button_signal_t::slot_type& cb) -{ - if (!signal) - { - signal = new LLToolBar::button_signal_t(); - } - - return signal->connect(cb); -} - -boost::signals2::connection LLToolBar::setButtonAddCallback(const button_signal_t::slot_type& cb) -{ - return connectSignal(mButtonAddSignal, cb); -} - -boost::signals2::connection LLToolBar::setButtonEnterCallback(const button_signal_t::slot_type& cb) -{ - return connectSignal(mButtonEnterSignal, cb); -} - -boost::signals2::connection LLToolBar::setButtonLeaveCallback(const button_signal_t::slot_type& cb) -{ - return connectSignal(mButtonLeaveSignal, cb); -} - -boost::signals2::connection LLToolBar::setButtonRemoveCallback(const button_signal_t::slot_type& cb) -{ - return connectSignal(mButtonRemoveSignal, cb); -} - -bool LLToolBar::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - // If we have a drop callback, that means that we can handle the drop - bool handled = mHandleDropCallback != nullptr; - - // if drop is set, it's time to call the callback to get the operation done - if (handled && drop) - { - handled = mHandleDropCallback(cargo_data, x, y, this); - } - - // We accept only single tool drop on toolbars - *accept = handled ? ACCEPT_YES_SINGLE : ACCEPT_NO; - - // We'll use that flag to change the visual aspect of the toolbar target on draw() - mDragAndDropTarget = false; - - // Convert drag position into insert position and rank - if (!isReadOnly() && handled && !drop) - { - if (cargo_type == DAD_WIDGET) - { - LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; - LLCommandId dragged_command(inv_item->getUUID()); - int orig_rank = getRankFromPosition(dragged_command); - mDragRank = getRankFromPosition(x, y); - // Don't DaD if we're dragging a command on itself - mDragAndDropTarget = ((orig_rank != RANK_NONE) && ((mDragRank == orig_rank) || ((mDragRank - 1) == orig_rank))); - //LL_INFOS() << "Merov debug : DaD, rank = " << mDragRank << ", dragged uui = " << inv_item->getUUID() << LL_ENDL; - /* Do the following if you want to animate the button itself - LLCommandId dragged_command(inv_item->getUUID()); - removeCommand(dragged_command); - addCommand(dragged_command,rank); - */ - } - else - { - handled = false; - } - } - - return handled; -} - -LLToolBarButton::LLToolBarButton(const Params& p) -: LLButton(p), - mMouseDownX(0), - mMouseDownY(0), - mWidthRange(p.button_width), - mDesiredHeight(p.desired_height), - mId(""), - mIsEnabledSignal(NULL), - mIsRunningSignal(NULL), - mIsStartingSignal(NULL), - mIsDragged(false), - mStartDragItemCallback(NULL), - mHandleDragItemCallback(NULL), - mOriginalImageSelected(p.image_selected), - mOriginalImageUnselected(p.image_unselected), - mOriginalImagePressed(p.image_pressed), - mOriginalImagePressedSelected(p.image_pressed_selected), - mOriginalLabelColor(p.label_color), - mOriginalLabelColorSelected(p.label_color_selected), - mOriginalImageOverlayColor(p.image_overlay_color), - mOriginalImageOverlaySelectedColor(p.image_overlay_selected_color) -{ -} - -LLToolBarButton::~LLToolBarButton() -{ - delete mIsEnabledSignal; - delete mIsRunningSignal; - delete mIsStartingSignal; -} - -bool LLToolBarButton::handleMouseDown(S32 x, S32 y, MASK mask) -{ - mMouseDownX = x; - mMouseDownY = y; - return LLButton::handleMouseDown(x, y, mask); -} - -bool LLToolBarButton::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - S32 mouse_distance_squared = (x - mMouseDownX) * (x - mMouseDownX) + (y - mMouseDownY) * (y - mMouseDownY); - if (mouse_distance_squared > DRAG_N_DROP_DISTANCE_THRESHOLD * DRAG_N_DROP_DISTANCE_THRESHOLD - && hasMouseCapture() && - mStartDragItemCallback && mHandleDragItemCallback) - { - if (!mIsDragged) - { - mStartDragItemCallback(x, y, this); - mIsDragged = true; - handled = true; - } - else - { - handled = mHandleDragItemCallback(x, y, mId.uuid(), LLAssetType::AT_WIDGET); - } - } - else - { - handled = LLButton::handleHover(x, y, mask); - } - - return handled; -} - -void LLToolBarButton::onMouseEnter(S32 x, S32 y, MASK mask) -{ - LLUICtrl::onMouseEnter(x, y, mask); - - // Always highlight toolbar buttons, even if they are disabled - if (!gFocusMgr.getMouseCapture() || gFocusMgr.getMouseCapture() == this) - { - mNeedsHighlight = true; - } - - LLToolBar* parent_toolbar = getParentByType(); - if (parent_toolbar && parent_toolbar->mButtonEnterSignal) - { - (*(parent_toolbar->mButtonEnterSignal))(this); - } -} - -void LLToolBarButton::onMouseLeave(S32 x, S32 y, MASK mask) -{ - LLButton::onMouseLeave(x, y, mask); - - LLToolBar* parent_toolbar = getParentByType(); - if (parent_toolbar && parent_toolbar->mButtonLeaveSignal) - { - (*(parent_toolbar->mButtonLeaveSignal))(this); - } -} - -void LLToolBarButton::onMouseCaptureLost() -{ - mIsDragged = false; -} - -void LLToolBarButton::onCommit() -{ - LLCommand* command = LLCommandManager::instance().getCommand(mId); - - if (!mIsEnabledSignal || (*mIsEnabledSignal)(this, command->isEnabledParameters())) - { - LLButton::onCommit(); - } -} - -void LLToolBarButton::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLButton::reshape(mWidthRange.clamp(width), height, called_from_parent); -} - -void LLToolBarButton::setEnabled(bool enabled) -{ - if (enabled) - { - mImageSelected = mOriginalImageSelected; - mImageUnselected = mOriginalImageUnselected; - mImagePressed = mOriginalImagePressed; - mImagePressedSelected = mOriginalImagePressedSelected; - mUnselectedLabelColor = mOriginalLabelColor; - mSelectedLabelColor = mOriginalLabelColorSelected; - mImageOverlayColor = mOriginalImageOverlayColor; - mImageOverlaySelectedColor = mOriginalImageOverlaySelectedColor; - } - else - { - mImageSelected = mImageDisabledSelected; - mImageUnselected = mImageDisabled; - mImagePressed = mImageDisabled; - mImagePressedSelected = mImageDisabledSelected; - mUnselectedLabelColor = mDisabledLabelColor; - mSelectedLabelColor = mDisabledSelectedLabelColor; - mImageOverlayColor = mImageOverlayDisabledColor; - mImageOverlaySelectedColor = mImageOverlayDisabledColor; - } -} - -const std::string LLToolBarButton::getToolTip() const -{ - std::string tooltip; - - if (labelIsTruncated() || getCurrentLabel().empty()) - { - tooltip = LLTrans::getString(LLCommandManager::instance().getCommand(mId)->labelRef()) + " -- " + LLView::getToolTip(); - } - else - { - tooltip = LLView::getToolTip(); - } - - LLToolBar* parent_toolbar = getParentByType(); - if (parent_toolbar && parent_toolbar->mButtonTooltipSuffix.length() > 0) - { - tooltip = tooltip + "\n(" + parent_toolbar->mButtonTooltipSuffix + ")"; - } - - return tooltip; -} - -void LLToolBar::LLCenterLayoutPanel::handleReshape(const LLRect& rect, bool by_user) -{ - LLLayoutPanel::handleReshape(rect, by_user); - - if (!mReshapeCallback.empty()) - { - LLRect r; - localRectToOtherView(mButtonPanel->getRect(), &r, gFloaterView); - r.stretch(FLOATER_MIN_VISIBLE_PIXELS); - mReshapeCallback(mLocationId, r); - } -} +/** + * @file lltoolbar.cpp + * @author Richard Nelson + * @brief User customizable toolbar class + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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 "lltoolbar.h" + +#include "llcommandmanager.h" +#include "llmenugl.h" +#include "lltrans.h" +#include "llinventory.h" +#include "lliconctrl.h" + +// uncomment this and remove the one in llui.cpp when there is an external reference to this translation unit +// thanks, MSVC! +//static LLDefaultChildRegistry::Register r1("toolbar"); + +namespace LLToolBarEnums +{ + LLView::EOrientation getOrientation(SideType sideType) + { + LLView::EOrientation orientation = LLLayoutStack::HORIZONTAL; + + if ((sideType == SIDE_LEFT) || (sideType == SIDE_RIGHT)) + { + orientation = LLLayoutStack::VERTICAL; + } + + return orientation; + } +} + +using namespace LLToolBarEnums; + + +namespace LLInitParam +{ + void TypeValues::declareValues() + { + declare("icons_with_text", BTNTYPE_ICONS_WITH_TEXT); + declare("icons_only", BTNTYPE_ICONS_ONLY); + } + + void TypeValues::declareValues() + { + declare("bottom", SIDE_BOTTOM); + declare("left", SIDE_LEFT); + declare("right", SIDE_RIGHT); + declare("top", SIDE_TOP); + } +} + +LLToolBar::Params::Params() +: button_display_mode("button_display_mode"), + commands("command"), + side("side", SIDE_TOP), + button_icon("button_icon"), + button_icon_and_text("button_icon_and_text"), + read_only("read_only", false), + wrap("wrap", true), + pad_left("pad_left"), + pad_top("pad_top"), + pad_right("pad_right"), + pad_bottom("pad_bottom"), + pad_between("pad_between"), + min_girth("min_girth"), + button_panel("button_panel") +{} + +LLToolBar::LLToolBar(const LLToolBar::Params& p) +: LLUICtrl(p), + mReadOnly(p.read_only), + mButtonType(p.button_display_mode), + mSideType(p.side), + mWrap(p.wrap), + mNeedsLayout(false), + mModified(false), + mButtonPanel(NULL), + mCenteringStack(NULL), + mPadLeft(p.pad_left), + mPadRight(p.pad_right), + mPadTop(p.pad_top), + mPadBottom(p.pad_bottom), + mPadBetween(p.pad_between), + mMinGirth(p.min_girth), + mPopupMenuHandle(), + mRightMouseTargetButton(NULL), + mStartDragItemCallback(NULL), + mHandleDragItemCallback(NULL), + mHandleDropCallback(NULL), + mButtonAddSignal(NULL), + mButtonEnterSignal(NULL), + mButtonLeaveSignal(NULL), + mButtonRemoveSignal(NULL), + mDragAndDropTarget(false), + mCaretIcon(NULL), + mCenterPanel(NULL) +{ + mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_WITH_TEXT] = p.button_icon_and_text; + mButtonParams[LLToolBarEnums::BTNTYPE_ICONS_ONLY] = p.button_icon; +} + +LLToolBar::~LLToolBar() +{ + auto menu = mPopupMenuHandle.get(); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } + delete mButtonAddSignal; + delete mButtonEnterSignal; + delete mButtonLeaveSignal; + delete mButtonRemoveSignal; +} + +void LLToolBar::createContextMenu() +{ + if (!mPopupMenuHandle.get()) + { + // Setup bindings specific to this instance for the context menu options + + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commit_reg; + commit_reg.add("Toolbars.EnableSetting", boost::bind(&LLToolBar::onSettingEnable, this, _2)); + commit_reg.add("Toolbars.RemoveSelectedCommand", boost::bind(&LLToolBar::onRemoveSelectedCommand, this)); + + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_reg; + enable_reg.add("Toolbars.CheckSetting", boost::bind(&LLToolBar::isSettingChecked, this, _2)); + + // Create the context menu + llassert(LLMenuGL::sMenuContainer != NULL); + LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile("menu_toolbars.xml", LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); + + if (menu) + { + menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor")); + mPopupMenuHandle = menu->getHandle(); + mRemoveButtonHandle = menu->getChild("Remove button")->getHandle(); + } + else + { + LL_WARNS() << "Unable to load toolbars context menu." << LL_ENDL; + } + } + + if (mRemoveButtonHandle.get()) + { + // Disable/Enable the "Remove button" menu item depending on whether or not a button was clicked + mRemoveButtonHandle.get()->setEnabled(mRightMouseTargetButton != NULL); + } +} + +void LLToolBar::initFromParams(const LLToolBar::Params& p) +{ + // Initialize the base object + LLUICtrl::initFromParams(p); + + LLView::EOrientation orientation = getOrientation(p.side); + + LLLayoutStack::Params centering_stack_p; + centering_stack_p.name = "centering_stack"; + centering_stack_p.rect = getLocalRect(); + centering_stack_p.follows.flags = FOLLOWS_ALL; + centering_stack_p.orientation = orientation; + centering_stack_p.mouse_opaque = false; + + mCenteringStack = LLUICtrlFactory::create(centering_stack_p); + addChild(mCenteringStack); + + LLLayoutPanel::Params border_panel_p; + border_panel_p.name = "border_panel"; + border_panel_p.rect = getLocalRect(); + border_panel_p.auto_resize = true; + border_panel_p.user_resize = false; + border_panel_p.mouse_opaque = false; + + mCenteringStack->addChild(LLUICtrlFactory::create(border_panel_p)); + + LLLayoutPanel::Params center_panel_p; + center_panel_p.name = "center_panel"; + center_panel_p.rect = getLocalRect(); + center_panel_p.auto_resize = false; + center_panel_p.user_resize = false; + center_panel_p.mouse_opaque = false; + mCenterPanel = LLUICtrlFactory::create(center_panel_p); + mCenteringStack->addChild(mCenterPanel); + + LLPanel::Params button_panel_p(p.button_panel); + button_panel_p.rect = mCenterPanel->getLocalRect(); + button_panel_p.follows.flags = FOLLOWS_BOTTOM|FOLLOWS_LEFT; + mButtonPanel = LLUICtrlFactory::create(button_panel_p); + mCenterPanel->setButtonPanel(mButtonPanel); + mCenterPanel->addChild(mButtonPanel); + + mCenteringStack->addChild(LLUICtrlFactory::create(border_panel_p)); + + for (const auto& id : p.commands) + { + addCommand(id); + } + + mNeedsLayout = true; +} + +bool LLToolBar::addCommand(const LLCommandId& commandId, int rank) +{ + LLCommand * command = LLCommandManager::instance().getCommand(commandId); + if (!command) return false; + + // Create the button and do the things that don't need ordering + LLToolBarButton* button = createButton(commandId); + mButtonPanel->addChild(button); + mButtonMap.insert(std::make_pair(commandId.uuid(), button)); + + // Insert the command and button in the right place in their respective lists + if ((rank >= mButtonCommands.size()) || (rank == RANK_NONE)) + { + // In that case, back load + mButtonCommands.push_back(command->id()); + mButtons.push_back(button); + } + else + { + // Insert in place: iterate to the right spot... + std::list::iterator it_button = mButtons.begin(); + command_id_list_t::iterator it_command = mButtonCommands.begin(); + while (rank > 0) + { + ++it_button; + ++it_command; + rank--; + } + // ...then insert + mButtonCommands.insert(it_command, command->id()); + mButtons.insert(it_button,button); + } + + mNeedsLayout = true; + + updateLayoutAsNeeded(); + + + if (mButtonAddSignal) + { + (*mButtonAddSignal)(button); + } + + return true; +} + +// Remove a command from the list +// Returns the rank of the command in the original list so that doing addCommand(id,rank) right after +// a removeCommand(id) would leave the list unchanged. +// Returns RANK_NONE if the command is not found in the list +int LLToolBar::removeCommand(const LLCommandId& commandId) +{ + if (!hasCommand(commandId)) return RANK_NONE; + + // First erase the map record + command_id_map::iterator it = mButtonMap.find(commandId.uuid()); + mButtonMap.erase(it); + + // Now iterate on the commands and buttons to identify the relevant records + int rank = 0; + std::list::iterator it_button = mButtons.begin(); + command_id_list_t::iterator it_command = mButtonCommands.begin(); + while (*it_command != commandId) + { + ++it_button; + ++it_command; + ++rank; + } + + if (mButtonRemoveSignal) + { + (*mButtonRemoveSignal)(*it_button); + } + + // Delete the button and erase the command and button records + delete (*it_button); + mButtonCommands.erase(it_command); + mButtons.erase(it_button); + + mNeedsLayout = true; + + return rank; +} + +void LLToolBar::clearCommandsList() +{ + // Clears the commands list + mButtonCommands.clear(); + // This will clear the buttons + createButtons(); +} + +bool LLToolBar::hasCommand(const LLCommandId& commandId) const +{ + if (commandId != LLCommandId::null) + { + command_id_map::const_iterator it = mButtonMap.find(commandId.uuid()); + return (it != mButtonMap.end()); + } + + return false; +} + +bool LLToolBar::enableCommand(const LLCommandId& commandId, bool enabled) +{ + LLButton * command_button = NULL; + + if (commandId != LLCommandId::null) + { + command_id_map::iterator it = mButtonMap.find(commandId.uuid()); + if (it != mButtonMap.end()) + { + command_button = it->second; + command_button->setEnabled(enabled); + } + } + + return (command_button != NULL); +} + +bool LLToolBar::stopCommandInProgress(const LLCommandId& commandId) +{ + // + // Note from Leslie: + // + // This implementation was largely put in place to handle EXP-1348 which is related to + // dragging and dropping the "speak" button. The "speak" button can be in one of two + // modes, i.e., either a toggle action or a push-to-talk action. Because of this it + // responds to mouse down and mouse up in different ways, based on which behavior the + // button is currently set to obey. This was the simplest way of getting the button + // to turn off the microphone for both behaviors without risking duplicate state. + // + + LLToolBarButton * command_button = NULL; + + if (commandId != LLCommandId::null) + { + LLCommand* command = LLCommandManager::instance().getCommand(commandId); + llassert(command); + + // If this command has an explicit function for execution stop + if (command->executeStopFunctionName().length() > 0) + { + command_id_map::iterator it = mButtonMap.find(commandId.uuid()); + if (it != mButtonMap.end()) + { + command_button = it->second; + llassert(command_button->mIsRunningSignal); + + // Check to see if it is running + if ((*command_button->mIsRunningSignal)(command_button, command->isRunningParameters())) + { + // Trigger an additional button commit, which calls mouse down, mouse up and commit + command_button->onCommit(); + } + } + } + } + + return (command_button != NULL); +} + +bool LLToolBar::flashCommand(const LLCommandId& commandId, bool flash, bool force_flashing/* = false */) +{ + LLButton * command_button = NULL; + + if (commandId != LLCommandId::null) + { + command_id_map::iterator it = mButtonMap.find(commandId.uuid()); + if (it != mButtonMap.end()) + { + command_button = it->second; + command_button->setFlashing((bool)(flash),(bool)(force_flashing)); + } + } + + return (command_button != NULL); +} + +bool LLToolBar::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLRect button_panel_rect; + mButtonPanel->localRectToOtherView(mButtonPanel->getLocalRect(), &button_panel_rect, this); + bool handle_it_here = !mReadOnly && button_panel_rect.pointInRect(x, y); + + if (handle_it_here) + { + // Determine which button the mouse was over during the click in case the context menu action + // is intended to affect the button. + mRightMouseTargetButton = NULL; + for (LLToolBarButton* button : mButtons) + { + LLRect button_rect; + button->localRectToOtherView(button->getLocalRect(), &button_rect, this); + + if (button_rect.pointInRect(x, y)) + { + mRightMouseTargetButton = button; + break; + } + } + + createContextMenu(); + + LLContextMenu * menu = (LLContextMenu *) mPopupMenuHandle.get(); + + if (menu) + { + menu->show(x, y); + + LLMenuGL::showPopup(this, menu, x, y); + } + } + + return handle_it_here; +} + +bool LLToolBar::isSettingChecked(const LLSD& userdata) +{ + bool retval = false; + + const std::string setting_name = userdata.asString(); + + if (setting_name == "icons_with_text") + { + retval = (mButtonType == BTNTYPE_ICONS_WITH_TEXT); + } + else if (setting_name == "icons_only") + { + retval = (mButtonType == BTNTYPE_ICONS_ONLY); + } + + return retval; +} + +void LLToolBar::onSettingEnable(const LLSD& userdata) +{ + llassert(!mReadOnly); + + const std::string setting_name = userdata.asString(); + + if (setting_name == "icons_with_text") + { + setButtonType(BTNTYPE_ICONS_WITH_TEXT); + } + else if (setting_name == "icons_only") + { + setButtonType(BTNTYPE_ICONS_ONLY); + } +} + +void LLToolBar::onRemoveSelectedCommand() +{ + llassert(!mReadOnly); + + if (mRightMouseTargetButton) + { + removeCommand(mRightMouseTargetButton->getCommandId()); + + mRightMouseTargetButton = NULL; + } +} + +void LLToolBar::setButtonType(LLToolBarEnums::ButtonType button_type) +{ + bool regenerate_buttons = (mButtonType != button_type); + + mButtonType = button_type; + + if (regenerate_buttons) + { + createButtons(); + } +} + +void LLToolBar::resizeButtonsInRow(std::vector& buttons_in_row, S32 max_row_girth) +{ + // make buttons in current row all same girth + for (LLToolBarButton* button : buttons_in_row) + { + if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL) + { + button->reshape(button->mWidthRange.clamp(button->getRect().getWidth()), max_row_girth); + } + else // VERTICAL + { + button->reshape(max_row_girth, button->getRect().getHeight()); + } + } +} + +// Returns the position of the coordinates as a rank in the button list. +// The rank is the position a tool dropped in (x,y) would assume in the button list. +// The returned value is between 0 and mButtons.size(), 0 being the first element to the left +// (or top) and mButtons.size() the last one to the right (or bottom). +// Various drag data are stored in the toolbar object though are not exposed outside (and shouldn't). +int LLToolBar::getRankFromPosition(S32 x, S32 y) +{ + if (mButtons.empty()) + { + return RANK_NONE; + } + + int rank = 0; + + // Convert the toolbar coord into button panel coords + LLView::EOrientation orientation = getOrientation(mSideType); + S32 button_panel_x = 0; + S32 button_panel_y = 0; + localPointToOtherView(x, y, &button_panel_x, &button_panel_y, mButtonPanel); + S32 dx = x - button_panel_x; + S32 dy = y - button_panel_y; + + // Simply compare the passed coord with the buttons outbound box + padding + std::list::iterator it_button = mButtons.begin(); + std::list::iterator end_button = mButtons.end(); + LLRect button_rect; + while (it_button != end_button) + { + button_rect = (*it_button)->getRect(); + S32 point_x = button_rect.mRight + mPadRight; + S32 point_y = button_rect.mBottom - mPadBottom; + + if ((button_panel_x < point_x) && (button_panel_y > point_y)) + { + break; + } + rank++; + ++it_button; + } + + // Update the passed coordinates to the hit button relevant corner + // (different depending on toolbar orientation) + if (rank < mButtons.size()) + { + if (orientation == LLLayoutStack::HORIZONTAL) + { + // Horizontal + S32 mid_point = (button_rect.mRight + button_rect.mLeft) / 2; + if (button_panel_x < mid_point) + { + mDragx = button_rect.mLeft - mPadLeft; + mDragy = button_rect.mTop + mPadTop; + } + else + { + rank++; + mDragx = button_rect.mRight + mPadRight - 1; + mDragy = button_rect.mTop + mPadTop; + } + } + else + { + // Vertical + S32 mid_point = (button_rect.mTop + button_rect.mBottom) / 2; + if (button_panel_y > mid_point) + { + mDragx = button_rect.mLeft - mPadLeft; + mDragy = button_rect.mTop + mPadTop; + } + else + { + rank++; + mDragx = button_rect.mLeft - mPadLeft; + mDragy = button_rect.mBottom - mPadBottom + 1; + } + } + } + else + { + // We hit passed the end of the list so put the insertion point at the end + if (orientation == LLLayoutStack::HORIZONTAL) + { + mDragx = button_rect.mRight + mPadRight; + mDragy = button_rect.mTop + mPadTop; + } + else + { + mDragx = button_rect.mLeft - mPadLeft; + mDragy = button_rect.mBottom - mPadBottom; + } + } + + // Update the "girth" of the caret, i.e. the width or height (depending of orientation) + if (orientation == LLLayoutStack::HORIZONTAL) + { + mDragGirth = button_rect.getHeight() + mPadBottom + mPadTop; + } + else + { + mDragGirth = button_rect.getWidth() + mPadLeft + mPadRight; + } + + // The delta account for the coord model change (i.e. convert back to toolbar coord) + mDragx += dx; + mDragy += dy; + + return rank; +} + +int LLToolBar::getRankFromPosition(const LLCommandId& id) +{ + if (!hasCommand(id)) + { + return RANK_NONE; + } + int rank = 0; + std::list::iterator it_button = mButtons.begin(); + std::list::iterator end_button = mButtons.end(); + while (it_button != end_button) + { + if ((*it_button)->mId == id) + { + break; + } + rank++; + ++it_button; + } + return rank; +} + +void LLToolBar::updateLayoutAsNeeded() +{ + if (!mNeedsLayout) return; + + LLView::EOrientation orientation = getOrientation(mSideType); + + // our terminology for orientation-agnostic layout is such that + // length refers to a distance in the direction we stack the buttons + // and girth refers to a distance in the direction buttons wrap + S32 max_row_girth = 0; + S32 max_row_length = 0; + + S32 max_length; + S32 cur_start; + S32 cur_row ; + S32 row_pad_start; + S32 row_pad_end; + S32 girth_pad_end; + S32 row_running_length; + + if (orientation == LLLayoutStack::HORIZONTAL) + { + max_length = getRect().getWidth() - mPadLeft - mPadRight; + row_pad_start = mPadLeft; + row_pad_end = mPadRight; + cur_row = mPadTop; + girth_pad_end = mPadBottom; + } + else // VERTICAL + { + max_length = getRect().getHeight() - mPadTop - mPadBottom; + row_pad_start = mPadTop; + row_pad_end = mPadBottom; + cur_row = mPadLeft; + girth_pad_end = mPadRight; + } + + row_running_length = row_pad_start; + cur_start = row_pad_start; + + + LLRect panel_rect = mButtonPanel->getLocalRect(); + + std::vector buttons_in_row; + + for (LLToolBarButton* button : mButtons) + { + button->reshape(button->mWidthRange.getMin(), button->mDesiredHeight); + button->autoResize(); + + S32 button_clamped_width = button->mWidthRange.clamp(button->getRect().getWidth()); + S32 button_length = (orientation == LLLayoutStack::HORIZONTAL) + ? button_clamped_width + : button->getRect().getHeight(); + S32 button_girth = (orientation == LLLayoutStack::HORIZONTAL) + ? button->getRect().getHeight() + : button_clamped_width; + + // wrap if needed + if (mWrap + && row_running_length + button_length > max_length // out of room... + && cur_start != row_pad_start) // ...and not first button in row + { + if (orientation == LLLayoutStack::VERTICAL) + { // row girth (width in this case) is clamped to allowable button widths + max_row_girth = button->mWidthRange.clamp(max_row_girth); + } + + // make buttons in current row all same girth + resizeButtonsInRow(buttons_in_row, max_row_girth); + buttons_in_row.clear(); + + max_row_length = llmax(max_row_length, row_running_length); + row_running_length = row_pad_start; + cur_start = row_pad_start; + cur_row += max_row_girth + mPadBetween; + max_row_girth = 0; + } + + LLRect button_rect; + if (orientation == LLLayoutStack::HORIZONTAL) + { + button_rect.setLeftTopAndSize(cur_start, panel_rect.mTop - cur_row, button_clamped_width, button->getRect().getHeight()); + } + else // VERTICAL + { + button_rect.setLeftTopAndSize(cur_row, panel_rect.mTop - cur_start, button_clamped_width, button->getRect().getHeight()); + } + button->setShape(button_rect); + + buttons_in_row.push_back(button); + + row_running_length += button_length + mPadBetween; + cur_start = row_running_length; + max_row_girth = llmax(button_girth, max_row_girth); + } + + // final resizing in "girth" direction + S32 total_girth = cur_row // current row position... + + max_row_girth // ...incremented by size of final row... + + girth_pad_end; // ...plus padding reserved on end + total_girth = llmax(total_girth,mMinGirth); + + max_row_length = llmax(max_row_length, row_running_length - mPadBetween + row_pad_end); + + resizeButtonsInRow(buttons_in_row, max_row_girth); + + // grow and optionally shift toolbar to accommodate buttons + if (orientation == LLLayoutStack::HORIZONTAL) + { + if (mSideType == SIDE_TOP) + { // shift down to maintain top edge + translate(0, getRect().getHeight() - total_girth); + } + + reshape(getRect().getWidth(), total_girth); + mButtonPanel->reshape(max_row_length, total_girth); + } + else // VERTICAL + { + if (mSideType == SIDE_RIGHT) + { // shift left to maintain right edge + translate(getRect().getWidth() - total_girth, 0); + } + + reshape(total_girth, getRect().getHeight()); + mButtonPanel->reshape(total_girth, max_row_length); + } + + // make parent fit button panel + mButtonPanel->getParent()->setShape(mButtonPanel->getLocalRect()); + + // re-center toolbar buttons + mCenteringStack->updateLayout(); + + if (!mButtons.empty()) + { + mButtonPanel->setVisible(true); + mButtonPanel->setMouseOpaque(true); + } + + // don't clear flag until after we've resized ourselves, to avoid laying out every frame + mNeedsLayout = false; +} + + +void LLToolBar::draw() +{ + if (mButtons.empty()) + { + mButtonPanel->setVisible(false); + mButtonPanel->setMouseOpaque(false); + } + else + { + mButtonPanel->setVisible(true); + mButtonPanel->setMouseOpaque(true); + } + + // Update enable/disable state and highlight state for editable toolbars + if (!mReadOnly) + { + for (toolbar_button_list::iterator btn_it = mButtons.begin(); btn_it != mButtons.end(); ++btn_it) + { + LLToolBarButton* btn = *btn_it; + LLCommand* command = LLCommandManager::instance().getCommand(btn->mId); + + if (command && btn->mIsEnabledSignal) + { + const bool button_command_enabled = (*btn->mIsEnabledSignal)(btn, command->isEnabledParameters()); + btn->setEnabled(button_command_enabled); + } + + if (command && btn->mIsRunningSignal) + { + const bool button_command_running = (*btn->mIsRunningSignal)(btn, command->isRunningParameters()); + btn->setToggleState(button_command_running); + } + } + } + + updateLayoutAsNeeded(); + // rect may have shifted during layout + LLUI::popMatrix(); + LLUI::pushMatrix(); + LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom); + + // Position the caret + if (!mCaretIcon) + { + mCaretIcon = getChild("caret"); + } + + LLIconCtrl* caret = mCaretIcon; + caret->setVisible(false); + if (mDragAndDropTarget && !mButtonCommands.empty()) + { + LLRect caret_rect = caret->getRect(); + if (getOrientation(mSideType) == LLLayoutStack::HORIZONTAL) + { + caret->setRect(LLRect(mDragx-caret_rect.getWidth()/2+1, + mDragy, + mDragx+caret_rect.getWidth()/2+1, + mDragy-mDragGirth)); + } + else + { + caret->setRect(LLRect(mDragx, + mDragy+caret_rect.getHeight()/2, + mDragx+mDragGirth, + mDragy-caret_rect.getHeight()/2)); + } + caret->setVisible(true); + } + + LLUICtrl::draw(); + caret->setVisible(false); + mDragAndDropTarget = false; +} + +void LLToolBar::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLUICtrl::reshape(width, height, called_from_parent); + mNeedsLayout = true; +} + +void LLToolBar::createButtons() +{ + std::set set_flashing; + + for (LLToolBarButton* button : mButtons) + { + if (button->getFlashTimer() && button->getFlashTimer()->isFlashingInProgress()) + { + set_flashing.insert(button->getCommandId().uuid()); + } + + if (mButtonRemoveSignal) + { + (*mButtonRemoveSignal)(button); + } + + delete button; + } + mButtons.clear(); + mButtonMap.clear(); + mRightMouseTargetButton = NULL; + + for (const LLCommandId& command_id : mButtonCommands) + { + LLToolBarButton* button = createButton(command_id); + mButtons.push_back(button); + mButtonPanel->addChild(button); + mButtonMap.insert(std::make_pair(command_id.uuid(), button)); + + if (mButtonAddSignal) + { + (*mButtonAddSignal)(button); + } + + if (set_flashing.find(button->getCommandId().uuid()) != set_flashing.end()) + { + button->setFlashing(true); + } + } + mNeedsLayout = true; +} + +void LLToolBarButton::callIfEnabled(LLUICtrl::commit_callback_t commit, LLUICtrl* ctrl, const LLSD& param ) +{ + LLCommand* command = LLCommandManager::instance().getCommand(mId); + + if (!mIsEnabledSignal || (*mIsEnabledSignal)(this, command->isEnabledParameters())) + { + commit(ctrl, param); + } +} + +LLToolBarButton* LLToolBar::createButton(const LLCommandId& id) +{ + LLCommand* commandp = LLCommandManager::instance().getCommand(id); + if (!commandp) return NULL; + + LLToolBarButton::Params button_p; + button_p.name = commandp->name(); + button_p.label = LLTrans::getString(commandp->labelRef()); + button_p.tool_tip = LLTrans::getString(commandp->tooltipRef()); + button_p.image_overlay = LLUI::getUIImage(commandp->icon()); + button_p.button_flash_enable = commandp->isFlashingAllowed(); + button_p.overwriteFrom(mButtonParams[mButtonType]); + LLToolBarButton* button = LLUICtrlFactory::create(button_p); + + if (!mReadOnly) + { + enable_callback_t isEnabledCB; + + const std::string& isEnabledFunction = commandp->isEnabledFunctionName(); + if (isEnabledFunction.length() > 0) + { + LLUICtrl::EnableCallbackParam isEnabledParam; + isEnabledParam.function_name = isEnabledFunction; + isEnabledParam.parameter = commandp->isEnabledParameters(); + isEnabledCB = initEnableCallback(isEnabledParam); + + if (NULL == button->mIsEnabledSignal) + { + button->mIsEnabledSignal = new enable_signal_t(); + } + + button->mIsEnabledSignal->connect(isEnabledCB); + } + + LLUICtrl::CommitCallbackParam executeParam; + executeParam.function_name = commandp->executeFunctionName(); + executeParam.parameter = commandp->executeParameters(); + + // If we have a "stop" function then we map the command to mouse down / mouse up otherwise commit + const std::string& executeStopFunction = commandp->executeStopFunctionName(); + if (executeStopFunction.length() > 0) + { + LLUICtrl::CommitCallbackParam executeStopParam; + executeStopParam.function_name = executeStopFunction; + executeStopParam.parameter = commandp->executeStopParameters(); + LLUICtrl::commit_callback_t execute_func = initCommitCallback(executeParam); + button->setFunctionName(commandp->executeFunctionName()); + LL_DEBUGS("UIUsage") << "button function name a -> " << commandp->executeFunctionName() << LL_ENDL; + LLUICtrl::commit_callback_t stop_func = initCommitCallback(executeStopParam); + + button->setMouseDownCallback(boost::bind(&LLToolBarButton::callIfEnabled, button, execute_func, _1, _2)); + button->setMouseUpCallback(boost::bind(&LLToolBarButton::callIfEnabled, button, stop_func, _1, _2)); + } + else + { + button->setFunctionName(commandp->executeFunctionName()); + LL_DEBUGS("UIUsage") << "button function name b -> " << commandp->executeFunctionName() << LL_ENDL; + button->setCommitCallback(executeParam); + } + + // Set up "is running" query callback + const std::string& isRunningFunction = commandp->isRunningFunctionName(); + if (isRunningFunction.length() > 0) + { + LLUICtrl::EnableCallbackParam isRunningParam; + isRunningParam.function_name = isRunningFunction; + isRunningParam.parameter = commandp->isRunningParameters(); + enable_signal_t::slot_type isRunningCB = initEnableCallback(isRunningParam); + + if (NULL == button->mIsRunningSignal) + { + button->mIsRunningSignal = new enable_signal_t(); + } + + button->mIsRunningSignal->connect(isRunningCB); + } + } + + // Drag and drop behavior must work also if provided in the Toybox and, potentially, any read-only toolbar + button->setStartDragCallback(mStartDragItemCallback); + button->setHandleDragCallback(mHandleDragItemCallback); + + button->setCommandId(id); + + return button; +} + +boost::signals2::connection connectSignal(LLToolBar::button_signal_t*& signal, const LLToolBar::button_signal_t::slot_type& cb) +{ + if (!signal) + { + signal = new LLToolBar::button_signal_t(); + } + + return signal->connect(cb); +} + +boost::signals2::connection LLToolBar::setButtonAddCallback(const button_signal_t::slot_type& cb) +{ + return connectSignal(mButtonAddSignal, cb); +} + +boost::signals2::connection LLToolBar::setButtonEnterCallback(const button_signal_t::slot_type& cb) +{ + return connectSignal(mButtonEnterSignal, cb); +} + +boost::signals2::connection LLToolBar::setButtonLeaveCallback(const button_signal_t::slot_type& cb) +{ + return connectSignal(mButtonLeaveSignal, cb); +} + +boost::signals2::connection LLToolBar::setButtonRemoveCallback(const button_signal_t::slot_type& cb) +{ + return connectSignal(mButtonRemoveSignal, cb); +} + +bool LLToolBar::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // If we have a drop callback, that means that we can handle the drop + bool handled = mHandleDropCallback != nullptr; + + // if drop is set, it's time to call the callback to get the operation done + if (handled && drop) + { + handled = mHandleDropCallback(cargo_data, x, y, this); + } + + // We accept only single tool drop on toolbars + *accept = handled ? ACCEPT_YES_SINGLE : ACCEPT_NO; + + // We'll use that flag to change the visual aspect of the toolbar target on draw() + mDragAndDropTarget = false; + + // Convert drag position into insert position and rank + if (!isReadOnly() && handled && !drop) + { + if (cargo_type == DAD_WIDGET) + { + LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; + LLCommandId dragged_command(inv_item->getUUID()); + int orig_rank = getRankFromPosition(dragged_command); + mDragRank = getRankFromPosition(x, y); + // Don't DaD if we're dragging a command on itself + mDragAndDropTarget = ((orig_rank != RANK_NONE) && ((mDragRank == orig_rank) || ((mDragRank - 1) == orig_rank))); + //LL_INFOS() << "Merov debug : DaD, rank = " << mDragRank << ", dragged uui = " << inv_item->getUUID() << LL_ENDL; + /* Do the following if you want to animate the button itself + LLCommandId dragged_command(inv_item->getUUID()); + removeCommand(dragged_command); + addCommand(dragged_command,rank); + */ + } + else + { + handled = false; + } + } + + return handled; +} + +LLToolBarButton::LLToolBarButton(const Params& p) +: LLButton(p), + mMouseDownX(0), + mMouseDownY(0), + mWidthRange(p.button_width), + mDesiredHeight(p.desired_height), + mId(""), + mIsEnabledSignal(NULL), + mIsRunningSignal(NULL), + mIsStartingSignal(NULL), + mIsDragged(false), + mStartDragItemCallback(NULL), + mHandleDragItemCallback(NULL), + mOriginalImageSelected(p.image_selected), + mOriginalImageUnselected(p.image_unselected), + mOriginalImagePressed(p.image_pressed), + mOriginalImagePressedSelected(p.image_pressed_selected), + mOriginalLabelColor(p.label_color), + mOriginalLabelColorSelected(p.label_color_selected), + mOriginalImageOverlayColor(p.image_overlay_color), + mOriginalImageOverlaySelectedColor(p.image_overlay_selected_color) +{ +} + +LLToolBarButton::~LLToolBarButton() +{ + delete mIsEnabledSignal; + delete mIsRunningSignal; + delete mIsStartingSignal; +} + +bool LLToolBarButton::handleMouseDown(S32 x, S32 y, MASK mask) +{ + mMouseDownX = x; + mMouseDownY = y; + return LLButton::handleMouseDown(x, y, mask); +} + +bool LLToolBarButton::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + S32 mouse_distance_squared = (x - mMouseDownX) * (x - mMouseDownX) + (y - mMouseDownY) * (y - mMouseDownY); + if (mouse_distance_squared > DRAG_N_DROP_DISTANCE_THRESHOLD * DRAG_N_DROP_DISTANCE_THRESHOLD + && hasMouseCapture() && + mStartDragItemCallback && mHandleDragItemCallback) + { + if (!mIsDragged) + { + mStartDragItemCallback(x, y, this); + mIsDragged = true; + handled = true; + } + else + { + handled = mHandleDragItemCallback(x, y, mId.uuid(), LLAssetType::AT_WIDGET); + } + } + else + { + handled = LLButton::handleHover(x, y, mask); + } + + return handled; +} + +void LLToolBarButton::onMouseEnter(S32 x, S32 y, MASK mask) +{ + LLUICtrl::onMouseEnter(x, y, mask); + + // Always highlight toolbar buttons, even if they are disabled + if (!gFocusMgr.getMouseCapture() || gFocusMgr.getMouseCapture() == this) + { + mNeedsHighlight = true; + } + + LLToolBar* parent_toolbar = getParentByType(); + if (parent_toolbar && parent_toolbar->mButtonEnterSignal) + { + (*(parent_toolbar->mButtonEnterSignal))(this); + } +} + +void LLToolBarButton::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLButton::onMouseLeave(x, y, mask); + + LLToolBar* parent_toolbar = getParentByType(); + if (parent_toolbar && parent_toolbar->mButtonLeaveSignal) + { + (*(parent_toolbar->mButtonLeaveSignal))(this); + } +} + +void LLToolBarButton::onMouseCaptureLost() +{ + mIsDragged = false; +} + +void LLToolBarButton::onCommit() +{ + LLCommand* command = LLCommandManager::instance().getCommand(mId); + + if (!mIsEnabledSignal || (*mIsEnabledSignal)(this, command->isEnabledParameters())) + { + LLButton::onCommit(); + } +} + +void LLToolBarButton::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLButton::reshape(mWidthRange.clamp(width), height, called_from_parent); +} + +void LLToolBarButton::setEnabled(bool enabled) +{ + if (enabled) + { + mImageSelected = mOriginalImageSelected; + mImageUnselected = mOriginalImageUnselected; + mImagePressed = mOriginalImagePressed; + mImagePressedSelected = mOriginalImagePressedSelected; + mUnselectedLabelColor = mOriginalLabelColor; + mSelectedLabelColor = mOriginalLabelColorSelected; + mImageOverlayColor = mOriginalImageOverlayColor; + mImageOverlaySelectedColor = mOriginalImageOverlaySelectedColor; + } + else + { + mImageSelected = mImageDisabledSelected; + mImageUnselected = mImageDisabled; + mImagePressed = mImageDisabled; + mImagePressedSelected = mImageDisabledSelected; + mUnselectedLabelColor = mDisabledLabelColor; + mSelectedLabelColor = mDisabledSelectedLabelColor; + mImageOverlayColor = mImageOverlayDisabledColor; + mImageOverlaySelectedColor = mImageOverlayDisabledColor; + } +} + +const std::string LLToolBarButton::getToolTip() const +{ + std::string tooltip; + + if (labelIsTruncated() || getCurrentLabel().empty()) + { + tooltip = LLTrans::getString(LLCommandManager::instance().getCommand(mId)->labelRef()) + " -- " + LLView::getToolTip(); + } + else + { + tooltip = LLView::getToolTip(); + } + + LLToolBar* parent_toolbar = getParentByType(); + if (parent_toolbar && parent_toolbar->mButtonTooltipSuffix.length() > 0) + { + tooltip = tooltip + "\n(" + parent_toolbar->mButtonTooltipSuffix + ")"; + } + + return tooltip; +} + +void LLToolBar::LLCenterLayoutPanel::handleReshape(const LLRect& rect, bool by_user) +{ + LLLayoutPanel::handleReshape(rect, by_user); + + if (!mReshapeCallback.empty()) + { + LLRect r; + localRectToOtherView(mButtonPanel->getRect(), &r, gFloaterView); + r.stretch(FLOATER_MIN_VISIBLE_PIXELS); + mReshapeCallback(mLocationId, r); + } +} diff --git a/indra/llui/lltoolbar.h b/indra/llui/lltoolbar.h index 4dee33f4ab..e8ec1e41df 100644 --- a/indra/llui/lltoolbar.h +++ b/indra/llui/lltoolbar.h @@ -1,331 +1,331 @@ -/** - * @file lltoolbar.h - * @author Richard Nelson - * @brief User customizable toolbar class - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, 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$ - */ - -#ifndef LL_LLTOOLBAR_H -#define LL_LLTOOLBAR_H - -#include "llbutton.h" -#include "llcommandmanager.h" -#include "lllayoutstack.h" -#include "lluictrl.h" -#include "llcommandmanager.h" -#include "llassettype.h" - -class LLToolBar; -class LLToolBarButton; -class LLIconCtrl; - -typedef boost::function tool_startdrag_callback_t; -typedef boost::function tool_handledrag_callback_t; -typedef boost::function tool_handledrop_callback_t; - -class LLToolBarButton : public LLButton -{ - friend class LLToolBar; -public: - struct Params : public LLInitParam::Block - { - Optional button_width; - Optional desired_height; - - Params() - : button_width("button_width"), - desired_height("desired_height", 20) - {} - - }; - - LLToolBarButton(const Params& p); - ~LLToolBarButton(); - - bool handleMouseDown(S32 x, S32 y, MASK mask); - bool handleHover(S32 x, S32 y, MASK mask); - - void reshape(S32 width, S32 height, bool called_from_parent = true); - void setEnabled(bool enabled); - void setCommandId(const LLCommandId& id) { mId = id; } - LLCommandId getCommandId() { return mId; } - - void setStartDragCallback(tool_startdrag_callback_t cb) { mStartDragItemCallback = cb; } - void setHandleDragCallback(tool_handledrag_callback_t cb) { mHandleDragItemCallback = cb; } - - void onMouseEnter(S32 x, S32 y, MASK mask); - void onMouseLeave(S32 x, S32 y, MASK mask); - void onMouseCaptureLost(); - - void onCommit(); - - virtual const std::string getToolTip() const; - -private: - void callIfEnabled(LLUICtrl::commit_callback_t commit, LLUICtrl* ctrl, const LLSD& param ); - - LLCommandId mId; - S32 mMouseDownX; - S32 mMouseDownY; - LLUI::RangeS32 mWidthRange; - S32 mDesiredHeight; - bool mIsDragged; - tool_startdrag_callback_t mStartDragItemCallback; - tool_handledrag_callback_t mHandleDragItemCallback; - - enable_signal_t* mIsEnabledSignal; - enable_signal_t* mIsRunningSignal; - enable_signal_t* mIsStartingSignal; - LLPointer mOriginalImageSelected, - mOriginalImageUnselected, - mOriginalImagePressed, - mOriginalImagePressedSelected; - LLUIColor mOriginalLabelColor, - mOriginalLabelColorSelected, - mOriginalImageOverlayColor, - mOriginalImageOverlaySelectedColor; -}; - - -namespace LLToolBarEnums -{ - enum ButtonType - { - BTNTYPE_ICONS_WITH_TEXT = 0, - BTNTYPE_ICONS_ONLY, - - BTNTYPE_COUNT - }; - - enum SideType - { - SIDE_BOTTOM, - SIDE_LEFT, - SIDE_RIGHT, - SIDE_TOP, - }; - - enum EToolBarLocation - { - TOOLBAR_NONE = 0, - TOOLBAR_LEFT, - TOOLBAR_RIGHT, - TOOLBAR_BOTTOM, - - TOOLBAR_COUNT, - - TOOLBAR_FIRST = TOOLBAR_LEFT, - TOOLBAR_LAST = TOOLBAR_BOTTOM, - }; - - LLView::EOrientation getOrientation(SideType sideType); -} - -// NOTE: This needs to occur before Param block declaration for proper compilation. -namespace LLInitParam -{ - template<> - struct TypeValues : public TypeValuesHelper - { - static void declareValues(); - }; - - template<> - struct TypeValues : public TypeValuesHelper - { - static void declareValues(); - }; -} - - -class LLToolBar -: public LLUICtrl -{ - friend class LLToolBarButton; -public: - - class LLCenterLayoutPanel : public LLLayoutPanel - { - public: - typedef boost::function reshape_callback_t; - - virtual ~LLCenterLayoutPanel() {} - /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); - - void setLocationId(LLToolBarEnums::EToolBarLocation id) { mLocationId = id; } - void setReshapeCallback(reshape_callback_t cb) { mReshapeCallback = cb; } - void setButtonPanel(LLPanel * panel) { mButtonPanel = panel; } - - protected: - friend class LLUICtrlFactory; - LLCenterLayoutPanel(const Params& params) : LLLayoutPanel(params), mButtonPanel(NULL) {} - - private: - reshape_callback_t mReshapeCallback; - LLToolBarEnums::EToolBarLocation mLocationId; - LLPanel * mButtonPanel; - }; - - struct Params : public LLInitParam::Block - { - Mandatory button_display_mode; - Mandatory side; - - Optional button_icon, - button_icon_and_text; - - Optional read_only, - wrap; - - Optional pad_left, - pad_top, - pad_right, - pad_bottom, - pad_between, - min_girth; - - // default command set - Multiple commands; - - Optional button_panel; - - Params(); - }; - - // virtuals - void draw(); - void reshape(S32 width, S32 height, bool called_from_parent = true); - bool handleRightMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - - static const int RANK_NONE = -1; - bool addCommand(const LLCommandId& commandId, int rank = RANK_NONE); - int removeCommand(const LLCommandId& commandId); // Returns the rank the removed command was at, RANK_NONE if not found - bool hasCommand(const LLCommandId& commandId) const; // is this command bound to a button in this toolbar - bool enableCommand(const LLCommandId& commandId, bool enabled); // enable/disable button bound to the specified command, if it exists in this toolbar - bool stopCommandInProgress(const LLCommandId& commandId); // stop command if it is currently active - bool flashCommand(const LLCommandId& commandId, bool flash, bool force_flashing = false); // flash button associated with given command, if in this toolbar - - void setStartDragCallback(tool_startdrag_callback_t cb) { mStartDragItemCallback = cb; } // connects drag and drop behavior to external logic - void setHandleDragCallback(tool_handledrag_callback_t cb) { mHandleDragItemCallback = cb; } - void setHandleDropCallback(tool_handledrop_callback_t cb) { mHandleDropCallback = cb; } - bool isReadOnly() const { return mReadOnly; } - LLCenterLayoutPanel * getCenterLayoutPanel() const { return mCenterPanel; } - - LLToolBarButton* createButton(const LLCommandId& id); - - typedef boost::signals2::signal button_signal_t; - boost::signals2::connection setButtonAddCallback(const button_signal_t::slot_type& cb); - boost::signals2::connection setButtonEnterCallback(const button_signal_t::slot_type& cb); - boost::signals2::connection setButtonLeaveCallback(const button_signal_t::slot_type& cb); - boost::signals2::connection setButtonRemoveCallback(const button_signal_t::slot_type& cb); - - // append the specified string to end of tooltip - void setTooltipButtonSuffix(const std::string& suffix) { mButtonTooltipSuffix = suffix; } - - LLToolBarEnums::SideType getSideType() const { return mSideType; } - bool hasButtons() const { return !mButtons.empty(); } - bool isModified() const { return mModified; } - - int getRankFromPosition(S32 x, S32 y); - int getRankFromPosition(const LLCommandId& id); - - // Methods used in loading and saving toolbar settings - void setButtonType(LLToolBarEnums::ButtonType button_type); - LLToolBarEnums::ButtonType getButtonType() { return mButtonType; } - command_id_list_t& getCommandsList() { return mButtonCommands; } - void clearCommandsList(); - -private: - friend class LLUICtrlFactory; - LLToolBar(const Params&); - ~LLToolBar(); - - void initFromParams(const Params&); - void createContextMenu(); - void updateLayoutAsNeeded(); - void createButtons(); - void resizeButtonsInRow(std::vector& buttons_in_row, S32 max_row_girth); - bool isSettingChecked(const LLSD& userdata); - void onSettingEnable(const LLSD& userdata); - void onRemoveSelectedCommand(); - -private: - // static layout state - const bool mReadOnly; - const LLToolBarEnums::SideType mSideType; - const bool mWrap; - const S32 mPadLeft, - mPadRight, - mPadTop, - mPadBottom, - mPadBetween, - mMinGirth; - - // drag and drop state - tool_startdrag_callback_t mStartDragItemCallback; - tool_handledrag_callback_t mHandleDragItemCallback; - tool_handledrop_callback_t mHandleDropCallback; - bool mDragAndDropTarget; - int mDragRank; - S32 mDragx, - mDragy, - mDragGirth; - - typedef std::list toolbar_button_list; - typedef std::map command_id_map; - toolbar_button_list mButtons; - command_id_list_t mButtonCommands; - command_id_map mButtonMap; - - LLToolBarEnums::ButtonType mButtonType; - LLToolBarButton::Params mButtonParams[LLToolBarEnums::BTNTYPE_COUNT]; - - // related widgets - LLLayoutStack* mCenteringStack; - LLCenterLayoutPanel* mCenterPanel; - LLPanel* mButtonPanel; - LLHandle mPopupMenuHandle; - LLHandle mRemoveButtonHandle; - - LLToolBarButton* mRightMouseTargetButton; - - bool mNeedsLayout; - bool mModified; - - button_signal_t* mButtonAddSignal; - button_signal_t* mButtonEnterSignal; - button_signal_t* mButtonLeaveSignal; - button_signal_t* mButtonRemoveSignal; - - std::string mButtonTooltipSuffix; - - LLIconCtrl* mCaretIcon; -}; - - -#endif // LL_LLTOOLBAR_H +/** + * @file lltoolbar.h + * @author Richard Nelson + * @brief User customizable toolbar class + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, 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$ + */ + +#ifndef LL_LLTOOLBAR_H +#define LL_LLTOOLBAR_H + +#include "llbutton.h" +#include "llcommandmanager.h" +#include "lllayoutstack.h" +#include "lluictrl.h" +#include "llcommandmanager.h" +#include "llassettype.h" + +class LLToolBar; +class LLToolBarButton; +class LLIconCtrl; + +typedef boost::function tool_startdrag_callback_t; +typedef boost::function tool_handledrag_callback_t; +typedef boost::function tool_handledrop_callback_t; + +class LLToolBarButton : public LLButton +{ + friend class LLToolBar; +public: + struct Params : public LLInitParam::Block + { + Optional button_width; + Optional desired_height; + + Params() + : button_width("button_width"), + desired_height("desired_height", 20) + {} + + }; + + LLToolBarButton(const Params& p); + ~LLToolBarButton(); + + bool handleMouseDown(S32 x, S32 y, MASK mask); + bool handleHover(S32 x, S32 y, MASK mask); + + void reshape(S32 width, S32 height, bool called_from_parent = true); + void setEnabled(bool enabled); + void setCommandId(const LLCommandId& id) { mId = id; } + LLCommandId getCommandId() { return mId; } + + void setStartDragCallback(tool_startdrag_callback_t cb) { mStartDragItemCallback = cb; } + void setHandleDragCallback(tool_handledrag_callback_t cb) { mHandleDragItemCallback = cb; } + + void onMouseEnter(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + void onMouseCaptureLost(); + + void onCommit(); + + virtual const std::string getToolTip() const; + +private: + void callIfEnabled(LLUICtrl::commit_callback_t commit, LLUICtrl* ctrl, const LLSD& param ); + + LLCommandId mId; + S32 mMouseDownX; + S32 mMouseDownY; + LLUI::RangeS32 mWidthRange; + S32 mDesiredHeight; + bool mIsDragged; + tool_startdrag_callback_t mStartDragItemCallback; + tool_handledrag_callback_t mHandleDragItemCallback; + + enable_signal_t* mIsEnabledSignal; + enable_signal_t* mIsRunningSignal; + enable_signal_t* mIsStartingSignal; + LLPointer mOriginalImageSelected, + mOriginalImageUnselected, + mOriginalImagePressed, + mOriginalImagePressedSelected; + LLUIColor mOriginalLabelColor, + mOriginalLabelColorSelected, + mOriginalImageOverlayColor, + mOriginalImageOverlaySelectedColor; +}; + + +namespace LLToolBarEnums +{ + enum ButtonType + { + BTNTYPE_ICONS_WITH_TEXT = 0, + BTNTYPE_ICONS_ONLY, + + BTNTYPE_COUNT + }; + + enum SideType + { + SIDE_BOTTOM, + SIDE_LEFT, + SIDE_RIGHT, + SIDE_TOP, + }; + + enum EToolBarLocation + { + TOOLBAR_NONE = 0, + TOOLBAR_LEFT, + TOOLBAR_RIGHT, + TOOLBAR_BOTTOM, + + TOOLBAR_COUNT, + + TOOLBAR_FIRST = TOOLBAR_LEFT, + TOOLBAR_LAST = TOOLBAR_BOTTOM, + }; + + LLView::EOrientation getOrientation(SideType sideType); +} + +// NOTE: This needs to occur before Param block declaration for proper compilation. +namespace LLInitParam +{ + template<> + struct TypeValues : public TypeValuesHelper + { + static void declareValues(); + }; + + template<> + struct TypeValues : public TypeValuesHelper + { + static void declareValues(); + }; +} + + +class LLToolBar +: public LLUICtrl +{ + friend class LLToolBarButton; +public: + + class LLCenterLayoutPanel : public LLLayoutPanel + { + public: + typedef boost::function reshape_callback_t; + + virtual ~LLCenterLayoutPanel() {} + /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); + + void setLocationId(LLToolBarEnums::EToolBarLocation id) { mLocationId = id; } + void setReshapeCallback(reshape_callback_t cb) { mReshapeCallback = cb; } + void setButtonPanel(LLPanel * panel) { mButtonPanel = panel; } + + protected: + friend class LLUICtrlFactory; + LLCenterLayoutPanel(const Params& params) : LLLayoutPanel(params), mButtonPanel(NULL) {} + + private: + reshape_callback_t mReshapeCallback; + LLToolBarEnums::EToolBarLocation mLocationId; + LLPanel * mButtonPanel; + }; + + struct Params : public LLInitParam::Block + { + Mandatory button_display_mode; + Mandatory side; + + Optional button_icon, + button_icon_and_text; + + Optional read_only, + wrap; + + Optional pad_left, + pad_top, + pad_right, + pad_bottom, + pad_between, + min_girth; + + // default command set + Multiple commands; + + Optional button_panel; + + Params(); + }; + + // virtuals + void draw(); + void reshape(S32 width, S32 height, bool called_from_parent = true); + bool handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + + static const int RANK_NONE = -1; + bool addCommand(const LLCommandId& commandId, int rank = RANK_NONE); + int removeCommand(const LLCommandId& commandId); // Returns the rank the removed command was at, RANK_NONE if not found + bool hasCommand(const LLCommandId& commandId) const; // is this command bound to a button in this toolbar + bool enableCommand(const LLCommandId& commandId, bool enabled); // enable/disable button bound to the specified command, if it exists in this toolbar + bool stopCommandInProgress(const LLCommandId& commandId); // stop command if it is currently active + bool flashCommand(const LLCommandId& commandId, bool flash, bool force_flashing = false); // flash button associated with given command, if in this toolbar + + void setStartDragCallback(tool_startdrag_callback_t cb) { mStartDragItemCallback = cb; } // connects drag and drop behavior to external logic + void setHandleDragCallback(tool_handledrag_callback_t cb) { mHandleDragItemCallback = cb; } + void setHandleDropCallback(tool_handledrop_callback_t cb) { mHandleDropCallback = cb; } + bool isReadOnly() const { return mReadOnly; } + LLCenterLayoutPanel * getCenterLayoutPanel() const { return mCenterPanel; } + + LLToolBarButton* createButton(const LLCommandId& id); + + typedef boost::signals2::signal button_signal_t; + boost::signals2::connection setButtonAddCallback(const button_signal_t::slot_type& cb); + boost::signals2::connection setButtonEnterCallback(const button_signal_t::slot_type& cb); + boost::signals2::connection setButtonLeaveCallback(const button_signal_t::slot_type& cb); + boost::signals2::connection setButtonRemoveCallback(const button_signal_t::slot_type& cb); + + // append the specified string to end of tooltip + void setTooltipButtonSuffix(const std::string& suffix) { mButtonTooltipSuffix = suffix; } + + LLToolBarEnums::SideType getSideType() const { return mSideType; } + bool hasButtons() const { return !mButtons.empty(); } + bool isModified() const { return mModified; } + + int getRankFromPosition(S32 x, S32 y); + int getRankFromPosition(const LLCommandId& id); + + // Methods used in loading and saving toolbar settings + void setButtonType(LLToolBarEnums::ButtonType button_type); + LLToolBarEnums::ButtonType getButtonType() { return mButtonType; } + command_id_list_t& getCommandsList() { return mButtonCommands; } + void clearCommandsList(); + +private: + friend class LLUICtrlFactory; + LLToolBar(const Params&); + ~LLToolBar(); + + void initFromParams(const Params&); + void createContextMenu(); + void updateLayoutAsNeeded(); + void createButtons(); + void resizeButtonsInRow(std::vector& buttons_in_row, S32 max_row_girth); + bool isSettingChecked(const LLSD& userdata); + void onSettingEnable(const LLSD& userdata); + void onRemoveSelectedCommand(); + +private: + // static layout state + const bool mReadOnly; + const LLToolBarEnums::SideType mSideType; + const bool mWrap; + const S32 mPadLeft, + mPadRight, + mPadTop, + mPadBottom, + mPadBetween, + mMinGirth; + + // drag and drop state + tool_startdrag_callback_t mStartDragItemCallback; + tool_handledrag_callback_t mHandleDragItemCallback; + tool_handledrop_callback_t mHandleDropCallback; + bool mDragAndDropTarget; + int mDragRank; + S32 mDragx, + mDragy, + mDragGirth; + + typedef std::list toolbar_button_list; + typedef std::map command_id_map; + toolbar_button_list mButtons; + command_id_list_t mButtonCommands; + command_id_map mButtonMap; + + LLToolBarEnums::ButtonType mButtonType; + LLToolBarButton::Params mButtonParams[LLToolBarEnums::BTNTYPE_COUNT]; + + // related widgets + LLLayoutStack* mCenteringStack; + LLCenterLayoutPanel* mCenterPanel; + LLPanel* mButtonPanel; + LLHandle mPopupMenuHandle; + LLHandle mRemoveButtonHandle; + + LLToolBarButton* mRightMouseTargetButton; + + bool mNeedsLayout; + bool mModified; + + button_signal_t* mButtonAddSignal; + button_signal_t* mButtonEnterSignal; + button_signal_t* mButtonLeaveSignal; + button_signal_t* mButtonRemoveSignal; + + std::string mButtonTooltipSuffix; + + LLIconCtrl* mCaretIcon; +}; + + +#endif // LL_LLTOOLBAR_H diff --git a/indra/llui/lltooltip.cpp b/indra/llui/lltooltip.cpp index 5664a1a153..86525c2f7e 100644 --- a/indra/llui/lltooltip.cpp +++ b/indra/llui/lltooltip.cpp @@ -1,648 +1,648 @@ -/** - * @file lltooltip.cpp - * @brief LLToolTipMgr class implementation and related classes - * - * $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" - -// self include -#include "lltooltip.h" - -// Library includes -#include "lltextbox.h" -#include "lliconctrl.h" -#include "llbutton.h" -#include "llmenugl.h" // hideMenus() -#include "llui.h" // positionViewNearMouse() -#include "llwindow.h" -#include "lltrans.h" -// -// Constants -// - -// -// Local globals -// - -LLToolTipView *gToolTipView = NULL; - -// -// Member functions -// - -static LLDefaultChildRegistry::Register register_tooltip_view("tooltip_view"); - -LLToolTipView::Params::Params() -{ - changeDefault(mouse_opaque, false); -} - -LLToolTipView::LLToolTipView(const LLToolTipView::Params& p) -: LLView(p) -{ -} - -void LLToolTipView::draw() -{ - LLToolTipMgr::instance().updateToolTipVisibility(); - - // do the usual thing - LLView::draw(); -} - -bool LLToolTipView::handleHover(S32 x, S32 y, MASK mask) -{ - static S32 last_x = x; - static S32 last_y = y; - - LLToolTipMgr& tooltip_mgr = LLToolTipMgr::instance(); - - if (x != last_x && y != last_y && !tooltip_mgr.getMouseNearRect().pointInRect(x, y)) - { - // allow new tooltips because mouse moved outside of mouse near rect - tooltip_mgr.unblockToolTips(); - } - - last_x = x; - last_y = y; - return LLView::handleHover(x, y, mask); -} - -bool LLToolTipView::handleMouseDown(S32 x, S32 y, MASK mask) -{ - LLToolTipMgr::instance().blockToolTips(); - - if (LLView::handleMouseDown(x, y, mask)) - { - // If we are handling the mouse event menu holder - // won't get a chance to close menus so do this here - LLMenuGL::sMenuContainer->hideMenus(); - return true; - } - - return false; -} - -bool LLToolTipView::handleMiddleMouseDown(S32 x, S32 y, MASK mask) -{ - LLToolTipMgr::instance().blockToolTips(); - return LLView::handleMiddleMouseDown(x, y, mask); -} - -bool LLToolTipView::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - LLToolTipMgr::instance().blockToolTips(); - return LLView::handleRightMouseDown(x, y, mask); -} - - -bool LLToolTipView::handleScrollWheel( S32 x, S32 y, S32 clicks ) -{ - LLToolTipMgr::instance().blockToolTips(); - return false; -} - -void LLToolTipView::drawStickyRect() -{ - gl_rect_2d(LLToolTipMgr::instance().getMouseNearRect(), LLColor4::white, false); -} - -// defaults for floater param block pulled from widgets/floater.xml -static LLWidgetNameRegistry::StaticRegistrar sRegisterInspectorParams(&typeid(LLInspector::Params), "inspector"); - -// -// LLToolTip -// - - -static LLDefaultChildRegistry::Register register_tooltip("tool_tip"); - - -LLToolTip::Params::Params() -: max_width("max_width", 200), - padding("padding", 4), - wrap("wrap", true), - pos("pos"), - message("message"), - delay_time("delay_time", LLUI::getInstance()->mSettingGroups["config"]->getF32( "ToolTipDelay" )), - visible_time_over("visible_time_over", LLUI::getInstance()->mSettingGroups["config"]->getF32( "ToolTipVisibleTimeOver" )), - visible_time_near("visible_time_near", LLUI::getInstance()->mSettingGroups["config"]->getF32( "ToolTipVisibleTimeNear" )), - visible_time_far("visible_time_far", LLUI::getInstance()->mSettingGroups["config"]->getF32( "ToolTipVisibleTimeFar" )), - sticky_rect("sticky_rect"), - image("image"), - text_color("text_color"), - time_based_media("time_based_media", false), - web_based_media("web_based_media", false), - media_playing("media_playing", false), - allow_paste_tooltip("allow_paste_tooltip", false) -{ - changeDefault(chrome, true); -} - -LLToolTip::LLToolTip(const LLToolTip::Params& p) -: LLPanel(p), - mHasClickCallback(p.click_callback.isProvided()), - mPadding(p.padding), - mMaxWidth(p.max_width), - mTextBox(NULL), - mInfoButton(NULL), - mPlayMediaButton(NULL), - mHomePageButton(NULL), - mIsTooltipPastable(p.allow_paste_tooltip) -{ - LLTextBox::Params params; - params.name = params.initial_value().asString(); - // bake textbox padding into initial rect - params.rect = LLRect (mPadding, mPadding + 1, mPadding + 1, mPadding); - params.h_pad = 0; - params.v_pad = 0; - params.mouse_opaque = false; - params.text_color = p.text_color; - params.bg_visible = false; - params.font = p.font; - params.use_ellipses = true; - params.wrap = p.wrap; - params.font_valign = LLFontGL::VCENTER; - params.parse_urls = false; // disallow hyperlinks in tooltips, as they want to spawn their own explanatory tooltips - mTextBox = LLUICtrlFactory::create (params); - addChild(mTextBox); - - S32 TOOLTIP_ICON_SIZE = 0; - S32 TOOLTIP_PLAYBUTTON_SIZE = 0; - if (p.image.isProvided()) - { - LLButton::Params icon_params; - icon_params.name = "tooltip_info"; - LLRect icon_rect; - LLUIImage* imagep = p.image; - TOOLTIP_ICON_SIZE = (imagep ? imagep->getWidth() : 16); - icon_rect.setOriginAndSize(mPadding, mPadding, TOOLTIP_ICON_SIZE, TOOLTIP_ICON_SIZE); - icon_params.rect = icon_rect; - icon_params.image_unselected(imagep); - icon_params.image_selected(imagep); - - icon_params.scale_image(true); - icon_params.flash_color.control = "ButtonUnselectedFgColor"; - mInfoButton = LLUICtrlFactory::create(icon_params); - if (p.click_callback.isProvided()) - { - mInfoButton->setCommitCallback(boost::bind(p.click_callback())); - } - addChild(mInfoButton); - - // move text over to fit image in - mTextBox->translate(TOOLTIP_ICON_SIZE + mPadding, 0); - } - - if (p.time_based_media) - { - LLButton::Params p_button; - p_button.name(std::string("play_media")); - p_button.label(""); // provide label but set to empty so name does not overwrite it -angela - TOOLTIP_PLAYBUTTON_SIZE = 16; - LLRect button_rect; - button_rect.setOriginAndSize((mPadding +TOOLTIP_ICON_SIZE+ mPadding ), mPadding, TOOLTIP_ICON_SIZE, TOOLTIP_ICON_SIZE); - p_button.rect = button_rect; - p_button.image_selected.name("button_anim_pause.tga"); - p_button.image_unselected.name("button_anim_play.tga"); - p_button.scale_image(true); - - mPlayMediaButton = LLUICtrlFactory::create(p_button); - if(p.click_playmedia_callback.isProvided()) - { - mPlayMediaButton->setCommitCallback(boost::bind(p.click_playmedia_callback())); - } - mPlayMediaButton->setToggleState(p.media_playing); - addChild(mPlayMediaButton); - - // move text over to fit image in - mTextBox->translate(TOOLTIP_PLAYBUTTON_SIZE + mPadding, 0); - } - - if (p.web_based_media) - { - LLButton::Params p_w_button; - p_w_button.name(std::string("home_page")); - p_w_button.label(""); // provid label but set to empty so name does not overwrite it -angela - TOOLTIP_PLAYBUTTON_SIZE = 16; - LLRect button_rect; - button_rect.setOriginAndSize((mPadding +TOOLTIP_ICON_SIZE+ mPadding ), mPadding, TOOLTIP_ICON_SIZE, TOOLTIP_ICON_SIZE); - p_w_button.rect = button_rect; - p_w_button.image_unselected.name("map_home.tga"); - p_w_button.scale_image(true); - - mHomePageButton = LLUICtrlFactory::create(p_w_button); - if(p.click_homepage_callback.isProvided()) - { - mHomePageButton->setCommitCallback(boost::bind(p.click_homepage_callback())); - } - addChild(mHomePageButton); - - // move text over to fit image in - mTextBox->translate(TOOLTIP_PLAYBUTTON_SIZE + mPadding, 0); - } - - if (p.click_callback.isProvided()) - { - setMouseUpCallback(boost::bind(p.click_callback())); - } -} - -void LLToolTip::initFromParams(const LLToolTip::Params& p) -{ - LLPanel::initFromParams(p); - - // do this *after* we've had our size set in LLPanel::initFromParams(); - const S32 REALLY_LARGE_HEIGHT = 10000; - mTextBox->reshape(mMaxWidth, REALLY_LARGE_HEIGHT); - - if (p.styled_message.isProvided()) - { - for (LLInitParam::ParamIterator::const_iterator text_it = p.styled_message.begin(); - text_it != p.styled_message.end(); - ++text_it) - { - mTextBox->appendText(text_it->text(), false, text_it->style); - } - } - else - { - mTextBox->setText(p.message()); - } - - mIsTooltipPastable = p.allow_paste_tooltip; - - updateTextBox(); - snapToChildren(); -} - -void LLToolTip::updateTextBox() -{ - S32 text_width = llmin(mMaxWidth, mTextBox->getTextPixelWidth() + 1); - S32 text_height = mTextBox->getTextPixelHeight(); - mTextBox->reshape(text_width, text_height); -} - -void LLToolTip::snapToChildren() -{ - // reshape tooltip panel to fit text box - LLRect tooltip_rect = calcBoundingRect(); - tooltip_rect.mTop += mPadding; - tooltip_rect.mRight += mPadding; - tooltip_rect.mBottom = 0; - tooltip_rect.mLeft = 0; - - if (mInfoButton) - { - mTextBox->reshape(mTextBox->getRect().getWidth(), llmax(mTextBox->getRect().getHeight(), tooltip_rect.getHeight() - 2 * mPadding)); - - LLRect text_rect = mTextBox->getRect(); - LLRect icon_rect = mInfoButton->getRect(); - mInfoButton->translate(0, text_rect.getCenterY() - icon_rect.getCenterY()); - } - - setShape(tooltip_rect); -} - -void LLToolTip::setVisible(bool visible) -{ - // fade out tooltip over time - if (visible) - { - mVisibleTimer.start(); - mFadeTimer.stop(); - LLPanel::setVisible(true); - } - else - { - mVisibleTimer.stop(); - // don't actually change mVisible state, start fade out transition instead - if (!mFadeTimer.getStarted()) - { - mFadeTimer.start(); - } - } -} - -bool LLToolTip::handleHover(S32 x, S32 y, MASK mask) -{ - //mInfoButton->setFlashing(true); - if(mInfoButton) - mInfoButton->setHighlight(true); - - LLPanel::handleHover(x, y, mask); - if (mHasClickCallback) - { - getWindow()->setCursor(UI_CURSOR_HAND); - } - return true; -} - -void LLToolTip::onMouseLeave(S32 x, S32 y, MASK mask) -{ - //mInfoButton->setFlashing(true); - if(mInfoButton) - mInfoButton->setHighlight(false); - LLUICtrl::onMouseLeave(x, y, mask); -} - -void LLToolTip::draw() -{ - F32 alpha = 1.f; - - if (mFadeTimer.getStarted()) - { - static LLCachedControl tool_tip_fade_time(*LLUI::getInstance()->mSettingGroups["config"], "ToolTipFadeTime", 0.2f); - alpha = clamp_rescale(mFadeTimer.getElapsedTimeF32(), 0.f, tool_tip_fade_time(), 1.f, 0.f); - if (alpha == 0.f) - { - // finished fading out, so hide ourselves - mFadeTimer.stop(); - LLPanel::setVisible(false); - } - } - - // draw tooltip contents with appropriate alpha - { - LLViewDrawContext context(alpha); - LLPanel::draw(); - } -} - -bool LLToolTip::isFading() -{ - return mFadeTimer.getStarted(); -} - -F32 LLToolTip::getVisibleTime() -{ - return mVisibleTimer.getStarted() ? mVisibleTimer.getElapsedTimeF32() : 0.f; -} - -bool LLToolTip::hasClickCallback() -{ - return mHasClickCallback; -} - -void LLToolTip::getToolTipMessage(std::string & message) -{ - if (mTextBox) - { - message = mTextBox->getText(); - } -} - - - -// -// LLToolTipMgr -// - -LLToolTipMgr::LLToolTipMgr() -: mToolTipsBlocked(false), - mToolTip(NULL), - mNeedsToolTip(false) -{} - -void LLToolTipMgr::createToolTip(const LLToolTip::Params& params) -{ - // block all other tooltips until tooltips re-enabled (e.g. mouse moved) - blockToolTips(); - - delete mToolTip; - - LLToolTip::Params tooltip_params(params); - // block mouse events if there is a click handler registered (specifically, hover) - if (params.click_callback.isProvided() && !params.mouse_opaque.isProvided()) - { - // set mouse_opaque to true if it wasn't already set to something else - // this prevents mouse down from going "through" the tooltip and ultimately - // causing the tooltip to disappear - tooltip_params.mouse_opaque = true; - } - tooltip_params.rect = LLRect (0, 1, 1, 0); - - if (tooltip_params.create_callback.isProvided()) - { - mToolTip = tooltip_params.create_callback()(tooltip_params); - if (mToolTip == NULL) - { - return; - } - } - else - mToolTip = LLUICtrlFactory::create (tooltip_params); - - gToolTipView->addChild(mToolTip); - - if (params.pos.isProvided()) - { - LLCoordGL pos = params.pos; - // try to spawn at requested position - LLUI::getInstance()->positionViewNearMouse(mToolTip, pos.mX, pos.mY); - } - else - { - // just spawn at mouse location - LLUI::getInstance()->positionViewNearMouse(mToolTip); - } - - //...update "sticky" rect and tooltip position - if (params.sticky_rect.isProvided()) - { - mMouseNearRect = params.sticky_rect; - } - else - { - S32 mouse_x; - S32 mouse_y; - LLUI::getInstance()->getMousePositionLocal(gToolTipView->getParent(), &mouse_x, &mouse_y); - - // allow mouse a little bit of slop before changing tooltips - mMouseNearRect.setCenterAndSize(mouse_x, mouse_y, 3, 3); - } - - // allow mouse to move all the way to the tooltip without changing tooltips - // (tooltip can still time out) - if (mToolTip->hasClickCallback()) - { - // keep tooltip up when we mouse over it - mMouseNearRect.unionWith(mToolTip->getRect()); - } -} - - -void LLToolTipMgr::show(const std::string& msg, bool allow_paste_tooltip) -{ - show(LLToolTip::Params().message(msg).allow_paste_tooltip(allow_paste_tooltip)); -} - -void LLToolTipMgr::show(const LLToolTip::Params& params) -{ - if (!params.styled_message.isProvided() - && (!params.message.isProvided() || params.message().empty()) - && !params.image.isProvided() && !params.create_callback.isProvided()) return; - - // fill in default tooltip params from tool_tip.xml - LLToolTip::Params params_with_defaults(params); - params_with_defaults.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); - if (!params_with_defaults.validateBlock()) - { - LL_WARNS() << "Could not display tooltip!" << LL_ENDL; - return; - } - - // are we ready to show the tooltip? - if (!mToolTipsBlocked // we haven't hit a key, moved the mouse, etc. - && LLUI::getInstance()->getMouseIdleTime() > params_with_defaults.delay_time) // the mouse has been still long enough - { - bool tooltip_changed = mLastToolTipParams.message() != params_with_defaults.message() - || mLastToolTipParams.pos() != params_with_defaults.pos() - || mLastToolTipParams.time_based_media() != params_with_defaults.time_based_media() - || mLastToolTipParams.web_based_media() != params_with_defaults.web_based_media(); - - bool tooltip_shown = mToolTip - && mToolTip->getVisible() - && !mToolTip->isFading(); - - mNeedsToolTip = tooltip_changed || !tooltip_shown; - // store description of tooltip for later creation - mNextToolTipParams = params_with_defaults; - } -} - -// allow new tooltips to be created, e.g. after mouse has moved -void LLToolTipMgr::unblockToolTips() -{ - mToolTipsBlocked = false; -} - -// disallow new tooltips until unblockTooltips called -void LLToolTipMgr::blockToolTips() -{ - hideToolTips(); - mToolTipsBlocked = true; -} - -void LLToolTipMgr::hideToolTips() -{ - if (mToolTip) - { - mToolTip->setVisible(false); - } -} - -bool LLToolTipMgr::toolTipVisible() -{ - return mToolTip ? mToolTip->isInVisibleChain() : false; -} - -LLRect LLToolTipMgr::getToolTipRect() -{ - if (mToolTip && mToolTip->getVisible()) - { - return mToolTip->getRect(); - } - return LLRect(); -} - - -LLRect LLToolTipMgr::getMouseNearRect() -{ - return toolTipVisible() ? mMouseNearRect : LLRect(); -} - -// every frame, determine if current tooltip should be hidden -void LLToolTipMgr::updateToolTipVisibility() -{ - // create new tooltip if we have one ready to go - if (mNeedsToolTip) - { - mNeedsToolTip = false; - createToolTip(mNextToolTipParams); - mLastToolTipParams = mNextToolTipParams; - - return; - } - - // hide tooltips when mouse cursor is hidden - if (LLUI::getInstance()->getWindow()->isCursorHidden()) - { - blockToolTips(); - return; - } - - // hide existing tooltips if they have timed out - F32 tooltip_timeout = 0.f; - if (toolTipVisible()) - { - S32 mouse_x, mouse_y; - LLUI::getInstance()->getMousePositionLocal(gToolTipView, &mouse_x, &mouse_y); - - // mouse far away from tooltip - tooltip_timeout = mLastToolTipParams.visible_time_far; - // mouse near rect will only include the tooltip if the - // tooltip is clickable - if (mMouseNearRect.pointInRect(mouse_x, mouse_y)) - { - // mouse "close" to tooltip - tooltip_timeout = mLastToolTipParams.visible_time_near; - - // if tooltip is clickable (has large mMouseNearRect) - // than having cursor over tooltip keeps it up indefinitely - if (mToolTip->parentPointInView(mouse_x, mouse_y)) - { - // mouse over tooltip itself, don't time out - tooltip_timeout = mLastToolTipParams.visible_time_over; - } - } - - if (mToolTip->getVisibleTime() > tooltip_timeout) - { - hideToolTips(); - unblockToolTips(); - } - } -} - - -// Return the current tooltip text -void LLToolTipMgr::getToolTipMessage(std::string & message) -{ - if (toolTipVisible()) - { - mToolTip->getToolTipMessage(message); - } -} - -bool LLToolTipMgr::isTooltipPastable() -{ - if (toolTipVisible()) - { - return mToolTip->isTooltipPastable(); - } - return false; - } - -// EOF +/** + * @file lltooltip.cpp + * @brief LLToolTipMgr class implementation and related classes + * + * $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" + +// self include +#include "lltooltip.h" + +// Library includes +#include "lltextbox.h" +#include "lliconctrl.h" +#include "llbutton.h" +#include "llmenugl.h" // hideMenus() +#include "llui.h" // positionViewNearMouse() +#include "llwindow.h" +#include "lltrans.h" +// +// Constants +// + +// +// Local globals +// + +LLToolTipView *gToolTipView = NULL; + +// +// Member functions +// + +static LLDefaultChildRegistry::Register register_tooltip_view("tooltip_view"); + +LLToolTipView::Params::Params() +{ + changeDefault(mouse_opaque, false); +} + +LLToolTipView::LLToolTipView(const LLToolTipView::Params& p) +: LLView(p) +{ +} + +void LLToolTipView::draw() +{ + LLToolTipMgr::instance().updateToolTipVisibility(); + + // do the usual thing + LLView::draw(); +} + +bool LLToolTipView::handleHover(S32 x, S32 y, MASK mask) +{ + static S32 last_x = x; + static S32 last_y = y; + + LLToolTipMgr& tooltip_mgr = LLToolTipMgr::instance(); + + if (x != last_x && y != last_y && !tooltip_mgr.getMouseNearRect().pointInRect(x, y)) + { + // allow new tooltips because mouse moved outside of mouse near rect + tooltip_mgr.unblockToolTips(); + } + + last_x = x; + last_y = y; + return LLView::handleHover(x, y, mask); +} + +bool LLToolTipView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLToolTipMgr::instance().blockToolTips(); + + if (LLView::handleMouseDown(x, y, mask)) + { + // If we are handling the mouse event menu holder + // won't get a chance to close menus so do this here + LLMenuGL::sMenuContainer->hideMenus(); + return true; + } + + return false; +} + +bool LLToolTipView::handleMiddleMouseDown(S32 x, S32 y, MASK mask) +{ + LLToolTipMgr::instance().blockToolTips(); + return LLView::handleMiddleMouseDown(x, y, mask); +} + +bool LLToolTipView::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLToolTipMgr::instance().blockToolTips(); + return LLView::handleRightMouseDown(x, y, mask); +} + + +bool LLToolTipView::handleScrollWheel( S32 x, S32 y, S32 clicks ) +{ + LLToolTipMgr::instance().blockToolTips(); + return false; +} + +void LLToolTipView::drawStickyRect() +{ + gl_rect_2d(LLToolTipMgr::instance().getMouseNearRect(), LLColor4::white, false); +} + +// defaults for floater param block pulled from widgets/floater.xml +static LLWidgetNameRegistry::StaticRegistrar sRegisterInspectorParams(&typeid(LLInspector::Params), "inspector"); + +// +// LLToolTip +// + + +static LLDefaultChildRegistry::Register register_tooltip("tool_tip"); + + +LLToolTip::Params::Params() +: max_width("max_width", 200), + padding("padding", 4), + wrap("wrap", true), + pos("pos"), + message("message"), + delay_time("delay_time", LLUI::getInstance()->mSettingGroups["config"]->getF32( "ToolTipDelay" )), + visible_time_over("visible_time_over", LLUI::getInstance()->mSettingGroups["config"]->getF32( "ToolTipVisibleTimeOver" )), + visible_time_near("visible_time_near", LLUI::getInstance()->mSettingGroups["config"]->getF32( "ToolTipVisibleTimeNear" )), + visible_time_far("visible_time_far", LLUI::getInstance()->mSettingGroups["config"]->getF32( "ToolTipVisibleTimeFar" )), + sticky_rect("sticky_rect"), + image("image"), + text_color("text_color"), + time_based_media("time_based_media", false), + web_based_media("web_based_media", false), + media_playing("media_playing", false), + allow_paste_tooltip("allow_paste_tooltip", false) +{ + changeDefault(chrome, true); +} + +LLToolTip::LLToolTip(const LLToolTip::Params& p) +: LLPanel(p), + mHasClickCallback(p.click_callback.isProvided()), + mPadding(p.padding), + mMaxWidth(p.max_width), + mTextBox(NULL), + mInfoButton(NULL), + mPlayMediaButton(NULL), + mHomePageButton(NULL), + mIsTooltipPastable(p.allow_paste_tooltip) +{ + LLTextBox::Params params; + params.name = params.initial_value().asString(); + // bake textbox padding into initial rect + params.rect = LLRect (mPadding, mPadding + 1, mPadding + 1, mPadding); + params.h_pad = 0; + params.v_pad = 0; + params.mouse_opaque = false; + params.text_color = p.text_color; + params.bg_visible = false; + params.font = p.font; + params.use_ellipses = true; + params.wrap = p.wrap; + params.font_valign = LLFontGL::VCENTER; + params.parse_urls = false; // disallow hyperlinks in tooltips, as they want to spawn their own explanatory tooltips + mTextBox = LLUICtrlFactory::create (params); + addChild(mTextBox); + + S32 TOOLTIP_ICON_SIZE = 0; + S32 TOOLTIP_PLAYBUTTON_SIZE = 0; + if (p.image.isProvided()) + { + LLButton::Params icon_params; + icon_params.name = "tooltip_info"; + LLRect icon_rect; + LLUIImage* imagep = p.image; + TOOLTIP_ICON_SIZE = (imagep ? imagep->getWidth() : 16); + icon_rect.setOriginAndSize(mPadding, mPadding, TOOLTIP_ICON_SIZE, TOOLTIP_ICON_SIZE); + icon_params.rect = icon_rect; + icon_params.image_unselected(imagep); + icon_params.image_selected(imagep); + + icon_params.scale_image(true); + icon_params.flash_color.control = "ButtonUnselectedFgColor"; + mInfoButton = LLUICtrlFactory::create(icon_params); + if (p.click_callback.isProvided()) + { + mInfoButton->setCommitCallback(boost::bind(p.click_callback())); + } + addChild(mInfoButton); + + // move text over to fit image in + mTextBox->translate(TOOLTIP_ICON_SIZE + mPadding, 0); + } + + if (p.time_based_media) + { + LLButton::Params p_button; + p_button.name(std::string("play_media")); + p_button.label(""); // provide label but set to empty so name does not overwrite it -angela + TOOLTIP_PLAYBUTTON_SIZE = 16; + LLRect button_rect; + button_rect.setOriginAndSize((mPadding +TOOLTIP_ICON_SIZE+ mPadding ), mPadding, TOOLTIP_ICON_SIZE, TOOLTIP_ICON_SIZE); + p_button.rect = button_rect; + p_button.image_selected.name("button_anim_pause.tga"); + p_button.image_unselected.name("button_anim_play.tga"); + p_button.scale_image(true); + + mPlayMediaButton = LLUICtrlFactory::create(p_button); + if(p.click_playmedia_callback.isProvided()) + { + mPlayMediaButton->setCommitCallback(boost::bind(p.click_playmedia_callback())); + } + mPlayMediaButton->setToggleState(p.media_playing); + addChild(mPlayMediaButton); + + // move text over to fit image in + mTextBox->translate(TOOLTIP_PLAYBUTTON_SIZE + mPadding, 0); + } + + if (p.web_based_media) + { + LLButton::Params p_w_button; + p_w_button.name(std::string("home_page")); + p_w_button.label(""); // provid label but set to empty so name does not overwrite it -angela + TOOLTIP_PLAYBUTTON_SIZE = 16; + LLRect button_rect; + button_rect.setOriginAndSize((mPadding +TOOLTIP_ICON_SIZE+ mPadding ), mPadding, TOOLTIP_ICON_SIZE, TOOLTIP_ICON_SIZE); + p_w_button.rect = button_rect; + p_w_button.image_unselected.name("map_home.tga"); + p_w_button.scale_image(true); + + mHomePageButton = LLUICtrlFactory::create(p_w_button); + if(p.click_homepage_callback.isProvided()) + { + mHomePageButton->setCommitCallback(boost::bind(p.click_homepage_callback())); + } + addChild(mHomePageButton); + + // move text over to fit image in + mTextBox->translate(TOOLTIP_PLAYBUTTON_SIZE + mPadding, 0); + } + + if (p.click_callback.isProvided()) + { + setMouseUpCallback(boost::bind(p.click_callback())); + } +} + +void LLToolTip::initFromParams(const LLToolTip::Params& p) +{ + LLPanel::initFromParams(p); + + // do this *after* we've had our size set in LLPanel::initFromParams(); + const S32 REALLY_LARGE_HEIGHT = 10000; + mTextBox->reshape(mMaxWidth, REALLY_LARGE_HEIGHT); + + if (p.styled_message.isProvided()) + { + for (LLInitParam::ParamIterator::const_iterator text_it = p.styled_message.begin(); + text_it != p.styled_message.end(); + ++text_it) + { + mTextBox->appendText(text_it->text(), false, text_it->style); + } + } + else + { + mTextBox->setText(p.message()); + } + + mIsTooltipPastable = p.allow_paste_tooltip; + + updateTextBox(); + snapToChildren(); +} + +void LLToolTip::updateTextBox() +{ + S32 text_width = llmin(mMaxWidth, mTextBox->getTextPixelWidth() + 1); + S32 text_height = mTextBox->getTextPixelHeight(); + mTextBox->reshape(text_width, text_height); +} + +void LLToolTip::snapToChildren() +{ + // reshape tooltip panel to fit text box + LLRect tooltip_rect = calcBoundingRect(); + tooltip_rect.mTop += mPadding; + tooltip_rect.mRight += mPadding; + tooltip_rect.mBottom = 0; + tooltip_rect.mLeft = 0; + + if (mInfoButton) + { + mTextBox->reshape(mTextBox->getRect().getWidth(), llmax(mTextBox->getRect().getHeight(), tooltip_rect.getHeight() - 2 * mPadding)); + + LLRect text_rect = mTextBox->getRect(); + LLRect icon_rect = mInfoButton->getRect(); + mInfoButton->translate(0, text_rect.getCenterY() - icon_rect.getCenterY()); + } + + setShape(tooltip_rect); +} + +void LLToolTip::setVisible(bool visible) +{ + // fade out tooltip over time + if (visible) + { + mVisibleTimer.start(); + mFadeTimer.stop(); + LLPanel::setVisible(true); + } + else + { + mVisibleTimer.stop(); + // don't actually change mVisible state, start fade out transition instead + if (!mFadeTimer.getStarted()) + { + mFadeTimer.start(); + } + } +} + +bool LLToolTip::handleHover(S32 x, S32 y, MASK mask) +{ + //mInfoButton->setFlashing(true); + if(mInfoButton) + mInfoButton->setHighlight(true); + + LLPanel::handleHover(x, y, mask); + if (mHasClickCallback) + { + getWindow()->setCursor(UI_CURSOR_HAND); + } + return true; +} + +void LLToolTip::onMouseLeave(S32 x, S32 y, MASK mask) +{ + //mInfoButton->setFlashing(true); + if(mInfoButton) + mInfoButton->setHighlight(false); + LLUICtrl::onMouseLeave(x, y, mask); +} + +void LLToolTip::draw() +{ + F32 alpha = 1.f; + + if (mFadeTimer.getStarted()) + { + static LLCachedControl tool_tip_fade_time(*LLUI::getInstance()->mSettingGroups["config"], "ToolTipFadeTime", 0.2f); + alpha = clamp_rescale(mFadeTimer.getElapsedTimeF32(), 0.f, tool_tip_fade_time(), 1.f, 0.f); + if (alpha == 0.f) + { + // finished fading out, so hide ourselves + mFadeTimer.stop(); + LLPanel::setVisible(false); + } + } + + // draw tooltip contents with appropriate alpha + { + LLViewDrawContext context(alpha); + LLPanel::draw(); + } +} + +bool LLToolTip::isFading() +{ + return mFadeTimer.getStarted(); +} + +F32 LLToolTip::getVisibleTime() +{ + return mVisibleTimer.getStarted() ? mVisibleTimer.getElapsedTimeF32() : 0.f; +} + +bool LLToolTip::hasClickCallback() +{ + return mHasClickCallback; +} + +void LLToolTip::getToolTipMessage(std::string & message) +{ + if (mTextBox) + { + message = mTextBox->getText(); + } +} + + + +// +// LLToolTipMgr +// + +LLToolTipMgr::LLToolTipMgr() +: mToolTipsBlocked(false), + mToolTip(NULL), + mNeedsToolTip(false) +{} + +void LLToolTipMgr::createToolTip(const LLToolTip::Params& params) +{ + // block all other tooltips until tooltips re-enabled (e.g. mouse moved) + blockToolTips(); + + delete mToolTip; + + LLToolTip::Params tooltip_params(params); + // block mouse events if there is a click handler registered (specifically, hover) + if (params.click_callback.isProvided() && !params.mouse_opaque.isProvided()) + { + // set mouse_opaque to true if it wasn't already set to something else + // this prevents mouse down from going "through" the tooltip and ultimately + // causing the tooltip to disappear + tooltip_params.mouse_opaque = true; + } + tooltip_params.rect = LLRect (0, 1, 1, 0); + + if (tooltip_params.create_callback.isProvided()) + { + mToolTip = tooltip_params.create_callback()(tooltip_params); + if (mToolTip == NULL) + { + return; + } + } + else + mToolTip = LLUICtrlFactory::create (tooltip_params); + + gToolTipView->addChild(mToolTip); + + if (params.pos.isProvided()) + { + LLCoordGL pos = params.pos; + // try to spawn at requested position + LLUI::getInstance()->positionViewNearMouse(mToolTip, pos.mX, pos.mY); + } + else + { + // just spawn at mouse location + LLUI::getInstance()->positionViewNearMouse(mToolTip); + } + + //...update "sticky" rect and tooltip position + if (params.sticky_rect.isProvided()) + { + mMouseNearRect = params.sticky_rect; + } + else + { + S32 mouse_x; + S32 mouse_y; + LLUI::getInstance()->getMousePositionLocal(gToolTipView->getParent(), &mouse_x, &mouse_y); + + // allow mouse a little bit of slop before changing tooltips + mMouseNearRect.setCenterAndSize(mouse_x, mouse_y, 3, 3); + } + + // allow mouse to move all the way to the tooltip without changing tooltips + // (tooltip can still time out) + if (mToolTip->hasClickCallback()) + { + // keep tooltip up when we mouse over it + mMouseNearRect.unionWith(mToolTip->getRect()); + } +} + + +void LLToolTipMgr::show(const std::string& msg, bool allow_paste_tooltip) +{ + show(LLToolTip::Params().message(msg).allow_paste_tooltip(allow_paste_tooltip)); +} + +void LLToolTipMgr::show(const LLToolTip::Params& params) +{ + if (!params.styled_message.isProvided() + && (!params.message.isProvided() || params.message().empty()) + && !params.image.isProvided() && !params.create_callback.isProvided()) return; + + // fill in default tooltip params from tool_tip.xml + LLToolTip::Params params_with_defaults(params); + params_with_defaults.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); + if (!params_with_defaults.validateBlock()) + { + LL_WARNS() << "Could not display tooltip!" << LL_ENDL; + return; + } + + // are we ready to show the tooltip? + if (!mToolTipsBlocked // we haven't hit a key, moved the mouse, etc. + && LLUI::getInstance()->getMouseIdleTime() > params_with_defaults.delay_time) // the mouse has been still long enough + { + bool tooltip_changed = mLastToolTipParams.message() != params_with_defaults.message() + || mLastToolTipParams.pos() != params_with_defaults.pos() + || mLastToolTipParams.time_based_media() != params_with_defaults.time_based_media() + || mLastToolTipParams.web_based_media() != params_with_defaults.web_based_media(); + + bool tooltip_shown = mToolTip + && mToolTip->getVisible() + && !mToolTip->isFading(); + + mNeedsToolTip = tooltip_changed || !tooltip_shown; + // store description of tooltip for later creation + mNextToolTipParams = params_with_defaults; + } +} + +// allow new tooltips to be created, e.g. after mouse has moved +void LLToolTipMgr::unblockToolTips() +{ + mToolTipsBlocked = false; +} + +// disallow new tooltips until unblockTooltips called +void LLToolTipMgr::blockToolTips() +{ + hideToolTips(); + mToolTipsBlocked = true; +} + +void LLToolTipMgr::hideToolTips() +{ + if (mToolTip) + { + mToolTip->setVisible(false); + } +} + +bool LLToolTipMgr::toolTipVisible() +{ + return mToolTip ? mToolTip->isInVisibleChain() : false; +} + +LLRect LLToolTipMgr::getToolTipRect() +{ + if (mToolTip && mToolTip->getVisible()) + { + return mToolTip->getRect(); + } + return LLRect(); +} + + +LLRect LLToolTipMgr::getMouseNearRect() +{ + return toolTipVisible() ? mMouseNearRect : LLRect(); +} + +// every frame, determine if current tooltip should be hidden +void LLToolTipMgr::updateToolTipVisibility() +{ + // create new tooltip if we have one ready to go + if (mNeedsToolTip) + { + mNeedsToolTip = false; + createToolTip(mNextToolTipParams); + mLastToolTipParams = mNextToolTipParams; + + return; + } + + // hide tooltips when mouse cursor is hidden + if (LLUI::getInstance()->getWindow()->isCursorHidden()) + { + blockToolTips(); + return; + } + + // hide existing tooltips if they have timed out + F32 tooltip_timeout = 0.f; + if (toolTipVisible()) + { + S32 mouse_x, mouse_y; + LLUI::getInstance()->getMousePositionLocal(gToolTipView, &mouse_x, &mouse_y); + + // mouse far away from tooltip + tooltip_timeout = mLastToolTipParams.visible_time_far; + // mouse near rect will only include the tooltip if the + // tooltip is clickable + if (mMouseNearRect.pointInRect(mouse_x, mouse_y)) + { + // mouse "close" to tooltip + tooltip_timeout = mLastToolTipParams.visible_time_near; + + // if tooltip is clickable (has large mMouseNearRect) + // than having cursor over tooltip keeps it up indefinitely + if (mToolTip->parentPointInView(mouse_x, mouse_y)) + { + // mouse over tooltip itself, don't time out + tooltip_timeout = mLastToolTipParams.visible_time_over; + } + } + + if (mToolTip->getVisibleTime() > tooltip_timeout) + { + hideToolTips(); + unblockToolTips(); + } + } +} + + +// Return the current tooltip text +void LLToolTipMgr::getToolTipMessage(std::string & message) +{ + if (toolTipVisible()) + { + mToolTip->getToolTipMessage(message); + } +} + +bool LLToolTipMgr::isTooltipPastable() +{ + if (toolTipVisible()) + { + return mToolTip->isTooltipPastable(); + } + return false; + } + +// EOF diff --git a/indra/llui/lltooltip.h b/indra/llui/lltooltip.h index 74ae5a219d..8515504e3b 100644 --- a/indra/llui/lltooltip.h +++ b/indra/llui/lltooltip.h @@ -1,185 +1,185 @@ -/** - * @file lltooltip.h - * @brief LLToolTipMgr class definition and related classes - * - * $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$ - */ - -#ifndef LL_LLTOOLTIP_H -#define LL_LLTOOLTIP_H - -// Library includes -#include "llsingleton.h" -#include "llinitparam.h" -#include "llpanel.h" -#include "llstyle.h" - -// -// Classes -// -class LLToolTipView : public LLView -{ -public: - struct Params : public LLInitParam::Block - { - Params(); - }; - LLToolTipView(const LLToolTipView::Params&); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleScrollWheel( S32 x, S32 y, S32 clicks ); - - void drawStickyRect(); - - /*virtual*/ void draw(); -}; - -class LLToolTip : public LLPanel -{ -public: - - struct StyledText : public LLInitParam::Block - { - Mandatory text; - Optional style; - }; - - struct Params : public LLInitParam::Block - { - typedef boost::function click_callback_t; - typedef boost::function create_callback_t; - - Optional message; - Multiple styled_message; - - Optional pos; - Optional delay_time, - visible_time_over, // time for which tooltip is visible while mouse on it - visible_time_near, // time for which tooltip is visible while mouse near it - visible_time_far; // time for which tooltip is visible while mouse moved away - Optional sticky_rect; - Optional font; - Optional image; - Optional text_color; - Optional time_based_media, - web_based_media, - media_playing; - Optional create_callback; - Optional create_params; - Optional click_callback, - click_playmedia_callback, - click_homepage_callback; - Optional max_width, - padding; - Optional wrap; - - Optional allow_paste_tooltip; - - Params(); - }; - /*virtual*/ void draw(); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); - /*virtual*/ void setVisible(bool visible); - - bool isFading(); - F32 getVisibleTime(); - bool hasClickCallback(); - - LLToolTip(const Params& p); - virtual void initFromParams(const LLToolTip::Params& params); - - void getToolTipMessage(std::string & message); - bool isTooltipPastable() { return mIsTooltipPastable; } - -protected: - void updateTextBox(); - void snapToChildren(); - -protected: - class LLTextBox* mTextBox; - class LLButton* mInfoButton; - class LLButton* mPlayMediaButton; - class LLButton* mHomePageButton; - - LLFrameTimer mFadeTimer; - LLFrameTimer mVisibleTimer; - bool mHasClickCallback; - S32 mPadding; // pixels - S32 mMaxWidth; - - bool mIsTooltipPastable; -}; - -// used for the inspector tooltips which need different background images etc. -class LLInspector : public LLToolTip -{ -public: - struct Params : public LLInitParam::Block - {}; -}; - -class LLToolTipMgr : public LLSingleton -{ - LLSINGLETON(LLToolTipMgr); - LOG_CLASS(LLToolTipMgr); - -public: - void show(const LLToolTip::Params& params); - void show(const std::string& message, bool allow_paste_tooltip = false); - - void unblockToolTips(); - void blockToolTips(); - - void hideToolTips(); - bool toolTipVisible(); - LLRect getToolTipRect(); - LLRect getMouseNearRect(); - void updateToolTipVisibility(); - - void getToolTipMessage(std::string & message); - bool isTooltipPastable(); - -private: - void createToolTip(const LLToolTip::Params& params); - - bool mToolTipsBlocked; - class LLToolTip* mToolTip; - - // tooltip creation is deferred until the UI is drawn every frame - // so the last tooltip to be created in a given frame will win - LLToolTip::Params mLastToolTipParams; // description of last tooltip we showed - LLToolTip::Params mNextToolTipParams; // description of next tooltip we want to show - bool mNeedsToolTip; // do we want to show a tooltip - - LLRect mMouseNearRect; -}; - -// -// Globals -// - -extern LLToolTipView *gToolTipView; - -#endif +/** + * @file lltooltip.h + * @brief LLToolTipMgr class definition and related classes + * + * $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$ + */ + +#ifndef LL_LLTOOLTIP_H +#define LL_LLTOOLTIP_H + +// Library includes +#include "llsingleton.h" +#include "llinitparam.h" +#include "llpanel.h" +#include "llstyle.h" + +// +// Classes +// +class LLToolTipView : public LLView +{ +public: + struct Params : public LLInitParam::Block + { + Params(); + }; + LLToolTipView(const LLToolTipView::Params&); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleScrollWheel( S32 x, S32 y, S32 clicks ); + + void drawStickyRect(); + + /*virtual*/ void draw(); +}; + +class LLToolTip : public LLPanel +{ +public: + + struct StyledText : public LLInitParam::Block + { + Mandatory text; + Optional style; + }; + + struct Params : public LLInitParam::Block + { + typedef boost::function click_callback_t; + typedef boost::function create_callback_t; + + Optional message; + Multiple styled_message; + + Optional pos; + Optional delay_time, + visible_time_over, // time for which tooltip is visible while mouse on it + visible_time_near, // time for which tooltip is visible while mouse near it + visible_time_far; // time for which tooltip is visible while mouse moved away + Optional sticky_rect; + Optional font; + Optional image; + Optional text_color; + Optional time_based_media, + web_based_media, + media_playing; + Optional create_callback; + Optional create_params; + Optional click_callback, + click_playmedia_callback, + click_homepage_callback; + Optional max_width, + padding; + Optional wrap; + + Optional allow_paste_tooltip; + + Params(); + }; + /*virtual*/ void draw(); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + /*virtual*/ void setVisible(bool visible); + + bool isFading(); + F32 getVisibleTime(); + bool hasClickCallback(); + + LLToolTip(const Params& p); + virtual void initFromParams(const LLToolTip::Params& params); + + void getToolTipMessage(std::string & message); + bool isTooltipPastable() { return mIsTooltipPastable; } + +protected: + void updateTextBox(); + void snapToChildren(); + +protected: + class LLTextBox* mTextBox; + class LLButton* mInfoButton; + class LLButton* mPlayMediaButton; + class LLButton* mHomePageButton; + + LLFrameTimer mFadeTimer; + LLFrameTimer mVisibleTimer; + bool mHasClickCallback; + S32 mPadding; // pixels + S32 mMaxWidth; + + bool mIsTooltipPastable; +}; + +// used for the inspector tooltips which need different background images etc. +class LLInspector : public LLToolTip +{ +public: + struct Params : public LLInitParam::Block + {}; +}; + +class LLToolTipMgr : public LLSingleton +{ + LLSINGLETON(LLToolTipMgr); + LOG_CLASS(LLToolTipMgr); + +public: + void show(const LLToolTip::Params& params); + void show(const std::string& message, bool allow_paste_tooltip = false); + + void unblockToolTips(); + void blockToolTips(); + + void hideToolTips(); + bool toolTipVisible(); + LLRect getToolTipRect(); + LLRect getMouseNearRect(); + void updateToolTipVisibility(); + + void getToolTipMessage(std::string & message); + bool isTooltipPastable(); + +private: + void createToolTip(const LLToolTip::Params& params); + + bool mToolTipsBlocked; + class LLToolTip* mToolTip; + + // tooltip creation is deferred until the UI is drawn every frame + // so the last tooltip to be created in a given frame will win + LLToolTip::Params mLastToolTipParams; // description of last tooltip we showed + LLToolTip::Params mNextToolTipParams; // description of next tooltip we want to show + bool mNeedsToolTip; // do we want to show a tooltip + + LLRect mMouseNearRect; +}; + +// +// Globals +// + +extern LLToolTipView *gToolTipView; + +#endif diff --git a/indra/llui/lltransutil.cpp b/indra/llui/lltransutil.cpp index 7f44f05f2b..4af5376a8b 100644 --- a/indra/llui/lltransutil.cpp +++ b/indra/llui/lltransutil.cpp @@ -1,74 +1,74 @@ -/** - * @file lltrans.cpp - * @brief LLTrans implementation - * - * $LicenseInfo:firstyear=2000&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 "lltransutil.h" - -#include "lltrans.h" -#include "lluictrlfactory.h" -#include "llxmlnode.h" -#include "lldir.h" - -bool LLTransUtil::parseStrings(const std::string& xml_filename, const std::set& default_args) -{ - LLXMLNodePtr root; - // Pass LLDir::ALL_SKINS to load a composite of all the individual string - // definitions in the default skin and the current skin. This means an - // individual skin can provide an xml_filename that overrides only a - // subset of the available string definitions; any string definition not - // overridden by that skin will be sought in the default skin. - bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root, LLDir::ALL_SKINS); - if (!success) - { - const std::string error_string = - "Second Life viewer couldn't access some of the files it needs and will be closed." - "\n\nPlease reinstall viewer from https://secondlife.com/support/downloads/ and " - "contact https://support.secondlife.com if issue persists after reinstall."; - LLError::LLUserWarningMsg::show(error_string); - gDirUtilp->dumpCurrentDirectories(LLError::LEVEL_WARN); - LL_ERRS() << "Couldn't load string table " << xml_filename << " " << errno << LL_ENDL; - return false; - } - - return LLTrans::parseStrings(root, default_args); -} - - -bool LLTransUtil::parseLanguageStrings(const std::string& xml_filename) -{ - LLXMLNodePtr root; - bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); - - if (!success) - { - LLError::LLUserWarningMsg::showMissingFiles(); - LL_ERRS() << "Couldn't load localization table " << xml_filename << LL_ENDL; - return false; - } - - return LLTrans::parseLanguageStrings(root); -} +/** + * @file lltrans.cpp + * @brief LLTrans implementation + * + * $LicenseInfo:firstyear=2000&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 "lltransutil.h" + +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "llxmlnode.h" +#include "lldir.h" + +bool LLTransUtil::parseStrings(const std::string& xml_filename, const std::set& default_args) +{ + LLXMLNodePtr root; + // Pass LLDir::ALL_SKINS to load a composite of all the individual string + // definitions in the default skin and the current skin. This means an + // individual skin can provide an xml_filename that overrides only a + // subset of the available string definitions; any string definition not + // overridden by that skin will be sought in the default skin. + bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root, LLDir::ALL_SKINS); + if (!success) + { + const std::string error_string = + "Second Life viewer couldn't access some of the files it needs and will be closed." + "\n\nPlease reinstall viewer from https://secondlife.com/support/downloads/ and " + "contact https://support.secondlife.com if issue persists after reinstall."; + LLError::LLUserWarningMsg::show(error_string); + gDirUtilp->dumpCurrentDirectories(LLError::LEVEL_WARN); + LL_ERRS() << "Couldn't load string table " << xml_filename << " " << errno << LL_ENDL; + return false; + } + + return LLTrans::parseStrings(root, default_args); +} + + +bool LLTransUtil::parseLanguageStrings(const std::string& xml_filename) +{ + LLXMLNodePtr root; + bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); + + if (!success) + { + LLError::LLUserWarningMsg::showMissingFiles(); + LL_ERRS() << "Couldn't load localization table " << xml_filename << LL_ENDL; + return false; + } + + return LLTrans::parseLanguageStrings(root); +} diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index 325dccf941..66ec3ad9bd 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -1,738 +1,738 @@ -/** - * @file llui.cpp - * @brief UI implementation - * - * $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$ - */ - -// Utilities functions the user interface needs - -#include "linden_common.h" - -#include -#include - -// Linden library includes -#include "v2math.h" -#include "m3math.h" -#include "v4color.h" -#include "llrender.h" -#include "llrect.h" -#include "lldir.h" -#include "llgl.h" -#include "llsd.h" - -// Project includes -#include "llcommandmanager.h" -#include "llcontrol.h" -#include "llui.h" -#include "lluicolortable.h" -#include "llview.h" -#include "lllineeditor.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llmenugl.h" -#include "llmenubutton.h" -#include "llloadingindicator.h" -#include "llwindow.h" - -// for registration -#include "llfiltereditor.h" -#include "llflyoutbutton.h" -#include "llsearcheditor.h" -#include "lltoolbar.h" -#include "llcleanup.h" - -// for XUIParse -#include "llquaternion.h" -#include -#include -#include - -// -// Globals -// - -// Language for UI construction -std::map gTranslation; -std::list gUntranslated; - -// register filter editor here -static LLDefaultChildRegistry::Register register_filter_editor("filter_editor"); -static LLDefaultChildRegistry::Register register_flyout_button("flyout_button"); -static LLDefaultChildRegistry::Register register_search_editor("search_editor"); - -// register other widgets which otherwise may not be linked in -static LLDefaultChildRegistry::Register register_loading_indicator("loading_indicator"); -static LLDefaultChildRegistry::Register register_toolbar("toolbar"); - -// -// Functions -// - -LLUUID find_ui_sound(const char * namep) -{ - std::string name = ll_safe_string(namep); - LLUUID uuid = LLUUID(NULL); - LLUI *ui_inst = LLUI::getInstance(); - if (!ui_inst->mSettingGroups["config"]->controlExists(name)) - { - LL_WARNS() << "tried to make UI sound for unknown sound name: " << name << LL_ENDL; - } - else - { - uuid = LLUUID(ui_inst->mSettingGroups["config"]->getString(name)); - if (uuid.isNull()) - { - if (ui_inst->mSettingGroups["config"]->getString(name) == LLUUID::null.asString()) - { - if (ui_inst->mSettingGroups["config"]->getBOOL("UISndDebugSpamToggle")) - { - LL_INFOS() << "UI sound name: " << name << " triggered but silent (null uuid)" << LL_ENDL; - } - } - else - { - LL_WARNS() << "UI sound named: " << name << " does not translate to a valid uuid" << LL_ENDL; - } - } - else if (ui_inst->mAudioCallback != NULL) - { - if (ui_inst->mSettingGroups["config"]->getBOOL("UISndDebugSpamToggle")) - { - LL_INFOS() << "UI sound name: " << name << LL_ENDL; - } - } - } - - return uuid; -} - -void make_ui_sound(const char* namep) -{ - LLUUID soundUUID = find_ui_sound(namep); - if(soundUUID.notNull()) - { - LLUI::getInstance()->mAudioCallback(soundUUID); - } -} - -void make_ui_sound_deferred(const char* namep) -{ - LLUUID soundUUID = find_ui_sound(namep); - if(soundUUID.notNull()) - { - LLUI::getInstance()->mDeferredAudioCallback(soundUUID); - } -} - -LLUI::LLUI(const settings_map_t& settings, - LLImageProviderInterface* image_provider, - LLUIAudioCallback audio_callback, - LLUIAudioCallback deferred_audio_callback) -: mSettingGroups(settings), -mAudioCallback(audio_callback), -mDeferredAudioCallback(deferred_audio_callback), -mWindow(NULL), // set later in startup -mRootView(NULL), -mHelpImpl(NULL) -{ - LLRender2D::initParamSingleton(image_provider); - - if ((get_ptr_in_map(mSettingGroups, std::string("config")) == NULL) || - (get_ptr_in_map(mSettingGroups, std::string("floater")) == NULL) || - (get_ptr_in_map(mSettingGroups, std::string("ignores")) == NULL)) - { - LL_ERRS() << "Failure to initialize configuration groups" << LL_ENDL; - } - - LLFontGL::sShadowColor = LLUIColorTable::instance().getColor("ColorDropShadow"); - - LLUICtrl::CommitCallbackRegistry::Registrar& reg = LLUICtrl::CommitCallbackRegistry::defaultRegistrar(); - - // Callbacks for associating controls with floater visibility: - reg.add("Floater.Toggle", boost::bind(&LLFloaterReg::toggleInstance, _2, LLSD())); - reg.add("Floater.ToggleOrBringToFront", boost::bind(&LLFloaterReg::toggleInstanceOrBringToFront, _2, LLSD())); - reg.add("Floater.Show", boost::bind(&LLFloaterReg::showInstance, _2, LLSD(), false)); - reg.add("Floater.ShowOrBringToFront", boost::bind(&LLFloaterReg::showInstanceOrBringToFront, _2, LLSD())); - reg.add("Floater.Hide", boost::bind(&LLFloaterReg::hideInstance, _2, LLSD())); - - // Button initialization callback for toggle buttons - reg.add("Button.SetFloaterToggle", boost::bind(&LLButton::setFloaterToggle, _1, _2)); - - // Button initialization callback for toggle buttons on dockable floaters - reg.add("Button.SetDockableFloaterToggle", boost::bind(&LLButton::setDockableFloaterToggle, _1, _2)); - - // Display the help topic for the current context - reg.add("Button.ShowHelp", boost::bind(&LLButton::showHelp, _1, _2)); - - // Currently unused, but kept for reference: - reg.add("Button.ToggleFloater", boost::bind(&LLButton::toggleFloaterAndSetToggleState, _1, _2)); - - // Used by menus along with Floater.Toggle to display visibility as a check-mark - LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.Visible", boost::bind(&LLFloaterReg::instanceVisible, _2, LLSD())); - LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.IsOpen", boost::bind(&LLFloaterReg::instanceVisible, _2, LLSD())); - - // Parse the master list of commands - LLCommandManager::load(); -} - -void LLUI::setPopupFuncs(const add_popup_t& add_popup, const remove_popup_t& remove_popup, const clear_popups_t& clear_popups) -{ - mAddPopupFunc = add_popup; - mRemovePopupFunc = remove_popup; - mClearPopupsFunc = clear_popups; -} - -void LLUI::setMousePositionScreen(S32 x, S32 y) -{ -#if defined(LL_DARWIN) - S32 screen_x = ll_round(((F32)x * getScaleFactor().mV[VX]) / LLView::getWindow()->getSystemUISize()); - S32 screen_y = ll_round(((F32)y * getScaleFactor().mV[VY]) / LLView::getWindow()->getSystemUISize()); -#else - S32 screen_x = ll_round((F32)x * getScaleFactor().mV[VX]); - S32 screen_y = ll_round((F32)y * getScaleFactor().mV[VY]); -#endif - - LLView::getWindow()->setCursorPosition(LLCoordGL(screen_x, screen_y).convert()); -} - -void LLUI::getMousePositionScreen(S32 *x, S32 *y) -{ - LLCoordWindow cursor_pos_window; - getWindow()->getCursorPosition(&cursor_pos_window); - LLCoordGL cursor_pos_gl(cursor_pos_window.convert()); - *x = ll_round((F32)cursor_pos_gl.mX / getScaleFactor().mV[VX]); - *y = ll_round((F32)cursor_pos_gl.mY / getScaleFactor().mV[VY]); -} - -void LLUI::setMousePositionLocal(const LLView* viewp, S32 x, S32 y) -{ - S32 screen_x, screen_y; - viewp->localPointToScreen(x, y, &screen_x, &screen_y); - - setMousePositionScreen(screen_x, screen_y); -} - -void LLUI::getMousePositionLocal(const LLView* viewp, S32 *x, S32 *y) -{ - S32 screen_x, screen_y; - getMousePositionScreen(&screen_x, &screen_y); - viewp->screenPointToLocal(screen_x, screen_y, x, y); -} - - -// On Windows, the user typically sets the language when they install the -// app (by running it with a shortcut that sets InstallLanguage). On Mac, -// or on Windows if the SecondLife.exe executable is run directly, the -// language follows the OS language. In all cases the user can override -// the language manually in preferences. JC -std::string LLUI::getUILanguage() -{ - std::string language = "en"; - if (mSettingGroups["config"]) - { - language = mSettingGroups["config"]->getString("Language"); - if (language.empty() || language == "default") - { - language = mSettingGroups["config"]->getString("InstallLanguage"); - } - if (language.empty() || language == "default") - { - language = mSettingGroups["config"]->getString("SystemLanguage"); - } - if (language.empty() || language == "default") - { - language = "en"; - } - } - return language; -} - -// static -std::string LLUI::getLanguage() -{ - // Note: lldateutil_test redefines this function - return LLUI::getInstance()->getUILanguage(); -} - -struct SubDir : public LLInitParam::Block -{ - Mandatory value; - - SubDir() - : value("value") - {} -}; - -struct Directory : public LLInitParam::Block -{ - Multiple > subdirs; - - Directory() - : subdirs("subdir") - {} -}; - -struct Paths : public LLInitParam::Block -{ - Multiple > directories; - - Paths() - : directories("directory") - {} -}; - - -//static -std::string LLUI::locateSkin(const std::string& filename) -{ - std::string found_file = filename; - if (gDirUtilp->fileExists(found_file)) - { - return found_file; - } - - found_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); // Should be CUSTOM_SKINS? - if (gDirUtilp->fileExists(found_file)) - { - return found_file; - } - - found_file = gDirUtilp->findSkinnedFilename(LLDir::XUI, filename); - if (! found_file.empty()) - { - return found_file; - } - - found_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename); - if (gDirUtilp->fileExists(found_file)) - { - return found_file; - } - LL_WARNS("LLUI") << "Can't find '" << filename - << "' in user settings, any skin directory or app_settings" << LL_ENDL; - return ""; -} - -LLVector2 LLUI::getWindowSize() -{ - LLCoordWindow window_rect; - mWindow->getSize(&window_rect); - - return LLVector2(window_rect.mX / getScaleFactor().mV[VX], window_rect.mY / getScaleFactor().mV[VY]); -} - -void LLUI::screenPointToGL(S32 screen_x, S32 screen_y, S32 *gl_x, S32 *gl_y) -{ - *gl_x = ll_round((F32)screen_x * getScaleFactor().mV[VX]); - *gl_y = ll_round((F32)screen_y * getScaleFactor().mV[VY]); -} - -void LLUI::glPointToScreen(S32 gl_x, S32 gl_y, S32 *screen_x, S32 *screen_y) -{ - *screen_x = ll_round((F32)gl_x / getScaleFactor().mV[VX]); - *screen_y = ll_round((F32)gl_y / getScaleFactor().mV[VY]); -} - -void LLUI::screenRectToGL(const LLRect& screen, LLRect *gl) -{ - screenPointToGL(screen.mLeft, screen.mTop, &gl->mLeft, &gl->mTop); - screenPointToGL(screen.mRight, screen.mBottom, &gl->mRight, &gl->mBottom); -} - -void LLUI::glRectToScreen(const LLRect& gl, LLRect *screen) -{ - glPointToScreen(gl.mLeft, gl.mTop, &screen->mLeft, &screen->mTop); - glPointToScreen(gl.mRight, gl.mBottom, &screen->mRight, &screen->mBottom); -} - - -LLControlGroup& LLUI::getControlControlGroup (const std::string& controlname) -{ - for (settings_map_t::iterator itor = mSettingGroups.begin(); - itor != mSettingGroups.end(); ++itor) - { - LLControlGroup* control_group = itor->second; - if(control_group != NULL) - { - if (control_group->controlExists(controlname)) - return *control_group; - } - } - - return *mSettingGroups["config"]; // default group -} - -void LLUI::addPopup(LLView* viewp) -{ - if (mAddPopupFunc) - { - mAddPopupFunc(viewp); - } -} - -void LLUI::removePopup(LLView* viewp) -{ - if (mRemovePopupFunc) - { - mRemovePopupFunc(viewp); - } -} - -void LLUI::clearPopups() -{ - if (mClearPopupsFunc) - { - mClearPopupsFunc(); - } -} - -void LLUI::reportBadKeystroke() -{ - make_ui_sound("UISndBadKeystroke"); -} - -// spawn_x and spawn_y are top left corner of view in screen GL coordinates -void LLUI::positionViewNearMouse(LLView* view, S32 spawn_x, S32 spawn_y) -{ - const S32 CURSOR_HEIGHT = 16; // Approximate "normal" cursor size - const S32 CURSOR_WIDTH = 8; - - LLView* parent = view->getParent(); - - S32 mouse_x; - S32 mouse_y; - getMousePositionScreen(&mouse_x, &mouse_y); - - // If no spawn location provided, use mouse position - if (spawn_x == S32_MAX || spawn_y == S32_MAX) - { - spawn_x = mouse_x + CURSOR_WIDTH; - spawn_y = mouse_y - CURSOR_HEIGHT; - } - - LLRect virtual_window_rect = parent->getLocalRect(); - - LLRect mouse_rect; - const S32 MOUSE_CURSOR_PADDING = 1; - mouse_rect.setLeftTopAndSize(mouse_x - MOUSE_CURSOR_PADDING, - mouse_y + MOUSE_CURSOR_PADDING, - CURSOR_WIDTH + MOUSE_CURSOR_PADDING * 2, - CURSOR_HEIGHT + MOUSE_CURSOR_PADDING * 2); - - S32 local_x, local_y; - // convert screen coordinates to tooltip view-local coordinates - parent->screenPointToLocal(spawn_x, spawn_y, &local_x, &local_y); - - // Start at spawn position (using left/top) - view->setOrigin( local_x, local_y - view->getRect().getHeight()); - // Make sure we're on-screen and not overlapping the mouse - view->translateIntoRectWithExclusion( virtual_window_rect, mouse_rect ); -} - -LLView* LLUI::resolvePath(LLView* context, const std::string& path) -{ - // Nothing about resolvePath() should require non-const LLView*. If caller - // wants non-const, call the const flavor and then cast away const-ness. - return const_cast(resolvePath(const_cast(context), path)); -} - -const LLView* LLUI::resolvePath(const LLView* context, const std::string& path) -{ - // Create an iterator over slash-separated parts of 'path'. Dereferencing - // this iterator returns an iterator_range over the substring. Unlike - // LLStringUtil::getTokens(), this split_iterator doesn't combine adjacent - // delimiters: leading/trailing slash produces an empty substring, double - // slash produces an empty substring. That's what we need. - boost::split_iterator ti(path, boost::first_finder("/")), tend; - - if (ti == tend) - { - // 'path' is completely empty, no navigation - return context; - } - - // leading / means "start at root" - if (ti->empty()) - { - context = getRootView(); - ++ti; - } - - bool recurse = false; - for (; ti != tend && context; ++ti) - { - if (ti->empty()) - { - recurse = true; - } - else - { - std::string part(ti->begin(), ti->end()); - context = context->findChildView(LLURI::unescape(part), recurse); - recurse = false; - } - } - - return context; -} - -//static -LLVector2& LLUI::getScaleFactor() -{ - return LLRender::sUIGLScaleFactor; -} - -//static -void LLUI::setScaleFactor(const LLVector2& scale_factor) -{ - LLRender::sUIGLScaleFactor = scale_factor; -} - - -// LLLocalClipRect and LLScreenClipRect moved to lllocalcliprect.h/cpp - -namespace LLInitParam -{ - ParamValue::ParamValue(const LLUIColor& color) - : super_t(color), - red("red"), - green("green"), - blue("blue"), - alpha("alpha"), - control("") - { - updateBlockFromValue(false); - } - - void ParamValue::updateValueFromBlock() - { - if (control.isProvided() && !control().empty()) - { - updateValue(LLUIColorTable::instance().getColor(control)); - } - else - { - updateValue(LLColor4(red, green, blue, alpha)); - } - } - - void ParamValue::updateBlockFromValue(bool make_block_authoritative) - { - LLColor4 color = getValue(); - red.set(color.mV[VRED], make_block_authoritative); - green.set(color.mV[VGREEN], make_block_authoritative); - blue.set(color.mV[VBLUE], make_block_authoritative); - alpha.set(color.mV[VALPHA], make_block_authoritative); - control.set("", make_block_authoritative); - } - - bool ParamCompare::equals(const LLFontGL* a, const LLFontGL* b) - { - return !(a->getFontDesc() < b->getFontDesc()) - && !(b->getFontDesc() < a->getFontDesc()); - } - - ParamValue::ParamValue(const LLFontGL* fontp) - : super_t(fontp), - name("name"), - size("size"), - style("style") - { - if (!fontp) - { - updateValue(LLFontGL::getFontDefault()); - } - addSynonym(name, ""); - updateBlockFromValue(false); - } - - void ParamValue::updateValueFromBlock() - { - const LLFontGL* res_fontp = LLFontGL::getFontByName(name); - if (res_fontp) - { - updateValue(res_fontp); - return; - } - - U8 fontstyle = 0; - fontstyle = LLFontGL::getStyleFromString(style()); - LLFontDescriptor desc(name(), size(), fontstyle); - const LLFontGL* fontp = LLFontGL::getFont(desc); - if (fontp) - { - updateValue(fontp); - } - else - { - updateValue(LLFontGL::getFontDefault()); - } - } - - void ParamValue::updateBlockFromValue(bool make_block_authoritative) - { - if (getValue()) - { - name.set(LLFontGL::nameFromFont(getValue()), make_block_authoritative); - size.set(LLFontGL::sizeFromFont(getValue()), make_block_authoritative); - style.set(LLFontGL::getStringFromStyle(getValue()->getFontDesc().getStyle()), make_block_authoritative); - } - } - - ParamValue::ParamValue(const LLRect& rect) - : super_t(rect), - left("left"), - top("top"), - right("right"), - bottom("bottom"), - width("width"), - height("height") - { - updateBlockFromValue(false); - } - - void ParamValue::updateValueFromBlock() - { - LLRect rect; - - //calculate from params - // prefer explicit left and right - if (left.isProvided() && right.isProvided()) - { - rect.mLeft = left; - rect.mRight = right; - } - // otherwise use width along with specified side, if any - else if (width.isProvided()) - { - // only right + width provided - if (right.isProvided()) - { - rect.mRight = right; - rect.mLeft = right - width; - } - else // left + width, or just width - { - rect.mLeft = left; - rect.mRight = left + width; - } - } - // just left, just right, or none - else - { - rect.mLeft = left; - rect.mRight = right; - } - - // prefer explicit bottom and top - if (bottom.isProvided() && top.isProvided()) - { - rect.mBottom = bottom; - rect.mTop = top; - } - // otherwise height along with specified side, if any - else if (height.isProvided()) - { - // top + height provided - if (top.isProvided()) - { - rect.mTop = top; - rect.mBottom = top - height; - } - // bottom + height or just height - else - { - rect.mBottom = bottom; - rect.mTop = bottom + height; - } - } - // just bottom, just top, or none - else - { - rect.mBottom = bottom; - rect.mTop = top; - } - updateValue(rect); - } - - void ParamValue::updateBlockFromValue(bool make_block_authoritative) - { - // because of the ambiguity in specifying a rect by position and/or dimensions - // we use the lowest priority pairing so that any valid pairing in xui - // will override those calculated from the rect object - // in this case, that is left+width and bottom+height - LLRect& value = getValue(); - - right.set(value.mRight, false); - left.set(value.mLeft, make_block_authoritative); - width.set(value.getWidth(), make_block_authoritative); - - top.set(value.mTop, false); - bottom.set(value.mBottom, make_block_authoritative); - height.set(value.getHeight(), make_block_authoritative); - } - - ParamValue::ParamValue(const LLCoordGL& coord) - : super_t(coord), - x("x"), - y("y") - { - updateBlockFromValue(false); - } - - void ParamValue::updateValueFromBlock() - { - updateValue(LLCoordGL(x, y)); - } - - void ParamValue::updateBlockFromValue(bool make_block_authoritative) - { - x.set(getValue().mX, make_block_authoritative); - y.set(getValue().mY, make_block_authoritative); - } - - - void TypeValues::declareValues() - { - declare("left", LLFontGL::LEFT); - declare("right", LLFontGL::RIGHT); - declare("center", LLFontGL::HCENTER); - } - - void TypeValues::declareValues() - { - declare("top", LLFontGL::TOP); - declare("center", LLFontGL::VCENTER); - declare("baseline", LLFontGL::BASELINE); - declare("bottom", LLFontGL::BOTTOM); - } - - void TypeValues::declareValues() - { - declare("none", LLFontGL::NO_SHADOW); - declare("hard", LLFontGL::DROP_SHADOW); - declare("soft", LLFontGL::DROP_SHADOW_SOFT); - } -} - +/** + * @file llui.cpp + * @brief UI implementation + * + * $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$ + */ + +// Utilities functions the user interface needs + +#include "linden_common.h" + +#include +#include + +// Linden library includes +#include "v2math.h" +#include "m3math.h" +#include "v4color.h" +#include "llrender.h" +#include "llrect.h" +#include "lldir.h" +#include "llgl.h" +#include "llsd.h" + +// Project includes +#include "llcommandmanager.h" +#include "llcontrol.h" +#include "llui.h" +#include "lluicolortable.h" +#include "llview.h" +#include "lllineeditor.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llmenugl.h" +#include "llmenubutton.h" +#include "llloadingindicator.h" +#include "llwindow.h" + +// for registration +#include "llfiltereditor.h" +#include "llflyoutbutton.h" +#include "llsearcheditor.h" +#include "lltoolbar.h" +#include "llcleanup.h" + +// for XUIParse +#include "llquaternion.h" +#include +#include +#include + +// +// Globals +// + +// Language for UI construction +std::map gTranslation; +std::list gUntranslated; + +// register filter editor here +static LLDefaultChildRegistry::Register register_filter_editor("filter_editor"); +static LLDefaultChildRegistry::Register register_flyout_button("flyout_button"); +static LLDefaultChildRegistry::Register register_search_editor("search_editor"); + +// register other widgets which otherwise may not be linked in +static LLDefaultChildRegistry::Register register_loading_indicator("loading_indicator"); +static LLDefaultChildRegistry::Register register_toolbar("toolbar"); + +// +// Functions +// + +LLUUID find_ui_sound(const char * namep) +{ + std::string name = ll_safe_string(namep); + LLUUID uuid = LLUUID(NULL); + LLUI *ui_inst = LLUI::getInstance(); + if (!ui_inst->mSettingGroups["config"]->controlExists(name)) + { + LL_WARNS() << "tried to make UI sound for unknown sound name: " << name << LL_ENDL; + } + else + { + uuid = LLUUID(ui_inst->mSettingGroups["config"]->getString(name)); + if (uuid.isNull()) + { + if (ui_inst->mSettingGroups["config"]->getString(name) == LLUUID::null.asString()) + { + if (ui_inst->mSettingGroups["config"]->getBOOL("UISndDebugSpamToggle")) + { + LL_INFOS() << "UI sound name: " << name << " triggered but silent (null uuid)" << LL_ENDL; + } + } + else + { + LL_WARNS() << "UI sound named: " << name << " does not translate to a valid uuid" << LL_ENDL; + } + } + else if (ui_inst->mAudioCallback != NULL) + { + if (ui_inst->mSettingGroups["config"]->getBOOL("UISndDebugSpamToggle")) + { + LL_INFOS() << "UI sound name: " << name << LL_ENDL; + } + } + } + + return uuid; +} + +void make_ui_sound(const char* namep) +{ + LLUUID soundUUID = find_ui_sound(namep); + if(soundUUID.notNull()) + { + LLUI::getInstance()->mAudioCallback(soundUUID); + } +} + +void make_ui_sound_deferred(const char* namep) +{ + LLUUID soundUUID = find_ui_sound(namep); + if(soundUUID.notNull()) + { + LLUI::getInstance()->mDeferredAudioCallback(soundUUID); + } +} + +LLUI::LLUI(const settings_map_t& settings, + LLImageProviderInterface* image_provider, + LLUIAudioCallback audio_callback, + LLUIAudioCallback deferred_audio_callback) +: mSettingGroups(settings), +mAudioCallback(audio_callback), +mDeferredAudioCallback(deferred_audio_callback), +mWindow(NULL), // set later in startup +mRootView(NULL), +mHelpImpl(NULL) +{ + LLRender2D::initParamSingleton(image_provider); + + if ((get_ptr_in_map(mSettingGroups, std::string("config")) == NULL) || + (get_ptr_in_map(mSettingGroups, std::string("floater")) == NULL) || + (get_ptr_in_map(mSettingGroups, std::string("ignores")) == NULL)) + { + LL_ERRS() << "Failure to initialize configuration groups" << LL_ENDL; + } + + LLFontGL::sShadowColor = LLUIColorTable::instance().getColor("ColorDropShadow"); + + LLUICtrl::CommitCallbackRegistry::Registrar& reg = LLUICtrl::CommitCallbackRegistry::defaultRegistrar(); + + // Callbacks for associating controls with floater visibility: + reg.add("Floater.Toggle", boost::bind(&LLFloaterReg::toggleInstance, _2, LLSD())); + reg.add("Floater.ToggleOrBringToFront", boost::bind(&LLFloaterReg::toggleInstanceOrBringToFront, _2, LLSD())); + reg.add("Floater.Show", boost::bind(&LLFloaterReg::showInstance, _2, LLSD(), false)); + reg.add("Floater.ShowOrBringToFront", boost::bind(&LLFloaterReg::showInstanceOrBringToFront, _2, LLSD())); + reg.add("Floater.Hide", boost::bind(&LLFloaterReg::hideInstance, _2, LLSD())); + + // Button initialization callback for toggle buttons + reg.add("Button.SetFloaterToggle", boost::bind(&LLButton::setFloaterToggle, _1, _2)); + + // Button initialization callback for toggle buttons on dockable floaters + reg.add("Button.SetDockableFloaterToggle", boost::bind(&LLButton::setDockableFloaterToggle, _1, _2)); + + // Display the help topic for the current context + reg.add("Button.ShowHelp", boost::bind(&LLButton::showHelp, _1, _2)); + + // Currently unused, but kept for reference: + reg.add("Button.ToggleFloater", boost::bind(&LLButton::toggleFloaterAndSetToggleState, _1, _2)); + + // Used by menus along with Floater.Toggle to display visibility as a check-mark + LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.Visible", boost::bind(&LLFloaterReg::instanceVisible, _2, LLSD())); + LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.IsOpen", boost::bind(&LLFloaterReg::instanceVisible, _2, LLSD())); + + // Parse the master list of commands + LLCommandManager::load(); +} + +void LLUI::setPopupFuncs(const add_popup_t& add_popup, const remove_popup_t& remove_popup, const clear_popups_t& clear_popups) +{ + mAddPopupFunc = add_popup; + mRemovePopupFunc = remove_popup; + mClearPopupsFunc = clear_popups; +} + +void LLUI::setMousePositionScreen(S32 x, S32 y) +{ +#if defined(LL_DARWIN) + S32 screen_x = ll_round(((F32)x * getScaleFactor().mV[VX]) / LLView::getWindow()->getSystemUISize()); + S32 screen_y = ll_round(((F32)y * getScaleFactor().mV[VY]) / LLView::getWindow()->getSystemUISize()); +#else + S32 screen_x = ll_round((F32)x * getScaleFactor().mV[VX]); + S32 screen_y = ll_round((F32)y * getScaleFactor().mV[VY]); +#endif + + LLView::getWindow()->setCursorPosition(LLCoordGL(screen_x, screen_y).convert()); +} + +void LLUI::getMousePositionScreen(S32 *x, S32 *y) +{ + LLCoordWindow cursor_pos_window; + getWindow()->getCursorPosition(&cursor_pos_window); + LLCoordGL cursor_pos_gl(cursor_pos_window.convert()); + *x = ll_round((F32)cursor_pos_gl.mX / getScaleFactor().mV[VX]); + *y = ll_round((F32)cursor_pos_gl.mY / getScaleFactor().mV[VY]); +} + +void LLUI::setMousePositionLocal(const LLView* viewp, S32 x, S32 y) +{ + S32 screen_x, screen_y; + viewp->localPointToScreen(x, y, &screen_x, &screen_y); + + setMousePositionScreen(screen_x, screen_y); +} + +void LLUI::getMousePositionLocal(const LLView* viewp, S32 *x, S32 *y) +{ + S32 screen_x, screen_y; + getMousePositionScreen(&screen_x, &screen_y); + viewp->screenPointToLocal(screen_x, screen_y, x, y); +} + + +// On Windows, the user typically sets the language when they install the +// app (by running it with a shortcut that sets InstallLanguage). On Mac, +// or on Windows if the SecondLife.exe executable is run directly, the +// language follows the OS language. In all cases the user can override +// the language manually in preferences. JC +std::string LLUI::getUILanguage() +{ + std::string language = "en"; + if (mSettingGroups["config"]) + { + language = mSettingGroups["config"]->getString("Language"); + if (language.empty() || language == "default") + { + language = mSettingGroups["config"]->getString("InstallLanguage"); + } + if (language.empty() || language == "default") + { + language = mSettingGroups["config"]->getString("SystemLanguage"); + } + if (language.empty() || language == "default") + { + language = "en"; + } + } + return language; +} + +// static +std::string LLUI::getLanguage() +{ + // Note: lldateutil_test redefines this function + return LLUI::getInstance()->getUILanguage(); +} + +struct SubDir : public LLInitParam::Block +{ + Mandatory value; + + SubDir() + : value("value") + {} +}; + +struct Directory : public LLInitParam::Block +{ + Multiple > subdirs; + + Directory() + : subdirs("subdir") + {} +}; + +struct Paths : public LLInitParam::Block +{ + Multiple > directories; + + Paths() + : directories("directory") + {} +}; + + +//static +std::string LLUI::locateSkin(const std::string& filename) +{ + std::string found_file = filename; + if (gDirUtilp->fileExists(found_file)) + { + return found_file; + } + + found_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); // Should be CUSTOM_SKINS? + if (gDirUtilp->fileExists(found_file)) + { + return found_file; + } + + found_file = gDirUtilp->findSkinnedFilename(LLDir::XUI, filename); + if (! found_file.empty()) + { + return found_file; + } + + found_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, filename); + if (gDirUtilp->fileExists(found_file)) + { + return found_file; + } + LL_WARNS("LLUI") << "Can't find '" << filename + << "' in user settings, any skin directory or app_settings" << LL_ENDL; + return ""; +} + +LLVector2 LLUI::getWindowSize() +{ + LLCoordWindow window_rect; + mWindow->getSize(&window_rect); + + return LLVector2(window_rect.mX / getScaleFactor().mV[VX], window_rect.mY / getScaleFactor().mV[VY]); +} + +void LLUI::screenPointToGL(S32 screen_x, S32 screen_y, S32 *gl_x, S32 *gl_y) +{ + *gl_x = ll_round((F32)screen_x * getScaleFactor().mV[VX]); + *gl_y = ll_round((F32)screen_y * getScaleFactor().mV[VY]); +} + +void LLUI::glPointToScreen(S32 gl_x, S32 gl_y, S32 *screen_x, S32 *screen_y) +{ + *screen_x = ll_round((F32)gl_x / getScaleFactor().mV[VX]); + *screen_y = ll_round((F32)gl_y / getScaleFactor().mV[VY]); +} + +void LLUI::screenRectToGL(const LLRect& screen, LLRect *gl) +{ + screenPointToGL(screen.mLeft, screen.mTop, &gl->mLeft, &gl->mTop); + screenPointToGL(screen.mRight, screen.mBottom, &gl->mRight, &gl->mBottom); +} + +void LLUI::glRectToScreen(const LLRect& gl, LLRect *screen) +{ + glPointToScreen(gl.mLeft, gl.mTop, &screen->mLeft, &screen->mTop); + glPointToScreen(gl.mRight, gl.mBottom, &screen->mRight, &screen->mBottom); +} + + +LLControlGroup& LLUI::getControlControlGroup (const std::string& controlname) +{ + for (settings_map_t::iterator itor = mSettingGroups.begin(); + itor != mSettingGroups.end(); ++itor) + { + LLControlGroup* control_group = itor->second; + if(control_group != NULL) + { + if (control_group->controlExists(controlname)) + return *control_group; + } + } + + return *mSettingGroups["config"]; // default group +} + +void LLUI::addPopup(LLView* viewp) +{ + if (mAddPopupFunc) + { + mAddPopupFunc(viewp); + } +} + +void LLUI::removePopup(LLView* viewp) +{ + if (mRemovePopupFunc) + { + mRemovePopupFunc(viewp); + } +} + +void LLUI::clearPopups() +{ + if (mClearPopupsFunc) + { + mClearPopupsFunc(); + } +} + +void LLUI::reportBadKeystroke() +{ + make_ui_sound("UISndBadKeystroke"); +} + +// spawn_x and spawn_y are top left corner of view in screen GL coordinates +void LLUI::positionViewNearMouse(LLView* view, S32 spawn_x, S32 spawn_y) +{ + const S32 CURSOR_HEIGHT = 16; // Approximate "normal" cursor size + const S32 CURSOR_WIDTH = 8; + + LLView* parent = view->getParent(); + + S32 mouse_x; + S32 mouse_y; + getMousePositionScreen(&mouse_x, &mouse_y); + + // If no spawn location provided, use mouse position + if (spawn_x == S32_MAX || spawn_y == S32_MAX) + { + spawn_x = mouse_x + CURSOR_WIDTH; + spawn_y = mouse_y - CURSOR_HEIGHT; + } + + LLRect virtual_window_rect = parent->getLocalRect(); + + LLRect mouse_rect; + const S32 MOUSE_CURSOR_PADDING = 1; + mouse_rect.setLeftTopAndSize(mouse_x - MOUSE_CURSOR_PADDING, + mouse_y + MOUSE_CURSOR_PADDING, + CURSOR_WIDTH + MOUSE_CURSOR_PADDING * 2, + CURSOR_HEIGHT + MOUSE_CURSOR_PADDING * 2); + + S32 local_x, local_y; + // convert screen coordinates to tooltip view-local coordinates + parent->screenPointToLocal(spawn_x, spawn_y, &local_x, &local_y); + + // Start at spawn position (using left/top) + view->setOrigin( local_x, local_y - view->getRect().getHeight()); + // Make sure we're on-screen and not overlapping the mouse + view->translateIntoRectWithExclusion( virtual_window_rect, mouse_rect ); +} + +LLView* LLUI::resolvePath(LLView* context, const std::string& path) +{ + // Nothing about resolvePath() should require non-const LLView*. If caller + // wants non-const, call the const flavor and then cast away const-ness. + return const_cast(resolvePath(const_cast(context), path)); +} + +const LLView* LLUI::resolvePath(const LLView* context, const std::string& path) +{ + // Create an iterator over slash-separated parts of 'path'. Dereferencing + // this iterator returns an iterator_range over the substring. Unlike + // LLStringUtil::getTokens(), this split_iterator doesn't combine adjacent + // delimiters: leading/trailing slash produces an empty substring, double + // slash produces an empty substring. That's what we need. + boost::split_iterator ti(path, boost::first_finder("/")), tend; + + if (ti == tend) + { + // 'path' is completely empty, no navigation + return context; + } + + // leading / means "start at root" + if (ti->empty()) + { + context = getRootView(); + ++ti; + } + + bool recurse = false; + for (; ti != tend && context; ++ti) + { + if (ti->empty()) + { + recurse = true; + } + else + { + std::string part(ti->begin(), ti->end()); + context = context->findChildView(LLURI::unescape(part), recurse); + recurse = false; + } + } + + return context; +} + +//static +LLVector2& LLUI::getScaleFactor() +{ + return LLRender::sUIGLScaleFactor; +} + +//static +void LLUI::setScaleFactor(const LLVector2& scale_factor) +{ + LLRender::sUIGLScaleFactor = scale_factor; +} + + +// LLLocalClipRect and LLScreenClipRect moved to lllocalcliprect.h/cpp + +namespace LLInitParam +{ + ParamValue::ParamValue(const LLUIColor& color) + : super_t(color), + red("red"), + green("green"), + blue("blue"), + alpha("alpha"), + control("") + { + updateBlockFromValue(false); + } + + void ParamValue::updateValueFromBlock() + { + if (control.isProvided() && !control().empty()) + { + updateValue(LLUIColorTable::instance().getColor(control)); + } + else + { + updateValue(LLColor4(red, green, blue, alpha)); + } + } + + void ParamValue::updateBlockFromValue(bool make_block_authoritative) + { + LLColor4 color = getValue(); + red.set(color.mV[VRED], make_block_authoritative); + green.set(color.mV[VGREEN], make_block_authoritative); + blue.set(color.mV[VBLUE], make_block_authoritative); + alpha.set(color.mV[VALPHA], make_block_authoritative); + control.set("", make_block_authoritative); + } + + bool ParamCompare::equals(const LLFontGL* a, const LLFontGL* b) + { + return !(a->getFontDesc() < b->getFontDesc()) + && !(b->getFontDesc() < a->getFontDesc()); + } + + ParamValue::ParamValue(const LLFontGL* fontp) + : super_t(fontp), + name("name"), + size("size"), + style("style") + { + if (!fontp) + { + updateValue(LLFontGL::getFontDefault()); + } + addSynonym(name, ""); + updateBlockFromValue(false); + } + + void ParamValue::updateValueFromBlock() + { + const LLFontGL* res_fontp = LLFontGL::getFontByName(name); + if (res_fontp) + { + updateValue(res_fontp); + return; + } + + U8 fontstyle = 0; + fontstyle = LLFontGL::getStyleFromString(style()); + LLFontDescriptor desc(name(), size(), fontstyle); + const LLFontGL* fontp = LLFontGL::getFont(desc); + if (fontp) + { + updateValue(fontp); + } + else + { + updateValue(LLFontGL::getFontDefault()); + } + } + + void ParamValue::updateBlockFromValue(bool make_block_authoritative) + { + if (getValue()) + { + name.set(LLFontGL::nameFromFont(getValue()), make_block_authoritative); + size.set(LLFontGL::sizeFromFont(getValue()), make_block_authoritative); + style.set(LLFontGL::getStringFromStyle(getValue()->getFontDesc().getStyle()), make_block_authoritative); + } + } + + ParamValue::ParamValue(const LLRect& rect) + : super_t(rect), + left("left"), + top("top"), + right("right"), + bottom("bottom"), + width("width"), + height("height") + { + updateBlockFromValue(false); + } + + void ParamValue::updateValueFromBlock() + { + LLRect rect; + + //calculate from params + // prefer explicit left and right + if (left.isProvided() && right.isProvided()) + { + rect.mLeft = left; + rect.mRight = right; + } + // otherwise use width along with specified side, if any + else if (width.isProvided()) + { + // only right + width provided + if (right.isProvided()) + { + rect.mRight = right; + rect.mLeft = right - width; + } + else // left + width, or just width + { + rect.mLeft = left; + rect.mRight = left + width; + } + } + // just left, just right, or none + else + { + rect.mLeft = left; + rect.mRight = right; + } + + // prefer explicit bottom and top + if (bottom.isProvided() && top.isProvided()) + { + rect.mBottom = bottom; + rect.mTop = top; + } + // otherwise height along with specified side, if any + else if (height.isProvided()) + { + // top + height provided + if (top.isProvided()) + { + rect.mTop = top; + rect.mBottom = top - height; + } + // bottom + height or just height + else + { + rect.mBottom = bottom; + rect.mTop = bottom + height; + } + } + // just bottom, just top, or none + else + { + rect.mBottom = bottom; + rect.mTop = top; + } + updateValue(rect); + } + + void ParamValue::updateBlockFromValue(bool make_block_authoritative) + { + // because of the ambiguity in specifying a rect by position and/or dimensions + // we use the lowest priority pairing so that any valid pairing in xui + // will override those calculated from the rect object + // in this case, that is left+width and bottom+height + LLRect& value = getValue(); + + right.set(value.mRight, false); + left.set(value.mLeft, make_block_authoritative); + width.set(value.getWidth(), make_block_authoritative); + + top.set(value.mTop, false); + bottom.set(value.mBottom, make_block_authoritative); + height.set(value.getHeight(), make_block_authoritative); + } + + ParamValue::ParamValue(const LLCoordGL& coord) + : super_t(coord), + x("x"), + y("y") + { + updateBlockFromValue(false); + } + + void ParamValue::updateValueFromBlock() + { + updateValue(LLCoordGL(x, y)); + } + + void ParamValue::updateBlockFromValue(bool make_block_authoritative) + { + x.set(getValue().mX, make_block_authoritative); + y.set(getValue().mY, make_block_authoritative); + } + + + void TypeValues::declareValues() + { + declare("left", LLFontGL::LEFT); + declare("right", LLFontGL::RIGHT); + declare("center", LLFontGL::HCENTER); + } + + void TypeValues::declareValues() + { + declare("top", LLFontGL::TOP); + declare("center", LLFontGL::VCENTER); + declare("baseline", LLFontGL::BASELINE); + declare("bottom", LLFontGL::BOTTOM); + } + + void TypeValues::declareValues() + { + declare("none", LLFontGL::NO_SHADOW); + declare("hard", LLFontGL::DROP_SHADOW); + declare("soft", LLFontGL::DROP_SHADOW_SOFT); + } +} + diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 4c2ad9d92b..52c5f72a45 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -1,1137 +1,1137 @@ -/** - * @file lluictrl.cpp - * @author James Cook, Richard Nelson, Tom Yedwab - * @brief Abstract base class for UI controls - * - * $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" - -#define LLUICTRL_CPP -#include "lluictrl.h" -#include "llviewereventrecorder.h" -#include "llfocusmgr.h" -#include "llpanel.h" -#include "lluictrlfactory.h" -#include "lltabcontainer.h" -#include "llaccordionctrltab.h" -#include "lluiusage.h" - -static LLDefaultChildRegistry::Register r("ui_ctrl"); - -F32 LLUICtrl::sActiveControlTransparency = 1.0f; -F32 LLUICtrl::sInactiveControlTransparency = 1.0f; - -// Compiler optimization, generate extern template -template class LLUICtrl* LLView::getChild( - const std::string& name, bool recurse) const; - -LLUICtrl::CallbackParam::CallbackParam() -: name("name"), - function_name("function"), - parameter("parameter"), - control_name("control") // Shortcut to control -> "control_name" for backwards compatability -{ - addSynonym(parameter, "userdata"); -} - -LLUICtrl::EnableControls::EnableControls() -: enabled("enabled_control"), - disabled("disabled_control") -{} - -LLUICtrl::ControlVisibility::ControlVisibility() -: visible("visibility_control"), - invisible("invisibility_control") -{ - addSynonym(visible, "visiblity_control"); - addSynonym(invisible, "invisiblity_control"); -} - -LLUICtrl::Params::Params() -: tab_stop("tab_stop", true), - chrome("chrome", false), - requests_front("requests_front", false), - label("label"), - initial_value("value"), - init_callback("init_callback"), - commit_callback("commit_callback"), - validate_callback("validate_callback"), - mouseenter_callback("mouseenter_callback"), - mouseleave_callback("mouseleave_callback"), - control_name("control_name"), - font("font", LLFontGL::getFontEmojiMedium()), - font_halign("halign"), - font_valign("valign"), - length("length"), // ignore LLXMLNode cruft - type("type") // ignore LLXMLNode cruft -{ - addSynonym(initial_value, "initial_value"); -} - -// NOTE: the LLFocusableElement implementation has been moved from here to llfocusmgr.cpp. - -//static -const LLUICtrl::Params& LLUICtrl::getDefaultParams() -{ - return LLUICtrlFactory::getDefaultParams(); -} - - -LLUICtrl::LLUICtrl(const LLUICtrl::Params& p, const LLViewModelPtr& viewmodel) -: LLView(p), - mIsChrome(false), - mRequestsFront(p.requests_front), - mTabStop(false), - mTentative(false), - mViewModel(viewmodel), - mControlVariable(NULL), - mEnabledControlVariable(NULL), - mDisabledControlVariable(NULL), - mMakeVisibleControlVariable(NULL), - mMakeInvisibleControlVariable(NULL), - mCommitSignal(NULL), - mValidateSignal(NULL), - mMouseEnterSignal(NULL), - mMouseLeaveSignal(NULL), - mMouseDownSignal(NULL), - mMouseUpSignal(NULL), - mRightMouseDownSignal(NULL), - mRightMouseUpSignal(NULL), - mDoubleClickSignal(NULL), - mTransparencyType(TT_DEFAULT) -{ -} - -void LLUICtrl::initFromParams(const Params& p) -{ - LLView::initFromParams(p); - - mRequestsFront = p.requests_front; - - setIsChrome(p.chrome); - setControlName(p.control_name); - if(p.enabled_controls.isProvided()) - { - if (p.enabled_controls.enabled.isChosen()) - { - LLControlVariable* control = findControl(p.enabled_controls.enabled); - if (control) - { - setEnabledControlVariable(control); - } - else - { - LL_WARNS() << "Failed to assign 'enabled' control variable to " << getName() - << ": control " << p.enabled_controls.enabled() - << " does not exist." << LL_ENDL; - } - } - else if(p.enabled_controls.disabled.isChosen()) - { - LLControlVariable* control = findControl(p.enabled_controls.disabled); - if (control) - { - setDisabledControlVariable(control); - } - else - { - LL_WARNS() << "Failed to assign 'disabled' control variable to " << getName() - << ": control " << p.enabled_controls.disabled() - << " does not exist." << LL_ENDL; - } - } - } - if(p.controls_visibility.isProvided()) - { - if (p.controls_visibility.visible.isChosen()) - { - LLControlVariable* control = findControl(p.controls_visibility.visible); - if (control) - { - setMakeVisibleControlVariable(control); - } - else - { - LL_WARNS() << "Failed to assign visibility control variable to " << getName() - << ": control " << p.controls_visibility.visible() - << " does not exist." << LL_ENDL; - } - } - else if (p.controls_visibility.invisible.isChosen()) - { - LLControlVariable* control = findControl(p.controls_visibility.invisible); - if (control) - { - setMakeInvisibleControlVariable(control); - } - else - { - LL_WARNS() << "Failed to assign invisibility control variable to " << getName() - << ": control " << p.controls_visibility.invisible() - << " does not exist." << LL_ENDL; - } - } - } - - setTabStop(p.tab_stop); - - if (p.initial_value.isProvided() - && !p.control_name.isProvided()) - { - setValue(p.initial_value); - } - - if (p.commit_callback.isProvided()) - { - setCommitCallback(initCommitCallback(p.commit_callback)); - } - - if (p.validate_callback.isProvided()) - { - setValidateCallback(initEnableCallback(p.validate_callback)); - } - - if (p.init_callback.isProvided()) - { - if (p.init_callback.function.isProvided()) - { - p.init_callback.function()(this, p.init_callback.parameter); - } - else - { - commit_callback_t* initfunc = (CommitCallbackRegistry::getValue(p.init_callback.function_name)); - if (initfunc) - { - (*initfunc)(this, p.init_callback.parameter); - } - } - } - - if(p.mouseenter_callback.isProvided()) - { - setMouseEnterCallback(initCommitCallback(p.mouseenter_callback)); - } - - if(p.mouseleave_callback.isProvided()) - { - setMouseLeaveCallback(initCommitCallback(p.mouseleave_callback)); - } -} - - -LLUICtrl::~LLUICtrl() -{ - gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() - - if( gFocusMgr.getTopCtrl() == this ) - { - LL_WARNS() << "UI Control holding top ctrl deleted: " << getName() << ". Top view removed." << LL_ENDL; - gFocusMgr.removeTopCtrlWithoutCallback( this ); - } - - delete mCommitSignal; - delete mValidateSignal; - delete mMouseEnterSignal; - delete mMouseLeaveSignal; - delete mMouseDownSignal; - delete mMouseUpSignal; - delete mRightMouseDownSignal; - delete mRightMouseUpSignal; - delete mDoubleClickSignal; -} - -void default_commit_handler(LLUICtrl* ctrl, const LLSD& param) -{} - -bool default_enable_handler(LLUICtrl* ctrl, const LLSD& param) -{ - return true; -} - - -LLUICtrl::commit_signal_t::slot_type LLUICtrl::initCommitCallback(const CommitCallbackParam& cb) -{ - if (cb.function.isProvided()) - { - if (cb.parameter.isProvided()) - return boost::bind(cb.function(), _1, cb.parameter); - else - return cb.function(); - } - else - { - std::string function_name = cb.function_name; - setFunctionName(function_name); - commit_callback_t* func = (CommitCallbackRegistry::getValue(function_name)); - if (func) - { - if (cb.parameter.isProvided()) - return boost::bind((*func), _1, cb.parameter); - else - return commit_signal_t::slot_type(*func); - } - else if (!function_name.empty()) - { - LL_WARNS() << "No callback found for: '" << function_name << "' in control: " << getName() << LL_ENDL; - } - } - return default_commit_handler; -} - -LLUICtrl::enable_signal_t::slot_type LLUICtrl::initEnableCallback(const EnableCallbackParam& cb) -{ - // Set the callback function - if (cb.function.isProvided()) - { - if (cb.parameter.isProvided()) - return boost::bind(cb.function(), this, cb.parameter); - else - return cb.function(); - } - else - { - enable_callback_t* func = (EnableCallbackRegistry::getValue(cb.function_name)); - if (func) - { - if (cb.parameter.isProvided()) - return boost::bind((*func), this, cb.parameter); - else - return enable_signal_t::slot_type(*func); - } - } - return default_enable_handler; -} - -// virtual -void LLUICtrl::onMouseEnter(S32 x, S32 y, MASK mask) -{ - if (mMouseEnterSignal) - { - (*mMouseEnterSignal)(this, getValue()); - } -} - -// virtual -void LLUICtrl::onMouseLeave(S32 x, S32 y, MASK mask) -{ - if(mMouseLeaveSignal) - { - (*mMouseLeaveSignal)(this, getValue()); - } -} - -//virtual -bool LLUICtrl::handleMouseDown(S32 x, S32 y, MASK mask) -{ - - LL_DEBUGS() << "LLUICtrl::handleMouseDown calling LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << LL_ENDL; - - bool handled = LLView::handleMouseDown(x,y,mask); - - if (mMouseDownSignal) - { - (*mMouseDownSignal)(this,x,y,mask); - } - LL_DEBUGS() << "LLUICtrl::handleMousedown - handled is returning as: " << handled << " " << LL_ENDL; - - if (handled) { - LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-56,-56,getPathname()); - } - return handled; -} - -//virtual -bool LLUICtrl::handleMouseUp(S32 x, S32 y, MASK mask) -{ - - LL_DEBUGS() << "LLUICtrl::handleMouseUp calling LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << LL_ENDL; - - bool handled = LLView::handleMouseUp(x,y,mask); - if (handled) { - LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-56,-56,getPathname()); - } - if (mMouseUpSignal) - { - (*mMouseUpSignal)(this,x,y,mask); - } - - LL_DEBUGS() << "LLUICtrl::handleMouseUp - handled for xui " << getPathname() << " - is returning as: " << handled << " " << LL_ENDL; - - return handled; -} - -//virtual -bool LLUICtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = LLView::handleRightMouseDown(x,y,mask); - if (mRightMouseDownSignal) - { - (*mRightMouseDownSignal)(this,x,y,mask); - } - return handled; -} - -//virtual -bool LLUICtrl::handleRightMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = LLView::handleRightMouseUp(x,y,mask); - if(mRightMouseUpSignal) - { - (*mRightMouseUpSignal)(this,x,y,mask); - } - return handled; -} - -bool LLUICtrl::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - bool handled = LLView::handleDoubleClick(x, y, mask); - if (mDoubleClickSignal) - { - (*mDoubleClickSignal)(this, x, y, mask); - } - return handled; -} - -// can't tab to children of a non-tab-stop widget -bool LLUICtrl::canFocusChildren() const -{ - return hasTabStop(); -} - - -void LLUICtrl::onCommit() -{ - if (mCommitSignal) - { - if (!mFunctionName.empty()) - { - LL_DEBUGS("UIUsage") << "calling commit function " << mFunctionName << LL_ENDL; - LLUIUsage::instance().logCommand(mFunctionName); - LLUIUsage::instance().logControl(getPathname()); - } - else - { - //LL_DEBUGS("UIUsage") << "calling commit function " << "UNKNOWN" << LL_ENDL; - } - (*mCommitSignal)(this, getValue()); - } -} - -//virtual -bool LLUICtrl::isCtrl() const -{ - return true; -} - -//virtual -void LLUICtrl::setValue(const LLSD& value) -{ - mViewModel->setValue(value); -} - -//virtual -LLSD LLUICtrl::getValue() const -{ - return mViewModel->getValue(); -} - -/// When two widgets are displaying the same data (e.g. during a skin -/// change), share their ViewModel. -void LLUICtrl::shareViewModelFrom(const LLUICtrl& other) -{ - // Because mViewModel is an LLViewModelPtr, this assignment will quietly - // dispose of the previous LLViewModel -- unless it's already shared by - // somebody else. - mViewModel = other.mViewModel; -} - -//virtual -LLViewModel* LLUICtrl::getViewModel() const -{ - return mViewModel; -} - -//virtual -bool LLUICtrl::postBuild() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - // - // Find all of the children that want to be in front and move them to the front - // - - if (getChildCount() > 0) - { - std::vector childrenToMoveToFront; - - for (LLView::child_list_const_iter_t child_it = beginChild(); child_it != endChild(); ++child_it) - { - LLUICtrl* uictrl = dynamic_cast(*child_it); - - if (uictrl && uictrl->mRequestsFront) - { - childrenToMoveToFront.push_back(uictrl); - } - } - - for (std::vector::iterator it = childrenToMoveToFront.begin(); it != childrenToMoveToFront.end(); ++it) - { - sendChildToFront(*it); - } - } - - return LLView::postBuild(); -} - -bool LLUICtrl::setControlValue(const LLSD& value) -{ - if (mControlVariable) - { - mControlVariable->set(value); - return true; - } - return false; -} - -void LLUICtrl::setControlVariable(LLControlVariable* control) -{ - if (mControlVariable) - { - //RN: this will happen in practice, should we try to avoid it? - //LL_WARNS() << "setControlName called twice on same control!" << LL_ENDL; - mControlConnection.disconnect(); // disconnect current signal - mControlVariable = NULL; - } - - if (control) - { - mControlVariable = control; - mControlConnection = mControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("value"))); - setValue(mControlVariable->getValue()); - } -} - -void LLUICtrl::removeControlVariable() -{ - if (mControlVariable) - { - mControlConnection.disconnect(); - mControlVariable = NULL; - } -} - -//virtual -void LLUICtrl::setControlName(const std::string& control_name, LLView *context) -{ - if (context == NULL) - { - context = this; - } - - // Register new listener - if (!control_name.empty()) - { - LLControlVariable* control = context->findControl(control_name); - if (!control) - { - LL_WARNS() << "Failed to assign control variable to " << getName() - << ": control "<< control_name << " does not exist." << LL_ENDL; - } - setControlVariable(control); - } -} - -void LLUICtrl::setEnabledControlVariable(LLControlVariable* control) -{ - if (mEnabledControlVariable) - { - mEnabledControlConnection.disconnect(); // disconnect current signal - mEnabledControlVariable = NULL; - } - if (control) - { - mEnabledControlVariable = control; - mEnabledControlConnection = mEnabledControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("enabled"))); - setEnabled(mEnabledControlVariable->getValue().asBoolean()); - } -} - -void LLUICtrl::setDisabledControlVariable(LLControlVariable* control) -{ - if (mDisabledControlVariable) - { - mDisabledControlConnection.disconnect(); // disconnect current signal - mDisabledControlVariable = NULL; - } - if (control) - { - mDisabledControlVariable = control; - mDisabledControlConnection = mDisabledControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("disabled"))); - setEnabled(!(mDisabledControlVariable->getValue().asBoolean())); - } -} - -void LLUICtrl::setMakeVisibleControlVariable(LLControlVariable* control) -{ - if (mMakeVisibleControlVariable) - { - mMakeVisibleControlConnection.disconnect(); // disconnect current signal - mMakeVisibleControlVariable = NULL; - } - if (control) - { - mMakeVisibleControlVariable = control; - mMakeVisibleControlConnection = mMakeVisibleControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("visible"))); - setVisible(mMakeVisibleControlVariable->getValue().asBoolean()); - } -} - -void LLUICtrl::setMakeInvisibleControlVariable(LLControlVariable* control) -{ - if (mMakeInvisibleControlVariable) - { - mMakeInvisibleControlConnection.disconnect(); // disconnect current signal - mMakeInvisibleControlVariable = NULL; - } - if (control) - { - mMakeInvisibleControlVariable = control; - mMakeInvisibleControlConnection = mMakeInvisibleControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("invisible"))); - setVisible(!(mMakeInvisibleControlVariable->getValue().asBoolean())); - } -} - -void LLUICtrl::setFunctionName(const std::string& function_name) -{ - mFunctionName = function_name; -} - -// static -bool LLUICtrl::controlListener(const LLSD& newvalue, LLHandle handle, std::string type) -{ - LLUICtrl* ctrl = handle.get(); - if (ctrl) - { - if (type == "value") - { - ctrl->setValue(newvalue); - return true; - } - else if (type == "enabled") - { - ctrl->setEnabled(newvalue.asBoolean()); - return true; - } - else if(type =="disabled") - { - ctrl->setEnabled(!newvalue.asBoolean()); - return true; - } - else if (type == "visible") - { - ctrl->setVisible(newvalue.asBoolean()); - return true; - } - else if (type == "invisible") - { - ctrl->setVisible(!newvalue.asBoolean()); - return true; - } - } - return false; -} - -// virtual -bool LLUICtrl::setTextArg( const std::string& key, const LLStringExplicit& text ) -{ - return false; -} - -// virtual -bool LLUICtrl::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - return false; -} - -// virtual -LLCtrlSelectionInterface* LLUICtrl::getSelectionInterface() -{ - return NULL; -} - -// virtual -LLCtrlListInterface* LLUICtrl::getListInterface() -{ - return NULL; -} - -// virtual -LLCtrlScrollInterface* LLUICtrl::getScrollInterface() -{ - return NULL; -} - -bool LLUICtrl::hasFocus() const -{ - return (gFocusMgr.childHasKeyboardFocus(this)); -} - -void LLUICtrl::setFocus(bool b) -{ - // focus NEVER goes to ui ctrls that are disabled! - if (!getEnabled()) - { - return; - } - if( b ) - { - if (!hasFocus()) - { - gFocusMgr.setKeyboardFocus( this ); - } - } - else - { - if( gFocusMgr.childHasKeyboardFocus(this)) - { - gFocusMgr.setKeyboardFocus( NULL ); - } - } -} - -// virtual -void LLUICtrl::setTabStop( bool b ) -{ - mTabStop = b; -} - -// virtual -bool LLUICtrl::hasTabStop() const -{ - return mTabStop; -} - -// virtual -bool LLUICtrl::acceptsTextInput() const -{ - return false; -} - -//virtual -bool LLUICtrl::isDirty() const -{ - return mViewModel->isDirty(); -}; - -//virtual -void LLUICtrl::resetDirty() -{ - mViewModel->resetDirty(); -} - -// virtual -void LLUICtrl::onTabInto() -{ - onUpdateScrollToChild(this); -} - -// virtual -void LLUICtrl::clear() -{ -} - -// virtual -void LLUICtrl::setIsChrome(bool is_chrome) -{ - mIsChrome = is_chrome; -} - -// virtual -bool LLUICtrl::getIsChrome() const -{ - if (mIsChrome) - return true; - - LLView* parent_ctrl = getParent(); - while (parent_ctrl) - { - if (parent_ctrl->isCtrl()) - return ((LLUICtrl*)parent_ctrl)->getIsChrome(); - - parent_ctrl = parent_ctrl->getParent(); - } - - return false; -} - - -bool LLUICtrl::focusFirstItem(bool prefer_text_fields, bool focus_flash) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - // try to select default tab group child - LLViewQuery query = getTabOrderQuery(); - child_list_t result = query(this); - if(result.size() > 0) - { - LLUICtrl * ctrl = static_cast(result.back()); - if(!ctrl->hasFocus()) - { - ctrl->setFocus(true); - ctrl->onTabInto(); - if(focus_flash) - { - gFocusMgr.triggerFocusFlash(); - } - } - return true; - } - // search for text field first - if(prefer_text_fields) - { - LLViewQuery query = getTabOrderQuery(); - query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); - child_list_t result = query(this); - if(result.size() > 0) - { - LLUICtrl * ctrl = static_cast(result.back()); - if(!ctrl->hasFocus()) - { - ctrl->setFocus(true); - ctrl->onTabInto(); - if(focus_flash) - { - gFocusMgr.triggerFocusFlash(); - } - } - return true; - } - } - // no text field found, or we don't care about text fields - result = getTabOrderQuery().run(this); - if(result.size() > 0) - { - LLUICtrl * ctrl = static_cast(result.back()); - if(!ctrl->hasFocus()) - { - ctrl->setFocus(true); - ctrl->onTabInto(); - if(focus_flash) - { - gFocusMgr.triggerFocusFlash(); - } - } - return true; - } - return false; -} - - -bool LLUICtrl::focusNextItem(bool text_fields_only) -{ - // this assumes that this method is called on the focus root. - LLViewQuery query = getTabOrderQuery(); - static LLUICachedControl tab_to_text_fields_only ("TabToTextFieldsOnly", false); - if(text_fields_only || tab_to_text_fields_only) - { - query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); - } - child_list_t result = query(this); - return focusNext(result); -} - -bool LLUICtrl::focusPrevItem(bool text_fields_only) -{ - // this assumes that this method is called on the focus root. - LLViewQuery query = getTabOrderQuery(); - static LLUICachedControl tab_to_text_fields_only ("TabToTextFieldsOnly", false); - if(text_fields_only || tab_to_text_fields_only) - { - query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); - } - child_list_t result = query(this); - return focusPrev(result); -} - -LLUICtrl* LLUICtrl::findRootMostFocusRoot() -{ - LLUICtrl* focus_root = NULL; - LLUICtrl* next_view = this; - while(next_view && next_view->hasTabStop()) - { - if (next_view->isFocusRoot()) - { - focus_root = next_view; - } - next_view = next_view->getParentUICtrl(); - } - - return focus_root; -} - -// Skip over any parents that are not LLUICtrl's -// Used in focus logic since only LLUICtrl elements can have focus -LLUICtrl* LLUICtrl::getParentUICtrl() const -{ - LLView* parent = getParent(); - while (parent) - { - if (parent->isCtrl()) - { - return (LLUICtrl*)(parent); - } - else - { - parent = parent->getParent(); - } - } - return NULL; -} - -bool LLUICtrl::findHelpTopic(std::string& help_topic_out) -{ - LLUICtrl* ctrl = this; - - // search back through the control's parents for a panel - // or tab with a help_topic string defined - while (ctrl) - { - LLPanel *panel = dynamic_cast(ctrl); - - if (panel) - { - - LLView *child; - LLPanel *subpanel = NULL; - - // does the panel have a sub-panel with a help topic? - bfs_tree_iterator_t it = beginTreeBFS(); - // skip ourselves - ++it; - for (; it != endTreeBFS(); ++it) - { - child = *it; - // do we have a panel with a help topic? - LLPanel *panel = dynamic_cast(child); - if (panel && panel->isInVisibleChain() && !panel->getHelpTopic().empty()) - { - subpanel = panel; - break; - } - } - - if (subpanel) - { - help_topic_out = subpanel->getHelpTopic(); - return true; // success (subpanel) - } - - // does the panel have an active tab with a help topic? - LLPanel *tab_panel = NULL; - - it = beginTreeBFS(); - // skip ourselves - ++it; - for (; it != endTreeBFS(); ++it) - { - child = *it; - LLPanel *curTabPanel = NULL; - - // do we have a tab container? - LLTabContainer *tab = dynamic_cast(child); - if (tab && tab->getVisible()) - { - curTabPanel = tab->getCurrentPanel(); - } - - // do we have an accordion tab? - LLAccordionCtrlTab* accordion = dynamic_cast(child); - if (accordion && accordion->getDisplayChildren()) - { - curTabPanel = dynamic_cast(accordion->getAccordionView()); - } - - // if we found a valid tab, does it have a help topic? - if (curTabPanel && !curTabPanel->getHelpTopic().empty()) - { - tab_panel = curTabPanel; - break; - } - } - - if (tab_panel) - { - help_topic_out = tab_panel->getHelpTopic(); - return true; // success (tab) - } - - // otherwise, does the panel have a help topic itself? - if (!panel->getHelpTopic().empty()) - { - help_topic_out = panel->getHelpTopic(); - return true; // success (panel) - } - } - - ctrl = ctrl->getParentUICtrl(); - } - - return false; // no help topic found -} - -// *TODO: Deprecate; for backwards compatability only: -boost::signals2::connection LLUICtrl::setCommitCallback( boost::function cb, void* data) -{ - return setCommitCallback( boost::bind(cb, _1, data)); -} -boost::signals2::connection LLUICtrl::setValidateBeforeCommit( boost::function cb ) -{ - if (!mValidateSignal) mValidateSignal = new enable_signal_t(); - - return mValidateSignal->connect(boost::bind(cb, _2)); -} - -// virtual -void LLUICtrl::setTentative(bool b) -{ - mTentative = b; -} - -// virtual -bool LLUICtrl::getTentative() const -{ - return mTentative; -} - -// virtual -void LLUICtrl::setColor(const LLColor4& color) -{ } - -F32 LLUICtrl::getCurrentTransparency() -{ - F32 alpha = 0; - - switch(mTransparencyType) - { - case TT_DEFAULT: - alpha = getDrawContext().mAlpha; - break; - - case TT_ACTIVE: - alpha = sActiveControlTransparency; - break; - - case TT_INACTIVE: - alpha = sInactiveControlTransparency; - break; - - case TT_FADING: - alpha = sInactiveControlTransparency / 2; - break; - } - - return alpha; -} - -void LLUICtrl::setTransparencyType(ETypeTransparency type) -{ - mTransparencyType = type; -} - -boost::signals2::connection LLUICtrl::setCommitCallback(const CommitCallbackParam& cb) -{ - return setCommitCallback(initCommitCallback(cb)); -} - -boost::signals2::connection LLUICtrl::setValidateCallback(const EnableCallbackParam& cb) -{ - return setValidateCallback(initEnableCallback(cb)); -} - -boost::signals2::connection LLUICtrl::setCommitCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mCommitSignal) mCommitSignal = new commit_signal_t(); - - return mCommitSignal->connect(cb); -} - -boost::signals2::connection LLUICtrl::setValidateCallback( const enable_signal_t::slot_type& cb ) -{ - if (!mValidateSignal) mValidateSignal = new enable_signal_t(); - - return mValidateSignal->connect(cb); -} - -boost::signals2::connection LLUICtrl::setMouseEnterCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mMouseEnterSignal) mMouseEnterSignal = new commit_signal_t(); - - return mMouseEnterSignal->connect(cb); -} - -boost::signals2::connection LLUICtrl::setMouseLeaveCallback( const commit_signal_t::slot_type& cb ) -{ - if (!mMouseLeaveSignal) mMouseLeaveSignal = new commit_signal_t(); - - return mMouseLeaveSignal->connect(cb); -} - -boost::signals2::connection LLUICtrl::setMouseDownCallback( const mouse_signal_t::slot_type& cb ) -{ - if (!mMouseDownSignal) mMouseDownSignal = new mouse_signal_t(); - - return mMouseDownSignal->connect(cb); -} - -boost::signals2::connection LLUICtrl::setMouseUpCallback( const mouse_signal_t::slot_type& cb ) -{ - if (!mMouseUpSignal) mMouseUpSignal = new mouse_signal_t(); - - return mMouseUpSignal->connect(cb); -} - -boost::signals2::connection LLUICtrl::setRightMouseDownCallback( const mouse_signal_t::slot_type& cb ) -{ - if (!mRightMouseDownSignal) mRightMouseDownSignal = new mouse_signal_t(); - - return mRightMouseDownSignal->connect(cb); -} - -boost::signals2::connection LLUICtrl::setRightMouseUpCallback( const mouse_signal_t::slot_type& cb ) -{ - if (!mRightMouseUpSignal) mRightMouseUpSignal = new mouse_signal_t(); - - return mRightMouseUpSignal->connect(cb); -} - -boost::signals2::connection LLUICtrl::setDoubleClickCallback( const mouse_signal_t::slot_type& cb ) -{ - if (!mDoubleClickSignal) mDoubleClickSignal = new mouse_signal_t(); - - return mDoubleClickSignal->connect(cb); -} - -void LLUICtrl::addInfo(LLSD & info) -{ - LLView::addInfo(info); - info["value"] = getValue(); -} +/** + * @file lluictrl.cpp + * @author James Cook, Richard Nelson, Tom Yedwab + * @brief Abstract base class for UI controls + * + * $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" + +#define LLUICTRL_CPP +#include "lluictrl.h" +#include "llviewereventrecorder.h" +#include "llfocusmgr.h" +#include "llpanel.h" +#include "lluictrlfactory.h" +#include "lltabcontainer.h" +#include "llaccordionctrltab.h" +#include "lluiusage.h" + +static LLDefaultChildRegistry::Register r("ui_ctrl"); + +F32 LLUICtrl::sActiveControlTransparency = 1.0f; +F32 LLUICtrl::sInactiveControlTransparency = 1.0f; + +// Compiler optimization, generate extern template +template class LLUICtrl* LLView::getChild( + const std::string& name, bool recurse) const; + +LLUICtrl::CallbackParam::CallbackParam() +: name("name"), + function_name("function"), + parameter("parameter"), + control_name("control") // Shortcut to control -> "control_name" for backwards compatability +{ + addSynonym(parameter, "userdata"); +} + +LLUICtrl::EnableControls::EnableControls() +: enabled("enabled_control"), + disabled("disabled_control") +{} + +LLUICtrl::ControlVisibility::ControlVisibility() +: visible("visibility_control"), + invisible("invisibility_control") +{ + addSynonym(visible, "visiblity_control"); + addSynonym(invisible, "invisiblity_control"); +} + +LLUICtrl::Params::Params() +: tab_stop("tab_stop", true), + chrome("chrome", false), + requests_front("requests_front", false), + label("label"), + initial_value("value"), + init_callback("init_callback"), + commit_callback("commit_callback"), + validate_callback("validate_callback"), + mouseenter_callback("mouseenter_callback"), + mouseleave_callback("mouseleave_callback"), + control_name("control_name"), + font("font", LLFontGL::getFontEmojiMedium()), + font_halign("halign"), + font_valign("valign"), + length("length"), // ignore LLXMLNode cruft + type("type") // ignore LLXMLNode cruft +{ + addSynonym(initial_value, "initial_value"); +} + +// NOTE: the LLFocusableElement implementation has been moved from here to llfocusmgr.cpp. + +//static +const LLUICtrl::Params& LLUICtrl::getDefaultParams() +{ + return LLUICtrlFactory::getDefaultParams(); +} + + +LLUICtrl::LLUICtrl(const LLUICtrl::Params& p, const LLViewModelPtr& viewmodel) +: LLView(p), + mIsChrome(false), + mRequestsFront(p.requests_front), + mTabStop(false), + mTentative(false), + mViewModel(viewmodel), + mControlVariable(NULL), + mEnabledControlVariable(NULL), + mDisabledControlVariable(NULL), + mMakeVisibleControlVariable(NULL), + mMakeInvisibleControlVariable(NULL), + mCommitSignal(NULL), + mValidateSignal(NULL), + mMouseEnterSignal(NULL), + mMouseLeaveSignal(NULL), + mMouseDownSignal(NULL), + mMouseUpSignal(NULL), + mRightMouseDownSignal(NULL), + mRightMouseUpSignal(NULL), + mDoubleClickSignal(NULL), + mTransparencyType(TT_DEFAULT) +{ +} + +void LLUICtrl::initFromParams(const Params& p) +{ + LLView::initFromParams(p); + + mRequestsFront = p.requests_front; + + setIsChrome(p.chrome); + setControlName(p.control_name); + if(p.enabled_controls.isProvided()) + { + if (p.enabled_controls.enabled.isChosen()) + { + LLControlVariable* control = findControl(p.enabled_controls.enabled); + if (control) + { + setEnabledControlVariable(control); + } + else + { + LL_WARNS() << "Failed to assign 'enabled' control variable to " << getName() + << ": control " << p.enabled_controls.enabled() + << " does not exist." << LL_ENDL; + } + } + else if(p.enabled_controls.disabled.isChosen()) + { + LLControlVariable* control = findControl(p.enabled_controls.disabled); + if (control) + { + setDisabledControlVariable(control); + } + else + { + LL_WARNS() << "Failed to assign 'disabled' control variable to " << getName() + << ": control " << p.enabled_controls.disabled() + << " does not exist." << LL_ENDL; + } + } + } + if(p.controls_visibility.isProvided()) + { + if (p.controls_visibility.visible.isChosen()) + { + LLControlVariable* control = findControl(p.controls_visibility.visible); + if (control) + { + setMakeVisibleControlVariable(control); + } + else + { + LL_WARNS() << "Failed to assign visibility control variable to " << getName() + << ": control " << p.controls_visibility.visible() + << " does not exist." << LL_ENDL; + } + } + else if (p.controls_visibility.invisible.isChosen()) + { + LLControlVariable* control = findControl(p.controls_visibility.invisible); + if (control) + { + setMakeInvisibleControlVariable(control); + } + else + { + LL_WARNS() << "Failed to assign invisibility control variable to " << getName() + << ": control " << p.controls_visibility.invisible() + << " does not exist." << LL_ENDL; + } + } + } + + setTabStop(p.tab_stop); + + if (p.initial_value.isProvided() + && !p.control_name.isProvided()) + { + setValue(p.initial_value); + } + + if (p.commit_callback.isProvided()) + { + setCommitCallback(initCommitCallback(p.commit_callback)); + } + + if (p.validate_callback.isProvided()) + { + setValidateCallback(initEnableCallback(p.validate_callback)); + } + + if (p.init_callback.isProvided()) + { + if (p.init_callback.function.isProvided()) + { + p.init_callback.function()(this, p.init_callback.parameter); + } + else + { + commit_callback_t* initfunc = (CommitCallbackRegistry::getValue(p.init_callback.function_name)); + if (initfunc) + { + (*initfunc)(this, p.init_callback.parameter); + } + } + } + + if(p.mouseenter_callback.isProvided()) + { + setMouseEnterCallback(initCommitCallback(p.mouseenter_callback)); + } + + if(p.mouseleave_callback.isProvided()) + { + setMouseLeaveCallback(initCommitCallback(p.mouseleave_callback)); + } +} + + +LLUICtrl::~LLUICtrl() +{ + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() + + if( gFocusMgr.getTopCtrl() == this ) + { + LL_WARNS() << "UI Control holding top ctrl deleted: " << getName() << ". Top view removed." << LL_ENDL; + gFocusMgr.removeTopCtrlWithoutCallback( this ); + } + + delete mCommitSignal; + delete mValidateSignal; + delete mMouseEnterSignal; + delete mMouseLeaveSignal; + delete mMouseDownSignal; + delete mMouseUpSignal; + delete mRightMouseDownSignal; + delete mRightMouseUpSignal; + delete mDoubleClickSignal; +} + +void default_commit_handler(LLUICtrl* ctrl, const LLSD& param) +{} + +bool default_enable_handler(LLUICtrl* ctrl, const LLSD& param) +{ + return true; +} + + +LLUICtrl::commit_signal_t::slot_type LLUICtrl::initCommitCallback(const CommitCallbackParam& cb) +{ + if (cb.function.isProvided()) + { + if (cb.parameter.isProvided()) + return boost::bind(cb.function(), _1, cb.parameter); + else + return cb.function(); + } + else + { + std::string function_name = cb.function_name; + setFunctionName(function_name); + commit_callback_t* func = (CommitCallbackRegistry::getValue(function_name)); + if (func) + { + if (cb.parameter.isProvided()) + return boost::bind((*func), _1, cb.parameter); + else + return commit_signal_t::slot_type(*func); + } + else if (!function_name.empty()) + { + LL_WARNS() << "No callback found for: '" << function_name << "' in control: " << getName() << LL_ENDL; + } + } + return default_commit_handler; +} + +LLUICtrl::enable_signal_t::slot_type LLUICtrl::initEnableCallback(const EnableCallbackParam& cb) +{ + // Set the callback function + if (cb.function.isProvided()) + { + if (cb.parameter.isProvided()) + return boost::bind(cb.function(), this, cb.parameter); + else + return cb.function(); + } + else + { + enable_callback_t* func = (EnableCallbackRegistry::getValue(cb.function_name)); + if (func) + { + if (cb.parameter.isProvided()) + return boost::bind((*func), this, cb.parameter); + else + return enable_signal_t::slot_type(*func); + } + } + return default_enable_handler; +} + +// virtual +void LLUICtrl::onMouseEnter(S32 x, S32 y, MASK mask) +{ + if (mMouseEnterSignal) + { + (*mMouseEnterSignal)(this, getValue()); + } +} + +// virtual +void LLUICtrl::onMouseLeave(S32 x, S32 y, MASK mask) +{ + if(mMouseLeaveSignal) + { + (*mMouseLeaveSignal)(this, getValue()); + } +} + +//virtual +bool LLUICtrl::handleMouseDown(S32 x, S32 y, MASK mask) +{ + + LL_DEBUGS() << "LLUICtrl::handleMouseDown calling LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << LL_ENDL; + + bool handled = LLView::handleMouseDown(x,y,mask); + + if (mMouseDownSignal) + { + (*mMouseDownSignal)(this,x,y,mask); + } + LL_DEBUGS() << "LLUICtrl::handleMousedown - handled is returning as: " << handled << " " << LL_ENDL; + + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-56,-56,getPathname()); + } + return handled; +} + +//virtual +bool LLUICtrl::handleMouseUp(S32 x, S32 y, MASK mask) +{ + + LL_DEBUGS() << "LLUICtrl::handleMouseUp calling LLView)'s handleMouseUp (first initialized xui to: " << getPathname() << " )" << LL_ENDL; + + bool handled = LLView::handleMouseUp(x,y,mask); + if (handled) { + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-56,-56,getPathname()); + } + if (mMouseUpSignal) + { + (*mMouseUpSignal)(this,x,y,mask); + } + + LL_DEBUGS() << "LLUICtrl::handleMouseUp - handled for xui " << getPathname() << " - is returning as: " << handled << " " << LL_ENDL; + + return handled; +} + +//virtual +bool LLUICtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLView::handleRightMouseDown(x,y,mask); + if (mRightMouseDownSignal) + { + (*mRightMouseDownSignal)(this,x,y,mask); + } + return handled; +} + +//virtual +bool LLUICtrl::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = LLView::handleRightMouseUp(x,y,mask); + if(mRightMouseUpSignal) + { + (*mRightMouseUpSignal)(this,x,y,mask); + } + return handled; +} + +bool LLUICtrl::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + bool handled = LLView::handleDoubleClick(x, y, mask); + if (mDoubleClickSignal) + { + (*mDoubleClickSignal)(this, x, y, mask); + } + return handled; +} + +// can't tab to children of a non-tab-stop widget +bool LLUICtrl::canFocusChildren() const +{ + return hasTabStop(); +} + + +void LLUICtrl::onCommit() +{ + if (mCommitSignal) + { + if (!mFunctionName.empty()) + { + LL_DEBUGS("UIUsage") << "calling commit function " << mFunctionName << LL_ENDL; + LLUIUsage::instance().logCommand(mFunctionName); + LLUIUsage::instance().logControl(getPathname()); + } + else + { + //LL_DEBUGS("UIUsage") << "calling commit function " << "UNKNOWN" << LL_ENDL; + } + (*mCommitSignal)(this, getValue()); + } +} + +//virtual +bool LLUICtrl::isCtrl() const +{ + return true; +} + +//virtual +void LLUICtrl::setValue(const LLSD& value) +{ + mViewModel->setValue(value); +} + +//virtual +LLSD LLUICtrl::getValue() const +{ + return mViewModel->getValue(); +} + +/// When two widgets are displaying the same data (e.g. during a skin +/// change), share their ViewModel. +void LLUICtrl::shareViewModelFrom(const LLUICtrl& other) +{ + // Because mViewModel is an LLViewModelPtr, this assignment will quietly + // dispose of the previous LLViewModel -- unless it's already shared by + // somebody else. + mViewModel = other.mViewModel; +} + +//virtual +LLViewModel* LLUICtrl::getViewModel() const +{ + return mViewModel; +} + +//virtual +bool LLUICtrl::postBuild() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + // + // Find all of the children that want to be in front and move them to the front + // + + if (getChildCount() > 0) + { + std::vector childrenToMoveToFront; + + for (LLView::child_list_const_iter_t child_it = beginChild(); child_it != endChild(); ++child_it) + { + LLUICtrl* uictrl = dynamic_cast(*child_it); + + if (uictrl && uictrl->mRequestsFront) + { + childrenToMoveToFront.push_back(uictrl); + } + } + + for (std::vector::iterator it = childrenToMoveToFront.begin(); it != childrenToMoveToFront.end(); ++it) + { + sendChildToFront(*it); + } + } + + return LLView::postBuild(); +} + +bool LLUICtrl::setControlValue(const LLSD& value) +{ + if (mControlVariable) + { + mControlVariable->set(value); + return true; + } + return false; +} + +void LLUICtrl::setControlVariable(LLControlVariable* control) +{ + if (mControlVariable) + { + //RN: this will happen in practice, should we try to avoid it? + //LL_WARNS() << "setControlName called twice on same control!" << LL_ENDL; + mControlConnection.disconnect(); // disconnect current signal + mControlVariable = NULL; + } + + if (control) + { + mControlVariable = control; + mControlConnection = mControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("value"))); + setValue(mControlVariable->getValue()); + } +} + +void LLUICtrl::removeControlVariable() +{ + if (mControlVariable) + { + mControlConnection.disconnect(); + mControlVariable = NULL; + } +} + +//virtual +void LLUICtrl::setControlName(const std::string& control_name, LLView *context) +{ + if (context == NULL) + { + context = this; + } + + // Register new listener + if (!control_name.empty()) + { + LLControlVariable* control = context->findControl(control_name); + if (!control) + { + LL_WARNS() << "Failed to assign control variable to " << getName() + << ": control "<< control_name << " does not exist." << LL_ENDL; + } + setControlVariable(control); + } +} + +void LLUICtrl::setEnabledControlVariable(LLControlVariable* control) +{ + if (mEnabledControlVariable) + { + mEnabledControlConnection.disconnect(); // disconnect current signal + mEnabledControlVariable = NULL; + } + if (control) + { + mEnabledControlVariable = control; + mEnabledControlConnection = mEnabledControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("enabled"))); + setEnabled(mEnabledControlVariable->getValue().asBoolean()); + } +} + +void LLUICtrl::setDisabledControlVariable(LLControlVariable* control) +{ + if (mDisabledControlVariable) + { + mDisabledControlConnection.disconnect(); // disconnect current signal + mDisabledControlVariable = NULL; + } + if (control) + { + mDisabledControlVariable = control; + mDisabledControlConnection = mDisabledControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("disabled"))); + setEnabled(!(mDisabledControlVariable->getValue().asBoolean())); + } +} + +void LLUICtrl::setMakeVisibleControlVariable(LLControlVariable* control) +{ + if (mMakeVisibleControlVariable) + { + mMakeVisibleControlConnection.disconnect(); // disconnect current signal + mMakeVisibleControlVariable = NULL; + } + if (control) + { + mMakeVisibleControlVariable = control; + mMakeVisibleControlConnection = mMakeVisibleControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("visible"))); + setVisible(mMakeVisibleControlVariable->getValue().asBoolean()); + } +} + +void LLUICtrl::setMakeInvisibleControlVariable(LLControlVariable* control) +{ + if (mMakeInvisibleControlVariable) + { + mMakeInvisibleControlConnection.disconnect(); // disconnect current signal + mMakeInvisibleControlVariable = NULL; + } + if (control) + { + mMakeInvisibleControlVariable = control; + mMakeInvisibleControlConnection = mMakeInvisibleControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getHandle(), std::string("invisible"))); + setVisible(!(mMakeInvisibleControlVariable->getValue().asBoolean())); + } +} + +void LLUICtrl::setFunctionName(const std::string& function_name) +{ + mFunctionName = function_name; +} + +// static +bool LLUICtrl::controlListener(const LLSD& newvalue, LLHandle handle, std::string type) +{ + LLUICtrl* ctrl = handle.get(); + if (ctrl) + { + if (type == "value") + { + ctrl->setValue(newvalue); + return true; + } + else if (type == "enabled") + { + ctrl->setEnabled(newvalue.asBoolean()); + return true; + } + else if(type =="disabled") + { + ctrl->setEnabled(!newvalue.asBoolean()); + return true; + } + else if (type == "visible") + { + ctrl->setVisible(newvalue.asBoolean()); + return true; + } + else if (type == "invisible") + { + ctrl->setVisible(!newvalue.asBoolean()); + return true; + } + } + return false; +} + +// virtual +bool LLUICtrl::setTextArg( const std::string& key, const LLStringExplicit& text ) +{ + return false; +} + +// virtual +bool LLUICtrl::setLabelArg( const std::string& key, const LLStringExplicit& text ) +{ + return false; +} + +// virtual +LLCtrlSelectionInterface* LLUICtrl::getSelectionInterface() +{ + return NULL; +} + +// virtual +LLCtrlListInterface* LLUICtrl::getListInterface() +{ + return NULL; +} + +// virtual +LLCtrlScrollInterface* LLUICtrl::getScrollInterface() +{ + return NULL; +} + +bool LLUICtrl::hasFocus() const +{ + return (gFocusMgr.childHasKeyboardFocus(this)); +} + +void LLUICtrl::setFocus(bool b) +{ + // focus NEVER goes to ui ctrls that are disabled! + if (!getEnabled()) + { + return; + } + if( b ) + { + if (!hasFocus()) + { + gFocusMgr.setKeyboardFocus( this ); + } + } + else + { + if( gFocusMgr.childHasKeyboardFocus(this)) + { + gFocusMgr.setKeyboardFocus( NULL ); + } + } +} + +// virtual +void LLUICtrl::setTabStop( bool b ) +{ + mTabStop = b; +} + +// virtual +bool LLUICtrl::hasTabStop() const +{ + return mTabStop; +} + +// virtual +bool LLUICtrl::acceptsTextInput() const +{ + return false; +} + +//virtual +bool LLUICtrl::isDirty() const +{ + return mViewModel->isDirty(); +}; + +//virtual +void LLUICtrl::resetDirty() +{ + mViewModel->resetDirty(); +} + +// virtual +void LLUICtrl::onTabInto() +{ + onUpdateScrollToChild(this); +} + +// virtual +void LLUICtrl::clear() +{ +} + +// virtual +void LLUICtrl::setIsChrome(bool is_chrome) +{ + mIsChrome = is_chrome; +} + +// virtual +bool LLUICtrl::getIsChrome() const +{ + if (mIsChrome) + return true; + + LLView* parent_ctrl = getParent(); + while (parent_ctrl) + { + if (parent_ctrl->isCtrl()) + return ((LLUICtrl*)parent_ctrl)->getIsChrome(); + + parent_ctrl = parent_ctrl->getParent(); + } + + return false; +} + + +bool LLUICtrl::focusFirstItem(bool prefer_text_fields, bool focus_flash) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + // try to select default tab group child + LLViewQuery query = getTabOrderQuery(); + child_list_t result = query(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast(result.back()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(true); + ctrl->onTabInto(); + if(focus_flash) + { + gFocusMgr.triggerFocusFlash(); + } + } + return true; + } + // search for text field first + if(prefer_text_fields) + { + LLViewQuery query = getTabOrderQuery(); + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + child_list_t result = query(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast(result.back()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(true); + ctrl->onTabInto(); + if(focus_flash) + { + gFocusMgr.triggerFocusFlash(); + } + } + return true; + } + } + // no text field found, or we don't care about text fields + result = getTabOrderQuery().run(this); + if(result.size() > 0) + { + LLUICtrl * ctrl = static_cast(result.back()); + if(!ctrl->hasFocus()) + { + ctrl->setFocus(true); + ctrl->onTabInto(); + if(focus_flash) + { + gFocusMgr.triggerFocusFlash(); + } + } + return true; + } + return false; +} + + +bool LLUICtrl::focusNextItem(bool text_fields_only) +{ + // this assumes that this method is called on the focus root. + LLViewQuery query = getTabOrderQuery(); + static LLUICachedControl tab_to_text_fields_only ("TabToTextFieldsOnly", false); + if(text_fields_only || tab_to_text_fields_only) + { + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + } + child_list_t result = query(this); + return focusNext(result); +} + +bool LLUICtrl::focusPrevItem(bool text_fields_only) +{ + // this assumes that this method is called on the focus root. + LLViewQuery query = getTabOrderQuery(); + static LLUICachedControl tab_to_text_fields_only ("TabToTextFieldsOnly", false); + if(text_fields_only || tab_to_text_fields_only) + { + query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); + } + child_list_t result = query(this); + return focusPrev(result); +} + +LLUICtrl* LLUICtrl::findRootMostFocusRoot() +{ + LLUICtrl* focus_root = NULL; + LLUICtrl* next_view = this; + while(next_view && next_view->hasTabStop()) + { + if (next_view->isFocusRoot()) + { + focus_root = next_view; + } + next_view = next_view->getParentUICtrl(); + } + + return focus_root; +} + +// Skip over any parents that are not LLUICtrl's +// Used in focus logic since only LLUICtrl elements can have focus +LLUICtrl* LLUICtrl::getParentUICtrl() const +{ + LLView* parent = getParent(); + while (parent) + { + if (parent->isCtrl()) + { + return (LLUICtrl*)(parent); + } + else + { + parent = parent->getParent(); + } + } + return NULL; +} + +bool LLUICtrl::findHelpTopic(std::string& help_topic_out) +{ + LLUICtrl* ctrl = this; + + // search back through the control's parents for a panel + // or tab with a help_topic string defined + while (ctrl) + { + LLPanel *panel = dynamic_cast(ctrl); + + if (panel) + { + + LLView *child; + LLPanel *subpanel = NULL; + + // does the panel have a sub-panel with a help topic? + bfs_tree_iterator_t it = beginTreeBFS(); + // skip ourselves + ++it; + for (; it != endTreeBFS(); ++it) + { + child = *it; + // do we have a panel with a help topic? + LLPanel *panel = dynamic_cast(child); + if (panel && panel->isInVisibleChain() && !panel->getHelpTopic().empty()) + { + subpanel = panel; + break; + } + } + + if (subpanel) + { + help_topic_out = subpanel->getHelpTopic(); + return true; // success (subpanel) + } + + // does the panel have an active tab with a help topic? + LLPanel *tab_panel = NULL; + + it = beginTreeBFS(); + // skip ourselves + ++it; + for (; it != endTreeBFS(); ++it) + { + child = *it; + LLPanel *curTabPanel = NULL; + + // do we have a tab container? + LLTabContainer *tab = dynamic_cast(child); + if (tab && tab->getVisible()) + { + curTabPanel = tab->getCurrentPanel(); + } + + // do we have an accordion tab? + LLAccordionCtrlTab* accordion = dynamic_cast(child); + if (accordion && accordion->getDisplayChildren()) + { + curTabPanel = dynamic_cast(accordion->getAccordionView()); + } + + // if we found a valid tab, does it have a help topic? + if (curTabPanel && !curTabPanel->getHelpTopic().empty()) + { + tab_panel = curTabPanel; + break; + } + } + + if (tab_panel) + { + help_topic_out = tab_panel->getHelpTopic(); + return true; // success (tab) + } + + // otherwise, does the panel have a help topic itself? + if (!panel->getHelpTopic().empty()) + { + help_topic_out = panel->getHelpTopic(); + return true; // success (panel) + } + } + + ctrl = ctrl->getParentUICtrl(); + } + + return false; // no help topic found +} + +// *TODO: Deprecate; for backwards compatability only: +boost::signals2::connection LLUICtrl::setCommitCallback( boost::function cb, void* data) +{ + return setCommitCallback( boost::bind(cb, _1, data)); +} +boost::signals2::connection LLUICtrl::setValidateBeforeCommit( boost::function cb ) +{ + if (!mValidateSignal) mValidateSignal = new enable_signal_t(); + + return mValidateSignal->connect(boost::bind(cb, _2)); +} + +// virtual +void LLUICtrl::setTentative(bool b) +{ + mTentative = b; +} + +// virtual +bool LLUICtrl::getTentative() const +{ + return mTentative; +} + +// virtual +void LLUICtrl::setColor(const LLColor4& color) +{ } + +F32 LLUICtrl::getCurrentTransparency() +{ + F32 alpha = 0; + + switch(mTransparencyType) + { + case TT_DEFAULT: + alpha = getDrawContext().mAlpha; + break; + + case TT_ACTIVE: + alpha = sActiveControlTransparency; + break; + + case TT_INACTIVE: + alpha = sInactiveControlTransparency; + break; + + case TT_FADING: + alpha = sInactiveControlTransparency / 2; + break; + } + + return alpha; +} + +void LLUICtrl::setTransparencyType(ETypeTransparency type) +{ + mTransparencyType = type; +} + +boost::signals2::connection LLUICtrl::setCommitCallback(const CommitCallbackParam& cb) +{ + return setCommitCallback(initCommitCallback(cb)); +} + +boost::signals2::connection LLUICtrl::setValidateCallback(const EnableCallbackParam& cb) +{ + return setValidateCallback(initEnableCallback(cb)); +} + +boost::signals2::connection LLUICtrl::setCommitCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mCommitSignal) mCommitSignal = new commit_signal_t(); + + return mCommitSignal->connect(cb); +} + +boost::signals2::connection LLUICtrl::setValidateCallback( const enable_signal_t::slot_type& cb ) +{ + if (!mValidateSignal) mValidateSignal = new enable_signal_t(); + + return mValidateSignal->connect(cb); +} + +boost::signals2::connection LLUICtrl::setMouseEnterCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseEnterSignal) mMouseEnterSignal = new commit_signal_t(); + + return mMouseEnterSignal->connect(cb); +} + +boost::signals2::connection LLUICtrl::setMouseLeaveCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseLeaveSignal) mMouseLeaveSignal = new commit_signal_t(); + + return mMouseLeaveSignal->connect(cb); +} + +boost::signals2::connection LLUICtrl::setMouseDownCallback( const mouse_signal_t::slot_type& cb ) +{ + if (!mMouseDownSignal) mMouseDownSignal = new mouse_signal_t(); + + return mMouseDownSignal->connect(cb); +} + +boost::signals2::connection LLUICtrl::setMouseUpCallback( const mouse_signal_t::slot_type& cb ) +{ + if (!mMouseUpSignal) mMouseUpSignal = new mouse_signal_t(); + + return mMouseUpSignal->connect(cb); +} + +boost::signals2::connection LLUICtrl::setRightMouseDownCallback( const mouse_signal_t::slot_type& cb ) +{ + if (!mRightMouseDownSignal) mRightMouseDownSignal = new mouse_signal_t(); + + return mRightMouseDownSignal->connect(cb); +} + +boost::signals2::connection LLUICtrl::setRightMouseUpCallback( const mouse_signal_t::slot_type& cb ) +{ + if (!mRightMouseUpSignal) mRightMouseUpSignal = new mouse_signal_t(); + + return mRightMouseUpSignal->connect(cb); +} + +boost::signals2::connection LLUICtrl::setDoubleClickCallback( const mouse_signal_t::slot_type& cb ) +{ + if (!mDoubleClickSignal) mDoubleClickSignal = new mouse_signal_t(); + + return mDoubleClickSignal->connect(cb); +} + +void LLUICtrl::addInfo(LLSD & info) +{ + LLView::addInfo(info); + info["value"] = getValue(); +} diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index 8dac6bb38c..c56c3c43a4 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -1,341 +1,341 @@ -/** - * @file lluictrl.h - * @author James Cook, Richard Nelson, Tom Yedwab - * @brief Abstract base class for UI controls - * - * $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$ - */ - -#ifndef LL_LLUICTRL_H -#define LL_LLUICTRL_H - -//#include "llboost.h" -#include "llrect.h" -#include "llsd.h" -#include -#include - -#include "llinitparam.h" -#include "llview.h" -#include "llviewmodel.h" // *TODO move dependency to .cpp file -#include "llsearchablecontrol.h" - -const bool TAKE_FOCUS_YES = true; -const bool TAKE_FOCUS_NO = false; -const S32 DROP_SHADOW_FLOATER = 5; - -class LLUICtrl - : public LLView, public boost::signals2::trackable -{ -public: - typedef boost::function commit_callback_t; - typedef boost::signals2::signal commit_signal_t; - // *TODO: add xml support for this type of signal in the future - typedef boost::signals2::signal mouse_signal_t; - - typedef boost::function enable_callback_t; - typedef boost::signals2::signal enable_signal_t; - - struct CallbackParam : public LLInitParam::Block - { - Ignored name; - - Optional function_name; - Optional parameter; - - Optional control_name; - - CallbackParam(); - }; - - struct CommitCallbackParam : public LLInitParam::Block - { - Optional function; - }; - - // also used for visible callbacks - struct EnableCallbackParam : public LLInitParam::Block - { - Optional function; - }; - - struct EnableControls : public LLInitParam::ChoiceBlock - { - Alternative enabled; - Alternative disabled; - - EnableControls(); - }; - struct ControlVisibility : public LLInitParam::ChoiceBlock - { - Alternative visible; - Alternative invisible; - - ControlVisibility(); - }; - struct Params : public LLInitParam::Block - { - Optional label; - Optional tab_stop, - chrome, - requests_front; - Optional initial_value; - - Optional init_callback, - commit_callback; - Optional validate_callback; - - Optional mouseenter_callback, - mouseleave_callback; - - Optional control_name; - Optional enabled_controls; - Optional controls_visibility; - - // font params - Optional font; - Optional font_halign; - Optional font_valign; - - // cruft from LLXMLNode implementation - Ignored type, - length; - - Params(); - }; - - enum ETypeTransparency - { - TT_DEFAULT, - TT_ACTIVE, // focused floater - TT_INACTIVE, // other floaters - TT_FADING, // fading toast - }; - /*virtual*/ ~LLUICtrl(); - - void initFromParams(const Params& p); -protected: - friend class LLUICtrlFactory; - static const Params& getDefaultParams(); - LLUICtrl(const Params& p = getDefaultParams(), - const LLViewModelPtr& viewmodel=LLViewModelPtr(new LLViewModel)); - - commit_signal_t::slot_type initCommitCallback(const CommitCallbackParam& cb); - enable_signal_t::slot_type initEnableCallback(const EnableCallbackParam& cb); - - // We need this virtual so we can override it with derived versions - virtual LLViewModel* getViewModel() const; - // We shouldn't ever need to set this directly - //virtual void setViewModel(const LLViewModelPtr&); - - /*virtual*/ bool postBuild() override; - -public: - // LLView interface - /*virtual*/ bool setLabelArg( const std::string& key, const LLStringExplicit& text ) override; - /*virtual*/ bool isCtrl() const override; - /*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask) override; - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool canFocusChildren() const override; - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - - // From LLFocusableElement - /*virtual*/ void setFocus( bool b ) override; - /*virtual*/ bool hasFocus() const override; - - // New virtuals - - - // Return NULL by default (overrride if the class has the appropriate interface) - virtual class LLCtrlSelectionInterface* getSelectionInterface(); - virtual class LLCtrlListInterface* getListInterface(); - virtual class LLCtrlScrollInterface* getScrollInterface(); - - bool setControlValue(const LLSD& value); - void setControlVariable(LLControlVariable* control); - virtual void setControlName(const std::string& control, LLView *context = NULL); - void removeControlVariable(); - - LLControlVariable* getControlVariable() { return mControlVariable; } - - void setEnabledControlVariable(LLControlVariable* control); - void setDisabledControlVariable(LLControlVariable* control); - void setMakeVisibleControlVariable(LLControlVariable* control); - void setMakeInvisibleControlVariable(LLControlVariable* control); - - void setFunctionName(const std::string& function_name); - - virtual void setTentative(bool b); - virtual bool getTentative() const; - virtual void setValue(const LLSD& value); - virtual LLSD getValue() const; - /// When two widgets are displaying the same data (e.g. during a skin - /// change), share their ViewModel. - virtual void shareViewModelFrom(const LLUICtrl& other); - - virtual bool setTextArg( const std::string& key, const LLStringExplicit& text ); - virtual void setIsChrome(bool is_chrome); - - virtual bool acceptsTextInput() const; // Defaults to false - - // A control is dirty if the user has modified its value. - // Editable controls should override this. - virtual bool isDirty() const; // Defauls to false - virtual void resetDirty(); //Defaults to no-op - - // Call appropriate callback - virtual void onCommit(); - - // Default to no-op: - virtual void onTabInto(); - - // Clear any user-provided input (text in a text editor, checked checkbox, - // selected radio button, etc.). Defaults to no-op. - virtual void clear(); - - virtual void setColor(const LLColor4& color); - - // Ansariel: Changed to virtual. We might want to change the transparency ourself! - virtual F32 getCurrentTransparency(); - - void setTransparencyType(ETypeTransparency type); - ETypeTransparency getTransparencyType() const {return mTransparencyType;} - - bool focusNextItem(bool text_entry_only); - bool focusPrevItem(bool text_entry_only); - bool focusFirstItem(bool prefer_text_fields = false, bool focus_flash = true ); - - // Non Virtuals - LLHandle getHandle() const { return getDerivedHandle(); } - bool getIsChrome() const; - - void setTabStop( bool b ); - bool hasTabStop() const; - - LLUICtrl* getParentUICtrl() const; - - // return true if help topic found by crawling through parents - - // topic then put in help_topic_out - bool findHelpTopic(std::string& help_topic_out); - - boost::signals2::connection setCommitCallback(const CommitCallbackParam& cb); - boost::signals2::connection setValidateCallback(const EnableCallbackParam& cb); - - boost::signals2::connection setCommitCallback( const commit_signal_t::slot_type& cb ); - boost::signals2::connection setValidateCallback( const enable_signal_t::slot_type& cb ); - - boost::signals2::connection setMouseEnterCallback( const commit_signal_t::slot_type& cb ); - boost::signals2::connection setMouseLeaveCallback( const commit_signal_t::slot_type& cb ); - - boost::signals2::connection setMouseDownCallback( const mouse_signal_t::slot_type& cb ); - boost::signals2::connection setMouseUpCallback( const mouse_signal_t::slot_type& cb ); - boost::signals2::connection setRightMouseDownCallback( const mouse_signal_t::slot_type& cb ); - boost::signals2::connection setRightMouseUpCallback( const mouse_signal_t::slot_type& cb ); - - boost::signals2::connection setDoubleClickCallback( const mouse_signal_t::slot_type& cb ); - - // *TODO: Deprecate; for backwards compatability only: - boost::signals2::connection setCommitCallback( boost::function cb, void* data); - boost::signals2::connection setValidateBeforeCommit( boost::function cb ); - - LLUICtrl* findRootMostFocusRoot(); - - class LLTextInputFilter : public LLQueryFilter, public LLSingleton - { - LLSINGLETON_EMPTY_CTOR(LLTextInputFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override - { - return filterResult_t(view->isCtrl() && static_cast(view)->acceptsTextInput(), true); - } - }; - - template class CallbackRegistry : public LLRegistrySingleton - {}; - - class CommitCallbackRegistry : public CallbackRegistry - { - LLSINGLETON_EMPTY_CTOR(CommitCallbackRegistry); - }; - // the enable callback registry is also used for visiblity callbacks - class EnableCallbackRegistry : public CallbackRegistry - { - LLSINGLETON_EMPTY_CTOR(EnableCallbackRegistry); - }; - -protected: - - static bool controlListener(const LLSD& newvalue, LLHandle handle, std::string type); - - commit_signal_t* mCommitSignal; - enable_signal_t* mValidateSignal; - - commit_signal_t* mMouseEnterSignal; - commit_signal_t* mMouseLeaveSignal; - - mouse_signal_t* mMouseDownSignal; - mouse_signal_t* mMouseUpSignal; - mouse_signal_t* mRightMouseDownSignal; - mouse_signal_t* mRightMouseUpSignal; - - mouse_signal_t* mDoubleClickSignal; - - LLViewModelPtr mViewModel; - - LLControlVariable* mControlVariable; - boost::signals2::connection mControlConnection; - LLControlVariable* mEnabledControlVariable; - boost::signals2::connection mEnabledControlConnection; - LLControlVariable* mDisabledControlVariable; - boost::signals2::connection mDisabledControlConnection; - LLControlVariable* mMakeVisibleControlVariable; - boost::signals2::connection mMakeVisibleControlConnection; - LLControlVariable* mMakeInvisibleControlVariable; - boost::signals2::connection mMakeInvisibleControlConnection; - - std::string mFunctionName; - - static F32 sActiveControlTransparency; - static F32 sInactiveControlTransparency; - - /*virtual*/ void addInfo(LLSD & info) override; - -private: - - bool mIsChrome; - bool mRequestsFront; - bool mTabStop; - bool mTentative; - - ETypeTransparency mTransparencyType; -}; - -// Build time optimization, generate once in .cpp file -#ifndef LLUICTRL_CPP -extern template class LLUICtrl* LLView::getChild( - const std::string& name, bool recurse) const; -#endif - -#endif // LL_LLUICTRL_H +/** + * @file lluictrl.h + * @author James Cook, Richard Nelson, Tom Yedwab + * @brief Abstract base class for UI controls + * + * $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$ + */ + +#ifndef LL_LLUICTRL_H +#define LL_LLUICTRL_H + +//#include "llboost.h" +#include "llrect.h" +#include "llsd.h" +#include +#include + +#include "llinitparam.h" +#include "llview.h" +#include "llviewmodel.h" // *TODO move dependency to .cpp file +#include "llsearchablecontrol.h" + +const bool TAKE_FOCUS_YES = true; +const bool TAKE_FOCUS_NO = false; +const S32 DROP_SHADOW_FLOATER = 5; + +class LLUICtrl + : public LLView, public boost::signals2::trackable +{ +public: + typedef boost::function commit_callback_t; + typedef boost::signals2::signal commit_signal_t; + // *TODO: add xml support for this type of signal in the future + typedef boost::signals2::signal mouse_signal_t; + + typedef boost::function enable_callback_t; + typedef boost::signals2::signal enable_signal_t; + + struct CallbackParam : public LLInitParam::Block + { + Ignored name; + + Optional function_name; + Optional parameter; + + Optional control_name; + + CallbackParam(); + }; + + struct CommitCallbackParam : public LLInitParam::Block + { + Optional function; + }; + + // also used for visible callbacks + struct EnableCallbackParam : public LLInitParam::Block + { + Optional function; + }; + + struct EnableControls : public LLInitParam::ChoiceBlock + { + Alternative enabled; + Alternative disabled; + + EnableControls(); + }; + struct ControlVisibility : public LLInitParam::ChoiceBlock + { + Alternative visible; + Alternative invisible; + + ControlVisibility(); + }; + struct Params : public LLInitParam::Block + { + Optional label; + Optional tab_stop, + chrome, + requests_front; + Optional initial_value; + + Optional init_callback, + commit_callback; + Optional validate_callback; + + Optional mouseenter_callback, + mouseleave_callback; + + Optional control_name; + Optional enabled_controls; + Optional controls_visibility; + + // font params + Optional font; + Optional font_halign; + Optional font_valign; + + // cruft from LLXMLNode implementation + Ignored type, + length; + + Params(); + }; + + enum ETypeTransparency + { + TT_DEFAULT, + TT_ACTIVE, // focused floater + TT_INACTIVE, // other floaters + TT_FADING, // fading toast + }; + /*virtual*/ ~LLUICtrl(); + + void initFromParams(const Params& p); +protected: + friend class LLUICtrlFactory; + static const Params& getDefaultParams(); + LLUICtrl(const Params& p = getDefaultParams(), + const LLViewModelPtr& viewmodel=LLViewModelPtr(new LLViewModel)); + + commit_signal_t::slot_type initCommitCallback(const CommitCallbackParam& cb); + enable_signal_t::slot_type initEnableCallback(const EnableCallbackParam& cb); + + // We need this virtual so we can override it with derived versions + virtual LLViewModel* getViewModel() const; + // We shouldn't ever need to set this directly + //virtual void setViewModel(const LLViewModelPtr&); + + /*virtual*/ bool postBuild() override; + +public: + // LLView interface + /*virtual*/ bool setLabelArg( const std::string& key, const LLStringExplicit& text ) override; + /*virtual*/ bool isCtrl() const override; + /*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask) override; + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool canFocusChildren() const override; + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + + // From LLFocusableElement + /*virtual*/ void setFocus( bool b ) override; + /*virtual*/ bool hasFocus() const override; + + // New virtuals + + + // Return NULL by default (overrride if the class has the appropriate interface) + virtual class LLCtrlSelectionInterface* getSelectionInterface(); + virtual class LLCtrlListInterface* getListInterface(); + virtual class LLCtrlScrollInterface* getScrollInterface(); + + bool setControlValue(const LLSD& value); + void setControlVariable(LLControlVariable* control); + virtual void setControlName(const std::string& control, LLView *context = NULL); + void removeControlVariable(); + + LLControlVariable* getControlVariable() { return mControlVariable; } + + void setEnabledControlVariable(LLControlVariable* control); + void setDisabledControlVariable(LLControlVariable* control); + void setMakeVisibleControlVariable(LLControlVariable* control); + void setMakeInvisibleControlVariable(LLControlVariable* control); + + void setFunctionName(const std::string& function_name); + + virtual void setTentative(bool b); + virtual bool getTentative() const; + virtual void setValue(const LLSD& value); + virtual LLSD getValue() const; + /// When two widgets are displaying the same data (e.g. during a skin + /// change), share their ViewModel. + virtual void shareViewModelFrom(const LLUICtrl& other); + + virtual bool setTextArg( const std::string& key, const LLStringExplicit& text ); + virtual void setIsChrome(bool is_chrome); + + virtual bool acceptsTextInput() const; // Defaults to false + + // A control is dirty if the user has modified its value. + // Editable controls should override this. + virtual bool isDirty() const; // Defauls to false + virtual void resetDirty(); //Defaults to no-op + + // Call appropriate callback + virtual void onCommit(); + + // Default to no-op: + virtual void onTabInto(); + + // Clear any user-provided input (text in a text editor, checked checkbox, + // selected radio button, etc.). Defaults to no-op. + virtual void clear(); + + virtual void setColor(const LLColor4& color); + + // Ansariel: Changed to virtual. We might want to change the transparency ourself! + virtual F32 getCurrentTransparency(); + + void setTransparencyType(ETypeTransparency type); + ETypeTransparency getTransparencyType() const {return mTransparencyType;} + + bool focusNextItem(bool text_entry_only); + bool focusPrevItem(bool text_entry_only); + bool focusFirstItem(bool prefer_text_fields = false, bool focus_flash = true ); + + // Non Virtuals + LLHandle getHandle() const { return getDerivedHandle(); } + bool getIsChrome() const; + + void setTabStop( bool b ); + bool hasTabStop() const; + + LLUICtrl* getParentUICtrl() const; + + // return true if help topic found by crawling through parents - + // topic then put in help_topic_out + bool findHelpTopic(std::string& help_topic_out); + + boost::signals2::connection setCommitCallback(const CommitCallbackParam& cb); + boost::signals2::connection setValidateCallback(const EnableCallbackParam& cb); + + boost::signals2::connection setCommitCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setValidateCallback( const enable_signal_t::slot_type& cb ); + + boost::signals2::connection setMouseEnterCallback( const commit_signal_t::slot_type& cb ); + boost::signals2::connection setMouseLeaveCallback( const commit_signal_t::slot_type& cb ); + + boost::signals2::connection setMouseDownCallback( const mouse_signal_t::slot_type& cb ); + boost::signals2::connection setMouseUpCallback( const mouse_signal_t::slot_type& cb ); + boost::signals2::connection setRightMouseDownCallback( const mouse_signal_t::slot_type& cb ); + boost::signals2::connection setRightMouseUpCallback( const mouse_signal_t::slot_type& cb ); + + boost::signals2::connection setDoubleClickCallback( const mouse_signal_t::slot_type& cb ); + + // *TODO: Deprecate; for backwards compatability only: + boost::signals2::connection setCommitCallback( boost::function cb, void* data); + boost::signals2::connection setValidateBeforeCommit( boost::function cb ); + + LLUICtrl* findRootMostFocusRoot(); + + class LLTextInputFilter : public LLQueryFilter, public LLSingleton + { + LLSINGLETON_EMPTY_CTOR(LLTextInputFilter); + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override + { + return filterResult_t(view->isCtrl() && static_cast(view)->acceptsTextInput(), true); + } + }; + + template class CallbackRegistry : public LLRegistrySingleton + {}; + + class CommitCallbackRegistry : public CallbackRegistry + { + LLSINGLETON_EMPTY_CTOR(CommitCallbackRegistry); + }; + // the enable callback registry is also used for visiblity callbacks + class EnableCallbackRegistry : public CallbackRegistry + { + LLSINGLETON_EMPTY_CTOR(EnableCallbackRegistry); + }; + +protected: + + static bool controlListener(const LLSD& newvalue, LLHandle handle, std::string type); + + commit_signal_t* mCommitSignal; + enable_signal_t* mValidateSignal; + + commit_signal_t* mMouseEnterSignal; + commit_signal_t* mMouseLeaveSignal; + + mouse_signal_t* mMouseDownSignal; + mouse_signal_t* mMouseUpSignal; + mouse_signal_t* mRightMouseDownSignal; + mouse_signal_t* mRightMouseUpSignal; + + mouse_signal_t* mDoubleClickSignal; + + LLViewModelPtr mViewModel; + + LLControlVariable* mControlVariable; + boost::signals2::connection mControlConnection; + LLControlVariable* mEnabledControlVariable; + boost::signals2::connection mEnabledControlConnection; + LLControlVariable* mDisabledControlVariable; + boost::signals2::connection mDisabledControlConnection; + LLControlVariable* mMakeVisibleControlVariable; + boost::signals2::connection mMakeVisibleControlConnection; + LLControlVariable* mMakeInvisibleControlVariable; + boost::signals2::connection mMakeInvisibleControlConnection; + + std::string mFunctionName; + + static F32 sActiveControlTransparency; + static F32 sInactiveControlTransparency; + + /*virtual*/ void addInfo(LLSD & info) override; + +private: + + bool mIsChrome; + bool mRequestsFront; + bool mTabStop; + bool mTentative; + + ETypeTransparency mTransparencyType; +}; + +// Build time optimization, generate once in .cpp file +#ifndef LLUICTRL_CPP +extern template class LLUICtrl* LLView::getChild( + const std::string& name, bool recurse) const; +#endif + +#endif // LL_LLUICTRL_H diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index e7b98629d2..a2a6d661ff 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -1,279 +1,279 @@ -/** - * @file lluictrlfactory.cpp - * @brief Factory class for creating UI controls - * - * $LicenseInfo:firstyear=2003&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" - -#define LLUICTRLFACTORY_CPP -#include "lluictrlfactory.h" - -#include "llxmlnode.h" - -#include -#include - -// other library includes -#include "llcontrol.h" -#include "lldir.h" -#include "v4color.h" -#include "v3dmath.h" -#include "llquaternion.h" - -// this library includes -#include "llpanel.h" - -//----------------------------------------------------------------------------- - -// UI Ctrl class for padding -class LLUICtrlLocate : public LLUICtrl -{ -public: - struct Params : public LLInitParam::Block - { - Params() - { - name = "locate"; - tab_stop = false; - } - }; - - LLUICtrlLocate(const Params& p) : LLUICtrl(p) {} - virtual void draw() { } - -}; - -static LLDefaultChildRegistry::Register r1("locate"); - -// Build time optimization, generate this once in .cpp file -template class LLUICtrlFactory* LLSingleton::getInstance(); - -//----------------------------------------------------------------------------- -// LLUICtrlFactory() -//----------------------------------------------------------------------------- -LLUICtrlFactory::LLUICtrlFactory() - : mDummyPanel(NULL) // instantiated when first needed -{ -} - -LLUICtrlFactory::~LLUICtrlFactory() -{ - // go ahead and leak mDummyPanel since this is static destructor time - //delete mDummyPanel; - //mDummyPanel = NULL; -} - -void LLUICtrlFactory::loadWidgetTemplate(const std::string& widget_tag, LLInitParam::BaseBlock& block) -{ - std::string filename = gDirUtilp->add("widgets", widget_tag + ".xml"); - LLXMLNodePtr root_node; - std::vector search_paths = - gDirUtilp->findSkinnedFilenames(LLDir::XUI, filename); - - if (search_paths.empty()) - { - return; - } - - // "en" version, the default-language version of the file. - std::string base_filename = search_paths.front(); - if (!base_filename.empty()) - { - LLUICtrlFactory::instance().pushFileName(base_filename); - - if (!LLXMLNode::getLayeredXMLNode(root_node, search_paths)) - { - LL_WARNS() << "Couldn't parse widget from: " << base_filename << LL_ENDL; - return; - } - LLXUIParser parser; - parser.readXUI(root_node, block, base_filename); - LLUICtrlFactory::instance().popFileName(); - } -} - -//static -void LLUICtrlFactory::createChildren(LLView* viewp, LLXMLNodePtr node, const widget_registry_t& registry, LLXMLNodePtr output_node) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - if (node.isNull()) return; - - for (LLXMLNodePtr child_node = node->getFirstChild(); child_node.notNull(); child_node = child_node->getNextSibling()) - { - LLXMLNodePtr outputChild; - if (output_node) - { - outputChild = output_node->createChild("", false); - } - - if (!instance().createFromXML(child_node, viewp, LLStringUtil::null, registry, outputChild)) - { - // child_node is not a valid child for the current parent - std::string child_name = std::string(child_node->getName()->mString); - if (LLDefaultChildRegistry::instance().getValue(child_name)) - { - // This means that the registry assocaited with the parent widget does not have an entry - // for the child widget - // You might need to add something like: - // static ParentWidgetRegistry::Register register("child_widget_name"); - LL_WARNS() << child_name << " is not a valid child of " << node->getName()->mString << LL_ENDL; - } - else - { - LL_WARNS() << "Could not create widget named " << child_node->getName()->mString << LL_ENDL; - } - } - - if (outputChild && !outputChild->mChildren && outputChild->mAttributes.empty() && outputChild->getValue().empty()) - { - output_node->deleteChild(outputChild); - } - } - -} - -//----------------------------------------------------------------------------- -// getLayeredXMLNode() -//----------------------------------------------------------------------------- -bool LLUICtrlFactory::getLayeredXMLNode(const std::string &xui_filename, LLXMLNodePtr& root, - LLDir::ESkinConstraint constraint) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - std::vector paths = - gDirUtilp->findSkinnedFilenames(LLDir::XUI, xui_filename, constraint); - - if (paths.empty()) - { - // sometimes whole path is passed in as filename - paths.push_back(xui_filename); - } - - return LLXMLNode::getLayeredXMLNode(root, paths); -} - - -//----------------------------------------------------------------------------- -// saveToXML() -//----------------------------------------------------------------------------- -S32 LLUICtrlFactory::saveToXML(LLView* viewp, const std::string& filename) -{ - return 0; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -LLView *LLUICtrlFactory::createFromXML(LLXMLNodePtr node, LLView* parent, const std::string& filename, const widget_registry_t& registry, LLXMLNodePtr output_node) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - std::string ctrl_type = node->getName()->mString; - LLStringUtil::toLower(ctrl_type); - - const LLWidgetCreatorFunc* funcp = registry.getValue(ctrl_type); - if (funcp == NULL) - { - return NULL; - } - - if (parent == NULL) - { - if (mDummyPanel == NULL) - { - LLPanel::Params p; - mDummyPanel = create(p); - } - parent = mDummyPanel; - } - LLView *view = (*funcp)(node, parent, output_node); - - return view; -} - -std::string LLUICtrlFactory::getCurFileName() -{ - return mFileNames.empty() ? "" : mFileNames.back(); -} - - -void LLUICtrlFactory::pushFileName(const std::string& name) -{ - // Here we seem to be looking for the default language file ("en") rather - // than the localized one, if any. - mFileNames.push_back(gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, name)); -} - -void LLUICtrlFactory::popFileName() -{ - mFileNames.pop_back(); -} - -//static -void LLUICtrlFactory::setCtrlParent(LLView* view, LLView* parent, S32 tab_group) -{ - if (tab_group == S32_MAX) tab_group = parent->getLastTabGroup(); - parent->addChild(view, tab_group); -} - -//static -void LLUICtrlFactory::copyName(LLXMLNodePtr src, LLXMLNodePtr dest) -{ - dest->setName(src->getName()->mString); -} - -template -const LLInitParam::BaseBlock& get_empty_param_block() -{ - static typename T::Params params; - return params; -} - -// adds a widget and its param block to various registries -//static -void LLUICtrlFactory::registerWidget(const std::type_info* widget_type, const std::type_info* param_block_type, const std::string& name) -{ - // associate parameter block type with template .xml file - std::string* existing_name = LLWidgetNameRegistry::instance().getValue(param_block_type); - if (existing_name != NULL) - { - if(*existing_name != name) - { - std::cerr << "Duplicate entry for T::Params, try creating empty param block in derived classes that inherit T::Params" << std::endl; - // forcing crash here - char* foo = 0; - *foo = 1; - } - else - { - // widget already registered this name - return; - } - } - - LLWidgetNameRegistry::instance().defaultRegistrar().add(param_block_type, name); - //FIXME: comment this in when working on schema generation - //LLWidgetTypeRegistry::instance().defaultRegistrar().add(tag, widget_type); - //LLDefaultParamBlockRegistry::instance().defaultRegistrar().add(widget_type, &get_empty_param_block); -} - - +/** + * @file lluictrlfactory.cpp + * @brief Factory class for creating UI controls + * + * $LicenseInfo:firstyear=2003&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" + +#define LLUICTRLFACTORY_CPP +#include "lluictrlfactory.h" + +#include "llxmlnode.h" + +#include +#include + +// other library includes +#include "llcontrol.h" +#include "lldir.h" +#include "v4color.h" +#include "v3dmath.h" +#include "llquaternion.h" + +// this library includes +#include "llpanel.h" + +//----------------------------------------------------------------------------- + +// UI Ctrl class for padding +class LLUICtrlLocate : public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block + { + Params() + { + name = "locate"; + tab_stop = false; + } + }; + + LLUICtrlLocate(const Params& p) : LLUICtrl(p) {} + virtual void draw() { } + +}; + +static LLDefaultChildRegistry::Register r1("locate"); + +// Build time optimization, generate this once in .cpp file +template class LLUICtrlFactory* LLSingleton::getInstance(); + +//----------------------------------------------------------------------------- +// LLUICtrlFactory() +//----------------------------------------------------------------------------- +LLUICtrlFactory::LLUICtrlFactory() + : mDummyPanel(NULL) // instantiated when first needed +{ +} + +LLUICtrlFactory::~LLUICtrlFactory() +{ + // go ahead and leak mDummyPanel since this is static destructor time + //delete mDummyPanel; + //mDummyPanel = NULL; +} + +void LLUICtrlFactory::loadWidgetTemplate(const std::string& widget_tag, LLInitParam::BaseBlock& block) +{ + std::string filename = gDirUtilp->add("widgets", widget_tag + ".xml"); + LLXMLNodePtr root_node; + std::vector search_paths = + gDirUtilp->findSkinnedFilenames(LLDir::XUI, filename); + + if (search_paths.empty()) + { + return; + } + + // "en" version, the default-language version of the file. + std::string base_filename = search_paths.front(); + if (!base_filename.empty()) + { + LLUICtrlFactory::instance().pushFileName(base_filename); + + if (!LLXMLNode::getLayeredXMLNode(root_node, search_paths)) + { + LL_WARNS() << "Couldn't parse widget from: " << base_filename << LL_ENDL; + return; + } + LLXUIParser parser; + parser.readXUI(root_node, block, base_filename); + LLUICtrlFactory::instance().popFileName(); + } +} + +//static +void LLUICtrlFactory::createChildren(LLView* viewp, LLXMLNodePtr node, const widget_registry_t& registry, LLXMLNodePtr output_node) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + if (node.isNull()) return; + + for (LLXMLNodePtr child_node = node->getFirstChild(); child_node.notNull(); child_node = child_node->getNextSibling()) + { + LLXMLNodePtr outputChild; + if (output_node) + { + outputChild = output_node->createChild("", false); + } + + if (!instance().createFromXML(child_node, viewp, LLStringUtil::null, registry, outputChild)) + { + // child_node is not a valid child for the current parent + std::string child_name = std::string(child_node->getName()->mString); + if (LLDefaultChildRegistry::instance().getValue(child_name)) + { + // This means that the registry assocaited with the parent widget does not have an entry + // for the child widget + // You might need to add something like: + // static ParentWidgetRegistry::Register register("child_widget_name"); + LL_WARNS() << child_name << " is not a valid child of " << node->getName()->mString << LL_ENDL; + } + else + { + LL_WARNS() << "Could not create widget named " << child_node->getName()->mString << LL_ENDL; + } + } + + if (outputChild && !outputChild->mChildren && outputChild->mAttributes.empty() && outputChild->getValue().empty()) + { + output_node->deleteChild(outputChild); + } + } + +} + +//----------------------------------------------------------------------------- +// getLayeredXMLNode() +//----------------------------------------------------------------------------- +bool LLUICtrlFactory::getLayeredXMLNode(const std::string &xui_filename, LLXMLNodePtr& root, + LLDir::ESkinConstraint constraint) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + std::vector paths = + gDirUtilp->findSkinnedFilenames(LLDir::XUI, xui_filename, constraint); + + if (paths.empty()) + { + // sometimes whole path is passed in as filename + paths.push_back(xui_filename); + } + + return LLXMLNode::getLayeredXMLNode(root, paths); +} + + +//----------------------------------------------------------------------------- +// saveToXML() +//----------------------------------------------------------------------------- +S32 LLUICtrlFactory::saveToXML(LLView* viewp, const std::string& filename) +{ + return 0; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +LLView *LLUICtrlFactory::createFromXML(LLXMLNodePtr node, LLView* parent, const std::string& filename, const widget_registry_t& registry, LLXMLNodePtr output_node) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + std::string ctrl_type = node->getName()->mString; + LLStringUtil::toLower(ctrl_type); + + const LLWidgetCreatorFunc* funcp = registry.getValue(ctrl_type); + if (funcp == NULL) + { + return NULL; + } + + if (parent == NULL) + { + if (mDummyPanel == NULL) + { + LLPanel::Params p; + mDummyPanel = create(p); + } + parent = mDummyPanel; + } + LLView *view = (*funcp)(node, parent, output_node); + + return view; +} + +std::string LLUICtrlFactory::getCurFileName() +{ + return mFileNames.empty() ? "" : mFileNames.back(); +} + + +void LLUICtrlFactory::pushFileName(const std::string& name) +{ + // Here we seem to be looking for the default language file ("en") rather + // than the localized one, if any. + mFileNames.push_back(gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, name)); +} + +void LLUICtrlFactory::popFileName() +{ + mFileNames.pop_back(); +} + +//static +void LLUICtrlFactory::setCtrlParent(LLView* view, LLView* parent, S32 tab_group) +{ + if (tab_group == S32_MAX) tab_group = parent->getLastTabGroup(); + parent->addChild(view, tab_group); +} + +//static +void LLUICtrlFactory::copyName(LLXMLNodePtr src, LLXMLNodePtr dest) +{ + dest->setName(src->getName()->mString); +} + +template +const LLInitParam::BaseBlock& get_empty_param_block() +{ + static typename T::Params params; + return params; +} + +// adds a widget and its param block to various registries +//static +void LLUICtrlFactory::registerWidget(const std::type_info* widget_type, const std::type_info* param_block_type, const std::string& name) +{ + // associate parameter block type with template .xml file + std::string* existing_name = LLWidgetNameRegistry::instance().getValue(param_block_type); + if (existing_name != NULL) + { + if(*existing_name != name) + { + std::cerr << "Duplicate entry for T::Params, try creating empty param block in derived classes that inherit T::Params" << std::endl; + // forcing crash here + char* foo = 0; + *foo = 1; + } + else + { + // widget already registered this name + return; + } + } + + LLWidgetNameRegistry::instance().defaultRegistrar().add(param_block_type, name); + //FIXME: comment this in when working on schema generation + //LLWidgetTypeRegistry::instance().defaultRegistrar().add(tag, widget_type); + //LLDefaultParamBlockRegistry::instance().defaultRegistrar().add(widget_type, &get_empty_param_block); +} + + diff --git a/indra/llui/llundo.cpp b/indra/llui/llundo.cpp index 94b250d51e..0928cb4417 100644 --- a/indra/llui/llundo.cpp +++ b/indra/llui/llundo.cpp @@ -1,174 +1,174 @@ -/** - * @file llundo.cpp - * - * $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 "llundo.h" - - -// TODO: -// implement doubly linked circular list for ring buffer -// this will allow us to easily change the size of an undo buffer on the fly - -//----------------------------------------------------------------------------- -// LLUndoBuffer() -//----------------------------------------------------------------------------- -LLUndoBuffer::LLUndoBuffer( LLUndoAction (*create_func()), S32 initial_count ) -{ - mNextAction = 0; - mLastAction = 0; - mFirstAction = 0; - mOperationID = 0; - - mNumActions = initial_count; - - mActions = new LLUndoAction *[initial_count]; - - //initialize buffer with actions - for (S32 i = 0; i < initial_count; i++) - { - mActions[i] = create_func(); - if (!mActions[i]) - { - LL_ERRS() << "Unable to create action for undo buffer" << LL_ENDL; - } - } -} - -//----------------------------------------------------------------------------- -// ~LLUndoBuffer() -//----------------------------------------------------------------------------- -LLUndoBuffer::~LLUndoBuffer() -{ - for (S32 i = 0; i < mNumActions; i++) - { - delete mActions[i]; - } - - delete [] mActions; -} - -//----------------------------------------------------------------------------- -// getNextAction() -//----------------------------------------------------------------------------- -LLUndoBuffer::LLUndoAction* LLUndoBuffer::getNextAction(bool setClusterBegin) -{ - LLUndoAction *nextAction = mActions[mNextAction]; - - if (setClusterBegin) - { - mOperationID++; - } - mActions[mNextAction]->mClusterID = mOperationID; - - mNextAction = (mNextAction + 1) % mNumActions; - mLastAction = mNextAction; - - if (mNextAction == mFirstAction) - { - mActions[mFirstAction]->cleanup(); - mFirstAction = (mFirstAction + 1) % mNumActions; - } - - return nextAction; -} - -//----------------------------------------------------------------------------- -// undoAction() -//----------------------------------------------------------------------------- -bool LLUndoBuffer::undoAction() -{ - if (!canUndo()) - { - return false; - } - - S32 prevAction = (mNextAction + mNumActions - 1) % mNumActions; - - while(mActions[prevAction]->mClusterID == mOperationID) - { - // go ahead and decrement action index - mNextAction = prevAction; - - // undo this action - mActions[mNextAction]->undo(); - - // we're at the first action, so we don't know if we've actually undid everything - if (mNextAction == mFirstAction) - { - mOperationID--; - return false; - } - - // do wrap-around of index, but avoid negative numbers for modulo operator - prevAction = (mNextAction + mNumActions - 1) % mNumActions; - } - - mOperationID--; - - return true; -} - -//----------------------------------------------------------------------------- -// redoAction() -//----------------------------------------------------------------------------- -bool LLUndoBuffer::redoAction() -{ - if (!canRedo()) - { - return false; - } - - mOperationID++; - - while(mActions[mNextAction]->mClusterID == mOperationID) - { - if (mNextAction == mLastAction) - { - return false; - } - - mActions[mNextAction]->redo(); - - // do wrap-around of index - mNextAction = (mNextAction + 1) % mNumActions; - } - - return true; -} - -//----------------------------------------------------------------------------- -// flushActions() -//----------------------------------------------------------------------------- -void LLUndoBuffer::flushActions() -{ - for (S32 i = 0; i < mNumActions; i++) - { - mActions[i]->cleanup(); - } - mNextAction = 0; - mLastAction = 0; - mFirstAction = 0; - mOperationID = 0; -} +/** + * @file llundo.cpp + * + * $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 "llundo.h" + + +// TODO: +// implement doubly linked circular list for ring buffer +// this will allow us to easily change the size of an undo buffer on the fly + +//----------------------------------------------------------------------------- +// LLUndoBuffer() +//----------------------------------------------------------------------------- +LLUndoBuffer::LLUndoBuffer( LLUndoAction (*create_func()), S32 initial_count ) +{ + mNextAction = 0; + mLastAction = 0; + mFirstAction = 0; + mOperationID = 0; + + mNumActions = initial_count; + + mActions = new LLUndoAction *[initial_count]; + + //initialize buffer with actions + for (S32 i = 0; i < initial_count; i++) + { + mActions[i] = create_func(); + if (!mActions[i]) + { + LL_ERRS() << "Unable to create action for undo buffer" << LL_ENDL; + } + } +} + +//----------------------------------------------------------------------------- +// ~LLUndoBuffer() +//----------------------------------------------------------------------------- +LLUndoBuffer::~LLUndoBuffer() +{ + for (S32 i = 0; i < mNumActions; i++) + { + delete mActions[i]; + } + + delete [] mActions; +} + +//----------------------------------------------------------------------------- +// getNextAction() +//----------------------------------------------------------------------------- +LLUndoBuffer::LLUndoAction* LLUndoBuffer::getNextAction(bool setClusterBegin) +{ + LLUndoAction *nextAction = mActions[mNextAction]; + + if (setClusterBegin) + { + mOperationID++; + } + mActions[mNextAction]->mClusterID = mOperationID; + + mNextAction = (mNextAction + 1) % mNumActions; + mLastAction = mNextAction; + + if (mNextAction == mFirstAction) + { + mActions[mFirstAction]->cleanup(); + mFirstAction = (mFirstAction + 1) % mNumActions; + } + + return nextAction; +} + +//----------------------------------------------------------------------------- +// undoAction() +//----------------------------------------------------------------------------- +bool LLUndoBuffer::undoAction() +{ + if (!canUndo()) + { + return false; + } + + S32 prevAction = (mNextAction + mNumActions - 1) % mNumActions; + + while(mActions[prevAction]->mClusterID == mOperationID) + { + // go ahead and decrement action index + mNextAction = prevAction; + + // undo this action + mActions[mNextAction]->undo(); + + // we're at the first action, so we don't know if we've actually undid everything + if (mNextAction == mFirstAction) + { + mOperationID--; + return false; + } + + // do wrap-around of index, but avoid negative numbers for modulo operator + prevAction = (mNextAction + mNumActions - 1) % mNumActions; + } + + mOperationID--; + + return true; +} + +//----------------------------------------------------------------------------- +// redoAction() +//----------------------------------------------------------------------------- +bool LLUndoBuffer::redoAction() +{ + if (!canRedo()) + { + return false; + } + + mOperationID++; + + while(mActions[mNextAction]->mClusterID == mOperationID) + { + if (mNextAction == mLastAction) + { + return false; + } + + mActions[mNextAction]->redo(); + + // do wrap-around of index + mNextAction = (mNextAction + 1) % mNumActions; + } + + return true; +} + +//----------------------------------------------------------------------------- +// flushActions() +//----------------------------------------------------------------------------- +void LLUndoBuffer::flushActions() +{ + for (S32 i = 0; i < mNumActions; i++) + { + mActions[i]->cleanup(); + } + mNextAction = 0; + mLastAction = 0; + mFirstAction = 0; + mOperationID = 0; +} diff --git a/indra/llui/llundo.h b/indra/llui/llundo.h index 41e8a2b2f4..dc40702be0 100644 --- a/indra/llui/llundo.h +++ b/indra/llui/llundo.h @@ -1,68 +1,68 @@ -/** - * @file llundo.h - * @brief Generic interface for undo/redo circular buffer. - * - * $LicenseInfo:firstyear=2000&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$ - */ - -#ifndef LL_LLUNDO_H -#define LL_LLUNDO_H - - -class LLUndoBuffer -{ -public: - class LLUndoAction - { - friend class LLUndoBuffer; - public: - virtual void undo() = 0; - virtual void redo() = 0; - virtual void cleanup() {}; - protected: - LLUndoAction(): mClusterID(0) {}; - virtual ~LLUndoAction(){}; - private: - S32 mClusterID; - }; - - LLUndoBuffer( LLUndoAction (*create_func()), S32 initial_count ); - virtual ~LLUndoBuffer(); - - LLUndoAction *getNextAction(bool setClusterBegin = true); - bool undoAction(); - bool redoAction(); - bool canUndo() { return (mNextAction != mFirstAction); } - bool canRedo() { return (mNextAction != mLastAction); } - - void flushActions(); - -private: - LLUndoAction **mActions; // array of pointers to undoactions - S32 mNumActions; // total number of actions in ring buffer - S32 mNextAction; // next action to perform undo/redo on - S32 mLastAction; // last action actually added to undo buffer - S32 mFirstAction; // beginning of ring buffer (don't undo any further) - S32 mOperationID; // current operation id, for undoing and redoing in clusters -}; - -#endif //LL_LLUNDO_H +/** + * @file llundo.h + * @brief Generic interface for undo/redo circular buffer. + * + * $LicenseInfo:firstyear=2000&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$ + */ + +#ifndef LL_LLUNDO_H +#define LL_LLUNDO_H + + +class LLUndoBuffer +{ +public: + class LLUndoAction + { + friend class LLUndoBuffer; + public: + virtual void undo() = 0; + virtual void redo() = 0; + virtual void cleanup() {}; + protected: + LLUndoAction(): mClusterID(0) {}; + virtual ~LLUndoAction(){}; + private: + S32 mClusterID; + }; + + LLUndoBuffer( LLUndoAction (*create_func()), S32 initial_count ); + virtual ~LLUndoBuffer(); + + LLUndoAction *getNextAction(bool setClusterBegin = true); + bool undoAction(); + bool redoAction(); + bool canUndo() { return (mNextAction != mFirstAction); } + bool canRedo() { return (mNextAction != mLastAction); } + + void flushActions(); + +private: + LLUndoAction **mActions; // array of pointers to undoactions + S32 mNumActions; // total number of actions in ring buffer + S32 mNextAction; // next action to perform undo/redo on + S32 mLastAction; // last action actually added to undo buffer + S32 mFirstAction; // beginning of ring buffer (don't undo any further) + S32 mOperationID; // current operation id, for undoing and redoing in clusters +}; + +#endif //LL_LLUNDO_H diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 485dfd0abb..234ce9f4c3 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -1,1738 +1,1738 @@ -/** - * @file llurlentry.cpp - * @author Martin Reddy - * @brief Describes the Url types that can be registered in LLUrlRegistry - * - * $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 "llurlentry.h" -#include "lluictrl.h" -#include "lluri.h" -#include "llurlmatch.h" -#include "llurlregistry.h" -#include "lluriparser.h" - -#include "llavatarnamecache.h" -#include "llcachename.h" -#include "llkeyboard.h" -#include "llregex.h" -#include "llscrolllistctrl.h" // for LLUrlEntryKeybinding file parsing -#include "lltrans.h" -#include "lluicolortable.h" -#include "message.h" -#include "llexperiencecache.h" - -#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" - -// Utility functions -std::string localize_slapp_label(const std::string& url, const std::string& full_name); - - -LLUrlEntryBase::LLUrlEntryBase() -{ -} - -LLUrlEntryBase::~LLUrlEntryBase() -{ -} - -std::string LLUrlEntryBase::getUrl(const std::string &string) const -{ - return escapeUrl(string); -} - -//virtual -std::string LLUrlEntryBase::getIcon(const std::string &url) -{ - return mIcon; -} - -LLStyle::Params LLUrlEntryBase::getStyle() const -{ - LLStyle::Params style_params; - style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - style_params.font.style = "UNDERLINE"; - return style_params; -} - - -std::string LLUrlEntryBase::getIDStringFromUrl(const std::string &url) const -{ - // return the id from a SLURL in the format /app/{cmd}/{id}/about - LLURI uri(url); - LLSD path_array = uri.pathArray(); - if (path_array.size() == 4) - { - return path_array.get(2).asString(); - } - return ""; -} - -std::string LLUrlEntryBase::unescapeUrl(const std::string &url) const -{ - return LLURI::unescape(url); -} - -std::string LLUrlEntryBase::escapeUrl(const std::string &url) const -{ - static std::string no_escape_chars; - static bool initialized = false; - if (!initialized) - { - no_escape_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~!$?&()*+,@:;=/%#"; - - std::sort(no_escape_chars.begin(), no_escape_chars.end()); - initialized = true; - } - return LLURI::escape(url, no_escape_chars, true); -} - -std::string LLUrlEntryBase::getLabelFromWikiLink(const std::string &url) const -{ - // return the label part from [http://www.example.org Label] - const char *text = url.c_str(); - S32 start = 0; - while (! isspace(text[start])) - { - start++; - } - while (text[start] == ' ' || text[start] == '\t') - { - start++; - } - return unescapeUrl(url.substr(start, url.size()-start-1)); -} - -std::string LLUrlEntryBase::getUrlFromWikiLink(const std::string &string) const -{ - // return the url part from [http://www.example.org Label] - const char *text = string.c_str(); - S32 end = 0; - while (! isspace(text[end])) - { - end++; - } - return escapeUrl(string.substr(1, end-1)); -} - -void LLUrlEntryBase::addObserver(const std::string &id, - const std::string &url, - const LLUrlLabelCallback &cb) -{ - // add a callback to be notified when we have a label for the uuid - LLUrlEntryObserver observer; - observer.url = url; - observer.signal = new LLUrlLabelSignal(); - if (observer.signal) - { - observer.signal->connect(cb); - mObservers.insert(std::pair(id, observer)); - } -} - -// *NOTE: See also LLUrlEntryAgent::callObservers() -void LLUrlEntryBase::callObservers(const std::string &id, - const std::string &label, - const std::string &icon) -{ - // notify all callbacks waiting on the given uuid - typedef std::multimap::iterator observer_it; - std::pair matching_range = mObservers.equal_range(id); - for (observer_it it = matching_range.first; it != matching_range.second;) - { - // call the callback - give it the new label - LLUrlEntryObserver &observer = it->second; - (*observer.signal)(it->second.url, label, icon); - // then remove the signal - we only need to call it once - delete observer.signal; - mObservers.erase(it++); - } -} - -/// is this a match for a URL that should not be hyperlinked? -bool LLUrlEntryBase::isLinkDisabled() const -{ - // this allows us to have a global setting to turn off text hyperlink highlighting/action - static LLCachedControl globally_disabled(*LLUI::getInstance()->mSettingGroups["config"], "DisableTextHyperlinkActions", false); - - return globally_disabled; -} - -bool LLUrlEntryBase::isWikiLinkCorrect(const std::string &labeled_url) const -{ - LLWString wlabel = utf8str_to_wstring(getLabelFromWikiLink(labeled_url)); - wlabel.erase(std::remove(wlabel.begin(), wlabel.end(), L'\u200B'), wlabel.end()); - - // Unicode URL validation, see SL-15243 - std::replace_if(wlabel.begin(), - wlabel.end(), - [](const llwchar &chr) - { - return (chr == L'\u2024') // "One Dot Leader" - || (chr == L'\uFE52') // "Small Full Stop" - || (chr == L'\uFF0E') // "Fullwidth Full Stop" - // Not a decomposition, but suficiently similar - || (chr == L'\u05C5'); // "Hebrew Mark Lower Dot" - }, - L'\u002E'); // Dot "Full Stop" - - std::replace_if(wlabel.begin(), - wlabel.end(), - [](const llwchar &chr) - { - return (chr == L'\u02D0') // "Modifier Letter Colon" - || (chr == L'\uFF1A') // "Fullwidth Colon" - || (chr == L'\u2236') // "Ratio" - || (chr == L'\uFE55'); // "Small Colon" - }, - L'\u003A'); // Colon - - std::replace_if(wlabel.begin(), - wlabel.end(), - [](const llwchar &chr) - { - return (chr == L'\uFF0F'); // "Fullwidth Solidus" - }, - L'\u002F'); // Solidus - - std::string label = wstring_to_utf8str(wlabel); - if ((label.find(".com") != std::string::npos - || label.find("www.") != std::string::npos) - && label.find("://") == std::string::npos) - { - label = "http://" + label; - } - - return !LLUrlRegistry::instance().hasUrl(label); -} - -std::string LLUrlEntryBase::urlToLabelWithGreyQuery(const std::string &url) const -{ - if (url.empty()) - { - return url; - } - LLUriParser up(escapeUrl(url)); - if (up.normalize() == 0) - { - std::string label; - up.extractParts(); - up.glueFirst(label); - - return unescapeUrl(label); - } - return std::string(); -} - -std::string LLUrlEntryBase::urlToGreyQuery(const std::string &url) const -{ - std::string escaped_url = escapeUrl(url); - LLUriParser up(escaped_url); - - std::string label; - up.extractParts(); - up.glueFirst(label, false); - - size_t pos = escaped_url.find(label); - if (pos == std::string::npos) - { - return ""; - } - pos += label.size(); - return unescapeUrl(escaped_url.substr(pos)); -} - - -static std::string getStringAfterToken(const std::string str, const std::string token) -{ - size_t pos = str.find(token); - if (pos == std::string::npos) - { - return ""; - } - - pos += token.size(); - return str.substr(pos, str.size() - pos); -} - -// -// LLUrlEntryHTTP Describes generic http: and https: Urls -// -LLUrlEntryHTTP::LLUrlEntryHTTP() - : LLUrlEntryBase() -{ - mPattern = boost::regex("https?://([^\\s/?\\.#]+\\.?)+\\.\\w+(:\\d+)?(/\\S*)?", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_http.xml"; - mTooltip = LLTrans::getString("TooltipHttpUrl"); -} - -std::string LLUrlEntryHTTP::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - return urlToLabelWithGreyQuery(url); -} - -std::string LLUrlEntryHTTP::getQuery(const std::string &url) const -{ - return urlToGreyQuery(url); -} - -std::string LLUrlEntryHTTP::getUrl(const std::string &string) const -{ - if (string.find("://") == std::string::npos) - { - return "http://" + escapeUrl(string); - } - return escapeUrl(string); -} - -std::string LLUrlEntryHTTP::getTooltip(const std::string &url) const -{ - return unescapeUrl(url); -} - -// -// LLUrlEntryHTTP Describes generic http: and https: Urls with custom label -// We use the wikipedia syntax of [http://www.example.org Text] -// -LLUrlEntryHTTPLabel::LLUrlEntryHTTPLabel() -{ - mPattern = boost::regex("\\[https?://\\S+[ \t]+[^\\]]+\\]", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_http.xml"; - mTooltip = LLTrans::getString("TooltipHttpUrl"); -} - -std::string LLUrlEntryHTTPLabel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - std::string label = getLabelFromWikiLink(url); - return (!LLUrlRegistry::instance().hasUrl(label)) ? label : getUrl(url); -} - -std::string LLUrlEntryHTTPLabel::getTooltip(const std::string &string) const -{ - return getUrl(string); -} - -std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string) const -{ - return getUrlFromWikiLink(string); -} - -LLUrlEntryInvalidSLURL::LLUrlEntryInvalidSLURL() - : LLUrlEntryBase() -{ - mPattern = boost::regex("(https?://(maps.secondlife.com|slurl.com)/secondlife/|secondlife://(/app/(worldmap|teleport)/)?)[^ /]+(/-?[0-9]+){1,3}(/?(\\?title|\\?img|\\?msg)=\\S*)?/?", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_http.xml"; - mTooltip = LLTrans::getString("TooltipHttpUrl"); -} - -std::string LLUrlEntryInvalidSLURL::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - - return escapeUrl(url); -} - -std::string LLUrlEntryInvalidSLURL::getUrl(const std::string &string) const -{ - return escapeUrl(string); -} - -std::string LLUrlEntryInvalidSLURL::getTooltip(const std::string &url) const -{ - return unescapeUrl(url); -} - -bool LLUrlEntryInvalidSLURL::isSLURLvalid(const std::string &url) const -{ - S32 actual_parts; - - if(url.find(".com/secondlife/") != std::string::npos) - { - actual_parts = 5; - } - else if(url.find("/app/") != std::string::npos) - { - actual_parts = 6; - } - else - { - actual_parts = 3; - } - - LLURI uri(url); - LLSD path_array = uri.pathArray(); - S32 path_parts = path_array.size(); - S32 x,y,z; - - if (path_parts == actual_parts) - { - // handle slurl with (X,Y,Z) coordinates - LLStringUtil::convertToS32(path_array[path_parts-3],x); - LLStringUtil::convertToS32(path_array[path_parts-2],y); - LLStringUtil::convertToS32(path_array[path_parts-1],z); - - if((x>= 0 && x<= 256) && (y>= 0 && y<= 256) && (z>= 0)) - { - return true; - } - } - else if (path_parts == (actual_parts-1)) - { - // handle slurl with (X,Y) coordinates - - LLStringUtil::convertToS32(path_array[path_parts-2],x); - LLStringUtil::convertToS32(path_array[path_parts-1],y); - ; - if((x>= 0 && x<= 256) && (y>= 0 && y<= 256)) - { - return true; - } - } - else if (path_parts == (actual_parts-2)) - { - // handle slurl with (X) coordinate - LLStringUtil::convertToS32(path_array[path_parts-1],x); - if(x>= 0 && x<= 256) - { - return true; - } - } - - return false; -} - -// -// LLUrlEntrySLURL Describes generic http: and https: Urls -// -LLUrlEntrySLURL::LLUrlEntrySLURL() -{ - // see http://slurl.com/about.php for details on the SLURL format - mPattern = boost::regex("https?://(maps.secondlife.com|slurl.com)/secondlife/[^ /]+(/\\d+){0,3}(/?(\\?title|\\?img|\\?msg)=\\S*)?/?", - boost::regex::perl|boost::regex::icase); - mIcon = "Hand"; - mMenuName = "menu_url_slurl.xml"; - mTooltip = LLTrans::getString("TooltipSLURL"); -} - -std::string LLUrlEntrySLURL::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - // - // we handle SLURLs in the following formats: - // - http://slurl.com/secondlife/Place/X/Y/Z - // - http://slurl.com/secondlife/Place/X/Y - // - http://slurl.com/secondlife/Place/X - // - http://slurl.com/secondlife/Place - // - - LLURI uri(url); - LLSD path_array = uri.pathArray(); - S32 path_parts = path_array.size(); - if (path_parts == 5) - { - // handle slurl with (X,Y,Z) coordinates - std::string location = unescapeUrl(path_array[path_parts-4]); - std::string x = path_array[path_parts-3]; - std::string y = path_array[path_parts-2]; - std::string z = path_array[path_parts-1]; - return location + " (" + x + "," + y + "," + z + ")"; - } - else if (path_parts == 4) - { - // handle slurl with (X,Y) coordinates - std::string location = unescapeUrl(path_array[path_parts-3]); - std::string x = path_array[path_parts-2]; - std::string y = path_array[path_parts-1]; - return location + " (" + x + "," + y + ")"; - } - else if (path_parts == 3) - { - // handle slurl with (X) coordinate - std::string location = unescapeUrl(path_array[path_parts-2]); - std::string x = path_array[path_parts-1]; - return location + " (" + x + ")"; - } - else if (path_parts == 2) - { - // handle slurl with no coordinates - std::string location = unescapeUrl(path_array[path_parts-1]); - return location; - } - - return url; -} - -std::string LLUrlEntrySLURL::getLocation(const std::string &url) const -{ - // return the part of the Url after slurl.com/secondlife/ - const std::string search_string = "/secondlife"; - size_t pos = url.find(search_string); - if (pos == std::string::npos) - { - return ""; - } - - pos += search_string.size() + 1; - return url.substr(pos, url.size() - pos); -} - -// -// LLUrlEntrySeconlifeURL Describes *secondlife.com/ *lindenlab.com/ *secondlifegrid.net/ and *tilia-inc.com/ urls to substitute icon 'hand.png' before link -// -LLUrlEntrySecondlifeURL::LLUrlEntrySecondlifeURL() -{ - mPattern = boost::regex("((http://([-\\w\\.]*\\.)?(secondlife|lindenlab|tilia-inc)\\.com)" - "|" - "(http://([-\\w\\.]*\\.)?secondlifegrid\\.net)" - "|" - "(https://([-\\w\\.]*\\.)?(secondlife|lindenlab|tilia-inc)\\.com(:\\d{1,5})?)" - "|" - "(https://([-\\w\\.]*\\.)?secondlifegrid\\.net(:\\d{1,5})?)" - "|" - "(https?://([-\\w\\.]*\\.)?secondlife\\.io(:\\d{1,5})?))" - "\\/\\S*", - boost::regex::perl|boost::regex::icase); - - mIcon = "Hand"; - mMenuName = "menu_url_http.xml"; - mTooltip = LLTrans::getString("TooltipHttpUrl"); -} - -/// Return the url from a string that matched the regex -std::string LLUrlEntrySecondlifeURL::getUrl(const std::string &string) const -{ - if (string.find("://") == std::string::npos) - { - return "https://" + escapeUrl(string); - } - return escapeUrl(string); -} - -std::string LLUrlEntrySecondlifeURL::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - return urlToLabelWithGreyQuery(url); -} - -std::string LLUrlEntrySecondlifeURL::getQuery(const std::string &url) const -{ - return urlToGreyQuery(url); -} - -std::string LLUrlEntrySecondlifeURL::getTooltip(const std::string &url) const -{ - return url; -} - -// -// LLUrlEntrySimpleSecondlifeURL Describes *secondlife.com *lindenlab.com *secondlifegrid.net and *tilia-inc.com urls to substitute icon 'hand.png' before link -// -LLUrlEntrySimpleSecondlifeURL::LLUrlEntrySimpleSecondlifeURL() - { - mPattern = boost::regex("https?://([-\\w\\.]*\\.)?(secondlife|lindenlab|tilia-inc)\\.com(?!\\S)" - "|" - "https?://([-\\w\\.]*\\.)?secondlifegrid\\.net(?!\\S)", - boost::regex::perl|boost::regex::icase); - - mIcon = "Hand"; - mMenuName = "menu_url_http.xml"; -} - -// -// LLUrlEntryAgent Describes a Second Life agent Url, e.g., -// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about -// -LLUrlEntryAgent::LLUrlEntryAgent() -{ - mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/\\w+", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_agent.xml"; - mIcon = "Generic_Person"; -} - -// virtual -void LLUrlEntryAgent::callObservers(const std::string &id, - const std::string &label, - const std::string &icon) -{ - // notify all callbacks waiting on the given uuid - typedef std::multimap::iterator observer_it; - std::pair matching_range = mObservers.equal_range(id); - for (observer_it it = matching_range.first; it != matching_range.second;) - { - // call the callback - give it the new label - LLUrlEntryObserver &observer = it->second; - std::string final_label = localize_slapp_label(observer.url, label); - (*observer.signal)(observer.url, final_label, icon); - // then remove the signal - we only need to call it once - delete observer.signal; - mObservers.erase(it++); - } -} - -void LLUrlEntryAgent::onAvatarNameCache(const LLUUID& id, - const LLAvatarName& av_name) -{ - avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(id); - if (it != mAvatarNameCacheConnections.end()) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - mAvatarNameCacheConnections.erase(it); - } - - std::string label = av_name.getCompleteName(); - - // received the agent name from the server - tell our observers - callObservers(id.asString(), label, mIcon); -} - -LLUUID LLUrlEntryAgent::getID(const std::string &string) const -{ - return LLUUID(getIDStringFromUrl(string)); -} - -std::string LLUrlEntryAgent::getTooltip(const std::string &string) const -{ - // return a tooltip corresponding to the URL type instead of the generic one - std::string url = getUrl(string); - - if (LLStringUtil::endsWith(url, "/inspect")) - { - return LLTrans::getString("TooltipAgentInspect"); - } - if (LLStringUtil::endsWith(url, "/mute")) - { - return LLTrans::getString("TooltipAgentMute"); - } - if (LLStringUtil::endsWith(url, "/unmute")) - { - return LLTrans::getString("TooltipAgentUnmute"); - } - if (LLStringUtil::endsWith(url, "/im")) - { - return LLTrans::getString("TooltipAgentIM"); - } - if (LLStringUtil::endsWith(url, "/pay")) - { - return LLTrans::getString("TooltipAgentPay"); - } - if (LLStringUtil::endsWith(url, "/offerteleport")) - { - return LLTrans::getString("TooltipAgentOfferTeleport"); - } - if (LLStringUtil::endsWith(url, "/requestfriend")) - { - return LLTrans::getString("TooltipAgentRequestFriend"); - } - return LLTrans::getString("TooltipAgentUrl"); -} - -bool LLUrlEntryAgent::underlineOnHoverOnly(const std::string &string) const -{ - std::string url = getUrl(string); - return LLStringUtil::endsWith(url, "/about") || LLStringUtil::endsWith(url, "/inspect"); -} - -std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - if (!gCacheName) - { - // probably at the login screen, use short string for layout - return LLTrans::getString("LoadingData"); - } - - std::string agent_id_string = getIDStringFromUrl(url); - if (agent_id_string.empty()) - { - // something went wrong, just give raw url - return unescapeUrl(url); - } - - LLUUID agent_id(agent_id_string); - if (agent_id.isNull()) - { - return LLTrans::getString("AvatarNameNobody"); - } - - LLAvatarName av_name; - if (LLAvatarNameCache::get(agent_id, &av_name)) - { - std::string label = av_name.getCompleteName(); - - // handle suffixes like /mute or /offerteleport - label = localize_slapp_label(url, label); - return label; - } - else - { - avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(agent_id); - if (it != mAvatarNameCacheConnections.end()) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - mAvatarNameCacheConnections.erase(it); - } - mAvatarNameCacheConnections[agent_id] = LLAvatarNameCache::get(agent_id, boost::bind(&LLUrlEntryAgent::onAvatarNameCache, this, _1, _2)); - - addObserver(agent_id_string, url, cb); - return LLTrans::getString("LoadingData"); - } -} - -LLStyle::Params LLUrlEntryAgent::getStyle() const -{ - LLStyle::Params style_params = LLUrlEntryBase::getStyle(); - style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - return style_params; -} - -std::string localize_slapp_label(const std::string& url, const std::string& full_name) -{ - // customize label string based on agent SLapp suffix - if (LLStringUtil::endsWith(url, "/mute")) - { - return LLTrans::getString("SLappAgentMute") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/unmute")) - { - return LLTrans::getString("SLappAgentUnmute") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/im")) - { - return LLTrans::getString("SLappAgentIM") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/pay")) - { - return LLTrans::getString("SLappAgentPay") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/offerteleport")) - { - return LLTrans::getString("SLappAgentOfferTeleport") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/requestfriend")) - { - return LLTrans::getString("SLappAgentRequestFriend") + " " + full_name; - } - if (LLStringUtil::endsWith(url, "/removefriend")) - { - return LLTrans::getString("SLappAgentRemoveFriend") + " " + full_name; - } - return full_name; -} - - -std::string LLUrlEntryAgent::getIcon(const std::string &url) -{ - // *NOTE: Could look up a badge here by calling getIDStringFromUrl() - // and looking up the badge for the agent. - return mIcon; -} - -// -// LLUrlEntryAgentName describes a Second Life agent name Url, e.g., -// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) -// -LLUrlEntryAgentName::LLUrlEntryAgentName() -{} - -void LLUrlEntryAgentName::onAvatarNameCache(const LLUUID& id, - const LLAvatarName& av_name) -{ - avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(id); - if (it != mAvatarNameCacheConnections.end()) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - mAvatarNameCacheConnections.erase(it); - } - - std::string label = getName(av_name); - // received the agent name from the server - tell our observers - callObservers(id.asString(), label, mIcon); -} - -std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - if (!gCacheName) - { - // probably at the login screen, use short string for layout - return LLTrans::getString("LoadingData"); - } - - std::string agent_id_string = getIDStringFromUrl(url); - if (agent_id_string.empty()) - { - // something went wrong, just give raw url - return unescapeUrl(url); - } - - LLUUID agent_id(agent_id_string); - if (agent_id.isNull()) - { - return LLTrans::getString("AvatarNameNobody"); - } - - LLAvatarName av_name; - if (LLAvatarNameCache::get(agent_id, &av_name)) - { - return getName(av_name); - } - else - { - avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(agent_id); - if (it != mAvatarNameCacheConnections.end()) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - mAvatarNameCacheConnections.erase(it); - } - mAvatarNameCacheConnections[agent_id] = LLAvatarNameCache::get(agent_id, boost::bind(&LLUrlEntryAgentName::onAvatarNameCache, this, _1, _2)); - - addObserver(agent_id_string, url, cb); - return LLTrans::getString("LoadingData"); - } -} - -LLStyle::Params LLUrlEntryAgentName::getStyle() const -{ - // don't override default colors - return LLStyle::Params().is_link(false); -} - -// -// LLUrlEntryAgentCompleteName describes a Second Life agent complete name Url, e.g., -// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename -// -LLUrlEntryAgentCompleteName::LLUrlEntryAgentCompleteName() -{ - mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/completename", - boost::regex::perl|boost::regex::icase); -} - -std::string LLUrlEntryAgentCompleteName::getName(const LLAvatarName& avatar_name) -{ - return avatar_name.getCompleteName(true, true); -} - -// -// LLUrlEntryAgentLegacyName describes a Second Life agent legacy name Url, e.g., -// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/legacyname -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/legacyname -// -LLUrlEntryAgentLegacyName::LLUrlEntryAgentLegacyName() -{ - mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/legacyname", - boost::regex::perl|boost::regex::icase); -} - -std::string LLUrlEntryAgentLegacyName::getName(const LLAvatarName& avatar_name) -{ - return avatar_name.getLegacyName(); -} - -// -// LLUrlEntryAgentDisplayName describes a Second Life agent display name Url, e.g., -// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname -// -LLUrlEntryAgentDisplayName::LLUrlEntryAgentDisplayName() -{ - mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/displayname", - boost::regex::perl|boost::regex::icase); -} - -std::string LLUrlEntryAgentDisplayName::getName(const LLAvatarName& avatar_name) -{ - return avatar_name.getDisplayName(true); -} - -// -// LLUrlEntryAgentUserName describes a Second Life agent user name Url, e.g., -// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username -// -LLUrlEntryAgentUserName::LLUrlEntryAgentUserName() -{ - mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/username", - boost::regex::perl|boost::regex::icase); -} - -std::string LLUrlEntryAgentUserName::getName(const LLAvatarName& avatar_name) -{ - return avatar_name.getAccountName(); -} - -// -// LLUrlEntryGroup Describes a Second Life group Url, e.g., -// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about -// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect -// x-grid-location-info://lincoln.lindenlab.com/app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect -// -LLUrlEntryGroup::LLUrlEntryGroup() -{ - mPattern = boost::regex(APP_HEADER_REGEX "/group/[\\da-f-]+/\\w+", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_group.xml"; - mIcon = "Generic_Group"; - mTooltip = LLTrans::getString("TooltipGroupUrl"); -} - - - -void LLUrlEntryGroup::onGroupNameReceived(const LLUUID& id, - const std::string& name, - bool is_group) -{ - // received the group name from the server - tell our observers - callObservers(id.asString(), name, mIcon); -} - -LLUUID LLUrlEntryGroup::getID(const std::string &string) const -{ - return LLUUID(getIDStringFromUrl(string)); -} - - -std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - if (!gCacheName) - { - // probably at login screen, give something short for layout - return LLTrans::getString("LoadingData"); - } - - std::string group_id_string = getIDStringFromUrl(url); - if (group_id_string.empty()) - { - // something went wrong, give raw url - return unescapeUrl(url); - } - - LLUUID group_id(group_id_string); - std::string group_name; - if (group_id.isNull()) - { - return LLTrans::getString("GroupNameNone"); - } - else if (gCacheName->getGroupName(group_id, group_name)) - { - return group_name; - } - else - { - gCacheName->getGroup(group_id, - boost::bind(&LLUrlEntryGroup::onGroupNameReceived, - this, _1, _2, _3)); - addObserver(group_id_string, url, cb); - return LLTrans::getString("LoadingData"); - } -} - -LLStyle::Params LLUrlEntryGroup::getStyle() const -{ - LLStyle::Params style_params = LLUrlEntryBase::getStyle(); - style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - return style_params; -} - - -// -// LLUrlEntryInventory Describes a Second Life inventory Url, e.g., -// secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select -// -LLUrlEntryInventory::LLUrlEntryInventory() -{ - //*TODO: add supporting of inventory item names with whitespaces - //this pattern cann't parse for example - //secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces¶m2=value - //x-grid-location-info://lincoln.lindenlab.com/app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces¶m2=value - mPattern = boost::regex(APP_HEADER_REGEX "/inventory/[\\da-f-]+/\\w+\\S*", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_inventory.xml"; -} - -std::string LLUrlEntryInventory::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - std::string label = getStringAfterToken(url, "name="); - return LLURI::unescape(label.empty() ? url : label); -} - -// -// LLUrlEntryObjectIM Describes a Second Life inspector for the object Url, e.g., -// secondlife:///app/objectim/7bcd7864-da6b-e43f-4486-91d28a28d95b?name=Object&owner=3de548e1-57be-cfea-2b78-83ae3ad95998&slurl=Danger!%20Danger!/200/200/30/&groupowned=1 -// -LLUrlEntryObjectIM::LLUrlEntryObjectIM() -{ - mPattern = boost::regex("secondlife:///app/objectim/[\\da-f-]+\?\\S*\\w", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_objectim.xml"; -} - -std::string LLUrlEntryObjectIM::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - LLURI uri(url); - LLSD query_map = uri.queryMap(); - if (query_map.has("name")) - return query_map["name"]; - return unescapeUrl(url); -} - -std::string LLUrlEntryObjectIM::getLocation(const std::string &url) const -{ - LLURI uri(url); - LLSD query_map = uri.queryMap(); - if (query_map.has("slurl")) - return query_map["slurl"]; - return LLUrlEntryBase::getLocation(url); -} - -// -// LLUrlEntryChat Describes a Second Life chat Url, e.g., -// secondlife:///app/chat/42/This%20Is%20a%20test -// - -LLUrlEntryChat::LLUrlEntryChat() -{ - mPattern = boost::regex("secondlife:///app/chat/\\d+/\\S+", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_slapp.xml"; - mTooltip = LLTrans::getString("TooltipSLAPP"); -} - -std::string LLUrlEntryChat::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - return unescapeUrl(url); -} - -// LLUrlEntryParcel statics. -LLUUID LLUrlEntryParcel::sAgentID(LLUUID::null); -LLUUID LLUrlEntryParcel::sSessionID(LLUUID::null); -LLHost LLUrlEntryParcel::sRegionHost; -bool LLUrlEntryParcel::sDisconnected(false); -std::set LLUrlEntryParcel::sParcelInfoObservers; - -/// -/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., -/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about -/// x-grid-location-info://lincoln.lindenlab.com/app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about -/// -LLUrlEntryParcel::LLUrlEntryParcel() -{ - mPattern = boost::regex(APP_HEADER_REGEX "/parcel/[\\da-f-]+/about", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_parcel.xml"; - mTooltip = LLTrans::getString("TooltipParcelUrl"); - - sParcelInfoObservers.insert(this); -} - -LLUrlEntryParcel::~LLUrlEntryParcel() -{ - sParcelInfoObservers.erase(this); -} - -std::string LLUrlEntryParcel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - LLSD path_array = LLURI(url).pathArray(); - S32 path_parts = path_array.size(); - - if (path_parts < 3) // no parcel id - { - LL_WARNS() << "Failed to parse url [" << url << "]" << LL_ENDL; - return url; - } - - std::string parcel_id_string = unescapeUrl(path_array[2]); // parcel id - - // Add an observer to call LLUrlLabelCallback when we have parcel name. - addObserver(parcel_id_string, url, cb); - - LLUUID parcel_id(parcel_id_string); - - sendParcelInfoRequest(parcel_id); - - return unescapeUrl(url); -} - -void LLUrlEntryParcel::sendParcelInfoRequest(const LLUUID& parcel_id) -{ - if (sRegionHost.isInvalid() || sDisconnected) return; - - LLMessageSystem *msg = gMessageSystem; - msg->newMessage("ParcelInfoRequest"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, sAgentID ); - msg->addUUID("SessionID", sSessionID); - msg->nextBlock("Data"); - msg->addUUID("ParcelID", parcel_id); - msg->sendReliable(sRegionHost); -} - -void LLUrlEntryParcel::onParcelInfoReceived(const std::string &id, const std::string &label) -{ - callObservers(id, label.empty() ? LLTrans::getString("RegionInfoError") : label, mIcon); -} - -// static -void LLUrlEntryParcel::processParcelInfo(const LLParcelData& parcel_data) -{ - std::string label(LLStringUtil::null); - if (!parcel_data.name.empty()) - { - label = parcel_data.name; - } - // If parcel name is empty use Sim_name (x, y, z) for parcel label. - else if (!parcel_data.sim_name.empty()) - { - S32 region_x = ll_round(parcel_data.global_x) % REGION_WIDTH_UNITS; - S32 region_y = ll_round(parcel_data.global_y) % REGION_WIDTH_UNITS; - S32 region_z = ll_round(parcel_data.global_z); - - label = llformat("%s (%d, %d, %d)", - parcel_data.sim_name.c_str(), region_x, region_y, region_z); - } - - for (std::set::iterator iter = sParcelInfoObservers.begin(); - iter != sParcelInfoObservers.end(); - ++iter) - { - LLUrlEntryParcel* url_entry = *iter; - if (url_entry) - { - url_entry->onParcelInfoReceived(parcel_data.parcel_id.asString(), label); - } - } -} - -// -// LLUrlEntryPlace Describes secondlife:// URLs -// -LLUrlEntryPlace::LLUrlEntryPlace() -{ - mPattern = boost::regex("((x-grid-location-info://[-\\w\\.]+/region/)|(secondlife://))\\S+/?(\\d+/\\d+/\\d+|\\d+/\\d+)/?", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_slurl.xml"; - mTooltip = LLTrans::getString("TooltipSLURL"); -} - -std::string LLUrlEntryPlace::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - // - // we handle SLURLs in the following formats: - // - secondlife://Place/X/Y/Z - // - secondlife://Place/X/Y - // - LLURI uri(url); - std::string location = unescapeUrl(uri.hostName()); - LLSD path_array = uri.pathArray(); - S32 path_parts = path_array.size(); - if (path_parts == 3) - { - // handle slurl with (X,Y,Z) coordinates - std::string x = path_array[0]; - std::string y = path_array[1]; - std::string z = path_array[2]; - return location + " (" + x + "," + y + "," + z + ")"; - } - else if (path_parts == 2) - { - // handle slurl with (X,Y) coordinates - std::string x = path_array[0]; - std::string y = path_array[1]; - return location + " (" + x + "," + y + ")"; - } - - return url; -} - -std::string LLUrlEntryPlace::getLocation(const std::string &url) const -{ - // return the part of the Url after secondlife:// part - return ::getStringAfterToken(url, "://"); -} - -// -// LLUrlEntryRegion Describes secondlife:///app/region/REGION_NAME/X/Y/Z URLs, e.g. -// secondlife:///app/region/Ahern/128/128/0 -// -LLUrlEntryRegion::LLUrlEntryRegion() -{ - mPattern = boost::regex("secondlife:///app/region/[A-Za-z0-9()_%]+(/\\d+)?(/\\d+)?(/\\d+)?/?", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_slurl.xml"; - mTooltip = LLTrans::getString("TooltipSLURL"); -} - -std::string LLUrlEntryRegion::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - // - // we handle SLURLs in the following formats: - // - secondlife:///app/region/Place/X/Y/Z - // - secondlife:///app/region/Place/X/Y - // - secondlife:///app/region/Place/X - // - secondlife:///app/region/Place - // - - LLSD path_array = LLURI(url).pathArray(); - S32 path_parts = path_array.size(); - - if (path_parts < 3) // no region name - { - LL_WARNS() << "Failed to parse url [" << url << "]" << LL_ENDL; - return url; - } - - std::string label = unescapeUrl(path_array[2]); // region name - - if (path_parts > 3) // secondlife:///app/region/Place/X - { - std::string x = path_array[3]; - label += " (" + x; - - if (path_parts > 4) // secondlife:///app/region/Place/X/Y - { - std::string y = path_array[4]; - label += "," + y; - - if (path_parts > 5) // secondlife:///app/region/Place/X/Y/Z - { - std::string z = path_array[5]; - label = label + "," + z; - } - } - - label += ")"; - } - - return label; -} - -std::string LLUrlEntryRegion::getLocation(const std::string &url) const -{ - LLSD path_array = LLURI(url).pathArray(); - std::string region_name = unescapeUrl(path_array[2]); - return region_name; -} - -// -// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g., -// secondlife:///app/teleport/Ahern/50/50/50/ -// x-grid-location-info://lincoln.lindenlab.com/app/teleport/Ahern/50/50/50/ -// -LLUrlEntryTeleport::LLUrlEntryTeleport() -{ - mPattern = boost::regex(APP_HEADER_REGEX "/teleport/\\S+(/\\d+)?(/\\d+)?(/\\d+)?/?\\S*", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_teleport.xml"; - mTooltip = LLTrans::getString("TooltipTeleportUrl"); -} - -std::string LLUrlEntryTeleport::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - // - // we handle teleport SLURLs in the following formats: - // - secondlife:///app/teleport/Place/X/Y/Z - // - secondlife:///app/teleport/Place/X/Y - // - secondlife:///app/teleport/Place/X - // - secondlife:///app/teleport/Place - // - LLURI uri(url); - LLSD path_array = uri.pathArray(); - S32 path_parts = path_array.size(); - std::string host = uri.hostName(); - std::string label = LLTrans::getString("SLurlLabelTeleport"); - if (!host.empty()) - { - label += " " + host; - } - if (path_parts == 6) - { - // handle teleport url with (X,Y,Z) coordinates - std::string location = unescapeUrl(path_array[path_parts-4]); - std::string x = path_array[path_parts-3]; - std::string y = path_array[path_parts-2]; - std::string z = path_array[path_parts-1]; - return label + " " + location + " (" + x + "," + y + "," + z + ")"; - } - else if (path_parts == 5) - { - // handle teleport url with (X,Y) coordinates - std::string location = unescapeUrl(path_array[path_parts-3]); - std::string x = path_array[path_parts-2]; - std::string y = path_array[path_parts-1]; - return label + " " + location + " (" + x + "," + y + ")"; - } - else if (path_parts == 4) - { - // handle teleport url with (X) coordinate only - std::string location = unescapeUrl(path_array[path_parts-2]); - std::string x = path_array[path_parts-1]; - return label + " " + location + " (" + x + ")"; - } - else if (path_parts == 3) - { - // handle teleport url with no coordinates - std::string location = unescapeUrl(path_array[path_parts-1]); - return label + " " + location; - } - - return url; -} - -std::string LLUrlEntryTeleport::getLocation(const std::string &url) const -{ - // return the part of the Url after ///app/teleport - return ::getStringAfterToken(url, "app/teleport/"); -} - -// -// LLUrlEntrySL Describes a generic SLURL, e.g., a Url that starts -// with secondlife:// (used as a catch-all for cases not matched above) -// -LLUrlEntrySL::LLUrlEntrySL() -{ - mPattern = boost::regex("secondlife://(\\w+)?(:\\d+)?/\\S+", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_slapp.xml"; - mTooltip = LLTrans::getString("TooltipSLAPP"); -} - -std::string LLUrlEntrySL::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - return unescapeUrl(url); -} - -// -// LLUrlEntrySLLabel Describes a generic SLURL, e.g., a Url that starts -/// with secondlife:// with the ability to specify a custom label. -// -LLUrlEntrySLLabel::LLUrlEntrySLLabel() -{ - mPattern = boost::regex("\\[secondlife://\\S+[ \t]+[^\\]]+\\]", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_slapp.xml"; - mTooltip = LLTrans::getString("TooltipSLAPP"); -} - -std::string LLUrlEntrySLLabel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - std::string label = getLabelFromWikiLink(url); - return (!LLUrlRegistry::instance().hasUrl(label)) ? label : getUrl(url); -} - -std::string LLUrlEntrySLLabel::getUrl(const std::string &string) const -{ - return getUrlFromWikiLink(string); -} - -std::string LLUrlEntrySLLabel::getTooltip(const std::string &string) const -{ - // return a tooltip corresponding to the URL type instead of the generic one (EXT-4574) - std::string url = getUrl(string); - LLUrlMatch match; - if (LLUrlRegistry::instance().findUrl(url, match)) - { - return match.getTooltip(); - } - - // unrecognized URL? should not happen - return LLUrlEntryBase::getTooltip(string); -} - -bool LLUrlEntrySLLabel::underlineOnHoverOnly(const std::string &string) const -{ - std::string url = getUrl(string); - LLUrlMatch match; - if (LLUrlRegistry::instance().findUrl(url, match)) - { - return match.underlineOnHoverOnly(); - } - - // unrecognized URL? should not happen - return LLUrlEntryBase::underlineOnHoverOnly(string); -} - -// -// LLUrlEntryWorldMap Describes secondlife:/// URLs -// -LLUrlEntryWorldMap::LLUrlEntryWorldMap() -{ - mPattern = boost::regex(APP_HEADER_REGEX "/worldmap/\\S+/?(\\d+)?/?(\\d+)?/?(\\d+)?/?\\S*", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_map.xml"; - mTooltip = LLTrans::getString("TooltipMapUrl"); -} - -std::string LLUrlEntryWorldMap::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - // - // we handle SLURLs in the following formats: - // - secondlife:///app/worldmap/PLACE/X/Y/Z - // - secondlife:///app/worldmap/PLACE/X/Y - // - secondlife:///app/worldmap/PLACE/X - // - LLURI uri(url); - LLSD path_array = uri.pathArray(); - S32 path_parts = path_array.size(); - if (path_parts < 3) - { - return url; - } - - const std::string label = LLTrans::getString("SLurlLabelShowOnMap"); - std::string location = unescapeUrl(path_array[2]); - std::string x = (path_parts > 3) ? path_array[3] : "128"; - std::string y = (path_parts > 4) ? path_array[4] : "128"; - std::string z = (path_parts > 5) ? path_array[5] : "0"; - return label + " " + location + " (" + x + "," + y + "," + z + ")"; -} - -std::string LLUrlEntryWorldMap::getLocation(const std::string &url) const -{ - // return the part of the Url after secondlife:///app/worldmap/ part - return ::getStringAfterToken(url, "app/worldmap/"); -} - -// -// LLUrlEntryNoLink lets us turn of URL detection with ... tags -// -LLUrlEntryNoLink::LLUrlEntryNoLink() -{ - mPattern = boost::regex(".*?", - boost::regex::perl|boost::regex::icase); -} - -std::string LLUrlEntryNoLink::getUrl(const std::string &url) const -{ - // return the text between the and tags - return url.substr(8, url.size()-8-9); -} - -std::string LLUrlEntryNoLink::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - return getUrl(url); -} - -LLStyle::Params LLUrlEntryNoLink::getStyle() const -{ - // Don't render as URL (i.e. no context menu or hand cursor). - return LLStyle::Params().is_link(false); -} - - -// -// LLUrlEntryIcon describes an icon with ... tags -// -LLUrlEntryIcon::LLUrlEntryIcon() -{ - mPattern = boost::regex("\\s*([^<]*)?\\s*", - boost::regex::perl|boost::regex::icase); -} - -std::string LLUrlEntryIcon::getUrl(const std::string &url) const -{ - return LLStringUtil::null; -} - -std::string LLUrlEntryIcon::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - return LLStringUtil::null; -} - -std::string LLUrlEntryIcon::getIcon(const std::string &url) -{ - // Grep icon info between ... tags - // matches[1] contains the icon name/path - boost::match_results matches; - mIcon = (ll_regex_match(url, matches, mPattern) && matches[1].matched) - ? matches[1] - : LLStringUtil::null; - LLStringUtil::trim(mIcon); - return mIcon; -} - -// -// LLUrlEntryEmail Describes a generic mailto: Urls -// -LLUrlEntryEmail::LLUrlEntryEmail() - : LLUrlEntryBase() -{ - mPattern = boost::regex("(mailto:)?[\\w\\.\\-]+@[\\w\\.\\-]+\\.[a-z]{2,63}", - boost::regex::perl | boost::regex::icase); - mMenuName = "menu_url_email.xml"; - mTooltip = LLTrans::getString("TooltipEmail"); -} - -std::string LLUrlEntryEmail::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - int pos = url.find("mailto:"); - - if (pos == std::string::npos) - { - return escapeUrl(url); - } - - std::string ret = escapeUrl(url.substr(pos + 7, url.length() - pos + 8)); - return ret; -} - -std::string LLUrlEntryEmail::getUrl(const std::string &string) const -{ - if (string.find("mailto:") == std::string::npos) - { - return "mailto:" + escapeUrl(string); - } - return escapeUrl(string); -} - -LLUrlEntryExperienceProfile::LLUrlEntryExperienceProfile() -{ - mPattern = boost::regex(APP_HEADER_REGEX "/experience/[\\da-f-]+/profile", - boost::regex::perl|boost::regex::icase); - mIcon = "Generic_Experience"; - mMenuName = "menu_url_experience.xml"; -} - -std::string LLUrlEntryExperienceProfile::getLabel( const std::string &url, const LLUrlLabelCallback &cb ) -{ - if (!gCacheName) - { - // probably at the login screen, use short string for layout - return LLTrans::getString("LoadingData"); - } - - std::string experience_id_string = getIDStringFromUrl(url); - if (experience_id_string.empty()) - { - // something went wrong, just give raw url - return unescapeUrl(url); - } - - LLUUID experience_id(experience_id_string); - if (experience_id.isNull()) - { - return LLTrans::getString("ExperienceNameNull"); - } - - const LLSD& experience_details = LLExperienceCache::instance().get(experience_id); - if(!experience_details.isUndefined()) - { - std::string experience_name_string = experience_details[LLExperienceCache::NAME].asString(); - return experience_name_string.empty() ? LLTrans::getString("ExperienceNameUntitled") : experience_name_string; - } - - addObserver(experience_id_string, url, cb); - LLExperienceCache::instance().get(experience_id, boost::bind(&LLUrlEntryExperienceProfile::onExperienceDetails, this, _1)); - return LLTrans::getString("LoadingData"); - -} - -void LLUrlEntryExperienceProfile::onExperienceDetails( const LLSD& experience_details ) -{ - std::string name = experience_details[LLExperienceCache::NAME].asString(); - if(name.empty()) - { - name = LLTrans::getString("ExperienceNameUntitled"); - } - callObservers(experience_details[LLExperienceCache::EXPERIENCE_ID].asString(), name, LLStringUtil::null); -} - -// -// LLUrlEntryEmail Describes an IPv6 address -// -LLUrlEntryIPv6::LLUrlEntryIPv6() - : LLUrlEntryBase() -{ - mHostPath = "https?://\\[([a-f0-9:]+:+)+[a-f0-9]+]"; - mPattern = boost::regex(mHostPath + "(:\\d{1,5})?(/\\S*)?", - boost::regex::perl | boost::regex::icase); - mMenuName = "menu_url_http.xml"; - mTooltip = LLTrans::getString("TooltipHttpUrl"); -} - -std::string LLUrlEntryIPv6::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - boost::regex regex = boost::regex(mHostPath, boost::regex::perl | boost::regex::icase); - boost::match_results matches; - - if (boost::regex_search(url, matches, regex)) - { - return url.substr(0, matches[0].length()); - } - else - { - return url; - } -} - -std::string LLUrlEntryIPv6::getQuery(const std::string &url) const -{ - boost::regex regex = boost::regex(mHostPath, boost::regex::perl | boost::regex::icase); - boost::match_results matches; - - return boost::regex_replace(url, regex, ""); -} - -std::string LLUrlEntryIPv6::getUrl(const std::string &string) const -{ - return string; -} - - -// -// LLUrlEntryKeybinding Displays currently assigned key -// -LLUrlEntryKeybinding::LLUrlEntryKeybinding() - : LLUrlEntryBase() - , pHandler(NULL) -{ - mPattern = boost::regex(APP_HEADER_REGEX "/keybinding/\\w+(\\?mode=\\w+)?$", - boost::regex::perl | boost::regex::icase); - mMenuName = "menu_url_experience.xml"; - - initLocalization(); -} - -std::string LLUrlEntryKeybinding::getLabel(const std::string& url, const LLUrlLabelCallback& cb) -{ - std::string control = getControlName(url); - - std::map::iterator iter = mLocalizations.find(control); - - std::string keybind; - if (pHandler) - { - keybind = pHandler->getKeyBindingAsString(getMode(url), control); - } - - if (iter != mLocalizations.end()) - { - return iter->second.mLocalization + ": " + keybind; - } - - return control + ": " + keybind; -} - -std::string LLUrlEntryKeybinding::getTooltip(const std::string& url) const -{ - std::string control = getControlName(url); - - std::map::const_iterator iter = mLocalizations.find(control); - if (iter != mLocalizations.end()) - { - return iter->second.mTooltip; - } - return url; -} - -std::string LLUrlEntryKeybinding::getControlName(const std::string& url) const -{ - std::string search = "/keybinding/"; - size_t pos_start = url.find(search); - if (pos_start == std::string::npos) - { - return std::string(); - } - pos_start += search.size(); - - size_t pos_end = url.find("?mode="); - if (pos_end == std::string::npos) - { - pos_end = url.size(); - } - return url.substr(pos_start, pos_end - pos_start); -} - -std::string LLUrlEntryKeybinding::getMode(const std::string& url) const -{ - std::string search = "?mode="; - size_t pos_start = url.find(search); - if (pos_start == std::string::npos) - { - return std::string(); - } - pos_start += search.size(); - return url.substr(pos_start, url.size() - pos_start); -} - -void LLUrlEntryKeybinding::initLocalization() -{ - initLocalizationFromFile("control_table_contents_movement.xml"); - initLocalizationFromFile("control_table_contents_camera.xml"); - initLocalizationFromFile("control_table_contents_editing.xml"); - initLocalizationFromFile("control_table_contents_media.xml"); -} - -void LLUrlEntryKeybinding::initLocalizationFromFile(const std::string& filename) -{ - LLXMLNodePtr xmlNode; - LLScrollListCtrl::Contents contents; - if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode)) - { - LL_WARNS() << "Failed to load " << filename << LL_ENDL; - return; - } - LLXUIParser parser; - parser.readXUI(xmlNode, contents, filename); - - if (!contents.validateBlock()) - { - LL_WARNS() << "Failed to validate " << filename << LL_ENDL; - return; - } - - for (LLInitParam::ParamIterator::const_iterator row_it = contents.rows.begin(); - row_it != contents.rows.end(); - ++row_it) - { - std::string control = row_it->value.getValue().asString(); - if (!control.empty() && control != "menu_separator") - { - mLocalizations[control] = - LLLocalizationData( - row_it->columns.begin()->value.getValue().asString(), - row_it->columns.begin()->tool_tip.getValue() - ); - } - } -} +/** + * @file llurlentry.cpp + * @author Martin Reddy + * @brief Describes the Url types that can be registered in LLUrlRegistry + * + * $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 "llurlentry.h" +#include "lluictrl.h" +#include "lluri.h" +#include "llurlmatch.h" +#include "llurlregistry.h" +#include "lluriparser.h" + +#include "llavatarnamecache.h" +#include "llcachename.h" +#include "llkeyboard.h" +#include "llregex.h" +#include "llscrolllistctrl.h" // for LLUrlEntryKeybinding file parsing +#include "lltrans.h" +#include "lluicolortable.h" +#include "message.h" +#include "llexperiencecache.h" + +#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" + +// Utility functions +std::string localize_slapp_label(const std::string& url, const std::string& full_name); + + +LLUrlEntryBase::LLUrlEntryBase() +{ +} + +LLUrlEntryBase::~LLUrlEntryBase() +{ +} + +std::string LLUrlEntryBase::getUrl(const std::string &string) const +{ + return escapeUrl(string); +} + +//virtual +std::string LLUrlEntryBase::getIcon(const std::string &url) +{ + return mIcon; +} + +LLStyle::Params LLUrlEntryBase::getStyle() const +{ + LLStyle::Params style_params; + style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.font.style = "UNDERLINE"; + return style_params; +} + + +std::string LLUrlEntryBase::getIDStringFromUrl(const std::string &url) const +{ + // return the id from a SLURL in the format /app/{cmd}/{id}/about + LLURI uri(url); + LLSD path_array = uri.pathArray(); + if (path_array.size() == 4) + { + return path_array.get(2).asString(); + } + return ""; +} + +std::string LLUrlEntryBase::unescapeUrl(const std::string &url) const +{ + return LLURI::unescape(url); +} + +std::string LLUrlEntryBase::escapeUrl(const std::string &url) const +{ + static std::string no_escape_chars; + static bool initialized = false; + if (!initialized) + { + no_escape_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~!$?&()*+,@:;=/%#"; + + std::sort(no_escape_chars.begin(), no_escape_chars.end()); + initialized = true; + } + return LLURI::escape(url, no_escape_chars, true); +} + +std::string LLUrlEntryBase::getLabelFromWikiLink(const std::string &url) const +{ + // return the label part from [http://www.example.org Label] + const char *text = url.c_str(); + S32 start = 0; + while (! isspace(text[start])) + { + start++; + } + while (text[start] == ' ' || text[start] == '\t') + { + start++; + } + return unescapeUrl(url.substr(start, url.size()-start-1)); +} + +std::string LLUrlEntryBase::getUrlFromWikiLink(const std::string &string) const +{ + // return the url part from [http://www.example.org Label] + const char *text = string.c_str(); + S32 end = 0; + while (! isspace(text[end])) + { + end++; + } + return escapeUrl(string.substr(1, end-1)); +} + +void LLUrlEntryBase::addObserver(const std::string &id, + const std::string &url, + const LLUrlLabelCallback &cb) +{ + // add a callback to be notified when we have a label for the uuid + LLUrlEntryObserver observer; + observer.url = url; + observer.signal = new LLUrlLabelSignal(); + if (observer.signal) + { + observer.signal->connect(cb); + mObservers.insert(std::pair(id, observer)); + } +} + +// *NOTE: See also LLUrlEntryAgent::callObservers() +void LLUrlEntryBase::callObservers(const std::string &id, + const std::string &label, + const std::string &icon) +{ + // notify all callbacks waiting on the given uuid + typedef std::multimap::iterator observer_it; + std::pair matching_range = mObservers.equal_range(id); + for (observer_it it = matching_range.first; it != matching_range.second;) + { + // call the callback - give it the new label + LLUrlEntryObserver &observer = it->second; + (*observer.signal)(it->second.url, label, icon); + // then remove the signal - we only need to call it once + delete observer.signal; + mObservers.erase(it++); + } +} + +/// is this a match for a URL that should not be hyperlinked? +bool LLUrlEntryBase::isLinkDisabled() const +{ + // this allows us to have a global setting to turn off text hyperlink highlighting/action + static LLCachedControl globally_disabled(*LLUI::getInstance()->mSettingGroups["config"], "DisableTextHyperlinkActions", false); + + return globally_disabled; +} + +bool LLUrlEntryBase::isWikiLinkCorrect(const std::string &labeled_url) const +{ + LLWString wlabel = utf8str_to_wstring(getLabelFromWikiLink(labeled_url)); + wlabel.erase(std::remove(wlabel.begin(), wlabel.end(), L'\u200B'), wlabel.end()); + + // Unicode URL validation, see SL-15243 + std::replace_if(wlabel.begin(), + wlabel.end(), + [](const llwchar &chr) + { + return (chr == L'\u2024') // "One Dot Leader" + || (chr == L'\uFE52') // "Small Full Stop" + || (chr == L'\uFF0E') // "Fullwidth Full Stop" + // Not a decomposition, but suficiently similar + || (chr == L'\u05C5'); // "Hebrew Mark Lower Dot" + }, + L'\u002E'); // Dot "Full Stop" + + std::replace_if(wlabel.begin(), + wlabel.end(), + [](const llwchar &chr) + { + return (chr == L'\u02D0') // "Modifier Letter Colon" + || (chr == L'\uFF1A') // "Fullwidth Colon" + || (chr == L'\u2236') // "Ratio" + || (chr == L'\uFE55'); // "Small Colon" + }, + L'\u003A'); // Colon + + std::replace_if(wlabel.begin(), + wlabel.end(), + [](const llwchar &chr) + { + return (chr == L'\uFF0F'); // "Fullwidth Solidus" + }, + L'\u002F'); // Solidus + + std::string label = wstring_to_utf8str(wlabel); + if ((label.find(".com") != std::string::npos + || label.find("www.") != std::string::npos) + && label.find("://") == std::string::npos) + { + label = "http://" + label; + } + + return !LLUrlRegistry::instance().hasUrl(label); +} + +std::string LLUrlEntryBase::urlToLabelWithGreyQuery(const std::string &url) const +{ + if (url.empty()) + { + return url; + } + LLUriParser up(escapeUrl(url)); + if (up.normalize() == 0) + { + std::string label; + up.extractParts(); + up.glueFirst(label); + + return unescapeUrl(label); + } + return std::string(); +} + +std::string LLUrlEntryBase::urlToGreyQuery(const std::string &url) const +{ + std::string escaped_url = escapeUrl(url); + LLUriParser up(escaped_url); + + std::string label; + up.extractParts(); + up.glueFirst(label, false); + + size_t pos = escaped_url.find(label); + if (pos == std::string::npos) + { + return ""; + } + pos += label.size(); + return unescapeUrl(escaped_url.substr(pos)); +} + + +static std::string getStringAfterToken(const std::string str, const std::string token) +{ + size_t pos = str.find(token); + if (pos == std::string::npos) + { + return ""; + } + + pos += token.size(); + return str.substr(pos, str.size() - pos); +} + +// +// LLUrlEntryHTTP Describes generic http: and https: Urls +// +LLUrlEntryHTTP::LLUrlEntryHTTP() + : LLUrlEntryBase() +{ + mPattern = boost::regex("https?://([^\\s/?\\.#]+\\.?)+\\.\\w+(:\\d+)?(/\\S*)?", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_http.xml"; + mTooltip = LLTrans::getString("TooltipHttpUrl"); +} + +std::string LLUrlEntryHTTP::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return urlToLabelWithGreyQuery(url); +} + +std::string LLUrlEntryHTTP::getQuery(const std::string &url) const +{ + return urlToGreyQuery(url); +} + +std::string LLUrlEntryHTTP::getUrl(const std::string &string) const +{ + if (string.find("://") == std::string::npos) + { + return "http://" + escapeUrl(string); + } + return escapeUrl(string); +} + +std::string LLUrlEntryHTTP::getTooltip(const std::string &url) const +{ + return unescapeUrl(url); +} + +// +// LLUrlEntryHTTP Describes generic http: and https: Urls with custom label +// We use the wikipedia syntax of [http://www.example.org Text] +// +LLUrlEntryHTTPLabel::LLUrlEntryHTTPLabel() +{ + mPattern = boost::regex("\\[https?://\\S+[ \t]+[^\\]]+\\]", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_http.xml"; + mTooltip = LLTrans::getString("TooltipHttpUrl"); +} + +std::string LLUrlEntryHTTPLabel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + std::string label = getLabelFromWikiLink(url); + return (!LLUrlRegistry::instance().hasUrl(label)) ? label : getUrl(url); +} + +std::string LLUrlEntryHTTPLabel::getTooltip(const std::string &string) const +{ + return getUrl(string); +} + +std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string) const +{ + return getUrlFromWikiLink(string); +} + +LLUrlEntryInvalidSLURL::LLUrlEntryInvalidSLURL() + : LLUrlEntryBase() +{ + mPattern = boost::regex("(https?://(maps.secondlife.com|slurl.com)/secondlife/|secondlife://(/app/(worldmap|teleport)/)?)[^ /]+(/-?[0-9]+){1,3}(/?(\\?title|\\?img|\\?msg)=\\S*)?/?", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_http.xml"; + mTooltip = LLTrans::getString("TooltipHttpUrl"); +} + +std::string LLUrlEntryInvalidSLURL::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + + return escapeUrl(url); +} + +std::string LLUrlEntryInvalidSLURL::getUrl(const std::string &string) const +{ + return escapeUrl(string); +} + +std::string LLUrlEntryInvalidSLURL::getTooltip(const std::string &url) const +{ + return unescapeUrl(url); +} + +bool LLUrlEntryInvalidSLURL::isSLURLvalid(const std::string &url) const +{ + S32 actual_parts; + + if(url.find(".com/secondlife/") != std::string::npos) + { + actual_parts = 5; + } + else if(url.find("/app/") != std::string::npos) + { + actual_parts = 6; + } + else + { + actual_parts = 3; + } + + LLURI uri(url); + LLSD path_array = uri.pathArray(); + S32 path_parts = path_array.size(); + S32 x,y,z; + + if (path_parts == actual_parts) + { + // handle slurl with (X,Y,Z) coordinates + LLStringUtil::convertToS32(path_array[path_parts-3],x); + LLStringUtil::convertToS32(path_array[path_parts-2],y); + LLStringUtil::convertToS32(path_array[path_parts-1],z); + + if((x>= 0 && x<= 256) && (y>= 0 && y<= 256) && (z>= 0)) + { + return true; + } + } + else if (path_parts == (actual_parts-1)) + { + // handle slurl with (X,Y) coordinates + + LLStringUtil::convertToS32(path_array[path_parts-2],x); + LLStringUtil::convertToS32(path_array[path_parts-1],y); + ; + if((x>= 0 && x<= 256) && (y>= 0 && y<= 256)) + { + return true; + } + } + else if (path_parts == (actual_parts-2)) + { + // handle slurl with (X) coordinate + LLStringUtil::convertToS32(path_array[path_parts-1],x); + if(x>= 0 && x<= 256) + { + return true; + } + } + + return false; +} + +// +// LLUrlEntrySLURL Describes generic http: and https: Urls +// +LLUrlEntrySLURL::LLUrlEntrySLURL() +{ + // see http://slurl.com/about.php for details on the SLURL format + mPattern = boost::regex("https?://(maps.secondlife.com|slurl.com)/secondlife/[^ /]+(/\\d+){0,3}(/?(\\?title|\\?img|\\?msg)=\\S*)?/?", + boost::regex::perl|boost::regex::icase); + mIcon = "Hand"; + mMenuName = "menu_url_slurl.xml"; + mTooltip = LLTrans::getString("TooltipSLURL"); +} + +std::string LLUrlEntrySLURL::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + // + // we handle SLURLs in the following formats: + // - http://slurl.com/secondlife/Place/X/Y/Z + // - http://slurl.com/secondlife/Place/X/Y + // - http://slurl.com/secondlife/Place/X + // - http://slurl.com/secondlife/Place + // + + LLURI uri(url); + LLSD path_array = uri.pathArray(); + S32 path_parts = path_array.size(); + if (path_parts == 5) + { + // handle slurl with (X,Y,Z) coordinates + std::string location = unescapeUrl(path_array[path_parts-4]); + std::string x = path_array[path_parts-3]; + std::string y = path_array[path_parts-2]; + std::string z = path_array[path_parts-1]; + return location + " (" + x + "," + y + "," + z + ")"; + } + else if (path_parts == 4) + { + // handle slurl with (X,Y) coordinates + std::string location = unescapeUrl(path_array[path_parts-3]); + std::string x = path_array[path_parts-2]; + std::string y = path_array[path_parts-1]; + return location + " (" + x + "," + y + ")"; + } + else if (path_parts == 3) + { + // handle slurl with (X) coordinate + std::string location = unescapeUrl(path_array[path_parts-2]); + std::string x = path_array[path_parts-1]; + return location + " (" + x + ")"; + } + else if (path_parts == 2) + { + // handle slurl with no coordinates + std::string location = unescapeUrl(path_array[path_parts-1]); + return location; + } + + return url; +} + +std::string LLUrlEntrySLURL::getLocation(const std::string &url) const +{ + // return the part of the Url after slurl.com/secondlife/ + const std::string search_string = "/secondlife"; + size_t pos = url.find(search_string); + if (pos == std::string::npos) + { + return ""; + } + + pos += search_string.size() + 1; + return url.substr(pos, url.size() - pos); +} + +// +// LLUrlEntrySeconlifeURL Describes *secondlife.com/ *lindenlab.com/ *secondlifegrid.net/ and *tilia-inc.com/ urls to substitute icon 'hand.png' before link +// +LLUrlEntrySecondlifeURL::LLUrlEntrySecondlifeURL() +{ + mPattern = boost::regex("((http://([-\\w\\.]*\\.)?(secondlife|lindenlab|tilia-inc)\\.com)" + "|" + "(http://([-\\w\\.]*\\.)?secondlifegrid\\.net)" + "|" + "(https://([-\\w\\.]*\\.)?(secondlife|lindenlab|tilia-inc)\\.com(:\\d{1,5})?)" + "|" + "(https://([-\\w\\.]*\\.)?secondlifegrid\\.net(:\\d{1,5})?)" + "|" + "(https?://([-\\w\\.]*\\.)?secondlife\\.io(:\\d{1,5})?))" + "\\/\\S*", + boost::regex::perl|boost::regex::icase); + + mIcon = "Hand"; + mMenuName = "menu_url_http.xml"; + mTooltip = LLTrans::getString("TooltipHttpUrl"); +} + +/// Return the url from a string that matched the regex +std::string LLUrlEntrySecondlifeURL::getUrl(const std::string &string) const +{ + if (string.find("://") == std::string::npos) + { + return "https://" + escapeUrl(string); + } + return escapeUrl(string); +} + +std::string LLUrlEntrySecondlifeURL::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return urlToLabelWithGreyQuery(url); +} + +std::string LLUrlEntrySecondlifeURL::getQuery(const std::string &url) const +{ + return urlToGreyQuery(url); +} + +std::string LLUrlEntrySecondlifeURL::getTooltip(const std::string &url) const +{ + return url; +} + +// +// LLUrlEntrySimpleSecondlifeURL Describes *secondlife.com *lindenlab.com *secondlifegrid.net and *tilia-inc.com urls to substitute icon 'hand.png' before link +// +LLUrlEntrySimpleSecondlifeURL::LLUrlEntrySimpleSecondlifeURL() + { + mPattern = boost::regex("https?://([-\\w\\.]*\\.)?(secondlife|lindenlab|tilia-inc)\\.com(?!\\S)" + "|" + "https?://([-\\w\\.]*\\.)?secondlifegrid\\.net(?!\\S)", + boost::regex::perl|boost::regex::icase); + + mIcon = "Hand"; + mMenuName = "menu_url_http.xml"; +} + +// +// LLUrlEntryAgent Describes a Second Life agent Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about +// +LLUrlEntryAgent::LLUrlEntryAgent() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/\\w+", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_agent.xml"; + mIcon = "Generic_Person"; +} + +// virtual +void LLUrlEntryAgent::callObservers(const std::string &id, + const std::string &label, + const std::string &icon) +{ + // notify all callbacks waiting on the given uuid + typedef std::multimap::iterator observer_it; + std::pair matching_range = mObservers.equal_range(id); + for (observer_it it = matching_range.first; it != matching_range.second;) + { + // call the callback - give it the new label + LLUrlEntryObserver &observer = it->second; + std::string final_label = localize_slapp_label(observer.url, label); + (*observer.signal)(observer.url, final_label, icon); + // then remove the signal - we only need to call it once + delete observer.signal; + mObservers.erase(it++); + } +} + +void LLUrlEntryAgent::onAvatarNameCache(const LLUUID& id, + const LLAvatarName& av_name) +{ + avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(id); + if (it != mAvatarNameCacheConnections.end()) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + mAvatarNameCacheConnections.erase(it); + } + + std::string label = av_name.getCompleteName(); + + // received the agent name from the server - tell our observers + callObservers(id.asString(), label, mIcon); +} + +LLUUID LLUrlEntryAgent::getID(const std::string &string) const +{ + return LLUUID(getIDStringFromUrl(string)); +} + +std::string LLUrlEntryAgent::getTooltip(const std::string &string) const +{ + // return a tooltip corresponding to the URL type instead of the generic one + std::string url = getUrl(string); + + if (LLStringUtil::endsWith(url, "/inspect")) + { + return LLTrans::getString("TooltipAgentInspect"); + } + if (LLStringUtil::endsWith(url, "/mute")) + { + return LLTrans::getString("TooltipAgentMute"); + } + if (LLStringUtil::endsWith(url, "/unmute")) + { + return LLTrans::getString("TooltipAgentUnmute"); + } + if (LLStringUtil::endsWith(url, "/im")) + { + return LLTrans::getString("TooltipAgentIM"); + } + if (LLStringUtil::endsWith(url, "/pay")) + { + return LLTrans::getString("TooltipAgentPay"); + } + if (LLStringUtil::endsWith(url, "/offerteleport")) + { + return LLTrans::getString("TooltipAgentOfferTeleport"); + } + if (LLStringUtil::endsWith(url, "/requestfriend")) + { + return LLTrans::getString("TooltipAgentRequestFriend"); + } + return LLTrans::getString("TooltipAgentUrl"); +} + +bool LLUrlEntryAgent::underlineOnHoverOnly(const std::string &string) const +{ + std::string url = getUrl(string); + return LLStringUtil::endsWith(url, "/about") || LLStringUtil::endsWith(url, "/inspect"); +} + +std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + if (!gCacheName) + { + // probably at the login screen, use short string for layout + return LLTrans::getString("LoadingData"); + } + + std::string agent_id_string = getIDStringFromUrl(url); + if (agent_id_string.empty()) + { + // something went wrong, just give raw url + return unescapeUrl(url); + } + + LLUUID agent_id(agent_id_string); + if (agent_id.isNull()) + { + return LLTrans::getString("AvatarNameNobody"); + } + + LLAvatarName av_name; + if (LLAvatarNameCache::get(agent_id, &av_name)) + { + std::string label = av_name.getCompleteName(); + + // handle suffixes like /mute or /offerteleport + label = localize_slapp_label(url, label); + return label; + } + else + { + avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(agent_id); + if (it != mAvatarNameCacheConnections.end()) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + mAvatarNameCacheConnections.erase(it); + } + mAvatarNameCacheConnections[agent_id] = LLAvatarNameCache::get(agent_id, boost::bind(&LLUrlEntryAgent::onAvatarNameCache, this, _1, _2)); + + addObserver(agent_id_string, url, cb); + return LLTrans::getString("LoadingData"); + } +} + +LLStyle::Params LLUrlEntryAgent::getStyle() const +{ + LLStyle::Params style_params = LLUrlEntryBase::getStyle(); + style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + return style_params; +} + +std::string localize_slapp_label(const std::string& url, const std::string& full_name) +{ + // customize label string based on agent SLapp suffix + if (LLStringUtil::endsWith(url, "/mute")) + { + return LLTrans::getString("SLappAgentMute") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/unmute")) + { + return LLTrans::getString("SLappAgentUnmute") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/im")) + { + return LLTrans::getString("SLappAgentIM") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/pay")) + { + return LLTrans::getString("SLappAgentPay") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/offerteleport")) + { + return LLTrans::getString("SLappAgentOfferTeleport") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/requestfriend")) + { + return LLTrans::getString("SLappAgentRequestFriend") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/removefriend")) + { + return LLTrans::getString("SLappAgentRemoveFriend") + " " + full_name; + } + return full_name; +} + + +std::string LLUrlEntryAgent::getIcon(const std::string &url) +{ + // *NOTE: Could look up a badge here by calling getIDStringFromUrl() + // and looking up the badge for the agent. + return mIcon; +} + +// +// LLUrlEntryAgentName describes a Second Life agent name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) +// +LLUrlEntryAgentName::LLUrlEntryAgentName() +{} + +void LLUrlEntryAgentName::onAvatarNameCache(const LLUUID& id, + const LLAvatarName& av_name) +{ + avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(id); + if (it != mAvatarNameCacheConnections.end()) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + mAvatarNameCacheConnections.erase(it); + } + + std::string label = getName(av_name); + // received the agent name from the server - tell our observers + callObservers(id.asString(), label, mIcon); +} + +std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + if (!gCacheName) + { + // probably at the login screen, use short string for layout + return LLTrans::getString("LoadingData"); + } + + std::string agent_id_string = getIDStringFromUrl(url); + if (agent_id_string.empty()) + { + // something went wrong, just give raw url + return unescapeUrl(url); + } + + LLUUID agent_id(agent_id_string); + if (agent_id.isNull()) + { + return LLTrans::getString("AvatarNameNobody"); + } + + LLAvatarName av_name; + if (LLAvatarNameCache::get(agent_id, &av_name)) + { + return getName(av_name); + } + else + { + avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(agent_id); + if (it != mAvatarNameCacheConnections.end()) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + mAvatarNameCacheConnections.erase(it); + } + mAvatarNameCacheConnections[agent_id] = LLAvatarNameCache::get(agent_id, boost::bind(&LLUrlEntryAgentName::onAvatarNameCache, this, _1, _2)); + + addObserver(agent_id_string, url, cb); + return LLTrans::getString("LoadingData"); + } +} + +LLStyle::Params LLUrlEntryAgentName::getStyle() const +{ + // don't override default colors + return LLStyle::Params().is_link(false); +} + +// +// LLUrlEntryAgentCompleteName describes a Second Life agent complete name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename +// +LLUrlEntryAgentCompleteName::LLUrlEntryAgentCompleteName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/completename", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentCompleteName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.getCompleteName(true, true); +} + +// +// LLUrlEntryAgentLegacyName describes a Second Life agent legacy name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/legacyname +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/legacyname +// +LLUrlEntryAgentLegacyName::LLUrlEntryAgentLegacyName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/legacyname", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentLegacyName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.getLegacyName(); +} + +// +// LLUrlEntryAgentDisplayName describes a Second Life agent display name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname +// +LLUrlEntryAgentDisplayName::LLUrlEntryAgentDisplayName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/displayname", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentDisplayName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.getDisplayName(true); +} + +// +// LLUrlEntryAgentUserName describes a Second Life agent user name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username +// +LLUrlEntryAgentUserName::LLUrlEntryAgentUserName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/username", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentUserName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.getAccountName(); +} + +// +// LLUrlEntryGroup Describes a Second Life group Url, e.g., +// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about +// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect +// x-grid-location-info://lincoln.lindenlab.com/app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect +// +LLUrlEntryGroup::LLUrlEntryGroup() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/group/[\\da-f-]+/\\w+", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_group.xml"; + mIcon = "Generic_Group"; + mTooltip = LLTrans::getString("TooltipGroupUrl"); +} + + + +void LLUrlEntryGroup::onGroupNameReceived(const LLUUID& id, + const std::string& name, + bool is_group) +{ + // received the group name from the server - tell our observers + callObservers(id.asString(), name, mIcon); +} + +LLUUID LLUrlEntryGroup::getID(const std::string &string) const +{ + return LLUUID(getIDStringFromUrl(string)); +} + + +std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + if (!gCacheName) + { + // probably at login screen, give something short for layout + return LLTrans::getString("LoadingData"); + } + + std::string group_id_string = getIDStringFromUrl(url); + if (group_id_string.empty()) + { + // something went wrong, give raw url + return unescapeUrl(url); + } + + LLUUID group_id(group_id_string); + std::string group_name; + if (group_id.isNull()) + { + return LLTrans::getString("GroupNameNone"); + } + else if (gCacheName->getGroupName(group_id, group_name)) + { + return group_name; + } + else + { + gCacheName->getGroup(group_id, + boost::bind(&LLUrlEntryGroup::onGroupNameReceived, + this, _1, _2, _3)); + addObserver(group_id_string, url, cb); + return LLTrans::getString("LoadingData"); + } +} + +LLStyle::Params LLUrlEntryGroup::getStyle() const +{ + LLStyle::Params style_params = LLUrlEntryBase::getStyle(); + style_params.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.readonly_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + return style_params; +} + + +// +// LLUrlEntryInventory Describes a Second Life inventory Url, e.g., +// secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select +// +LLUrlEntryInventory::LLUrlEntryInventory() +{ + //*TODO: add supporting of inventory item names with whitespaces + //this pattern cann't parse for example + //secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces¶m2=value + //x-grid-location-info://lincoln.lindenlab.com/app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces¶m2=value + mPattern = boost::regex(APP_HEADER_REGEX "/inventory/[\\da-f-]+/\\w+\\S*", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_inventory.xml"; +} + +std::string LLUrlEntryInventory::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + std::string label = getStringAfterToken(url, "name="); + return LLURI::unescape(label.empty() ? url : label); +} + +// +// LLUrlEntryObjectIM Describes a Second Life inspector for the object Url, e.g., +// secondlife:///app/objectim/7bcd7864-da6b-e43f-4486-91d28a28d95b?name=Object&owner=3de548e1-57be-cfea-2b78-83ae3ad95998&slurl=Danger!%20Danger!/200/200/30/&groupowned=1 +// +LLUrlEntryObjectIM::LLUrlEntryObjectIM() +{ + mPattern = boost::regex("secondlife:///app/objectim/[\\da-f-]+\?\\S*\\w", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_objectim.xml"; +} + +std::string LLUrlEntryObjectIM::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + LLURI uri(url); + LLSD query_map = uri.queryMap(); + if (query_map.has("name")) + return query_map["name"]; + return unescapeUrl(url); +} + +std::string LLUrlEntryObjectIM::getLocation(const std::string &url) const +{ + LLURI uri(url); + LLSD query_map = uri.queryMap(); + if (query_map.has("slurl")) + return query_map["slurl"]; + return LLUrlEntryBase::getLocation(url); +} + +// +// LLUrlEntryChat Describes a Second Life chat Url, e.g., +// secondlife:///app/chat/42/This%20Is%20a%20test +// + +LLUrlEntryChat::LLUrlEntryChat() +{ + mPattern = boost::regex("secondlife:///app/chat/\\d+/\\S+", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_slapp.xml"; + mTooltip = LLTrans::getString("TooltipSLAPP"); +} + +std::string LLUrlEntryChat::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return unescapeUrl(url); +} + +// LLUrlEntryParcel statics. +LLUUID LLUrlEntryParcel::sAgentID(LLUUID::null); +LLUUID LLUrlEntryParcel::sSessionID(LLUUID::null); +LLHost LLUrlEntryParcel::sRegionHost; +bool LLUrlEntryParcel::sDisconnected(false); +std::set LLUrlEntryParcel::sParcelInfoObservers; + +/// +/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., +/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about +/// x-grid-location-info://lincoln.lindenlab.com/app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about +/// +LLUrlEntryParcel::LLUrlEntryParcel() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/parcel/[\\da-f-]+/about", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_parcel.xml"; + mTooltip = LLTrans::getString("TooltipParcelUrl"); + + sParcelInfoObservers.insert(this); +} + +LLUrlEntryParcel::~LLUrlEntryParcel() +{ + sParcelInfoObservers.erase(this); +} + +std::string LLUrlEntryParcel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + LLSD path_array = LLURI(url).pathArray(); + S32 path_parts = path_array.size(); + + if (path_parts < 3) // no parcel id + { + LL_WARNS() << "Failed to parse url [" << url << "]" << LL_ENDL; + return url; + } + + std::string parcel_id_string = unescapeUrl(path_array[2]); // parcel id + + // Add an observer to call LLUrlLabelCallback when we have parcel name. + addObserver(parcel_id_string, url, cb); + + LLUUID parcel_id(parcel_id_string); + + sendParcelInfoRequest(parcel_id); + + return unescapeUrl(url); +} + +void LLUrlEntryParcel::sendParcelInfoRequest(const LLUUID& parcel_id) +{ + if (sRegionHost.isInvalid() || sDisconnected) return; + + LLMessageSystem *msg = gMessageSystem; + msg->newMessage("ParcelInfoRequest"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, sAgentID ); + msg->addUUID("SessionID", sSessionID); + msg->nextBlock("Data"); + msg->addUUID("ParcelID", parcel_id); + msg->sendReliable(sRegionHost); +} + +void LLUrlEntryParcel::onParcelInfoReceived(const std::string &id, const std::string &label) +{ + callObservers(id, label.empty() ? LLTrans::getString("RegionInfoError") : label, mIcon); +} + +// static +void LLUrlEntryParcel::processParcelInfo(const LLParcelData& parcel_data) +{ + std::string label(LLStringUtil::null); + if (!parcel_data.name.empty()) + { + label = parcel_data.name; + } + // If parcel name is empty use Sim_name (x, y, z) for parcel label. + else if (!parcel_data.sim_name.empty()) + { + S32 region_x = ll_round(parcel_data.global_x) % REGION_WIDTH_UNITS; + S32 region_y = ll_round(parcel_data.global_y) % REGION_WIDTH_UNITS; + S32 region_z = ll_round(parcel_data.global_z); + + label = llformat("%s (%d, %d, %d)", + parcel_data.sim_name.c_str(), region_x, region_y, region_z); + } + + for (std::set::iterator iter = sParcelInfoObservers.begin(); + iter != sParcelInfoObservers.end(); + ++iter) + { + LLUrlEntryParcel* url_entry = *iter; + if (url_entry) + { + url_entry->onParcelInfoReceived(parcel_data.parcel_id.asString(), label); + } + } +} + +// +// LLUrlEntryPlace Describes secondlife:// URLs +// +LLUrlEntryPlace::LLUrlEntryPlace() +{ + mPattern = boost::regex("((x-grid-location-info://[-\\w\\.]+/region/)|(secondlife://))\\S+/?(\\d+/\\d+/\\d+|\\d+/\\d+)/?", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_slurl.xml"; + mTooltip = LLTrans::getString("TooltipSLURL"); +} + +std::string LLUrlEntryPlace::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + // + // we handle SLURLs in the following formats: + // - secondlife://Place/X/Y/Z + // - secondlife://Place/X/Y + // + LLURI uri(url); + std::string location = unescapeUrl(uri.hostName()); + LLSD path_array = uri.pathArray(); + S32 path_parts = path_array.size(); + if (path_parts == 3) + { + // handle slurl with (X,Y,Z) coordinates + std::string x = path_array[0]; + std::string y = path_array[1]; + std::string z = path_array[2]; + return location + " (" + x + "," + y + "," + z + ")"; + } + else if (path_parts == 2) + { + // handle slurl with (X,Y) coordinates + std::string x = path_array[0]; + std::string y = path_array[1]; + return location + " (" + x + "," + y + ")"; + } + + return url; +} + +std::string LLUrlEntryPlace::getLocation(const std::string &url) const +{ + // return the part of the Url after secondlife:// part + return ::getStringAfterToken(url, "://"); +} + +// +// LLUrlEntryRegion Describes secondlife:///app/region/REGION_NAME/X/Y/Z URLs, e.g. +// secondlife:///app/region/Ahern/128/128/0 +// +LLUrlEntryRegion::LLUrlEntryRegion() +{ + mPattern = boost::regex("secondlife:///app/region/[A-Za-z0-9()_%]+(/\\d+)?(/\\d+)?(/\\d+)?/?", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_slurl.xml"; + mTooltip = LLTrans::getString("TooltipSLURL"); +} + +std::string LLUrlEntryRegion::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + // + // we handle SLURLs in the following formats: + // - secondlife:///app/region/Place/X/Y/Z + // - secondlife:///app/region/Place/X/Y + // - secondlife:///app/region/Place/X + // - secondlife:///app/region/Place + // + + LLSD path_array = LLURI(url).pathArray(); + S32 path_parts = path_array.size(); + + if (path_parts < 3) // no region name + { + LL_WARNS() << "Failed to parse url [" << url << "]" << LL_ENDL; + return url; + } + + std::string label = unescapeUrl(path_array[2]); // region name + + if (path_parts > 3) // secondlife:///app/region/Place/X + { + std::string x = path_array[3]; + label += " (" + x; + + if (path_parts > 4) // secondlife:///app/region/Place/X/Y + { + std::string y = path_array[4]; + label += "," + y; + + if (path_parts > 5) // secondlife:///app/region/Place/X/Y/Z + { + std::string z = path_array[5]; + label = label + "," + z; + } + } + + label += ")"; + } + + return label; +} + +std::string LLUrlEntryRegion::getLocation(const std::string &url) const +{ + LLSD path_array = LLURI(url).pathArray(); + std::string region_name = unescapeUrl(path_array[2]); + return region_name; +} + +// +// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g., +// secondlife:///app/teleport/Ahern/50/50/50/ +// x-grid-location-info://lincoln.lindenlab.com/app/teleport/Ahern/50/50/50/ +// +LLUrlEntryTeleport::LLUrlEntryTeleport() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/teleport/\\S+(/\\d+)?(/\\d+)?(/\\d+)?/?\\S*", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_teleport.xml"; + mTooltip = LLTrans::getString("TooltipTeleportUrl"); +} + +std::string LLUrlEntryTeleport::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + // + // we handle teleport SLURLs in the following formats: + // - secondlife:///app/teleport/Place/X/Y/Z + // - secondlife:///app/teleport/Place/X/Y + // - secondlife:///app/teleport/Place/X + // - secondlife:///app/teleport/Place + // + LLURI uri(url); + LLSD path_array = uri.pathArray(); + S32 path_parts = path_array.size(); + std::string host = uri.hostName(); + std::string label = LLTrans::getString("SLurlLabelTeleport"); + if (!host.empty()) + { + label += " " + host; + } + if (path_parts == 6) + { + // handle teleport url with (X,Y,Z) coordinates + std::string location = unescapeUrl(path_array[path_parts-4]); + std::string x = path_array[path_parts-3]; + std::string y = path_array[path_parts-2]; + std::string z = path_array[path_parts-1]; + return label + " " + location + " (" + x + "," + y + "," + z + ")"; + } + else if (path_parts == 5) + { + // handle teleport url with (X,Y) coordinates + std::string location = unescapeUrl(path_array[path_parts-3]); + std::string x = path_array[path_parts-2]; + std::string y = path_array[path_parts-1]; + return label + " " + location + " (" + x + "," + y + ")"; + } + else if (path_parts == 4) + { + // handle teleport url with (X) coordinate only + std::string location = unescapeUrl(path_array[path_parts-2]); + std::string x = path_array[path_parts-1]; + return label + " " + location + " (" + x + ")"; + } + else if (path_parts == 3) + { + // handle teleport url with no coordinates + std::string location = unescapeUrl(path_array[path_parts-1]); + return label + " " + location; + } + + return url; +} + +std::string LLUrlEntryTeleport::getLocation(const std::string &url) const +{ + // return the part of the Url after ///app/teleport + return ::getStringAfterToken(url, "app/teleport/"); +} + +// +// LLUrlEntrySL Describes a generic SLURL, e.g., a Url that starts +// with secondlife:// (used as a catch-all for cases not matched above) +// +LLUrlEntrySL::LLUrlEntrySL() +{ + mPattern = boost::regex("secondlife://(\\w+)?(:\\d+)?/\\S+", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_slapp.xml"; + mTooltip = LLTrans::getString("TooltipSLAPP"); +} + +std::string LLUrlEntrySL::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return unescapeUrl(url); +} + +// +// LLUrlEntrySLLabel Describes a generic SLURL, e.g., a Url that starts +/// with secondlife:// with the ability to specify a custom label. +// +LLUrlEntrySLLabel::LLUrlEntrySLLabel() +{ + mPattern = boost::regex("\\[secondlife://\\S+[ \t]+[^\\]]+\\]", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_slapp.xml"; + mTooltip = LLTrans::getString("TooltipSLAPP"); +} + +std::string LLUrlEntrySLLabel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + std::string label = getLabelFromWikiLink(url); + return (!LLUrlRegistry::instance().hasUrl(label)) ? label : getUrl(url); +} + +std::string LLUrlEntrySLLabel::getUrl(const std::string &string) const +{ + return getUrlFromWikiLink(string); +} + +std::string LLUrlEntrySLLabel::getTooltip(const std::string &string) const +{ + // return a tooltip corresponding to the URL type instead of the generic one (EXT-4574) + std::string url = getUrl(string); + LLUrlMatch match; + if (LLUrlRegistry::instance().findUrl(url, match)) + { + return match.getTooltip(); + } + + // unrecognized URL? should not happen + return LLUrlEntryBase::getTooltip(string); +} + +bool LLUrlEntrySLLabel::underlineOnHoverOnly(const std::string &string) const +{ + std::string url = getUrl(string); + LLUrlMatch match; + if (LLUrlRegistry::instance().findUrl(url, match)) + { + return match.underlineOnHoverOnly(); + } + + // unrecognized URL? should not happen + return LLUrlEntryBase::underlineOnHoverOnly(string); +} + +// +// LLUrlEntryWorldMap Describes secondlife:/// URLs +// +LLUrlEntryWorldMap::LLUrlEntryWorldMap() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/worldmap/\\S+/?(\\d+)?/?(\\d+)?/?(\\d+)?/?\\S*", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_map.xml"; + mTooltip = LLTrans::getString("TooltipMapUrl"); +} + +std::string LLUrlEntryWorldMap::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + // + // we handle SLURLs in the following formats: + // - secondlife:///app/worldmap/PLACE/X/Y/Z + // - secondlife:///app/worldmap/PLACE/X/Y + // - secondlife:///app/worldmap/PLACE/X + // + LLURI uri(url); + LLSD path_array = uri.pathArray(); + S32 path_parts = path_array.size(); + if (path_parts < 3) + { + return url; + } + + const std::string label = LLTrans::getString("SLurlLabelShowOnMap"); + std::string location = unescapeUrl(path_array[2]); + std::string x = (path_parts > 3) ? path_array[3] : "128"; + std::string y = (path_parts > 4) ? path_array[4] : "128"; + std::string z = (path_parts > 5) ? path_array[5] : "0"; + return label + " " + location + " (" + x + "," + y + "," + z + ")"; +} + +std::string LLUrlEntryWorldMap::getLocation(const std::string &url) const +{ + // return the part of the Url after secondlife:///app/worldmap/ part + return ::getStringAfterToken(url, "app/worldmap/"); +} + +// +// LLUrlEntryNoLink lets us turn of URL detection with ... tags +// +LLUrlEntryNoLink::LLUrlEntryNoLink() +{ + mPattern = boost::regex(".*?", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryNoLink::getUrl(const std::string &url) const +{ + // return the text between the and tags + return url.substr(8, url.size()-8-9); +} + +std::string LLUrlEntryNoLink::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return getUrl(url); +} + +LLStyle::Params LLUrlEntryNoLink::getStyle() const +{ + // Don't render as URL (i.e. no context menu or hand cursor). + return LLStyle::Params().is_link(false); +} + + +// +// LLUrlEntryIcon describes an icon with ... tags +// +LLUrlEntryIcon::LLUrlEntryIcon() +{ + mPattern = boost::regex("\\s*([^<]*)?\\s*", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryIcon::getUrl(const std::string &url) const +{ + return LLStringUtil::null; +} + +std::string LLUrlEntryIcon::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return LLStringUtil::null; +} + +std::string LLUrlEntryIcon::getIcon(const std::string &url) +{ + // Grep icon info between ... tags + // matches[1] contains the icon name/path + boost::match_results matches; + mIcon = (ll_regex_match(url, matches, mPattern) && matches[1].matched) + ? matches[1] + : LLStringUtil::null; + LLStringUtil::trim(mIcon); + return mIcon; +} + +// +// LLUrlEntryEmail Describes a generic mailto: Urls +// +LLUrlEntryEmail::LLUrlEntryEmail() + : LLUrlEntryBase() +{ + mPattern = boost::regex("(mailto:)?[\\w\\.\\-]+@[\\w\\.\\-]+\\.[a-z]{2,63}", + boost::regex::perl | boost::regex::icase); + mMenuName = "menu_url_email.xml"; + mTooltip = LLTrans::getString("TooltipEmail"); +} + +std::string LLUrlEntryEmail::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + int pos = url.find("mailto:"); + + if (pos == std::string::npos) + { + return escapeUrl(url); + } + + std::string ret = escapeUrl(url.substr(pos + 7, url.length() - pos + 8)); + return ret; +} + +std::string LLUrlEntryEmail::getUrl(const std::string &string) const +{ + if (string.find("mailto:") == std::string::npos) + { + return "mailto:" + escapeUrl(string); + } + return escapeUrl(string); +} + +LLUrlEntryExperienceProfile::LLUrlEntryExperienceProfile() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/experience/[\\da-f-]+/profile", + boost::regex::perl|boost::regex::icase); + mIcon = "Generic_Experience"; + mMenuName = "menu_url_experience.xml"; +} + +std::string LLUrlEntryExperienceProfile::getLabel( const std::string &url, const LLUrlLabelCallback &cb ) +{ + if (!gCacheName) + { + // probably at the login screen, use short string for layout + return LLTrans::getString("LoadingData"); + } + + std::string experience_id_string = getIDStringFromUrl(url); + if (experience_id_string.empty()) + { + // something went wrong, just give raw url + return unescapeUrl(url); + } + + LLUUID experience_id(experience_id_string); + if (experience_id.isNull()) + { + return LLTrans::getString("ExperienceNameNull"); + } + + const LLSD& experience_details = LLExperienceCache::instance().get(experience_id); + if(!experience_details.isUndefined()) + { + std::string experience_name_string = experience_details[LLExperienceCache::NAME].asString(); + return experience_name_string.empty() ? LLTrans::getString("ExperienceNameUntitled") : experience_name_string; + } + + addObserver(experience_id_string, url, cb); + LLExperienceCache::instance().get(experience_id, boost::bind(&LLUrlEntryExperienceProfile::onExperienceDetails, this, _1)); + return LLTrans::getString("LoadingData"); + +} + +void LLUrlEntryExperienceProfile::onExperienceDetails( const LLSD& experience_details ) +{ + std::string name = experience_details[LLExperienceCache::NAME].asString(); + if(name.empty()) + { + name = LLTrans::getString("ExperienceNameUntitled"); + } + callObservers(experience_details[LLExperienceCache::EXPERIENCE_ID].asString(), name, LLStringUtil::null); +} + +// +// LLUrlEntryEmail Describes an IPv6 address +// +LLUrlEntryIPv6::LLUrlEntryIPv6() + : LLUrlEntryBase() +{ + mHostPath = "https?://\\[([a-f0-9:]+:+)+[a-f0-9]+]"; + mPattern = boost::regex(mHostPath + "(:\\d{1,5})?(/\\S*)?", + boost::regex::perl | boost::regex::icase); + mMenuName = "menu_url_http.xml"; + mTooltip = LLTrans::getString("TooltipHttpUrl"); +} + +std::string LLUrlEntryIPv6::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + boost::regex regex = boost::regex(mHostPath, boost::regex::perl | boost::regex::icase); + boost::match_results matches; + + if (boost::regex_search(url, matches, regex)) + { + return url.substr(0, matches[0].length()); + } + else + { + return url; + } +} + +std::string LLUrlEntryIPv6::getQuery(const std::string &url) const +{ + boost::regex regex = boost::regex(mHostPath, boost::regex::perl | boost::regex::icase); + boost::match_results matches; + + return boost::regex_replace(url, regex, ""); +} + +std::string LLUrlEntryIPv6::getUrl(const std::string &string) const +{ + return string; +} + + +// +// LLUrlEntryKeybinding Displays currently assigned key +// +LLUrlEntryKeybinding::LLUrlEntryKeybinding() + : LLUrlEntryBase() + , pHandler(NULL) +{ + mPattern = boost::regex(APP_HEADER_REGEX "/keybinding/\\w+(\\?mode=\\w+)?$", + boost::regex::perl | boost::regex::icase); + mMenuName = "menu_url_experience.xml"; + + initLocalization(); +} + +std::string LLUrlEntryKeybinding::getLabel(const std::string& url, const LLUrlLabelCallback& cb) +{ + std::string control = getControlName(url); + + std::map::iterator iter = mLocalizations.find(control); + + std::string keybind; + if (pHandler) + { + keybind = pHandler->getKeyBindingAsString(getMode(url), control); + } + + if (iter != mLocalizations.end()) + { + return iter->second.mLocalization + ": " + keybind; + } + + return control + ": " + keybind; +} + +std::string LLUrlEntryKeybinding::getTooltip(const std::string& url) const +{ + std::string control = getControlName(url); + + std::map::const_iterator iter = mLocalizations.find(control); + if (iter != mLocalizations.end()) + { + return iter->second.mTooltip; + } + return url; +} + +std::string LLUrlEntryKeybinding::getControlName(const std::string& url) const +{ + std::string search = "/keybinding/"; + size_t pos_start = url.find(search); + if (pos_start == std::string::npos) + { + return std::string(); + } + pos_start += search.size(); + + size_t pos_end = url.find("?mode="); + if (pos_end == std::string::npos) + { + pos_end = url.size(); + } + return url.substr(pos_start, pos_end - pos_start); +} + +std::string LLUrlEntryKeybinding::getMode(const std::string& url) const +{ + std::string search = "?mode="; + size_t pos_start = url.find(search); + if (pos_start == std::string::npos) + { + return std::string(); + } + pos_start += search.size(); + return url.substr(pos_start, url.size() - pos_start); +} + +void LLUrlEntryKeybinding::initLocalization() +{ + initLocalizationFromFile("control_table_contents_movement.xml"); + initLocalizationFromFile("control_table_contents_camera.xml"); + initLocalizationFromFile("control_table_contents_editing.xml"); + initLocalizationFromFile("control_table_contents_media.xml"); +} + +void LLUrlEntryKeybinding::initLocalizationFromFile(const std::string& filename) +{ + LLXMLNodePtr xmlNode; + LLScrollListCtrl::Contents contents; + if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode)) + { + LL_WARNS() << "Failed to load " << filename << LL_ENDL; + return; + } + LLXUIParser parser; + parser.readXUI(xmlNode, contents, filename); + + if (!contents.validateBlock()) + { + LL_WARNS() << "Failed to validate " << filename << LL_ENDL; + return; + } + + for (LLInitParam::ParamIterator::const_iterator row_it = contents.rows.begin(); + row_it != contents.rows.end(); + ++row_it) + { + std::string control = row_it->value.getValue().asString(); + if (!control.empty() && control != "menu_separator") + { + mLocalizations[control] = + LLLocalizationData( + row_it->columns.begin()->value.getValue().asString(), + row_it->columns.begin()->tool_tip.getValue() + ); + } + } +} diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 463a97afb0..84ff278942 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -1,586 +1,586 @@ -/** - * @file llurlentry.h - * @author Martin Reddy - * @brief Describes the Url types that can be registered in LLUrlRegistry - * - * $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$ - */ - -#ifndef LL_LLURLENTRY_H -#define LL_LLURLENTRY_H - -#include "lluuid.h" -#include "lluicolor.h" -#include "llstyle.h" - -#include "llavatarname.h" -#include "llhost.h" // for resolving parcel name by parcel id - -#include -#include -#include -#include - -class LLAvatarName; - -typedef boost::signals2::signal LLUrlLabelSignal; -typedef LLUrlLabelSignal::slot_type LLUrlLabelCallback; - -/// -/// LLUrlEntryBase is the base class of all Url types registered in the -/// LLUrlRegistry. Each derived classes provides a regular expression -/// to match the Url type (e.g., http://... or secondlife://...) along -/// with an optional icon to display next to instances of the Url in -/// a text display and a XUI file to use for any context menu popup. -/// Functions are also provided to compute an appropriate label and -/// tooltip/status bar text for the Url. -/// -/// Some derived classes of LLUrlEntryBase may wish to compute an -/// appropriate label for a Url by asking the server for information. -/// You must therefore provide a callback method, so that you can be -/// notified when an updated label has been received from the server. -/// This label should then be used to replace any previous label -/// that you received from getLabel() for the Url in question. -/// -class LLUrlEntryBase -{ -public: - LLUrlEntryBase(); - virtual ~LLUrlEntryBase(); - - /// Return the regex pattern that matches this Url - boost::regex getPattern() const { return mPattern; } - - /// Return the url from a string that matched the regex - virtual std::string getUrl(const std::string &string) const; - - /// Given a matched Url, return a label for the Url - virtual std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) { return url; } - - /// Return port, query and fragment parts for the Url - virtual std::string getQuery(const std::string &url) const { return ""; } - - /// Return an icon that can be displayed next to Urls of this type - virtual std::string getIcon(const std::string &url); - - /// Return the style to render the displayed text - virtual LLStyle::Params getStyle() const; - - /// Given a matched Url, return a tooltip string for the hyperlink - virtual std::string getTooltip(const std::string &string) const { return mTooltip; } - - /// Return the name of a XUI file containing the context menu items - std::string getMenuName() const { return mMenuName; } - - /// Return the name of a SL location described by this Url, if any - virtual std::string getLocation(const std::string &url) const { return ""; } - - /// Should this link text be underlined only when mouse is hovered over it? - virtual bool underlineOnHoverOnly(const std::string &string) const { return false; } - - virtual bool isTrusted() const { return false; } - - virtual LLUUID getID(const std::string &string) const { return LLUUID::null; } - - bool isLinkDisabled() const; - - bool isWikiLinkCorrect(const std::string &url) const; - - virtual bool isSLURLvalid(const std::string &url) const { return true; }; - -protected: - std::string getIDStringFromUrl(const std::string &url) const; - std::string escapeUrl(const std::string &url) const; - std::string unescapeUrl(const std::string &url) const; - std::string getLabelFromWikiLink(const std::string &url) const; - std::string getUrlFromWikiLink(const std::string &string) const; - void addObserver(const std::string &id, const std::string &url, const LLUrlLabelCallback &cb); - std::string urlToLabelWithGreyQuery(const std::string &url) const; - std::string urlToGreyQuery(const std::string &url) const; - virtual void callObservers(const std::string &id, const std::string &label, const std::string& icon); - - typedef struct { - std::string url; - LLUrlLabelSignal *signal; - } LLUrlEntryObserver; - - boost::regex mPattern; - std::string mIcon; - std::string mMenuName; - std::string mTooltip; - std::multimap mObservers; -}; - -/// -/// LLUrlEntryHTTP Describes generic http: and https: Urls -/// -class LLUrlEntryHTTP : public LLUrlEntryBase -{ -public: - LLUrlEntryHTTP(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getQuery(const std::string &url) const; - /*virtual*/ std::string getUrl(const std::string &string) const; - /*virtual*/ std::string getTooltip(const std::string &url) const; -}; - -/// -/// LLUrlEntryHTTPLabel Describes generic http: and https: Urls with custom labels -/// -class LLUrlEntryHTTPLabel : public LLUrlEntryBase -{ -public: - LLUrlEntryHTTPLabel(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getTooltip(const std::string &string) const; - /*virtual*/ std::string getUrl(const std::string &string) const; -}; - -class LLUrlEntryInvalidSLURL : public LLUrlEntryBase -{ -public: - LLUrlEntryInvalidSLURL(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getUrl(const std::string &string) const; - /*virtual*/ std::string getTooltip(const std::string &url) const; - - bool isSLURLvalid(const std::string &url) const; -}; - -/// -/// LLUrlEntrySLURL Describes http://slurl.com/... Urls -/// -class LLUrlEntrySLURL : public LLUrlEntryBase -{ -public: - LLUrlEntrySLURL(); - /*virtual*/ bool isTrusted() const { return true; } - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; -}; - -/// -/// LLUrlEntrySeconlifeURLs Describes *secondlife.com and *lindenlab.com Urls -/// -class LLUrlEntrySecondlifeURL : public LLUrlEntryBase -{ -public: - LLUrlEntrySecondlifeURL(); - /*virtual*/ bool isTrusted() const { return true; } - /*virtual*/ std::string getUrl(const std::string &string) const; - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getQuery(const std::string &url) const; - /*virtual*/ std::string getTooltip(const std::string &url) const; -}; - -/// -/// LLUrlEntrySeconlifeURLs Describes *secondlife.com and *lindenlab.com Urls -/// -class LLUrlEntrySimpleSecondlifeURL : public LLUrlEntrySecondlifeURL -{ -public: - LLUrlEntrySimpleSecondlifeURL(); -}; - -/// -/// LLUrlEntryAgent Describes a Second Life agent Url, e.g., -/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about -class LLUrlEntryAgent : public LLUrlEntryBase -{ -public: - LLUrlEntryAgent(); - ~LLUrlEntryAgent() - { - for (avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.begin(); it != mAvatarNameCacheConnections.end(); ++it) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - } - mAvatarNameCacheConnections.clear(); - } - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getIcon(const std::string &url); - /*virtual*/ std::string getTooltip(const std::string &string) const; - /*virtual*/ LLStyle::Params getStyle() const; - /*virtual*/ LLUUID getID(const std::string &string) const; - /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const; -protected: - /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); -private: - void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); - - typedef std::map avatar_name_cache_connection_map_t; - avatar_name_cache_connection_map_t mAvatarNameCacheConnections; -}; - -/// -/// LLUrlEntryAgentName Describes a Second Life agent name Url, e.g., -/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) -/// that displays various forms of user name -/// This is a base class for the various implementations of name display -class LLUrlEntryAgentName : public LLUrlEntryBase, public boost::signals2::trackable -{ -public: - LLUrlEntryAgentName(); - ~LLUrlEntryAgentName() - { - for (avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.begin(); it != mAvatarNameCacheConnections.end(); ++it) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - } - mAvatarNameCacheConnections.clear(); - } - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ LLStyle::Params getStyle() const; -protected: - // override this to pull out relevant name fields - virtual std::string getName(const LLAvatarName& avatar_name) = 0; -private: - void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); - - typedef std::map avatar_name_cache_connection_map_t; - avatar_name_cache_connection_map_t mAvatarNameCacheConnections; -}; - - -/// -/// LLUrlEntryAgentCompleteName Describes a Second Life agent name Url, e.g., -/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename -/// that displays the full display name + user name for an avatar -/// such as "James Linden (james.linden)" -class LLUrlEntryAgentCompleteName : public LLUrlEntryAgentName -{ -public: - LLUrlEntryAgentCompleteName(); -private: - /*virtual*/ std::string getName(const LLAvatarName& avatar_name); -}; - -class LLUrlEntryAgentLegacyName : public LLUrlEntryAgentName -{ -public: - LLUrlEntryAgentLegacyName(); -private: - /*virtual*/ std::string getName(const LLAvatarName& avatar_name); -}; - -/// -/// LLUrlEntryAgentDisplayName Describes a Second Life agent display name Url, e.g., -/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname -/// that displays the just the display name for an avatar -/// such as "James Linden" -class LLUrlEntryAgentDisplayName : public LLUrlEntryAgentName -{ -public: - LLUrlEntryAgentDisplayName(); -private: - /*virtual*/ std::string getName(const LLAvatarName& avatar_name); -}; - -/// -/// LLUrlEntryAgentUserName Describes a Second Life agent username Url, e.g., -/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username -/// that displays the just the display name for an avatar -/// such as "james.linden" -class LLUrlEntryAgentUserName : public LLUrlEntryAgentName -{ -public: - LLUrlEntryAgentUserName(); -private: - /*virtual*/ std::string getName(const LLAvatarName& avatar_name); -}; - -/// -/// LLUrlEntryExperienceProfile Describes a Second Life experience profile Url, e.g., -/// secondlife:///app/experience/0e346d8b-4433-4d66-a6b0-fd37083abc4c/profile -/// that displays the experience name -class LLUrlEntryExperienceProfile : public LLUrlEntryBase -{ -public: - LLUrlEntryExperienceProfile(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); -private: - void onExperienceDetails(const LLSD& experience_details); -}; - - -/// -/// LLUrlEntryGroup Describes a Second Life group Url, e.g., -/// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about -/// -class LLUrlEntryGroup : public LLUrlEntryBase -{ -public: - LLUrlEntryGroup(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ LLStyle::Params getStyle() const; - /*virtual*/ LLUUID getID(const std::string &string) const; -private: - void onGroupNameReceived(const LLUUID& id, const std::string& name, bool is_group); -}; - -/// -/// LLUrlEntryInventory Describes a Second Life inventory Url, e.g., -/// secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select -/// -class LLUrlEntryInventory : public LLUrlEntryBase -{ -public: - LLUrlEntryInventory(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); -private: -}; - -/// -/// LLUrlEntryObjectIM Describes a Second Life inspector for the object Url, e.g., -/// secondlife:///app/objectim/7bcd7864-da6b-e43f-4486-91d28a28d95b?name=Object&owner=3de548e1-57be-cfea-2b78-83ae3ad95998&slurl=Danger!%20Danger!/200/200/30/&groupowned=1 -/// -class LLUrlEntryObjectIM : public LLUrlEntryBase -{ -public: - LLUrlEntryObjectIM(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; -private: -}; - -// -// LLUrlEntryChat Describes a Second Life chat Url, e.g., -// secondlife:///app/chat/42/This%20Is%20a%20test -// -class LLUrlEntryChat : public LLUrlEntryBase -{ -public: - LLUrlEntryChat(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); -}; - -/// -/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., -/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about -/// -class LLUrlEntryParcel : public LLUrlEntryBase -{ -public: - struct LLParcelData - { - LLUUID parcel_id; - std::string name; - std::string sim_name; - F32 global_x; - F32 global_y; - F32 global_z; - }; - - LLUrlEntryParcel(); - ~LLUrlEntryParcel(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - - // Sends a parcel info request to sim. - void sendParcelInfoRequest(const LLUUID& parcel_id); - - // Calls observers of certain parcel id providing them with parcel label. - void onParcelInfoReceived(const std::string &id, const std::string &label); - - // Processes parcel label and triggers notifying observers. - static void processParcelInfo(const LLParcelData& parcel_data); - - // Next 4 setters are used to update agent and viewer connection information - // upon events like user login, viewer disconnect and user changing region host. - // These setters are made public to be accessible from newview and should not be - // used in other cases. - static void setAgentID(const LLUUID& id) { sAgentID = id; } - static void setSessionID(const LLUUID& id) { sSessionID = id; } - static void setRegionHost(const LLHost& host) { sRegionHost = host; } - static void setDisconnected(bool disconnected) { sDisconnected = disconnected; } - -private: - static LLUUID sAgentID; - static LLUUID sSessionID; - static LLHost sRegionHost; - static bool sDisconnected; - static std::set sParcelInfoObservers; -}; - -/// -/// LLUrlEntryPlace Describes a Second Life location Url, e.g., -/// secondlife://Ahern/50/50/50 -/// -class LLUrlEntryPlace : public LLUrlEntryBase -{ -public: - LLUrlEntryPlace(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; -}; - -/// -/// LLUrlEntryRegion Describes a Second Life location Url, e.g., -/// secondlife:///app/region/Ahern/128/128/0 -/// -class LLUrlEntryRegion : public LLUrlEntryBase -{ -public: - LLUrlEntryRegion(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; -}; - -/// -/// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g., -/// secondlife:///app/teleport/Ahern/50/50/50/ -/// -class LLUrlEntryTeleport : public LLUrlEntryBase -{ -public: - LLUrlEntryTeleport(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; -}; - -/// -/// LLUrlEntrySL Describes a generic SLURL, e.g., a Url that starts -/// with secondlife:// (used as a catch-all for cases not matched above) -/// -class LLUrlEntrySL : public LLUrlEntryBase -{ -public: - LLUrlEntrySL(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); -}; - -/// -/// LLUrlEntrySLLabel Describes a generic SLURL, e.g., a Url that starts -/// with secondlife:// with the ability to specify a custom label. -/// -class LLUrlEntrySLLabel : public LLUrlEntryBase -{ -public: - LLUrlEntrySLLabel(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getUrl(const std::string &string) const; - /*virtual*/ std::string getTooltip(const std::string &string) const; - /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const; -}; - -/// -/// LLUrlEntryWorldMap Describes a Second Life worldmap Url, e.g., -/// secondlife:///app/worldmap/Ahern/50/50/50 -/// -class LLUrlEntryWorldMap : public LLUrlEntryBase -{ -public: - LLUrlEntryWorldMap(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; -}; - -/// -/// LLUrlEntryNoLink lets us turn of URL detection with ... tags -/// -class LLUrlEntryNoLink : public LLUrlEntryBase -{ -public: - LLUrlEntryNoLink(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getUrl(const std::string &string) const; - /*virtual*/ LLStyle::Params getStyle() const; -}; - -/// -/// LLUrlEntryIcon describes an icon with ... tags -/// -class LLUrlEntryIcon : public LLUrlEntryBase -{ -public: - LLUrlEntryIcon(); - /*virtual*/ std::string getUrl(const std::string &string) const; - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getIcon(const std::string &url); -}; - -/// -/// LLUrlEntryEmail Describes a generic mailto: Urls -/// -class LLUrlEntryEmail : public LLUrlEntryBase -{ -public: - LLUrlEntryEmail(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getUrl(const std::string &string) const; -}; - -/// -/// LLUrlEntryEmail Describes an IPv6 address -/// -class LLUrlEntryIPv6 : public LLUrlEntryBase -{ -public: - LLUrlEntryIPv6(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getUrl(const std::string &string) const; - /*virtual*/ std::string getQuery(const std::string &url) const; - - std::string mHostPath; -}; - -class LLKeyBindingToStringHandler; - -/// -/// LLUrlEntryKeybinding A way to access keybindings and show currently used one in text. -/// secondlife:///app/keybinding/control_name -class LLUrlEntryKeybinding: public LLUrlEntryBase -{ -public: - LLUrlEntryKeybinding(); - /*virtual*/ std::string getLabel(const std::string& url, const LLUrlLabelCallback& cb); - /*virtual*/ std::string getTooltip(const std::string& url) const; - void setHandler(LLKeyBindingToStringHandler* handler) {pHandler = handler;} -private: - std::string getControlName(const std::string& url) const; - std::string getMode(const std::string& url) const; - void initLocalization(); - void initLocalizationFromFile(const std::string& filename); - - struct LLLocalizationData - { - LLLocalizationData() {} - LLLocalizationData(const std::string& localization, const std::string& tooltip) - : mLocalization(localization) - , mTooltip(tooltip) - {} - std::string mLocalization; - std::string mTooltip; - }; - - std::map mLocalizations; - LLKeyBindingToStringHandler* pHandler; -}; - -#endif +/** + * @file llurlentry.h + * @author Martin Reddy + * @brief Describes the Url types that can be registered in LLUrlRegistry + * + * $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$ + */ + +#ifndef LL_LLURLENTRY_H +#define LL_LLURLENTRY_H + +#include "lluuid.h" +#include "lluicolor.h" +#include "llstyle.h" + +#include "llavatarname.h" +#include "llhost.h" // for resolving parcel name by parcel id + +#include +#include +#include +#include + +class LLAvatarName; + +typedef boost::signals2::signal LLUrlLabelSignal; +typedef LLUrlLabelSignal::slot_type LLUrlLabelCallback; + +/// +/// LLUrlEntryBase is the base class of all Url types registered in the +/// LLUrlRegistry. Each derived classes provides a regular expression +/// to match the Url type (e.g., http://... or secondlife://...) along +/// with an optional icon to display next to instances of the Url in +/// a text display and a XUI file to use for any context menu popup. +/// Functions are also provided to compute an appropriate label and +/// tooltip/status bar text for the Url. +/// +/// Some derived classes of LLUrlEntryBase may wish to compute an +/// appropriate label for a Url by asking the server for information. +/// You must therefore provide a callback method, so that you can be +/// notified when an updated label has been received from the server. +/// This label should then be used to replace any previous label +/// that you received from getLabel() for the Url in question. +/// +class LLUrlEntryBase +{ +public: + LLUrlEntryBase(); + virtual ~LLUrlEntryBase(); + + /// Return the regex pattern that matches this Url + boost::regex getPattern() const { return mPattern; } + + /// Return the url from a string that matched the regex + virtual std::string getUrl(const std::string &string) const; + + /// Given a matched Url, return a label for the Url + virtual std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) { return url; } + + /// Return port, query and fragment parts for the Url + virtual std::string getQuery(const std::string &url) const { return ""; } + + /// Return an icon that can be displayed next to Urls of this type + virtual std::string getIcon(const std::string &url); + + /// Return the style to render the displayed text + virtual LLStyle::Params getStyle() const; + + /// Given a matched Url, return a tooltip string for the hyperlink + virtual std::string getTooltip(const std::string &string) const { return mTooltip; } + + /// Return the name of a XUI file containing the context menu items + std::string getMenuName() const { return mMenuName; } + + /// Return the name of a SL location described by this Url, if any + virtual std::string getLocation(const std::string &url) const { return ""; } + + /// Should this link text be underlined only when mouse is hovered over it? + virtual bool underlineOnHoverOnly(const std::string &string) const { return false; } + + virtual bool isTrusted() const { return false; } + + virtual LLUUID getID(const std::string &string) const { return LLUUID::null; } + + bool isLinkDisabled() const; + + bool isWikiLinkCorrect(const std::string &url) const; + + virtual bool isSLURLvalid(const std::string &url) const { return true; }; + +protected: + std::string getIDStringFromUrl(const std::string &url) const; + std::string escapeUrl(const std::string &url) const; + std::string unescapeUrl(const std::string &url) const; + std::string getLabelFromWikiLink(const std::string &url) const; + std::string getUrlFromWikiLink(const std::string &string) const; + void addObserver(const std::string &id, const std::string &url, const LLUrlLabelCallback &cb); + std::string urlToLabelWithGreyQuery(const std::string &url) const; + std::string urlToGreyQuery(const std::string &url) const; + virtual void callObservers(const std::string &id, const std::string &label, const std::string& icon); + + typedef struct { + std::string url; + LLUrlLabelSignal *signal; + } LLUrlEntryObserver; + + boost::regex mPattern; + std::string mIcon; + std::string mMenuName; + std::string mTooltip; + std::multimap mObservers; +}; + +/// +/// LLUrlEntryHTTP Describes generic http: and https: Urls +/// +class LLUrlEntryHTTP : public LLUrlEntryBase +{ +public: + LLUrlEntryHTTP(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getQuery(const std::string &url) const; + /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ std::string getTooltip(const std::string &url) const; +}; + +/// +/// LLUrlEntryHTTPLabel Describes generic http: and https: Urls with custom labels +/// +class LLUrlEntryHTTPLabel : public LLUrlEntryBase +{ +public: + LLUrlEntryHTTPLabel(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getTooltip(const std::string &string) const; + /*virtual*/ std::string getUrl(const std::string &string) const; +}; + +class LLUrlEntryInvalidSLURL : public LLUrlEntryBase +{ +public: + LLUrlEntryInvalidSLURL(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ std::string getTooltip(const std::string &url) const; + + bool isSLURLvalid(const std::string &url) const; +}; + +/// +/// LLUrlEntrySLURL Describes http://slurl.com/... Urls +/// +class LLUrlEntrySLURL : public LLUrlEntryBase +{ +public: + LLUrlEntrySLURL(); + /*virtual*/ bool isTrusted() const { return true; } + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLocation(const std::string &url) const; +}; + +/// +/// LLUrlEntrySeconlifeURLs Describes *secondlife.com and *lindenlab.com Urls +/// +class LLUrlEntrySecondlifeURL : public LLUrlEntryBase +{ +public: + LLUrlEntrySecondlifeURL(); + /*virtual*/ bool isTrusted() const { return true; } + /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getQuery(const std::string &url) const; + /*virtual*/ std::string getTooltip(const std::string &url) const; +}; + +/// +/// LLUrlEntrySeconlifeURLs Describes *secondlife.com and *lindenlab.com Urls +/// +class LLUrlEntrySimpleSecondlifeURL : public LLUrlEntrySecondlifeURL +{ +public: + LLUrlEntrySimpleSecondlifeURL(); +}; + +/// +/// LLUrlEntryAgent Describes a Second Life agent Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about +class LLUrlEntryAgent : public LLUrlEntryBase +{ +public: + LLUrlEntryAgent(); + ~LLUrlEntryAgent() + { + for (avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.begin(); it != mAvatarNameCacheConnections.end(); ++it) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + } + mAvatarNameCacheConnections.clear(); + } + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getIcon(const std::string &url); + /*virtual*/ std::string getTooltip(const std::string &string) const; + /*virtual*/ LLStyle::Params getStyle() const; + /*virtual*/ LLUUID getID(const std::string &string) const; + /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const; +protected: + /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); +private: + void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); + + typedef std::map avatar_name_cache_connection_map_t; + avatar_name_cache_connection_map_t mAvatarNameCacheConnections; +}; + +/// +/// LLUrlEntryAgentName Describes a Second Life agent name Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) +/// that displays various forms of user name +/// This is a base class for the various implementations of name display +class LLUrlEntryAgentName : public LLUrlEntryBase, public boost::signals2::trackable +{ +public: + LLUrlEntryAgentName(); + ~LLUrlEntryAgentName() + { + for (avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.begin(); it != mAvatarNameCacheConnections.end(); ++it) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + } + mAvatarNameCacheConnections.clear(); + } + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ LLStyle::Params getStyle() const; +protected: + // override this to pull out relevant name fields + virtual std::string getName(const LLAvatarName& avatar_name) = 0; +private: + void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); + + typedef std::map avatar_name_cache_connection_map_t; + avatar_name_cache_connection_map_t mAvatarNameCacheConnections; +}; + + +/// +/// LLUrlEntryAgentCompleteName Describes a Second Life agent name Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename +/// that displays the full display name + user name for an avatar +/// such as "James Linden (james.linden)" +class LLUrlEntryAgentCompleteName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentCompleteName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name); +}; + +class LLUrlEntryAgentLegacyName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentLegacyName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name); +}; + +/// +/// LLUrlEntryAgentDisplayName Describes a Second Life agent display name Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname +/// that displays the just the display name for an avatar +/// such as "James Linden" +class LLUrlEntryAgentDisplayName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentDisplayName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name); +}; + +/// +/// LLUrlEntryAgentUserName Describes a Second Life agent username Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username +/// that displays the just the display name for an avatar +/// such as "james.linden" +class LLUrlEntryAgentUserName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentUserName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name); +}; + +/// +/// LLUrlEntryExperienceProfile Describes a Second Life experience profile Url, e.g., +/// secondlife:///app/experience/0e346d8b-4433-4d66-a6b0-fd37083abc4c/profile +/// that displays the experience name +class LLUrlEntryExperienceProfile : public LLUrlEntryBase +{ +public: + LLUrlEntryExperienceProfile(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); +private: + void onExperienceDetails(const LLSD& experience_details); +}; + + +/// +/// LLUrlEntryGroup Describes a Second Life group Url, e.g., +/// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about +/// +class LLUrlEntryGroup : public LLUrlEntryBase +{ +public: + LLUrlEntryGroup(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ LLStyle::Params getStyle() const; + /*virtual*/ LLUUID getID(const std::string &string) const; +private: + void onGroupNameReceived(const LLUUID& id, const std::string& name, bool is_group); +}; + +/// +/// LLUrlEntryInventory Describes a Second Life inventory Url, e.g., +/// secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select +/// +class LLUrlEntryInventory : public LLUrlEntryBase +{ +public: + LLUrlEntryInventory(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); +private: +}; + +/// +/// LLUrlEntryObjectIM Describes a Second Life inspector for the object Url, e.g., +/// secondlife:///app/objectim/7bcd7864-da6b-e43f-4486-91d28a28d95b?name=Object&owner=3de548e1-57be-cfea-2b78-83ae3ad95998&slurl=Danger!%20Danger!/200/200/30/&groupowned=1 +/// +class LLUrlEntryObjectIM : public LLUrlEntryBase +{ +public: + LLUrlEntryObjectIM(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLocation(const std::string &url) const; +private: +}; + +// +// LLUrlEntryChat Describes a Second Life chat Url, e.g., +// secondlife:///app/chat/42/This%20Is%20a%20test +// +class LLUrlEntryChat : public LLUrlEntryBase +{ +public: + LLUrlEntryChat(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); +}; + +/// +/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., +/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about +/// +class LLUrlEntryParcel : public LLUrlEntryBase +{ +public: + struct LLParcelData + { + LLUUID parcel_id; + std::string name; + std::string sim_name; + F32 global_x; + F32 global_y; + F32 global_z; + }; + + LLUrlEntryParcel(); + ~LLUrlEntryParcel(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + + // Sends a parcel info request to sim. + void sendParcelInfoRequest(const LLUUID& parcel_id); + + // Calls observers of certain parcel id providing them with parcel label. + void onParcelInfoReceived(const std::string &id, const std::string &label); + + // Processes parcel label and triggers notifying observers. + static void processParcelInfo(const LLParcelData& parcel_data); + + // Next 4 setters are used to update agent and viewer connection information + // upon events like user login, viewer disconnect and user changing region host. + // These setters are made public to be accessible from newview and should not be + // used in other cases. + static void setAgentID(const LLUUID& id) { sAgentID = id; } + static void setSessionID(const LLUUID& id) { sSessionID = id; } + static void setRegionHost(const LLHost& host) { sRegionHost = host; } + static void setDisconnected(bool disconnected) { sDisconnected = disconnected; } + +private: + static LLUUID sAgentID; + static LLUUID sSessionID; + static LLHost sRegionHost; + static bool sDisconnected; + static std::set sParcelInfoObservers; +}; + +/// +/// LLUrlEntryPlace Describes a Second Life location Url, e.g., +/// secondlife://Ahern/50/50/50 +/// +class LLUrlEntryPlace : public LLUrlEntryBase +{ +public: + LLUrlEntryPlace(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLocation(const std::string &url) const; +}; + +/// +/// LLUrlEntryRegion Describes a Second Life location Url, e.g., +/// secondlife:///app/region/Ahern/128/128/0 +/// +class LLUrlEntryRegion : public LLUrlEntryBase +{ +public: + LLUrlEntryRegion(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLocation(const std::string &url) const; +}; + +/// +/// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g., +/// secondlife:///app/teleport/Ahern/50/50/50/ +/// +class LLUrlEntryTeleport : public LLUrlEntryBase +{ +public: + LLUrlEntryTeleport(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLocation(const std::string &url) const; +}; + +/// +/// LLUrlEntrySL Describes a generic SLURL, e.g., a Url that starts +/// with secondlife:// (used as a catch-all for cases not matched above) +/// +class LLUrlEntrySL : public LLUrlEntryBase +{ +public: + LLUrlEntrySL(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); +}; + +/// +/// LLUrlEntrySLLabel Describes a generic SLURL, e.g., a Url that starts +/// with secondlife:// with the ability to specify a custom label. +/// +class LLUrlEntrySLLabel : public LLUrlEntryBase +{ +public: + LLUrlEntrySLLabel(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ std::string getTooltip(const std::string &string) const; + /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const; +}; + +/// +/// LLUrlEntryWorldMap Describes a Second Life worldmap Url, e.g., +/// secondlife:///app/worldmap/Ahern/50/50/50 +/// +class LLUrlEntryWorldMap : public LLUrlEntryBase +{ +public: + LLUrlEntryWorldMap(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLocation(const std::string &url) const; +}; + +/// +/// LLUrlEntryNoLink lets us turn of URL detection with ... tags +/// +class LLUrlEntryNoLink : public LLUrlEntryBase +{ +public: + LLUrlEntryNoLink(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ LLStyle::Params getStyle() const; +}; + +/// +/// LLUrlEntryIcon describes an icon with ... tags +/// +class LLUrlEntryIcon : public LLUrlEntryBase +{ +public: + LLUrlEntryIcon(); + /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getIcon(const std::string &url); +}; + +/// +/// LLUrlEntryEmail Describes a generic mailto: Urls +/// +class LLUrlEntryEmail : public LLUrlEntryBase +{ +public: + LLUrlEntryEmail(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getUrl(const std::string &string) const; +}; + +/// +/// LLUrlEntryEmail Describes an IPv6 address +/// +class LLUrlEntryIPv6 : public LLUrlEntryBase +{ +public: + LLUrlEntryIPv6(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ std::string getQuery(const std::string &url) const; + + std::string mHostPath; +}; + +class LLKeyBindingToStringHandler; + +/// +/// LLUrlEntryKeybinding A way to access keybindings and show currently used one in text. +/// secondlife:///app/keybinding/control_name +class LLUrlEntryKeybinding: public LLUrlEntryBase +{ +public: + LLUrlEntryKeybinding(); + /*virtual*/ std::string getLabel(const std::string& url, const LLUrlLabelCallback& cb); + /*virtual*/ std::string getTooltip(const std::string& url) const; + void setHandler(LLKeyBindingToStringHandler* handler) {pHandler = handler;} +private: + std::string getControlName(const std::string& url) const; + std::string getMode(const std::string& url) const; + void initLocalization(); + void initLocalizationFromFile(const std::string& filename); + + struct LLLocalizationData + { + LLLocalizationData() {} + LLLocalizationData(const std::string& localization, const std::string& tooltip) + : mLocalization(localization) + , mTooltip(tooltip) + {} + std::string mLocalization; + std::string mTooltip; + }; + + std::map mLocalizations; + LLKeyBindingToStringHandler* pHandler; +}; + +#endif diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index c46c2d9f6c..28283964e2 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -1,2882 +1,2882 @@ -/** - * @file llview.cpp - * @author James Cook - * @brief Container for other views, anything that draws. - * - * $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" - -#define LLVIEW_CPP -#include "llview.h" - -#include -#include -#include - -#include "llrender.h" -#include "llevent.h" -#include "llfocusmgr.h" -#include "llrect.h" -#include "llstl.h" -#include "llui.h" -#include "lluictrl.h" -#include "llwindow.h" -#include "v3color.h" -#include "lluictrlfactory.h" -#include "lltooltip.h" -#include "llsdutil.h" -#include "llsdserialize.h" -#include "llviewereventrecorder.h" -#include "llkeyboard.h" -// for ui edit hack -#include "llbutton.h" -#include "lllineeditor.h" -#include "lltexteditor.h" -#include "lltextbox.h" - -static const S32 LINE_HEIGHT = 15; - -S32 LLView::sDepth = 0; -bool LLView::sDebugRects = false; -bool LLView::sDebugUnicode = false; -bool LLView::sDebugCamera = false; -bool LLView::sIsRectDirty = false; -LLRect LLView::sDirtyRect; -bool LLView::sDebugRectsShowNames = true; -bool LLView::sDebugKeys = false; -bool LLView::sDebugMouseHandling = false; -std::string LLView::sMouseHandlerMessage; -bool LLView::sForceReshape = false; -std::set LLView::sPreviewHighlightedElements; -bool LLView::sHighlightingDiffs = false; -LLView* LLView::sPreviewClickedElement = NULL; -bool LLView::sDrawPreviewHighlights = false; -S32 LLView::sLastLeftXML = S32_MIN; -S32 LLView::sLastBottomXML = S32_MIN; -std::vector LLViewDrawContext::sDrawContextStack; - -LLView::DrilldownFunc LLView::sDrilldown = - boost::bind(&LLView::pointInView, _1, _2, _3, HIT_TEST_USE_BOUNDING_RECT); - -//#if LL_DEBUG -bool LLView::sIsDrawing = false; -//#endif - -// Compiler optimization, generate extern template -template class LLView* LLView::getChild( - const std::string& name, bool recurse) const; - -static LLDefaultChildRegistry::Register r("view"); - -void deleteView(LLView *aView) -{ - delete aView; -} - -namespace LLInitParam -{ - void TypeValues::declareValues() - { - declare("horizontal", LLView::HORIZONTAL); - declare("vertical", LLView::VERTICAL); - } -} - - -LLView::Follows::Follows() -: string(""), - flags("flags", FOLLOWS_LEFT | FOLLOWS_TOP) -{} - -LLView::Params::Params() -: name("name", std::string("unnamed")), - enabled("enabled", true), - visible("visible", true), - mouse_opaque("mouse_opaque", true), - follows("follows"), - hover_cursor("hover_cursor", "UI_CURSOR_ARROW"), - use_bounding_rect("use_bounding_rect", false), - tab_group("tab_group", 0), - default_tab_group("default_tab_group"), - tool_tip("tool_tip"), - sound_flags("sound_flags", MOUSE_UP), - layout("layout"), - rect("rect"), - bottom_delta("bottom_delta", S32_MAX), - top_pad("top_pad"), - top_delta("top_delta", S32_MAX), - left_pad("left_pad"), - left_delta("left_delta", S32_MAX), - from_xui("from_xui", false), - focus_root("focus_root", false), - needs_translate("translate"), - xmlns("xmlns"), - xmlns_xsi("xmlns:xsi"), - xsi_schemaLocation("xsi:schemaLocation"), - xsi_type("xsi:type") - -{ - addSynonym(rect, ""); -} - -LLView::LLView(const LLView::Params& p) -: mVisible(p.visible), - mInDraw(false), - mName(p.name), - mParentView(NULL), - mReshapeFlags(FOLLOWS_NONE), - mFromXUI(p.from_xui), - mIsFocusRoot(p.focus_root), - mLastVisible(false), - mHoverCursor(getCursorFromString(p.hover_cursor)), - mEnabled(p.enabled), - mMouseOpaque(p.mouse_opaque), - mSoundFlags(p.sound_flags), - mUseBoundingRect(p.use_bounding_rect), - mDefaultTabGroup(p.default_tab_group), - mLastTabGroup(0), - mToolTipMsg((LLStringExplicit)p.tool_tip()), - mDefaultWidgets(NULL) -{ - // create rect first, as this will supply initial follows flags - setShape(p.rect); - parseFollowsFlags(p); -} - -LLView::~LLView() -{ - dirtyRect(); - //LL_INFOS() << "Deleting view " << mName << ":" << (void*) this << LL_ENDL; - if (LLView::sIsDrawing) - { - LL_DEBUGS() << "Deleting view " << mName << " during UI draw() phase" << LL_ENDL; - } -// llassert(LLView::sIsDrawing == false); - -// llassert_always(sDepth == 0); // avoid deleting views while drawing! It can subtly break list iterators - - if( hasMouseCapture() ) - { - //LL_WARNS() << "View holding mouse capture deleted: " << getName() << ". Mouse capture removed." << LL_ENDL; - gFocusMgr.removeMouseCaptureWithoutCallback( this ); - } - - deleteAllChildren(); - - if (mParentView != NULL) - { - mParentView->removeChild(this); - } - - if (mDefaultWidgets) - { - delete mDefaultWidgets; - mDefaultWidgets = NULL; - } -} - -// virtual -bool LLView::isCtrl() const -{ - return false; -} - -// virtual -bool LLView::isPanel() const -{ - return false; -} - -void LLView::setToolTip(const LLStringExplicit& msg) -{ - mToolTipMsg = msg; -} - -bool LLView::setToolTipArg(const LLStringExplicit& key, const LLStringExplicit& text) -{ - mToolTipMsg.setArg(key, text); - return true; -} - -void LLView::setToolTipArgs( const LLStringUtil::format_map_t& args ) -{ - mToolTipMsg.setArgList(args); -} - -// virtual -void LLView::setRect(const LLRect& rect) -{ - mRect = rect; - updateBoundingRect(); -} - -void LLView::setUseBoundingRect( bool use_bounding_rect ) -{ - if (mUseBoundingRect != use_bounding_rect) - { - mUseBoundingRect = use_bounding_rect; - updateBoundingRect(); - } -} - -bool LLView::getUseBoundingRect() const -{ - return mUseBoundingRect; -} - -// virtual -const std::string& LLView::getName() const -{ - static std::string no_name("(no name)"); - - return mName.empty() ? no_name : mName; -} - -void LLView::sendChildToFront(LLView* child) -{ -// llassert_always(sDepth == 0); // Avoid re-ordering while drawing; it can cause subtle iterator bugs - if (child && child->getParent() == this) - { - // minor optimization, but more importantly, - // won't temporarily create an empty list - if (child != mChildList.front()) - { - mChildList.remove( child ); - mChildList.push_front(child); - } - } -} - -void LLView::sendChildToBack(LLView* child) -{ -// llassert_always(sDepth == 0); // Avoid re-ordering while drawing; it can cause subtle iterator bugs - if (child && child->getParent() == this) - { - // minor optimization, but more importantly, - // won't temporarily create an empty list - if (child != mChildList.back()) - { - mChildList.remove( child ); - mChildList.push_back(child); - } - } -} - -// virtual -bool LLView::addChild(LLView* child, S32 tab_group) -{ - if (!child) - { - return false; - } - - if (this == child) - { - LL_ERRS() << "Adding view " << child->getName() << " as child of itself" << LL_ENDL; - } - - // remove from current parent - if (child->mParentView) - { - child->mParentView->removeChild(child); - } - - // add to front of child list, as normal - mChildList.push_front(child); - - // add to tab order list - if (tab_group != 0) - { - mTabOrder.insert(tab_order_pair_t(child, tab_group)); - } - - child->mParentView = this; - if (getVisible() && child->getVisible()) - { - // if child isn't visible it won't affect bounding rect - // if current view is not visible it will be recalculated - // on visibility change - updateBoundingRect(); - } - mLastTabGroup = tab_group; - return true; -} - - -bool LLView::addChildInBack(LLView* child, S32 tab_group) -{ - if(addChild(child, tab_group)) - { - sendChildToBack(child); - return true; - } - - return false; -} - -// remove the specified child from the view, and set it's parent to NULL. -void LLView::removeChild(LLView* child) -{ - //llassert_always(sDepth == 0); // Avoid re-ordering while drawing; it can cause subtle iterator bugs - if (child->mParentView == this) - { - // if we are removing an item we are currently iterating over, that would be bad - llassert(!child->mInDraw); - mChildList.remove( child ); - child->mParentView = NULL; - child_tab_order_t::iterator found = mTabOrder.find(child); - if (found != mTabOrder.end()) - { - mTabOrder.erase(found); - } - } - else - { - LL_WARNS() << "\"" << child->getName() << "\" is not a child of " << getName() << LL_ENDL; - } - updateBoundingRect(); -} - -bool LLView::isInVisibleChain() const -{ - bool visible = true; - - const LLView* viewp = this; - while(viewp) - { - if (!viewp->getVisible()) - { - visible = false; - break; - } - viewp = viewp->getParent(); - } - - return visible; -} - -bool LLView::isInEnabledChain() const -{ - bool enabled = true; - - const LLView* viewp = this; - while(viewp) - { - if (!viewp->getEnabled()) - { - enabled = false; - break; - } - viewp = viewp->getParent(); - } - - return enabled; -} - -static void buildPathname(std::ostream& out, const LLView* view) -{ - if (! (view && view->getParent())) - { - return; // Don't include root in the path. - } - - buildPathname(out, view->getParent()); - - // Build pathname into ostream on the way back from recursion. - out << '/'; - - // substitute all '/' in name with appropriate code - std::string name = view->getName(); - std::size_t found = name.find('/'); - std::size_t start = 0; - while (found != std::string::npos) - { - std::size_t sub_len = found - start; - if (sub_len > 0) - { - out << name.substr(start, sub_len); - } - out << "%2F"; - start = found + 1; - found = name.find('/', start); - } - if (start < name.size()) - { - out << name.substr(start, name.size() - start); - } -} - -std::string LLView::getPathname() const -{ - std::ostringstream out; - buildPathname(out, this); - return out.str(); -} - -//static -std::string LLView::getPathname(const LLView* view) -{ - if (! view) - { - return "NULL"; - } - return view->getPathname(); -} - -// virtual -bool LLView::canFocusChildren() const -{ - return true; -} - -//virtual -void LLView::setEnabled(bool enabled) -{ - mEnabled = enabled; -} - -//virtual -bool LLView::isAvailable() const -{ - return isInEnabledChain() && isInVisibleChain(); -} - -//static -bool LLView::isAvailable(const LLView* view) -{ - return view && view->isAvailable(); -} - -//virtual -bool LLView::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - return false; -} - -//virtual -LLRect LLView::getSnapRect() const -{ - return mRect; -} - -//virtual -LLRect LLView::getRequiredRect() -{ - return mRect; -} - -bool LLView::focusNextRoot() -{ - LLView::child_list_t result = LLView::getFocusRootsQuery().run(this); - return LLView::focusNext(result); -} - -bool LLView::focusPrevRoot() -{ - LLView::child_list_t result = LLView::getFocusRootsQuery().run(this); - return LLView::focusPrev(result); -} - -// static -bool LLView::focusNext(LLView::child_list_t & result) -{ - LLView::child_list_reverse_iter_t focused = result.rend(); - for(LLView::child_list_reverse_iter_t iter = result.rbegin(); - iter != result.rend(); - ++iter) - { - if(gFocusMgr.childHasKeyboardFocus(*iter)) - { - focused = iter; - break; - } - } - LLView::child_list_reverse_iter_t next = focused; - next = (next == result.rend()) ? result.rbegin() : ++next; - while(next != focused) - { - // wrap around to beginning if necessary - if(next == result.rend()) - { - next = result.rbegin(); - } - if ((*next)->isCtrl() && ((LLUICtrl*)*next)->hasTabStop()) - { - LLUICtrl * ctrl = static_cast(*next); - ctrl->setFocus(true); - ctrl->onTabInto(); - gFocusMgr.triggerFocusFlash(); - return true; - } - ++next; - } - return false; -} - -// static -bool LLView::focusPrev(LLView::child_list_t & result) -{ - LLView::child_list_iter_t focused = result.end(); - for(LLView::child_list_iter_t iter = result.begin(); - iter != result.end(); - ++iter) - { - if(gFocusMgr.childHasKeyboardFocus(*iter)) - { - focused = iter; - break; - } - } - LLView::child_list_iter_t next = focused; - next = (next == result.end()) ? result.begin() : ++next; - while(next != focused) - { - // wrap around to beginning if necessary - if(next == result.end()) - { - next = result.begin(); - } - if((*next)->isCtrl()) - { - LLUICtrl * ctrl = static_cast(*next); - if (!ctrl->hasFocus()) - { - ctrl->setFocus(true); - ctrl->onTabInto(); - gFocusMgr.triggerFocusFlash(); - } - return true; - } - ++next; - } - return false; -} - -// delete all children. Override this function if you need to -// perform any extra clean up such as cached pointers to selected -// children, etc. -void LLView::deleteAllChildren() -{ - // clear out the control ordering - mTabOrder.clear(); - - while (!mChildList.empty()) - { - LLView* viewp = mChildList.front(); - viewp->mParentView = NULL; - delete viewp; - mChildList.pop_front(); - } - updateBoundingRect(); -} - -void LLView::setAllChildrenEnabled(bool b) -{ - for (LLView* viewp : mChildList) - { - viewp->setEnabled(b); - } -} - -// virtual -void LLView::setVisible(bool visible) -{ - if ( mVisible != visible ) - { - mVisible = visible; - - // notify children of visibility change if root, or part of visible hierarchy - if (!getParent() || getParent()->isInVisibleChain()) - { - // tell all children of this view that the visibility may have changed - dirtyRect(); - onVisibilityChange( visible ); - } - updateBoundingRect(); - } -} - -// virtual -void LLView::onVisibilityChange ( bool new_visibility ) -{ - bool old_visibility; - bool log_visibility_change = LLViewerEventRecorder::instance().getLoggingStatus(); - for (LLView* viewp : mChildList) - { - if (!viewp) - { - continue; - } - - // only views that are themselves visible will have their overall visibility affected by their ancestors - old_visibility=viewp->getVisible(); - - if(log_visibility_change) - { - if (old_visibility!=new_visibility) - { - LLViewerEventRecorder::instance().logVisibilityChange( viewp->getPathname(), viewp->getName(), new_visibility,"widget"); - } - } - - if (old_visibility) - { - viewp->onVisibilityChange ( new_visibility ); - } - - if(log_visibility_change) - { - // Consider changing returns to confirm success and know which widget grabbed it - // For now assume success and log at highest xui possible - // NOTE we log actual state - which may differ if it somehow failed to set visibility - LL_DEBUGS() << "LLView::handleVisibilityChange - now: " << getVisible() << " xui: " << viewp->getPathname() << " name: " << viewp->getName() << LL_ENDL; - - } - } -} - -// virtual -void LLView::onUpdateScrollToChild(const LLUICtrl * cntrl) -{ - LLView* parent_view = getParent(); - if (parent_view) - { - parent_view->onUpdateScrollToChild(cntrl); - } -} - -// virtual -void LLView::translate(S32 x, S32 y) -{ - mRect.translate(x, y); - updateBoundingRect(); -} - -// virtual -bool LLView::canSnapTo(const LLView* other_view) -{ - return other_view != this && other_view->getVisible(); -} - -// virtual -void LLView::setSnappedTo(const LLView* snap_view) -{ -} - -bool LLView::handleHover(S32 x, S32 y, MASK mask) -{ - return childrenHandleHover( x, y, mask ) != NULL; -} - -void LLView::onMouseEnter(S32 x, S32 y, MASK mask) -{ - //LL_INFOS() << "Mouse entered " << getName() << LL_ENDL; -} - -void LLView::onMouseLeave(S32 x, S32 y, MASK mask) -{ - //LL_INFOS() << "Mouse left " << getName() << LL_ENDL; -} - -bool LLView::visibleAndContains(S32 local_x, S32 local_y) -{ - return sDrilldown(this, local_x, local_y) - && getVisible(); -} - -bool LLView::visibleEnabledAndContains(S32 local_x, S32 local_y) -{ - return visibleAndContains(local_x, local_y) - && getEnabled(); -} - -// This is NOT event recording related -void LLView::logMouseEvent() -{ - if (sDebugMouseHandling) - { - sMouseHandlerMessage = std::string("/") + mName + sMouseHandlerMessage; - } -} - -template -LLView* LLView::childrenHandleCharEvent(const std::string& desc, const METHOD& method, - CHARTYPE c, MASK mask) -{ - if ( getVisible() && getEnabled() ) - { - for (LLView* viewp : mChildList) - { - if ((viewp->*method)(c, mask, true)) - { - if (LLView::sDebugKeys) - { - LL_INFOS() << desc << " handled by " << viewp->getName() << LL_ENDL; - } - return viewp; - } - } - } - return NULL; -} - -// XDATA might be MASK, or S32 clicks -template -LLView* LLView::childrenHandleMouseEvent(const METHOD& method, S32 x, S32 y, XDATA extra, bool allow_mouse_block) -{ - for (LLView* viewp : mChildList) - { - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - - if (!viewp->visibleEnabledAndContains(local_x, local_y)) - { - continue; - } - - if ((viewp->*method)( local_x, local_y, extra ) - || (allow_mouse_block && viewp->blockMouseEvent( local_x, local_y ))) - { - LL_DEBUGS() << "LLView::childrenHandleMouseEvent calling updatemouseeventinfo - local_x|global x "<< local_x << " " << x << "local/global y " << local_y << " " << y << LL_ENDL; - LL_DEBUGS() << "LLView::childrenHandleMouseEvent getPathname for viewp result: " << viewp->getPathname() << "for this view: " << getPathname() << LL_ENDL; - - LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); - - // This is NOT event recording related - viewp->logMouseEvent(); - - return viewp; - } - } - return NULL; -} - -LLView* LLView::childrenHandleToolTip(S32 x, S32 y, MASK mask) -{ - for (LLView* viewp : mChildList) - { - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - // Differs from childrenHandleMouseEvent() in that we want to offer - // tooltips even for disabled widgets. - if(!viewp->visibleAndContains(local_x, local_y)) - { - continue; - } - - if (viewp->handleToolTip(local_x, local_y, mask) - || viewp->blockMouseEvent(local_x, local_y)) - { - // This is NOT event recording related - viewp->logMouseEvent(); - return viewp; - } - } - return NULL; -} - -LLView* LLView::childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - // default to not accepting drag and drop, will be overridden by handler - *accept = ACCEPT_NO; - - for (LLView* viewp : mChildList) - { - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if( !viewp->visibleEnabledAndContains(local_x, local_y)) - { - continue; - } - - // Differs from childrenHandleMouseEvent() simply in that this virtual - // method call diverges pretty radically from the usual (x, y, int). - if (viewp->handleDragAndDrop(local_x, local_y, mask, drop, - cargo_type, - cargo_data, - accept, - tooltip_msg) - || viewp->blockMouseEvent(local_x, local_y)) - { - return viewp; - } - } - return NULL; -} - -LLView* LLView::childrenHandleHover(S32 x, S32 y, MASK mask) -{ - for (LLView* viewp : mChildList) - { - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if(!viewp->visibleEnabledAndContains(local_x, local_y)) - { - continue; - } - - // This call differentiates this method from childrenHandleMouseEvent(). - LLUI::getInstance()->mWindow->setCursor(viewp->getHoverCursor()); - - if (viewp->handleHover(local_x, local_y, mask) - || viewp->blockMouseEvent(local_x, local_y)) - { - // This is NOT event recording related - viewp->logMouseEvent(); - return viewp; - } - } - return NULL; -} - -LLView* LLView::childFromPoint(S32 x, S32 y, bool recur) -{ - if (!getVisible()) - return NULL; - - for (LLView* viewp : mChildList) - { - S32 local_x = x - viewp->getRect().mLeft; - S32 local_y = y - viewp->getRect().mBottom; - if (!viewp->visibleAndContains(local_x, local_y)) - { - continue; - } - // Here we've found the first (frontmost) visible child at this level - // containing the specified point. Is the caller asking us to drill - // down and return the innermost leaf child at this point, or just the - // top-level child? - if (recur) - { - LLView* leaf(viewp->childFromPoint(local_x, local_y, recur)); - // Maybe viewp is already a leaf LLView, or maybe it has children - // but this particular (x, y) point falls between them. If the - // recursive call returns non-NULL, great, use that; else just use - // viewp. - return leaf? leaf : viewp; - } - return viewp; - - } - return 0; -} - -F32 LLView::getTooltipTimeout() -{ - static LLCachedControl tooltip_fast_delay(*LLUI::getInstance()->mSettingGroups["config"], "ToolTipFastDelay", 0.1f); - static LLCachedControl tooltip_delay(*LLUI::getInstance()->mSettingGroups["config"], "ToolTipDelay", 0.7f); - // allow "scrubbing" over ui by showing next tooltip immediately - // if previous one was still visible - return (F32)(LLToolTipMgr::instance().toolTipVisible() - ? tooltip_fast_delay - : tooltip_delay); -} - -// virtual -const std::string LLView::getToolTip() const -{ - if (sDebugUnicode) - { - std::string text = getText(); - if (!text.empty()) - { - const std::string& name = getName(); - std::string tooltip = llformat("Name: \"%s\"", name.c_str()); - - if (const LLFontGL* font = getFont()) - { - tooltip += llformat("\nFont: %s (%s)", - font->getFontDesc().getName().c_str(), - font->getFontDesc().getSize().c_str() - ); - } - - tooltip += "\n\n" + utf8str_showBytesUTF8(text); - - return tooltip; - } - } - - return mToolTipMsg.getString(); -} - -bool LLView::handleToolTip(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // parents provide tooltips first, which are optionally - // overridden by children, in case child is mouse_opaque - std::string tooltip = getToolTip(); - if (!tooltip.empty()) - { - static LLCachedControl allow_ui_tooltips(*LLUI::getInstance()->mSettingGroups["config"], "BasicUITooltips", true); - - // Even if we don't show tooltips, consume the event, nothing below should show tooltip - if (allow_ui_tooltips) - { - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(tooltip) - .sticky_rect(calcScreenRect()) - .delay_time(getTooltipTimeout())); - } - handled = true; - } - - // child tooltips will override our own - LLView* child_handler = childrenHandleToolTip(x, y, mask); - if (child_handler) - { - handled = true; - } - - return handled; -} - -bool LLView::handleKey(KEY key, MASK mask, bool called_from_parent) -{ - bool handled = false; - - if (getVisible() && getEnabled()) - { - if( called_from_parent ) - { - // Downward traversal - handled = childrenHandleKey( key, mask ) != NULL; - } - - if (!handled) - { - // For event logging we don't care which widget handles it - // So we capture the key at the end of this function once we know if it was handled - handled = handleKeyHere( key, mask ); - if (handled) - { - LL_DEBUGS() << "Key handled by " << getName() << LL_ENDL; - } - } - } - - if( !handled && !called_from_parent && mParentView) - { - // Upward traversal - handled = mParentView->handleKey( key, mask, false ); - } - return handled; -} - -bool LLView::handleKeyUp(KEY key, MASK mask, bool called_from_parent) -{ - bool handled = false; - - if (getVisible() && getEnabled()) - { - if (called_from_parent) - { - // Downward traversal - handled = childrenHandleKeyUp(key, mask) != NULL; - } - - if (!handled) - { - // For event logging we don't care which widget handles it - // So we capture the key at the end of this function once we know if it was handled - handled = handleKeyUpHere(key, mask); - if (handled) - { - LL_DEBUGS() << "Key handled by " << getName() << LL_ENDL; - } - } - } - - if (!handled && !called_from_parent && mParentView) - { - // Upward traversal - handled = mParentView->handleKeyUp(key, mask, false); - } - return handled; -} - -// Called from handleKey() -// Handles key in this object. Checking parents and children happens in handleKey() -bool LLView::handleKeyHere(KEY key, MASK mask) -{ - return false; -} - -// Called from handleKey() -// Handles key in this object. Checking parents and children happens in handleKey() -bool LLView::handleKeyUpHere(KEY key, MASK mask) -{ - return false; -} - -bool LLView::handleUnicodeChar(llwchar uni_char, bool called_from_parent) -{ - bool handled = false; - - if (getVisible() && getEnabled()) - { - if( called_from_parent ) - { - // Downward traversal - handled = childrenHandleUnicodeChar( uni_char ) != NULL; - } - - if (!handled) - { - handled = handleUnicodeCharHere(uni_char); - if (handled && LLView::sDebugKeys) - { - LL_INFOS() << "Unicode key " << wchar_utf8_preview(uni_char) << " is handled by " << getName() << LL_ENDL; - } - } - } - - if (!handled && !called_from_parent && mParentView) - { - // Upward traversal - handled = mParentView->handleUnicodeChar(uni_char, false); - } - - if (handled) - { - LLViewerEventRecorder::instance().logKeyUnicodeEvent(uni_char); - } - - return handled; -} - - -bool LLView::handleUnicodeCharHere(llwchar uni_char ) -{ - return false; -} - - -bool LLView::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - return childrenHandleDragAndDrop( x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) != NULL; -} - -void LLView::onMouseCaptureLost() -{ -} - -bool LLView::hasMouseCapture() -{ - return gFocusMgr.getMouseCapture() == this; -} - -bool LLView::handleMouseUp(S32 x, S32 y, MASK mask) -{ - LLView* r = childrenHandleMouseUp( x, y, mask ); - - return (r!=NULL); -} - -bool LLView::handleMouseDown(S32 x, S32 y, MASK mask) -{ - LLView* r= childrenHandleMouseDown(x, y, mask ); - - return (r!=NULL); -} - -bool LLView::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - return childrenHandleDoubleClick( x, y, mask ) != NULL; -} - -bool LLView::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - return childrenHandleScrollWheel( x, y, clicks ) != NULL; -} - -bool LLView::handleScrollHWheel(S32 x, S32 y, S32 clicks) -{ - return childrenHandleScrollHWheel( x, y, clicks ) != NULL; -} - -bool LLView::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - return childrenHandleRightMouseDown( x, y, mask ) != NULL; -} - -bool LLView::handleRightMouseUp(S32 x, S32 y, MASK mask) -{ - return childrenHandleRightMouseUp( x, y, mask ) != NULL; -} - -bool LLView::handleMiddleMouseDown(S32 x, S32 y, MASK mask) -{ - return childrenHandleMiddleMouseDown( x, y, mask ) != NULL; -} - -bool LLView::handleMiddleMouseUp(S32 x, S32 y, MASK mask) -{ - return childrenHandleMiddleMouseUp( x, y, mask ) != NULL; -} - -LLView* LLView::childrenHandleScrollWheel(S32 x, S32 y, S32 clicks) -{ - return childrenHandleMouseEvent(&LLView::handleScrollWheel, x, y, clicks, false); -} - -LLView* LLView::childrenHandleScrollHWheel(S32 x, S32 y, S32 clicks) -{ - return childrenHandleMouseEvent(&LLView::handleScrollHWheel, x, y, clicks, false); -} - -// Called during downward traversal -LLView* LLView::childrenHandleKey(KEY key, MASK mask) -{ - return childrenHandleCharEvent("Key", &LLView::handleKey, key, mask); -} - -// Called during downward traversal -LLView* LLView::childrenHandleKeyUp(KEY key, MASK mask) -{ - return childrenHandleCharEvent("Key Up", &LLView::handleKeyUp, key, mask); -} - -// Called during downward traversal -LLView* LLView::childrenHandleUnicodeChar(llwchar uni_char) -{ - return childrenHandleCharEvent("Unicode character", &LLView::handleUnicodeCharWithDummyMask, - uni_char, MASK_NONE); -} - -LLView* LLView::childrenHandleMouseDown(S32 x, S32 y, MASK mask) -{ - return childrenHandleMouseEvent(&LLView::handleMouseDown, x, y, mask); -} - -LLView* LLView::childrenHandleRightMouseDown(S32 x, S32 y, MASK mask) -{ - return childrenHandleMouseEvent(&LLView::handleRightMouseDown, x, y, mask); -} - -LLView* LLView::childrenHandleMiddleMouseDown(S32 x, S32 y, MASK mask) -{ - return childrenHandleMouseEvent(&LLView::handleMiddleMouseDown, x, y, mask); -} - -LLView* LLView::childrenHandleDoubleClick(S32 x, S32 y, MASK mask) -{ - return childrenHandleMouseEvent(&LLView::handleDoubleClick, x, y, mask); -} - -LLView* LLView::childrenHandleMouseUp(S32 x, S32 y, MASK mask) -{ - return childrenHandleMouseEvent(&LLView::handleMouseUp, x, y, mask); -} - -LLView* LLView::childrenHandleRightMouseUp(S32 x, S32 y, MASK mask) -{ - return childrenHandleMouseEvent(&LLView::handleRightMouseUp, x, y, mask); -} - -LLView* LLView::childrenHandleMiddleMouseUp(S32 x, S32 y, MASK mask) -{ - return childrenHandleMouseEvent(&LLView::handleMiddleMouseUp, x, y, mask); -} - -void LLView::draw() -{ - drawChildren(); -} - -void LLView::drawChildren() -{ - if (!mChildList.empty()) - { - LLView* rootp = LLUI::getInstance()->getRootView(); - ++sDepth; - - for (child_list_reverse_iter_t child_iter = mChildList.rbegin(); child_iter != mChildList.rend();) // ++child_iter) - { - child_list_reverse_iter_t child = child_iter++; - LLView *viewp = *child; - - if (viewp == NULL) - { - continue; - } - - if (viewp->getVisible() && viewp->getRect().isValid()) - { - LLRect screen_rect = viewp->calcScreenRect(); - if ( rootp->getLocalRect().overlaps(screen_rect) && sDirtyRect.overlaps(screen_rect)) - { - LLUI::pushMatrix(); - { - LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom); - // flag the fact we are in draw here, in case overridden draw() method attempts to remove this widget - viewp->mInDraw = true; - viewp->draw(); - viewp->mInDraw = false; - - if (sDebugRects) - { - viewp->drawDebugRect(); - - // Check for bogus rectangle - if (!getRect().isValid()) - { - LL_WARNS() << "Bogus rectangle for " << getName() << " with " << mRect << LL_ENDL; - } - } - } - LLUI::popMatrix(); - } - } - - } - --sDepth; - } -} - -void LLView::dirtyRect() -{ - LLView* child = getParent(); - LLView* parent = child ? child->getParent() : NULL; - LLView* cur = this; - while (child && parent && parent->getParent()) - { //find third to top-most view - cur = child; - child = parent; - parent = parent->getParent(); - } - - if (!sIsRectDirty) - { - sDirtyRect = cur->calcScreenRect(); - sIsRectDirty = true; - } - else - { - sDirtyRect.unionWith(cur->calcScreenRect()); - } -} - -//Draw a box for debugging. -void LLView::drawDebugRect() -{ - std::set::iterator preview_iter = std::find(sPreviewHighlightedElements.begin(), sPreviewHighlightedElements.end(), this); // figure out if it's a previewed element - - LLUI::pushMatrix(); - { - // drawing solids requires texturing be disabled - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - if (getUseBoundingRect()) - { - LLUI::translate((F32)mBoundingRect.mLeft - (F32)mRect.mLeft, (F32)mBoundingRect.mBottom - (F32)mRect.mBottom); - } - - LLRect debug_rect = getUseBoundingRect() ? mBoundingRect : mRect; - - // draw red rectangle for the border - LLColor4 border_color(0.25f, 0.25f, 0.25f, 1.f); - if(preview_iter != sPreviewHighlightedElements.end()) - { - if(LLView::sPreviewClickedElement && this == sPreviewClickedElement) - { - border_color = LLColor4::red; - } - else - { - static LLUIColor scroll_highlighted_color = LLUIColorTable::instance().getColor("ScrollHighlightedColor"); - border_color = scroll_highlighted_color; - } - } - else - { - border_color.mV[sDepth%3] = 1.f; - } - - gGL.color4fv( border_color.mV ); - - gGL.begin(LLRender::LINES); - gGL.vertex2i(0, debug_rect.getHeight() - 1); - gGL.vertex2i(0, 0); - - gGL.vertex2i(0, 0); - gGL.vertex2i(debug_rect.getWidth() - 1, 0); - - gGL.vertex2i(debug_rect.getWidth() - 1, 0); - gGL.vertex2i(debug_rect.getWidth() - 1, debug_rect.getHeight() - 1); - - gGL.vertex2i(debug_rect.getWidth() - 1, debug_rect.getHeight() - 1); - gGL.vertex2i(0, debug_rect.getHeight() - 1); - gGL.end(); - - // Draw the name if it's not a leaf node or not in editing or preview mode - if (mChildList.size() - && preview_iter == sPreviewHighlightedElements.end() - && sDebugRectsShowNames) - { - S32 x, y; - gGL.color4fv( border_color.mV ); - - x = debug_rect.getWidth() / 2; - - S32 rect_height = debug_rect.getHeight(); - S32 lines = rect_height / LINE_HEIGHT + 1; - - S32 depth = 0; - LLView * viewp = this; - while (NULL != viewp) - { - viewp = viewp->getParent(); - depth++; - } - - y = rect_height - LINE_HEIGHT * (depth % lines + 1); - - std::string debug_text = llformat("%s (%d x %d)", getName().c_str(), - debug_rect.getWidth(), debug_rect.getHeight()); - LLFontGL::getFontSansSerifSmall()->renderUTF8(debug_text, 0, (F32)x, (F32)y, border_color, - LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); - } - } - LLUI::popMatrix(); -} - -void LLView::drawChild(LLView* childp, S32 x_offset, S32 y_offset, bool force_draw) -{ - if (childp && childp->getParent() == this) - { - ++sDepth; - - if ((childp->getVisible() && childp->getRect().isValid()) - || force_draw) - { - gGL.matrixMode(LLRender::MM_MODELVIEW); - LLUI::pushMatrix(); - { - LLUI::translate((F32)childp->getRect().mLeft + x_offset, (F32)childp->getRect().mBottom + y_offset); - childp->draw(); - } - LLUI::popMatrix(); - } - - --sDepth; - } -} - - -void LLView::reshape(S32 width, S32 height, bool called_from_parent) -{ - // compute how much things changed and apply reshape logic to children - S32 delta_width = width - getRect().getWidth(); - S32 delta_height = height - getRect().getHeight(); - - if (delta_width || delta_height || sForceReshape) - { - // adjust our rectangle - mRect.mRight = getRect().mLeft + width; - mRect.mTop = getRect().mBottom + height; - - // move child views according to reshape flags - for (LLView* viewp : mChildList) - { - if (viewp != NULL) - { - LLRect child_rect( viewp->mRect ); - - if (viewp->followsRight() && viewp->followsLeft()) - { - child_rect.mRight += delta_width; - } - else if (viewp->followsRight()) - { - child_rect.mLeft += delta_width; - child_rect.mRight += delta_width; - } - else if (viewp->followsLeft()) - { - // left is 0, don't need to adjust coords - } - else - { - // BUG what to do when we don't follow anyone? - // for now, same as followsLeft - } - - if (viewp->followsTop() && viewp->followsBottom()) - { - child_rect.mTop += delta_height; - } - else if (viewp->followsTop()) - { - child_rect.mTop += delta_height; - child_rect.mBottom += delta_height; - } - else if (viewp->followsBottom()) - { - // bottom is 0, so don't need to adjust coords - } - else - { - // BUG what to do when we don't follow? - // for now, same as bottom - } - - S32 delta_x = child_rect.mLeft - viewp->getRect().mLeft; - S32 delta_y = child_rect.mBottom - viewp->getRect().mBottom; - viewp->translate( delta_x, delta_y ); - if (child_rect.getWidth() != viewp->getRect().getWidth() - || child_rect.getHeight() != viewp->getRect().getHeight() - || sForceReshape) - { - viewp->reshape(child_rect.getWidth(), child_rect.getHeight()); - } - } - } - } - - if (!called_from_parent) - { - if (mParentView) - { - mParentView->reshape(mParentView->getRect().getWidth(), mParentView->getRect().getHeight(), false); - } - } - - updateBoundingRect(); -} - -LLRect LLView::calcBoundingRect() -{ - LLRect local_bounding_rect = LLRect::null; - - for (LLView* childp : mChildList) - { - // ignore invisible and "top" children when calculating bounding rect - // such as combobox popups - if (!childp->getVisible() || childp == gFocusMgr.getTopCtrl()) - { - continue; - } - - LLRect child_bounding_rect = childp->getBoundingRect(); - - if (local_bounding_rect.isEmpty()) - { - // start out with bounding rect equal to first visible child's bounding rect - local_bounding_rect = child_bounding_rect; - } - else - { - // accumulate non-null children rectangles - if (!child_bounding_rect.isEmpty()) - { - local_bounding_rect.unionWith(child_bounding_rect); - } - } - } - - // convert to parent-relative coordinates - local_bounding_rect.translate(mRect.mLeft, mRect.mBottom); - return local_bounding_rect; -} - - -void LLView::updateBoundingRect() -{ - if (isDead()) return; - - LLRect cur_rect = mBoundingRect; - - if (getUseBoundingRect()) - { - mBoundingRect = calcBoundingRect(); - } - else - { - mBoundingRect = mRect; - } - - // give parent view a chance to resize, in case we just moved, for example - if (getParent() && getParent()->getUseBoundingRect()) - { - getParent()->updateBoundingRect(); - } - - if (mBoundingRect != cur_rect) - { - dirtyRect(); - } - -} - -LLRect LLView::calcScreenRect() const -{ - LLRect screen_rect; - localPointToScreen(0, 0, &screen_rect.mLeft, &screen_rect.mBottom); - localPointToScreen(getRect().getWidth(), getRect().getHeight(), &screen_rect.mRight, &screen_rect.mTop); - return screen_rect; -} - -LLRect LLView::calcScreenBoundingRect() const -{ - LLRect screen_rect; - // get bounding rect, if used - LLRect bounding_rect = getUseBoundingRect() ? mBoundingRect : mRect; - - // convert to local coordinates, as defined by mRect - bounding_rect.translate(-mRect.mLeft, -mRect.mBottom); - - localPointToScreen(bounding_rect.mLeft, bounding_rect.mBottom, &screen_rect.mLeft, &screen_rect.mBottom); - localPointToScreen(bounding_rect.mRight, bounding_rect.mTop, &screen_rect.mRight, &screen_rect.mTop); - return screen_rect; -} - -LLRect LLView::getLocalBoundingRect() const -{ - LLRect local_bounding_rect = getBoundingRect(); - local_bounding_rect.translate(-mRect.mLeft, -mRect.mBottom); - - return local_bounding_rect; -} - - -LLRect LLView::getLocalRect() const -{ - LLRect local_rect(0, getRect().getHeight(), getRect().getWidth(), 0); - return local_rect; -} - -LLRect LLView::getLocalSnapRect() const -{ - LLRect local_snap_rect = getSnapRect(); - local_snap_rect.translate(-getRect().mLeft, -getRect().mBottom); - return local_snap_rect; -} - -bool LLView::hasAncestor(const LLView* parentp) const -{ - if (!parentp) - { - return false; - } - - LLView* viewp = getParent(); - while(viewp) - { - if (viewp == parentp) - { - return true; - } - viewp = viewp->getParent(); - } - - return false; -} - -//----------------------------------------------------------------------------- - -bool LLView::childHasKeyboardFocus( const std::string& childname ) const -{ - LLView *focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); - - while (focus != NULL) - { - if (focus->getName() == childname) - { - return true; - } - - focus = focus->getParent(); - } - - return false; -} - -//----------------------------------------------------------------------------- - -bool LLView::hasChild(const std::string& childname, bool recurse) const -{ - return findChildView(childname, recurse) != NULL; -} - -//----------------------------------------------------------------------------- -// getChildView() -//----------------------------------------------------------------------------- -LLView* LLView::getChildView(const std::string& name, bool recurse) const -{ - return getChild(name, recurse); -} - -LLView* LLView::findChildView(const std::string& name, bool recurse) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - - // Look for direct children *first* - for (LLView* childp : mChildList) - { - llassert(childp); - if (childp->getName() == name) - { - return childp; - } - } - if (recurse) - { - // Look inside each child as well. - for (LLView* childp : mChildList) - { - llassert(childp); - LLView* viewp = childp->findChildView(name, recurse); - if ( viewp ) - { - return viewp; - } - } - } - return NULL; -} - -bool LLView::parentPointInView(S32 x, S32 y, EHitTestType type) const -{ - return (getUseBoundingRect() && type == HIT_TEST_USE_BOUNDING_RECT) - ? mBoundingRect.pointInRect( x, y ) - : mRect.pointInRect( x, y ); -} - -bool LLView::pointInView(S32 x, S32 y, EHitTestType type) const -{ - return (getUseBoundingRect() && type == HIT_TEST_USE_BOUNDING_RECT) - ? mBoundingRect.pointInRect( x + mRect.mLeft, y + mRect.mBottom ) - : mRect.localPointInRect( x, y ); -} - -bool LLView::blockMouseEvent(S32 x, S32 y) const -{ - return mMouseOpaque && pointInView(x, y, HIT_TEST_IGNORE_BOUNDING_RECT); -} - -// virtual -void LLView::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const -{ - *local_x = screen_x - getRect().mLeft; - *local_y = screen_y - getRect().mBottom; - - const LLView* cur = this; - while( cur->mParentView ) - { - cur = cur->mParentView; - *local_x -= cur->getRect().mLeft; - *local_y -= cur->getRect().mBottom; - } -} - -void LLView::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const -{ - *screen_x = local_x; - *screen_y = local_y; - - const LLView* cur = this; - do - { - LLRect cur_rect = cur->getRect(); - *screen_x += cur_rect.mLeft; - *screen_y += cur_rect.mBottom; - cur = cur->mParentView; - } - while( cur ); -} - -void LLView::screenRectToLocal(const LLRect& screen, LLRect* local) const -{ - *local = screen; - local->translate( -getRect().mLeft, -getRect().mBottom ); - - const LLView* cur = this; - while( cur->mParentView ) - { - cur = cur->mParentView; - local->translate( -cur->getRect().mLeft, -cur->getRect().mBottom ); - } -} - -void LLView::localRectToScreen(const LLRect& local, LLRect* screen) const -{ - *screen = local; - screen->translate( getRect().mLeft, getRect().mBottom ); - - const LLView* cur = this; - while( cur->mParentView ) - { - cur = cur->mParentView; - screen->translate( cur->getRect().mLeft, cur->getRect().mBottom ); - } -} - -LLView* LLView::getRootView() -{ - LLView* view = this; - while( view->mParentView ) - { - view = view->mParentView; - } - return view; -} - -LLView* LLView::findPrevSibling(LLView* child) -{ - child_list_t::iterator prev_it = std::find(mChildList.begin(), mChildList.end(), child); - if (prev_it != mChildList.end() && prev_it != mChildList.begin()) - { - return *(--prev_it); - } - return NULL; -} - -LLView* LLView::findNextSibling(LLView* child) -{ - child_list_t::iterator next_it = std::find(mChildList.begin(), mChildList.end(), child); - if (next_it != mChildList.end()) - { - next_it++; - } - - return (next_it != mChildList.end()) ? *next_it : NULL; -} - - -LLCoordGL getNeededTranslation(const LLRect& input, const LLRect& constraint, S32 min_overlap_pixels) -{ - LLCoordGL delta; - - const S32 KEEP_ONSCREEN_PIXELS_WIDTH = llmin(min_overlap_pixels, input.getWidth()); - const S32 KEEP_ONSCREEN_PIXELS_HEIGHT = llmin(min_overlap_pixels, input.getHeight()); - - if (KEEP_ONSCREEN_PIXELS_WIDTH <= constraint.getWidth() && - KEEP_ONSCREEN_PIXELS_HEIGHT <= constraint.getHeight()) - { - if (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH < constraint.mLeft) - { - delta.mX = constraint.mLeft - (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH); - } - else if (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH > constraint.mRight) - { - delta.mX = constraint.mRight - (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH); - } - - if (input.mTop > constraint.mTop) - { - delta.mY = constraint.mTop - input.mTop; - } - else if (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT < constraint.mBottom) - { - delta.mY = constraint.mBottom - (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT); - } - } - - return delta; -} - -// Moves the view so that it is entirely inside of constraint. -// If the view will not fit because it's too big, aligns with the top and left. -// (Why top and left? That's where the drag bars are for floaters.) -bool LLView::translateIntoRect(const LLRect& constraint, S32 min_overlap_pixels) -{ - return translateRectIntoRect(getRect(), constraint, min_overlap_pixels); -} - -bool LLView::translateRectIntoRect(const LLRect& rect, const LLRect& constraint, S32 min_overlap_pixels) -{ - LLCoordGL translation = getNeededTranslation(rect, constraint, min_overlap_pixels); - - if (translation.mX != 0 || translation.mY != 0) - { - translate(translation.mX, translation.mY); - return true; - } - - return false; -} - -// move this view into "inside" but not onto "exclude" -// NOTE: if this view is already contained in "inside", we ignore the "exclude" rect -bool LLView::translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, S32 min_overlap_pixels) -{ - LLCoordGL translation = getNeededTranslation(getRect(), inside, min_overlap_pixels); - - if (translation.mX != 0 || translation.mY != 0) - { - // translate ourselves into constraint rect - translate(translation.mX, translation.mY); - - // do we overlap with exclusion area? - // keep moving in the same direction to the other side of the exclusion rect - if (exclude.overlaps(getRect())) - { - // moving right - if (translation.mX > 0) - { - translate(exclude.mRight - getRect().mLeft, 0); - } - // moving left - else if (translation.mX < 0) - { - translate(exclude.mLeft - getRect().mRight, 0); - } - - // moving up - if (translation.mY > 0) - { - translate(0, exclude.mTop - getRect().mBottom); - } - // moving down - else if (translation.mY < 0) - { - translate(0, exclude.mBottom - getRect().mTop); - } - } - - return true; - } - return false; -} - - -void LLView::centerWithin(const LLRect& bounds) -{ - S32 left = bounds.mLeft + (bounds.getWidth() - getRect().getWidth()) / 2; - S32 bottom = bounds.mBottom + (bounds.getHeight() - getRect().getHeight()) / 2; - - translate( left - getRect().mLeft, bottom - getRect().mBottom ); -} - -bool LLView::localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, const LLView* other_view) const -{ - const LLView* cur_view = this; - const LLView* root_view = NULL; - - while (cur_view) - { - if (cur_view == other_view) - { - *other_x = x; - *other_y = y; - return true; - } - - x += cur_view->getRect().mLeft; - y += cur_view->getRect().mBottom; - - cur_view = cur_view->getParent(); - root_view = cur_view; - } - - // assuming common root between two views, chase other_view's parents up to root - cur_view = other_view; - while (cur_view) - { - x -= cur_view->getRect().mLeft; - y -= cur_view->getRect().mBottom; - - cur_view = cur_view->getParent(); - - if (cur_view == root_view) - { - *other_x = x; - *other_y = y; - return true; - } - } - - *other_x = x; - *other_y = y; - return false; -} - -bool LLView::localRectToOtherView( const LLRect& local, LLRect* other, const LLView* other_view ) const -{ - LLRect cur_rect = local; - const LLView* cur_view = this; - const LLView* root_view = NULL; - - while (cur_view) - { - if (cur_view == other_view) - { - *other = cur_rect; - return true; - } - - cur_rect.translate(cur_view->getRect().mLeft, cur_view->getRect().mBottom); - - cur_view = cur_view->getParent(); - root_view = cur_view; - } - - // assuming common root between two views, chase other_view's parents up to root - cur_view = other_view; - while (cur_view) - { - cur_rect.translate(-cur_view->getRect().mLeft, -cur_view->getRect().mBottom); - - cur_view = cur_view->getParent(); - - if (cur_view == root_view) - { - *other = cur_rect; - return true; - } - } - - *other = cur_rect; - return false; -} - - -class CompareByTabOrder -{ -public: - CompareByTabOrder(const LLView::child_tab_order_t& order, S32 default_tab_group = 0) - : mTabOrder(order), - mDefaultTabGroup(default_tab_group) - {} - virtual ~CompareByTabOrder() {} - - // This method compares two LLViews by the tab order specified in the comparator object. The - // code for this is a little convoluted because each argument can have four states: - // 1) not a control, 2) a control but not in the tab order, 3) a control in the tab order, 4) null - bool operator() (const LLView* const a, const LLView* const b) const - { - S32 a_group = 0, b_group = 0; - if(!a) return false; - if(!b) return true; - - LLView::child_tab_order_const_iter_t a_found = mTabOrder.find(a), b_found = mTabOrder.find(b); - if(a_found != mTabOrder.end()) - { - a_group = a_found->second; - } - if(b_found != mTabOrder.end()) - { - b_group = b_found->second; - } - - if(a_group < mDefaultTabGroup && b_group >= mDefaultTabGroup) return true; - if(b_group < mDefaultTabGroup && a_group >= mDefaultTabGroup) return false; - return a_group > b_group; // sort correctly if they're both on the same side of the default tab groupreturn a > b; - } -private: - // ok to store a reference, as this should only be allocated on stack during view query operations - const LLView::child_tab_order_t& mTabOrder; - const S32 mDefaultTabGroup; -}; - -class SortByTabOrder : public LLQuerySorter, public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(SortByTabOrder); - /*virtual*/ void sort(LLView * parent, LLView::child_list_t &children) const override - { - children.sort(CompareByTabOrder(parent->getTabOrder(), parent->getDefaultTabGroup())); - } -}; - -// static -const LLViewQuery & LLView::getTabOrderQuery() -{ - static LLViewQuery query; - if(query.getPreFilters().size() == 0) { - query.addPreFilter(LLVisibleFilter::getInstance()); - query.addPreFilter(LLEnabledFilter::getInstance()); - query.addPreFilter(LLTabStopFilter::getInstance()); - query.addPostFilter(LLLeavesFilter::getInstance()); - query.setSorter(SortByTabOrder::getInstance()); - } - return query; -} - -// This class is only used internally by getFocusRootsQuery below. -class LLFocusRootsFilter : public LLQueryFilter, public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLFocusRootsFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override - { - return filterResult_t(view->isCtrl() && view->isFocusRoot(), !view->isFocusRoot()); - } -}; - -// static -const LLViewQuery & LLView::getFocusRootsQuery() -{ - static LLViewQuery query; - if(query.getPreFilters().size() == 0) { - query.addPreFilter(LLVisibleFilter::getInstance()); - query.addPreFilter(LLEnabledFilter::getInstance()); - query.addPreFilter(LLFocusRootsFilter::getInstance()); - query.addPostFilter(LLRootsFilter::getInstance()); - } - return query; -} - - -void LLView::setShape(const LLRect& new_rect, bool by_user) -{ - if (new_rect != getRect()) - { - handleReshape(new_rect, by_user); - } -} - -void LLView::handleReshape(const LLRect& new_rect, bool by_user) -{ - reshape(new_rect.getWidth(), new_rect.getHeight()); - translate(new_rect.mLeft - getRect().mLeft, new_rect.mBottom - getRect().mBottom); -} - -LLView* LLView::findSnapRect(LLRect& new_rect, const LLCoordGL& mouse_dir, - LLView::ESnapType snap_type, S32 threshold, S32 padding) -{ - new_rect = mRect; - LLView* snap_view = NULL; - - if (!mParentView) - { - return NULL; - } - - S32 delta_x = 0; - S32 delta_y = 0; - if (mouse_dir.mX >= 0) - { - S32 new_right = mRect.mRight; - LLView* view = findSnapEdge(new_right, mouse_dir, SNAP_RIGHT, snap_type, threshold, padding); - delta_x = new_right - mRect.mRight; - snap_view = view ? view : snap_view; - } - - if (mouse_dir.mX <= 0) - { - S32 new_left = mRect.mLeft; - LLView* view = findSnapEdge(new_left, mouse_dir, SNAP_LEFT, snap_type, threshold, padding); - delta_x = new_left - mRect.mLeft; - snap_view = view ? view : snap_view; - } - - if (mouse_dir.mY >= 0) - { - S32 new_top = mRect.mTop; - LLView* view = findSnapEdge(new_top, mouse_dir, SNAP_TOP, snap_type, threshold, padding); - delta_y = new_top - mRect.mTop; - snap_view = view ? view : snap_view; - } - - if (mouse_dir.mY <= 0) - { - S32 new_bottom = mRect.mBottom; - LLView* view = findSnapEdge(new_bottom, mouse_dir, SNAP_BOTTOM, snap_type, threshold, padding); - delta_y = new_bottom - mRect.mBottom; - snap_view = view ? view : snap_view; - } - - new_rect.translate(delta_x, delta_y); - return snap_view; -} - -LLView* LLView::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding) -{ - LLRect snap_rect = getSnapRect(); - S32 snap_pos = 0; - switch(snap_edge) - { - case SNAP_LEFT: - snap_pos = snap_rect.mLeft; - break; - case SNAP_RIGHT: - snap_pos = snap_rect.mRight; - break; - case SNAP_TOP: - snap_pos = snap_rect.mTop; - break; - case SNAP_BOTTOM: - snap_pos = snap_rect.mBottom; - break; - } - - if (!mParentView) - { - new_edge_val = snap_pos; - return NULL; - } - - LLView* snap_view = NULL; - - // If the view is near the edge of its parent, snap it to - // the edge. - LLRect test_rect = snap_rect; - test_rect.stretch(padding); - - S32 x_threshold = threshold; - S32 y_threshold = threshold; - - LLRect parent_local_snap_rect = mParentView->getLocalSnapRect(); - - if (snap_type == SNAP_PARENT || snap_type == SNAP_PARENT_AND_SIBLINGS) - { - switch(snap_edge) - { - case SNAP_RIGHT: - if (llabs(parent_local_snap_rect.mRight - test_rect.mRight) <= x_threshold - && (parent_local_snap_rect.mRight - test_rect.mRight) * mouse_dir.mX >= 0) - { - snap_pos = parent_local_snap_rect.mRight - padding; - snap_view = mParentView; - x_threshold = llabs(parent_local_snap_rect.mRight - test_rect.mRight); - } - break; - case SNAP_LEFT: - if (llabs(test_rect.mLeft - parent_local_snap_rect.mLeft) <= x_threshold - && test_rect.mLeft * mouse_dir.mX <= 0) - { - snap_pos = parent_local_snap_rect.mLeft + padding; - snap_view = mParentView; - x_threshold = llabs(test_rect.mLeft - parent_local_snap_rect.mLeft); - } - break; - case SNAP_BOTTOM: - if (llabs(test_rect.mBottom - parent_local_snap_rect.mBottom) <= y_threshold - && test_rect.mBottom * mouse_dir.mY <= 0) - { - snap_pos = parent_local_snap_rect.mBottom + padding; - snap_view = mParentView; - y_threshold = llabs(test_rect.mBottom - parent_local_snap_rect.mBottom); - } - break; - case SNAP_TOP: - if (llabs(parent_local_snap_rect.mTop - test_rect.mTop) <= y_threshold && (parent_local_snap_rect.mTop - test_rect.mTop) * mouse_dir.mY >= 0) - { - snap_pos = parent_local_snap_rect.mTop - padding; - snap_view = mParentView; - y_threshold = llabs(parent_local_snap_rect.mTop - test_rect.mTop); - } - break; - default: - LL_ERRS() << "Invalid snap edge" << LL_ENDL; - } - } - - if (snap_type == SNAP_SIBLINGS || snap_type == SNAP_PARENT_AND_SIBLINGS) - { - for ( child_list_const_iter_t child_it = mParentView->getChildList()->begin(); - child_it != mParentView->getChildList()->end(); ++child_it) - { - LLView* siblingp = *child_it; - - if (!canSnapTo(siblingp)) continue; - - LLRect sibling_rect = siblingp->getSnapRect(); - - switch(snap_edge) - { - case SNAP_RIGHT: - if (llabs(test_rect.mRight - sibling_rect.mLeft) <= x_threshold - && (test_rect.mRight - sibling_rect.mLeft) * mouse_dir.mX <= 0) - { - snap_pos = sibling_rect.mLeft - padding; - snap_view = siblingp; - x_threshold = llabs(test_rect.mRight - sibling_rect.mLeft); - } - // if snapped with sibling along other axis, check for shared edge - else if (llabs(sibling_rect.mTop - (test_rect.mBottom - padding)) <= y_threshold - || llabs(sibling_rect.mBottom - (test_rect.mTop + padding)) <= x_threshold) - { - if (llabs(test_rect.mRight - sibling_rect.mRight) <= x_threshold - && (test_rect.mRight - sibling_rect.mRight) * mouse_dir.mX <= 0) - { - snap_pos = sibling_rect.mRight; - snap_view = siblingp; - x_threshold = llabs(test_rect.mRight - sibling_rect.mRight); - } - } - break; - case SNAP_LEFT: - if (llabs(test_rect.mLeft - sibling_rect.mRight) <= x_threshold - && (test_rect.mLeft - sibling_rect.mRight) * mouse_dir.mX <= 0) - { - snap_pos = sibling_rect.mRight + padding; - snap_view = siblingp; - x_threshold = llabs(test_rect.mLeft - sibling_rect.mRight); - } - // if snapped with sibling along other axis, check for shared edge - else if (llabs(sibling_rect.mTop - (test_rect.mBottom - padding)) <= y_threshold - || llabs(sibling_rect.mBottom - (test_rect.mTop + padding)) <= y_threshold) - { - if (llabs(test_rect.mLeft - sibling_rect.mLeft) <= x_threshold - && (test_rect.mLeft - sibling_rect.mLeft) * mouse_dir.mX <= 0) - { - snap_pos = sibling_rect.mLeft; - snap_view = siblingp; - x_threshold = llabs(test_rect.mLeft - sibling_rect.mLeft); - } - } - break; - case SNAP_BOTTOM: - if (llabs(test_rect.mBottom - sibling_rect.mTop) <= y_threshold - && (test_rect.mBottom - sibling_rect.mTop) * mouse_dir.mY <= 0) - { - snap_pos = sibling_rect.mTop + padding; - snap_view = siblingp; - y_threshold = llabs(test_rect.mBottom - sibling_rect.mTop); - } - // if snapped with sibling along other axis, check for shared edge - else if (llabs(sibling_rect.mRight - (test_rect.mLeft - padding)) <= x_threshold - || llabs(sibling_rect.mLeft - (test_rect.mRight + padding)) <= x_threshold) - { - if (llabs(test_rect.mBottom - sibling_rect.mBottom) <= y_threshold - && (test_rect.mBottom - sibling_rect.mBottom) * mouse_dir.mY <= 0) - { - snap_pos = sibling_rect.mBottom; - snap_view = siblingp; - y_threshold = llabs(test_rect.mBottom - sibling_rect.mBottom); - } - } - break; - case SNAP_TOP: - if (llabs(test_rect.mTop - sibling_rect.mBottom) <= y_threshold - && (test_rect.mTop - sibling_rect.mBottom) * mouse_dir.mY <= 0) - { - snap_pos = sibling_rect.mBottom - padding; - snap_view = siblingp; - y_threshold = llabs(test_rect.mTop - sibling_rect.mBottom); - } - // if snapped with sibling along other axis, check for shared edge - else if (llabs(sibling_rect.mRight - (test_rect.mLeft - padding)) <= x_threshold - || llabs(sibling_rect.mLeft - (test_rect.mRight + padding)) <= x_threshold) - { - if (llabs(test_rect.mTop - sibling_rect.mTop) <= y_threshold - && (test_rect.mTop - sibling_rect.mTop) * mouse_dir.mY <= 0) - { - snap_pos = sibling_rect.mTop; - snap_view = siblingp; - y_threshold = llabs(test_rect.mTop - sibling_rect.mTop); - } - } - break; - default: - LL_ERRS() << "Invalid snap edge" << LL_ENDL; - } - } - } - - new_edge_val = snap_pos; - return snap_view; -} - -//----------------------------------------------------------------------------- -// Listener dispatch functions -//----------------------------------------------------------------------------- - - -LLControlVariable *LLView::findControl(const std::string& name) -{ - // parse the name to locate which group it belongs to - std::size_t key_pos= name.find("."); - if(key_pos!= std::string::npos ) - { - std::string control_group_key = name.substr(0, key_pos); - LLControlVariable* control; - // check if it's in the control group that name indicated - if(LLUI::getInstance()->mSettingGroups[control_group_key]) - { - control = LLUI::getInstance()->mSettingGroups[control_group_key]->getControl(name); - if (control) - { - return control; - } - } - } - - LLControlGroup& control_group = LLUI::getInstance()->getControlControlGroup(name); - return control_group.getControl(name); -} - -void LLView::initFromParams(const LLView::Params& params) -{ - LLRect required_rect = getRequiredRect(); - - S32 width = llmax(getRect().getWidth(), required_rect.getWidth()); - S32 height = llmax(getRect().getHeight(), required_rect.getHeight()); - - reshape(width, height); - - // call virtual methods with most recent data - // use getters because these values might not come through parameter block - setEnabled(getEnabled()); - setVisible(getVisible()); - - if (!params.name().empty()) - { - setName(params.name()); - } - - mLayout = params.layout(); -} - -void LLView::parseFollowsFlags(const LLView::Params& params) -{ - // preserve follows flags set by code if user did not override - if (!params.follows.isProvided()) - { - return; - } - - // interpret either string or bitfield version of follows - if (params.follows.string.isChosen()) - { - setFollows(FOLLOWS_NONE); - - std::string follows = params.follows.string; - - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("|"); - tokenizer tokens(follows, sep); - tokenizer::iterator token_iter = tokens.begin(); - - while(token_iter != tokens.end()) - { - const std::string& token_str = *token_iter; - if (token_str == "left") - { - setFollowsLeft(); - } - else if (token_str == "right") - { - setFollowsRight(); - } - else if (token_str == "top") - { - setFollowsTop(); - } - else if (token_str == "bottom") - { - setFollowsBottom(); - } - else if (token_str == "all") - { - setFollowsAll(); - } - ++token_iter; - } - } - else if (params.follows.flags.isChosen()) - { - setFollows(params.follows.flags); - } -} - - -// static -//LLFontGL::HAlign LLView::selectFontHAlign(LLXMLNodePtr node) -//{ -// LLFontGL::HAlign gl_hfont_align = LLFontGL::LEFT; -// -// if (node->hasAttribute("halign")) -// { -// std::string horizontal_align_name; -// node->getAttributeString("halign", horizontal_align_name); -// gl_hfont_align = LLFontGL::hAlignFromName(horizontal_align_name); -// } -// return gl_hfont_align; -//} - -// Return the rectangle of the last-constructed child, -// if present and a first-class widget (eg, not a close box or drag handle) -// Returns true if found -static bool get_last_child_rect(LLView* parent, LLRect *rect) -{ - if (!parent) return false; - - LLView::child_list_t::const_iterator itor = - parent->getChildList()->begin(); - for (;itor != parent->getChildList()->end(); ++itor) - { - LLView *last_view = (*itor); - if (last_view->getFromXUI()) - { - *rect = last_view->getRect(); - return true; - } - } - return false; -} - -//static -void LLView::applyXUILayout(LLView::Params& p, LLView* parent, LLRect layout_rect) -{ - if (!parent) return; - - const S32 VPAD = 4; - const S32 MIN_WIDGET_HEIGHT = 10; - - // *NOTE: This will confuse export of floater/panel coordinates unless - // the default is also "topleft". JC - if (p.layout().empty()) - { - p.layout = parent->getLayout(); - } - - if (layout_rect.isEmpty()) - { - layout_rect = parent->getLocalRect(); - } - - // overwrite uninitialized rect params, using context - LLRect default_rect = parent->getLocalRect(); - - bool layout_topleft = (p.layout() == "topleft"); - - // convert negative or centered coordinates to parent relative values - // Note: some of this logic matches the logic in TypedParam::setValueFromBlock() - if (p.rect.left.isProvided()) - { - p.rect.left = p.rect.left + ((p.rect.left >= 0) ? layout_rect.mLeft : layout_rect.mRight); - } - if (p.rect.right.isProvided()) - { - p.rect.right = p.rect.right + ((p.rect.right >= 0) ? layout_rect.mLeft : layout_rect.mRight); - } - if (p.rect.bottom.isProvided()) - { - p.rect.bottom = p.rect.bottom + ((p.rect.bottom >= 0) ? layout_rect.mBottom : layout_rect.mTop); - if (layout_topleft) - { - //invert top to bottom - p.rect.bottom = layout_rect.mBottom + layout_rect.mTop - p.rect.bottom; - } - } - if (p.rect.top.isProvided()) - { - p.rect.top = p.rect.top + ((p.rect.top >= 0) ? layout_rect.mBottom : layout_rect.mTop); - if (layout_topleft) - { - //invert top to bottom - p.rect.top = layout_rect.mBottom + layout_rect.mTop - p.rect.top; - } - } - - // DEPRECATE: automatically fall back to height of MIN_WIDGET_HEIGHT pixels - if (!p.rect.height.isProvided() && !p.rect.top.isProvided() && p.rect.height == 0) - { - p.rect.height = MIN_WIDGET_HEIGHT; - } - - default_rect.translate(0, default_rect.getHeight()); - - // If there was a recently constructed child, use its rectangle - get_last_child_rect(parent, &default_rect); - - if (layout_topleft) - { - // Invert the sense of bottom_delta for topleft layout - if (p.bottom_delta.isProvided()) - { - p.bottom_delta = -p.bottom_delta; - } - else if (p.top_pad.isProvided()) - { - p.bottom_delta = -(p.rect.height + p.top_pad); - } - else if (p.top_delta.isProvided()) - { - p.bottom_delta = - -(p.top_delta + p.rect.height - default_rect.getHeight()); - } - else if (!p.left_delta.isProvided() - && !p.left_pad.isProvided()) - { - // set default position is just below last rect - p.bottom_delta.set(-(p.rect.height + VPAD), false); - } - else - { - p.bottom_delta.set(0, false); - } - - // default to same left edge - if (!p.left_delta.isProvided()) - { - p.left_delta.set(0, false); - } - if (p.left_pad.isProvided()) - { - // left_pad is based on prior widget's right edge - p.left_delta.set(p.left_pad + default_rect.getWidth(), false); - } - - default_rect.translate(p.left_delta, p.bottom_delta); - } - else - { - // set default position is just below last rect - if (!p.bottom_delta.isProvided()) - { - p.bottom_delta.set(-(p.rect.height + VPAD), false); - } - if (!p.left_delta.isProvided()) - { - p.left_delta.set(0, false); - } - default_rect.translate(p.left_delta, p.bottom_delta); - } - - // this handles case where *both* x and x_delta are provided - // ignore x in favor of default x + x_delta - if (p.bottom_delta.isProvided()) p.rect.bottom.set(0, false); - if (p.left_delta.isProvided()) p.rect.left.set(0, false); - - // selectively apply rectangle defaults, making sure that - // params are not flagged as having been "provided" - // as rect params are overconstrained and rely on provided flags - if (!p.rect.left.isProvided()) - { - p.rect.left.set(default_rect.mLeft, false); - //HACK: get around the fact that setting a rect param component value won't invalidate the existing rect object value - p.rect.paramChanged(p.rect.left, true); - } - if (!p.rect.bottom.isProvided()) - { - p.rect.bottom.set(default_rect.mBottom, false); - p.rect.paramChanged(p.rect.bottom, true); - } - if (!p.rect.top.isProvided()) - { - p.rect.top.set(default_rect.mTop, false); - p.rect.paramChanged(p.rect.top, true); - } - if (!p.rect.right.isProvided()) - { - p.rect.right.set(default_rect.mRight, false); - p.rect.paramChanged(p.rect.right, true); - - } - if (!p.rect.width.isProvided()) - { - p.rect.width.set(default_rect.getWidth(), false); - p.rect.paramChanged(p.rect.width, true); - } - if (!p.rect.height.isProvided()) - { - p.rect.height.set(default_rect.getHeight(), false); - p.rect.paramChanged(p.rect.height, true); - } -} - -static S32 invert_vertical(S32 y, LLView* parent) -{ - if (y < 0) - { - // already based on top-left, just invert - return -y; - } - else if (parent) - { - // use parent to flip coordinate - S32 parent_height = parent->getRect().getHeight(); - return parent_height - y; - } - else - { - LL_WARNS() << "Attempting to convert layout to top-left with no parent" << LL_ENDL; - return y; - } -} - -// Assumes that input is in bottom-left coordinates, hence must call -// _before_ convert_coords_to_top_left(). -static void convert_to_relative_layout(LLView::Params& p, LLView* parent) -{ - // Use setupParams to get the final widget rectangle - // according to our wacky layout rules. - LLView::Params final = p; - LLView::applyXUILayout(final, parent); - // Must actually extract the rectangle to get consistent - // right = left+width, top = bottom+height - LLRect final_rect = final.rect; - - // We prefer to write out top edge instead of bottom, regardless - // of whether we use relative positioning - bool converted_top = false; - - // Look for a last rectangle - LLRect last_rect; - if (get_last_child_rect(parent, &last_rect)) - { - // ...we have a previous widget to compare to - const S32 EDGE_THRESHOLD_PIXELS = 4; - S32 left_pad = final_rect.mLeft - last_rect.mRight; - S32 left_delta = final_rect.mLeft - last_rect.mLeft; - S32 top_pad = final_rect.mTop - last_rect.mBottom; - S32 top_delta = final_rect.mTop - last_rect.mTop; - // If my left edge is almost the same, or my top edge is - // almost the same... - if (llabs(left_delta) <= EDGE_THRESHOLD_PIXELS - || llabs(top_delta) <= EDGE_THRESHOLD_PIXELS) - { - // ...use relative positioning - // prefer top_pad if widgets are stacking vertically - // (coordinate system is still bottom-left here) - if (top_pad < 0) - { - p.top_pad = top_pad; - p.top_delta.setProvided(false); - } - else - { - p.top_pad.setProvided(false); - p.top_delta = top_delta; - } - // null out other vertical specifiers - p.rect.top.setProvided(false); - p.rect.bottom.setProvided(false); - p.bottom_delta.setProvided(false); - converted_top = true; - - // prefer left_pad if widgets are stacking horizontally - if (left_pad > 0) - { - p.left_pad = left_pad; - p.left_delta.setProvided(false); - } - else - { - p.left_pad.setProvided(false); - p.left_delta = left_delta; - } - p.rect.left.setProvided(false); - p.rect.right.setProvided(false); - } - } - - if (!converted_top) - { - // ...this is the first widget, or one that wasn't aligned - // prefer top/left specification - p.rect.top = final_rect.mTop; - p.rect.bottom.setProvided(false); - p.bottom_delta.setProvided(false); - p.top_pad.setProvided(false); - p.top_delta.setProvided(false); - } -} - -static void convert_coords_to_top_left(LLView::Params& p, LLView* parent) -{ - // Convert the coordinate system to be top-left based. - if (p.rect.top.isProvided()) - { - p.rect.top = invert_vertical(p.rect.top, parent); - } - if (p.rect.bottom.isProvided()) - { - p.rect.bottom = invert_vertical(p.rect.bottom, parent); - } - if (p.top_pad.isProvided()) - { - p.top_pad = -p.top_pad; - } - if (p.top_delta.isProvided()) - { - p.top_delta = -p.top_delta; - } - if (p.bottom_delta.isProvided()) - { - p.bottom_delta = -p.bottom_delta; - } - p.layout = "topleft"; -} - -//static -void LLView::setupParamsForExport(Params& p, LLView* parent) -{ - // Don't convert if already top-left based - if (p.layout() == "topleft") - { - return; - } - - // heuristic: Many of our floaters and panels were bulk-exported. - // These specify exactly bottom/left and height/width. - // Others were done by hand using bottom_delta and/or left_delta. - // Some rely on not specifying left to mean align with left edge. - // Try to convert both to use relative layout, but using top-left - // coordinates. - // Avoid rectangles where top/bottom/left/right was specified. - if (p.rect.height.isProvided() && p.rect.width.isProvided()) - { - if (p.rect.bottom.isProvided() && p.rect.left.isProvided()) - { - // standard bulk export, convert it - convert_to_relative_layout(p, parent); - } - else if (p.rect.bottom.isProvided() && p.left_delta.isProvided()) - { - // hand layout with left_delta - convert_to_relative_layout(p, parent); - } - else if (p.bottom_delta.isProvided()) - { - // hand layout with bottom_delta - // don't check for p.rect.left or p.left_delta because sometimes - // this layout doesn't set it for widgets that are left-aligned - convert_to_relative_layout(p, parent); - } - } - - convert_coords_to_top_left(p, parent); -} - -LLView::tree_iterator_t LLView::beginTreeDFS() -{ - return tree_iterator_t(this, - boost::bind(boost::mem_fn(&LLView::beginChild), _1), - boost::bind(boost::mem_fn(&LLView::endChild), _1)); -} - -LLView::tree_iterator_t LLView::endTreeDFS() -{ - // an empty iterator is an "end" iterator - return tree_iterator_t(); -} - -LLView::tree_post_iterator_t LLView::beginTreeDFSPost() -{ - return tree_post_iterator_t(this, - boost::bind(boost::mem_fn(&LLView::beginChild), _1), - boost::bind(boost::mem_fn(&LLView::endChild), _1)); -} - -LLView::tree_post_iterator_t LLView::endTreeDFSPost() -{ - // an empty iterator is an "end" iterator - return tree_post_iterator_t(); -} - -LLView::bfs_tree_iterator_t LLView::beginTreeBFS() -{ - return bfs_tree_iterator_t(this, - boost::bind(boost::mem_fn(&LLView::beginChild), _1), - boost::bind(boost::mem_fn(&LLView::endChild), _1)); -} - -LLView::bfs_tree_iterator_t LLView::endTreeBFS() -{ - // an empty iterator is an "end" iterator - return bfs_tree_iterator_t(); -} - - -LLView::root_to_view_iterator_t LLView::beginRootToView() -{ - return root_to_view_iterator_t(this, boost::bind(&LLView::getParent, _1)); -} - -LLView::root_to_view_iterator_t LLView::endRootToView() -{ - return root_to_view_iterator_t(); -} - - -// only create maps on demand, as they incur heap allocation/deallocation cost -// when a view is constructed/deconstructed -LLView& LLView::getDefaultWidgetContainer() const -{ - if (!mDefaultWidgets) - { - LLView::Params p; - p.name = "default widget container"; - p.visible = false; // ensures default widgets can't steal focus, etc. - mDefaultWidgets = new LLView(p); - } - return *mDefaultWidgets; -} - -S32 LLView::notifyParent(const LLSD& info) -{ - LLView* parent = getParent(); - if(parent) - return parent->notifyParent(info); - return 0; -} -bool LLView::notifyChildren(const LLSD& info) -{ - bool ret = false; - for (LLView* childp : mChildList) - { - ret = ret || childp->notifyChildren(info); - } - return ret; -} - -// convenient accessor for draw context -const LLViewDrawContext& LLView::getDrawContext() -{ - return LLViewDrawContext::getCurrentContext(); -} - -const LLViewDrawContext& LLViewDrawContext::getCurrentContext() -{ - static LLViewDrawContext default_context; - - if (sDrawContextStack.empty()) - return default_context; - - return *sDrawContextStack.back(); -} - -LLSD LLView::getInfo(void) -{ - LLSD info; - addInfo(info); - return info; -} - -void LLView::addInfo(LLSD & info) -{ - info["path"] = getPathname(); - info["class"] = typeid(*this).name(); - info["visible"] = getVisible(); - info["visible_chain"] = isInVisibleChain(); - info["enabled"] = getEnabled(); - info["enabled_chain"] = isInEnabledChain(); - info["available"] = isAvailable(); - LLRect rect(calcScreenRect()); - info["rect"] = LLSDMap("left", rect.mLeft)("top", rect.mTop) - ("right", rect.mRight)("bottom", rect.mBottom); -} +/** + * @file llview.cpp + * @author James Cook + * @brief Container for other views, anything that draws. + * + * $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" + +#define LLVIEW_CPP +#include "llview.h" + +#include +#include +#include + +#include "llrender.h" +#include "llevent.h" +#include "llfocusmgr.h" +#include "llrect.h" +#include "llstl.h" +#include "llui.h" +#include "lluictrl.h" +#include "llwindow.h" +#include "v3color.h" +#include "lluictrlfactory.h" +#include "lltooltip.h" +#include "llsdutil.h" +#include "llsdserialize.h" +#include "llviewereventrecorder.h" +#include "llkeyboard.h" +// for ui edit hack +#include "llbutton.h" +#include "lllineeditor.h" +#include "lltexteditor.h" +#include "lltextbox.h" + +static const S32 LINE_HEIGHT = 15; + +S32 LLView::sDepth = 0; +bool LLView::sDebugRects = false; +bool LLView::sDebugUnicode = false; +bool LLView::sDebugCamera = false; +bool LLView::sIsRectDirty = false; +LLRect LLView::sDirtyRect; +bool LLView::sDebugRectsShowNames = true; +bool LLView::sDebugKeys = false; +bool LLView::sDebugMouseHandling = false; +std::string LLView::sMouseHandlerMessage; +bool LLView::sForceReshape = false; +std::set LLView::sPreviewHighlightedElements; +bool LLView::sHighlightingDiffs = false; +LLView* LLView::sPreviewClickedElement = NULL; +bool LLView::sDrawPreviewHighlights = false; +S32 LLView::sLastLeftXML = S32_MIN; +S32 LLView::sLastBottomXML = S32_MIN; +std::vector LLViewDrawContext::sDrawContextStack; + +LLView::DrilldownFunc LLView::sDrilldown = + boost::bind(&LLView::pointInView, _1, _2, _3, HIT_TEST_USE_BOUNDING_RECT); + +//#if LL_DEBUG +bool LLView::sIsDrawing = false; +//#endif + +// Compiler optimization, generate extern template +template class LLView* LLView::getChild( + const std::string& name, bool recurse) const; + +static LLDefaultChildRegistry::Register r("view"); + +void deleteView(LLView *aView) +{ + delete aView; +} + +namespace LLInitParam +{ + void TypeValues::declareValues() + { + declare("horizontal", LLView::HORIZONTAL); + declare("vertical", LLView::VERTICAL); + } +} + + +LLView::Follows::Follows() +: string(""), + flags("flags", FOLLOWS_LEFT | FOLLOWS_TOP) +{} + +LLView::Params::Params() +: name("name", std::string("unnamed")), + enabled("enabled", true), + visible("visible", true), + mouse_opaque("mouse_opaque", true), + follows("follows"), + hover_cursor("hover_cursor", "UI_CURSOR_ARROW"), + use_bounding_rect("use_bounding_rect", false), + tab_group("tab_group", 0), + default_tab_group("default_tab_group"), + tool_tip("tool_tip"), + sound_flags("sound_flags", MOUSE_UP), + layout("layout"), + rect("rect"), + bottom_delta("bottom_delta", S32_MAX), + top_pad("top_pad"), + top_delta("top_delta", S32_MAX), + left_pad("left_pad"), + left_delta("left_delta", S32_MAX), + from_xui("from_xui", false), + focus_root("focus_root", false), + needs_translate("translate"), + xmlns("xmlns"), + xmlns_xsi("xmlns:xsi"), + xsi_schemaLocation("xsi:schemaLocation"), + xsi_type("xsi:type") + +{ + addSynonym(rect, ""); +} + +LLView::LLView(const LLView::Params& p) +: mVisible(p.visible), + mInDraw(false), + mName(p.name), + mParentView(NULL), + mReshapeFlags(FOLLOWS_NONE), + mFromXUI(p.from_xui), + mIsFocusRoot(p.focus_root), + mLastVisible(false), + mHoverCursor(getCursorFromString(p.hover_cursor)), + mEnabled(p.enabled), + mMouseOpaque(p.mouse_opaque), + mSoundFlags(p.sound_flags), + mUseBoundingRect(p.use_bounding_rect), + mDefaultTabGroup(p.default_tab_group), + mLastTabGroup(0), + mToolTipMsg((LLStringExplicit)p.tool_tip()), + mDefaultWidgets(NULL) +{ + // create rect first, as this will supply initial follows flags + setShape(p.rect); + parseFollowsFlags(p); +} + +LLView::~LLView() +{ + dirtyRect(); + //LL_INFOS() << "Deleting view " << mName << ":" << (void*) this << LL_ENDL; + if (LLView::sIsDrawing) + { + LL_DEBUGS() << "Deleting view " << mName << " during UI draw() phase" << LL_ENDL; + } +// llassert(LLView::sIsDrawing == false); + +// llassert_always(sDepth == 0); // avoid deleting views while drawing! It can subtly break list iterators + + if( hasMouseCapture() ) + { + //LL_WARNS() << "View holding mouse capture deleted: " << getName() << ". Mouse capture removed." << LL_ENDL; + gFocusMgr.removeMouseCaptureWithoutCallback( this ); + } + + deleteAllChildren(); + + if (mParentView != NULL) + { + mParentView->removeChild(this); + } + + if (mDefaultWidgets) + { + delete mDefaultWidgets; + mDefaultWidgets = NULL; + } +} + +// virtual +bool LLView::isCtrl() const +{ + return false; +} + +// virtual +bool LLView::isPanel() const +{ + return false; +} + +void LLView::setToolTip(const LLStringExplicit& msg) +{ + mToolTipMsg = msg; +} + +bool LLView::setToolTipArg(const LLStringExplicit& key, const LLStringExplicit& text) +{ + mToolTipMsg.setArg(key, text); + return true; +} + +void LLView::setToolTipArgs( const LLStringUtil::format_map_t& args ) +{ + mToolTipMsg.setArgList(args); +} + +// virtual +void LLView::setRect(const LLRect& rect) +{ + mRect = rect; + updateBoundingRect(); +} + +void LLView::setUseBoundingRect( bool use_bounding_rect ) +{ + if (mUseBoundingRect != use_bounding_rect) + { + mUseBoundingRect = use_bounding_rect; + updateBoundingRect(); + } +} + +bool LLView::getUseBoundingRect() const +{ + return mUseBoundingRect; +} + +// virtual +const std::string& LLView::getName() const +{ + static std::string no_name("(no name)"); + + return mName.empty() ? no_name : mName; +} + +void LLView::sendChildToFront(LLView* child) +{ +// llassert_always(sDepth == 0); // Avoid re-ordering while drawing; it can cause subtle iterator bugs + if (child && child->getParent() == this) + { + // minor optimization, but more importantly, + // won't temporarily create an empty list + if (child != mChildList.front()) + { + mChildList.remove( child ); + mChildList.push_front(child); + } + } +} + +void LLView::sendChildToBack(LLView* child) +{ +// llassert_always(sDepth == 0); // Avoid re-ordering while drawing; it can cause subtle iterator bugs + if (child && child->getParent() == this) + { + // minor optimization, but more importantly, + // won't temporarily create an empty list + if (child != mChildList.back()) + { + mChildList.remove( child ); + mChildList.push_back(child); + } + } +} + +// virtual +bool LLView::addChild(LLView* child, S32 tab_group) +{ + if (!child) + { + return false; + } + + if (this == child) + { + LL_ERRS() << "Adding view " << child->getName() << " as child of itself" << LL_ENDL; + } + + // remove from current parent + if (child->mParentView) + { + child->mParentView->removeChild(child); + } + + // add to front of child list, as normal + mChildList.push_front(child); + + // add to tab order list + if (tab_group != 0) + { + mTabOrder.insert(tab_order_pair_t(child, tab_group)); + } + + child->mParentView = this; + if (getVisible() && child->getVisible()) + { + // if child isn't visible it won't affect bounding rect + // if current view is not visible it will be recalculated + // on visibility change + updateBoundingRect(); + } + mLastTabGroup = tab_group; + return true; +} + + +bool LLView::addChildInBack(LLView* child, S32 tab_group) +{ + if(addChild(child, tab_group)) + { + sendChildToBack(child); + return true; + } + + return false; +} + +// remove the specified child from the view, and set it's parent to NULL. +void LLView::removeChild(LLView* child) +{ + //llassert_always(sDepth == 0); // Avoid re-ordering while drawing; it can cause subtle iterator bugs + if (child->mParentView == this) + { + // if we are removing an item we are currently iterating over, that would be bad + llassert(!child->mInDraw); + mChildList.remove( child ); + child->mParentView = NULL; + child_tab_order_t::iterator found = mTabOrder.find(child); + if (found != mTabOrder.end()) + { + mTabOrder.erase(found); + } + } + else + { + LL_WARNS() << "\"" << child->getName() << "\" is not a child of " << getName() << LL_ENDL; + } + updateBoundingRect(); +} + +bool LLView::isInVisibleChain() const +{ + bool visible = true; + + const LLView* viewp = this; + while(viewp) + { + if (!viewp->getVisible()) + { + visible = false; + break; + } + viewp = viewp->getParent(); + } + + return visible; +} + +bool LLView::isInEnabledChain() const +{ + bool enabled = true; + + const LLView* viewp = this; + while(viewp) + { + if (!viewp->getEnabled()) + { + enabled = false; + break; + } + viewp = viewp->getParent(); + } + + return enabled; +} + +static void buildPathname(std::ostream& out, const LLView* view) +{ + if (! (view && view->getParent())) + { + return; // Don't include root in the path. + } + + buildPathname(out, view->getParent()); + + // Build pathname into ostream on the way back from recursion. + out << '/'; + + // substitute all '/' in name with appropriate code + std::string name = view->getName(); + std::size_t found = name.find('/'); + std::size_t start = 0; + while (found != std::string::npos) + { + std::size_t sub_len = found - start; + if (sub_len > 0) + { + out << name.substr(start, sub_len); + } + out << "%2F"; + start = found + 1; + found = name.find('/', start); + } + if (start < name.size()) + { + out << name.substr(start, name.size() - start); + } +} + +std::string LLView::getPathname() const +{ + std::ostringstream out; + buildPathname(out, this); + return out.str(); +} + +//static +std::string LLView::getPathname(const LLView* view) +{ + if (! view) + { + return "NULL"; + } + return view->getPathname(); +} + +// virtual +bool LLView::canFocusChildren() const +{ + return true; +} + +//virtual +void LLView::setEnabled(bool enabled) +{ + mEnabled = enabled; +} + +//virtual +bool LLView::isAvailable() const +{ + return isInEnabledChain() && isInVisibleChain(); +} + +//static +bool LLView::isAvailable(const LLView* view) +{ + return view && view->isAvailable(); +} + +//virtual +bool LLView::setLabelArg( const std::string& key, const LLStringExplicit& text ) +{ + return false; +} + +//virtual +LLRect LLView::getSnapRect() const +{ + return mRect; +} + +//virtual +LLRect LLView::getRequiredRect() +{ + return mRect; +} + +bool LLView::focusNextRoot() +{ + LLView::child_list_t result = LLView::getFocusRootsQuery().run(this); + return LLView::focusNext(result); +} + +bool LLView::focusPrevRoot() +{ + LLView::child_list_t result = LLView::getFocusRootsQuery().run(this); + return LLView::focusPrev(result); +} + +// static +bool LLView::focusNext(LLView::child_list_t & result) +{ + LLView::child_list_reverse_iter_t focused = result.rend(); + for(LLView::child_list_reverse_iter_t iter = result.rbegin(); + iter != result.rend(); + ++iter) + { + if(gFocusMgr.childHasKeyboardFocus(*iter)) + { + focused = iter; + break; + } + } + LLView::child_list_reverse_iter_t next = focused; + next = (next == result.rend()) ? result.rbegin() : ++next; + while(next != focused) + { + // wrap around to beginning if necessary + if(next == result.rend()) + { + next = result.rbegin(); + } + if ((*next)->isCtrl() && ((LLUICtrl*)*next)->hasTabStop()) + { + LLUICtrl * ctrl = static_cast(*next); + ctrl->setFocus(true); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + return true; + } + ++next; + } + return false; +} + +// static +bool LLView::focusPrev(LLView::child_list_t & result) +{ + LLView::child_list_iter_t focused = result.end(); + for(LLView::child_list_iter_t iter = result.begin(); + iter != result.end(); + ++iter) + { + if(gFocusMgr.childHasKeyboardFocus(*iter)) + { + focused = iter; + break; + } + } + LLView::child_list_iter_t next = focused; + next = (next == result.end()) ? result.begin() : ++next; + while(next != focused) + { + // wrap around to beginning if necessary + if(next == result.end()) + { + next = result.begin(); + } + if((*next)->isCtrl()) + { + LLUICtrl * ctrl = static_cast(*next); + if (!ctrl->hasFocus()) + { + ctrl->setFocus(true); + ctrl->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + return true; + } + ++next; + } + return false; +} + +// delete all children. Override this function if you need to +// perform any extra clean up such as cached pointers to selected +// children, etc. +void LLView::deleteAllChildren() +{ + // clear out the control ordering + mTabOrder.clear(); + + while (!mChildList.empty()) + { + LLView* viewp = mChildList.front(); + viewp->mParentView = NULL; + delete viewp; + mChildList.pop_front(); + } + updateBoundingRect(); +} + +void LLView::setAllChildrenEnabled(bool b) +{ + for (LLView* viewp : mChildList) + { + viewp->setEnabled(b); + } +} + +// virtual +void LLView::setVisible(bool visible) +{ + if ( mVisible != visible ) + { + mVisible = visible; + + // notify children of visibility change if root, or part of visible hierarchy + if (!getParent() || getParent()->isInVisibleChain()) + { + // tell all children of this view that the visibility may have changed + dirtyRect(); + onVisibilityChange( visible ); + } + updateBoundingRect(); + } +} + +// virtual +void LLView::onVisibilityChange ( bool new_visibility ) +{ + bool old_visibility; + bool log_visibility_change = LLViewerEventRecorder::instance().getLoggingStatus(); + for (LLView* viewp : mChildList) + { + if (!viewp) + { + continue; + } + + // only views that are themselves visible will have their overall visibility affected by their ancestors + old_visibility=viewp->getVisible(); + + if(log_visibility_change) + { + if (old_visibility!=new_visibility) + { + LLViewerEventRecorder::instance().logVisibilityChange( viewp->getPathname(), viewp->getName(), new_visibility,"widget"); + } + } + + if (old_visibility) + { + viewp->onVisibilityChange ( new_visibility ); + } + + if(log_visibility_change) + { + // Consider changing returns to confirm success and know which widget grabbed it + // For now assume success and log at highest xui possible + // NOTE we log actual state - which may differ if it somehow failed to set visibility + LL_DEBUGS() << "LLView::handleVisibilityChange - now: " << getVisible() << " xui: " << viewp->getPathname() << " name: " << viewp->getName() << LL_ENDL; + + } + } +} + +// virtual +void LLView::onUpdateScrollToChild(const LLUICtrl * cntrl) +{ + LLView* parent_view = getParent(); + if (parent_view) + { + parent_view->onUpdateScrollToChild(cntrl); + } +} + +// virtual +void LLView::translate(S32 x, S32 y) +{ + mRect.translate(x, y); + updateBoundingRect(); +} + +// virtual +bool LLView::canSnapTo(const LLView* other_view) +{ + return other_view != this && other_view->getVisible(); +} + +// virtual +void LLView::setSnappedTo(const LLView* snap_view) +{ +} + +bool LLView::handleHover(S32 x, S32 y, MASK mask) +{ + return childrenHandleHover( x, y, mask ) != NULL; +} + +void LLView::onMouseEnter(S32 x, S32 y, MASK mask) +{ + //LL_INFOS() << "Mouse entered " << getName() << LL_ENDL; +} + +void LLView::onMouseLeave(S32 x, S32 y, MASK mask) +{ + //LL_INFOS() << "Mouse left " << getName() << LL_ENDL; +} + +bool LLView::visibleAndContains(S32 local_x, S32 local_y) +{ + return sDrilldown(this, local_x, local_y) + && getVisible(); +} + +bool LLView::visibleEnabledAndContains(S32 local_x, S32 local_y) +{ + return visibleAndContains(local_x, local_y) + && getEnabled(); +} + +// This is NOT event recording related +void LLView::logMouseEvent() +{ + if (sDebugMouseHandling) + { + sMouseHandlerMessage = std::string("/") + mName + sMouseHandlerMessage; + } +} + +template +LLView* LLView::childrenHandleCharEvent(const std::string& desc, const METHOD& method, + CHARTYPE c, MASK mask) +{ + if ( getVisible() && getEnabled() ) + { + for (LLView* viewp : mChildList) + { + if ((viewp->*method)(c, mask, true)) + { + if (LLView::sDebugKeys) + { + LL_INFOS() << desc << " handled by " << viewp->getName() << LL_ENDL; + } + return viewp; + } + } + } + return NULL; +} + +// XDATA might be MASK, or S32 clicks +template +LLView* LLView::childrenHandleMouseEvent(const METHOD& method, S32 x, S32 y, XDATA extra, bool allow_mouse_block) +{ + for (LLView* viewp : mChildList) + { + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + + if (!viewp->visibleEnabledAndContains(local_x, local_y)) + { + continue; + } + + if ((viewp->*method)( local_x, local_y, extra ) + || (allow_mouse_block && viewp->blockMouseEvent( local_x, local_y ))) + { + LL_DEBUGS() << "LLView::childrenHandleMouseEvent calling updatemouseeventinfo - local_x|global x "<< local_x << " " << x << "local/global y " << local_y << " " << y << LL_ENDL; + LL_DEBUGS() << "LLView::childrenHandleMouseEvent getPathname for viewp result: " << viewp->getPathname() << "for this view: " << getPathname() << LL_ENDL; + + LLViewerEventRecorder::instance().updateMouseEventInfo(x,y,-55,-55,getPathname()); + + // This is NOT event recording related + viewp->logMouseEvent(); + + return viewp; + } + } + return NULL; +} + +LLView* LLView::childrenHandleToolTip(S32 x, S32 y, MASK mask) +{ + for (LLView* viewp : mChildList) + { + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + // Differs from childrenHandleMouseEvent() in that we want to offer + // tooltips even for disabled widgets. + if(!viewp->visibleAndContains(local_x, local_y)) + { + continue; + } + + if (viewp->handleToolTip(local_x, local_y, mask) + || viewp->blockMouseEvent(local_x, local_y)) + { + // This is NOT event recording related + viewp->logMouseEvent(); + return viewp; + } + } + return NULL; +} + +LLView* LLView::childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // default to not accepting drag and drop, will be overridden by handler + *accept = ACCEPT_NO; + + for (LLView* viewp : mChildList) + { + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if( !viewp->visibleEnabledAndContains(local_x, local_y)) + { + continue; + } + + // Differs from childrenHandleMouseEvent() simply in that this virtual + // method call diverges pretty radically from the usual (x, y, int). + if (viewp->handleDragAndDrop(local_x, local_y, mask, drop, + cargo_type, + cargo_data, + accept, + tooltip_msg) + || viewp->blockMouseEvent(local_x, local_y)) + { + return viewp; + } + } + return NULL; +} + +LLView* LLView::childrenHandleHover(S32 x, S32 y, MASK mask) +{ + for (LLView* viewp : mChildList) + { + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if(!viewp->visibleEnabledAndContains(local_x, local_y)) + { + continue; + } + + // This call differentiates this method from childrenHandleMouseEvent(). + LLUI::getInstance()->mWindow->setCursor(viewp->getHoverCursor()); + + if (viewp->handleHover(local_x, local_y, mask) + || viewp->blockMouseEvent(local_x, local_y)) + { + // This is NOT event recording related + viewp->logMouseEvent(); + return viewp; + } + } + return NULL; +} + +LLView* LLView::childFromPoint(S32 x, S32 y, bool recur) +{ + if (!getVisible()) + return NULL; + + for (LLView* viewp : mChildList) + { + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if (!viewp->visibleAndContains(local_x, local_y)) + { + continue; + } + // Here we've found the first (frontmost) visible child at this level + // containing the specified point. Is the caller asking us to drill + // down and return the innermost leaf child at this point, or just the + // top-level child? + if (recur) + { + LLView* leaf(viewp->childFromPoint(local_x, local_y, recur)); + // Maybe viewp is already a leaf LLView, or maybe it has children + // but this particular (x, y) point falls between them. If the + // recursive call returns non-NULL, great, use that; else just use + // viewp. + return leaf? leaf : viewp; + } + return viewp; + + } + return 0; +} + +F32 LLView::getTooltipTimeout() +{ + static LLCachedControl tooltip_fast_delay(*LLUI::getInstance()->mSettingGroups["config"], "ToolTipFastDelay", 0.1f); + static LLCachedControl tooltip_delay(*LLUI::getInstance()->mSettingGroups["config"], "ToolTipDelay", 0.7f); + // allow "scrubbing" over ui by showing next tooltip immediately + // if previous one was still visible + return (F32)(LLToolTipMgr::instance().toolTipVisible() + ? tooltip_fast_delay + : tooltip_delay); +} + +// virtual +const std::string LLView::getToolTip() const +{ + if (sDebugUnicode) + { + std::string text = getText(); + if (!text.empty()) + { + const std::string& name = getName(); + std::string tooltip = llformat("Name: \"%s\"", name.c_str()); + + if (const LLFontGL* font = getFont()) + { + tooltip += llformat("\nFont: %s (%s)", + font->getFontDesc().getName().c_str(), + font->getFontDesc().getSize().c_str() + ); + } + + tooltip += "\n\n" + utf8str_showBytesUTF8(text); + + return tooltip; + } + } + + return mToolTipMsg.getString(); +} + +bool LLView::handleToolTip(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // parents provide tooltips first, which are optionally + // overridden by children, in case child is mouse_opaque + std::string tooltip = getToolTip(); + if (!tooltip.empty()) + { + static LLCachedControl allow_ui_tooltips(*LLUI::getInstance()->mSettingGroups["config"], "BasicUITooltips", true); + + // Even if we don't show tooltips, consume the event, nothing below should show tooltip + if (allow_ui_tooltips) + { + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(tooltip) + .sticky_rect(calcScreenRect()) + .delay_time(getTooltipTimeout())); + } + handled = true; + } + + // child tooltips will override our own + LLView* child_handler = childrenHandleToolTip(x, y, mask); + if (child_handler) + { + handled = true; + } + + return handled; +} + +bool LLView::handleKey(KEY key, MASK mask, bool called_from_parent) +{ + bool handled = false; + + if (getVisible() && getEnabled()) + { + if( called_from_parent ) + { + // Downward traversal + handled = childrenHandleKey( key, mask ) != NULL; + } + + if (!handled) + { + // For event logging we don't care which widget handles it + // So we capture the key at the end of this function once we know if it was handled + handled = handleKeyHere( key, mask ); + if (handled) + { + LL_DEBUGS() << "Key handled by " << getName() << LL_ENDL; + } + } + } + + if( !handled && !called_from_parent && mParentView) + { + // Upward traversal + handled = mParentView->handleKey( key, mask, false ); + } + return handled; +} + +bool LLView::handleKeyUp(KEY key, MASK mask, bool called_from_parent) +{ + bool handled = false; + + if (getVisible() && getEnabled()) + { + if (called_from_parent) + { + // Downward traversal + handled = childrenHandleKeyUp(key, mask) != NULL; + } + + if (!handled) + { + // For event logging we don't care which widget handles it + // So we capture the key at the end of this function once we know if it was handled + handled = handleKeyUpHere(key, mask); + if (handled) + { + LL_DEBUGS() << "Key handled by " << getName() << LL_ENDL; + } + } + } + + if (!handled && !called_from_parent && mParentView) + { + // Upward traversal + handled = mParentView->handleKeyUp(key, mask, false); + } + return handled; +} + +// Called from handleKey() +// Handles key in this object. Checking parents and children happens in handleKey() +bool LLView::handleKeyHere(KEY key, MASK mask) +{ + return false; +} + +// Called from handleKey() +// Handles key in this object. Checking parents and children happens in handleKey() +bool LLView::handleKeyUpHere(KEY key, MASK mask) +{ + return false; +} + +bool LLView::handleUnicodeChar(llwchar uni_char, bool called_from_parent) +{ + bool handled = false; + + if (getVisible() && getEnabled()) + { + if( called_from_parent ) + { + // Downward traversal + handled = childrenHandleUnicodeChar( uni_char ) != NULL; + } + + if (!handled) + { + handled = handleUnicodeCharHere(uni_char); + if (handled && LLView::sDebugKeys) + { + LL_INFOS() << "Unicode key " << wchar_utf8_preview(uni_char) << " is handled by " << getName() << LL_ENDL; + } + } + } + + if (!handled && !called_from_parent && mParentView) + { + // Upward traversal + handled = mParentView->handleUnicodeChar(uni_char, false); + } + + if (handled) + { + LLViewerEventRecorder::instance().logKeyUnicodeEvent(uni_char); + } + + return handled; +} + + +bool LLView::handleUnicodeCharHere(llwchar uni_char ) +{ + return false; +} + + +bool LLView::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + return childrenHandleDragAndDrop( x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) != NULL; +} + +void LLView::onMouseCaptureLost() +{ +} + +bool LLView::hasMouseCapture() +{ + return gFocusMgr.getMouseCapture() == this; +} + +bool LLView::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LLView* r = childrenHandleMouseUp( x, y, mask ); + + return (r!=NULL); +} + +bool LLView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLView* r= childrenHandleMouseDown(x, y, mask ); + + return (r!=NULL); +} + +bool LLView::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + return childrenHandleDoubleClick( x, y, mask ) != NULL; +} + +bool LLView::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + return childrenHandleScrollWheel( x, y, clicks ) != NULL; +} + +bool LLView::handleScrollHWheel(S32 x, S32 y, S32 clicks) +{ + return childrenHandleScrollHWheel( x, y, clicks ) != NULL; +} + +bool LLView::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + return childrenHandleRightMouseDown( x, y, mask ) != NULL; +} + +bool LLView::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + return childrenHandleRightMouseUp( x, y, mask ) != NULL; +} + +bool LLView::handleMiddleMouseDown(S32 x, S32 y, MASK mask) +{ + return childrenHandleMiddleMouseDown( x, y, mask ) != NULL; +} + +bool LLView::handleMiddleMouseUp(S32 x, S32 y, MASK mask) +{ + return childrenHandleMiddleMouseUp( x, y, mask ) != NULL; +} + +LLView* LLView::childrenHandleScrollWheel(S32 x, S32 y, S32 clicks) +{ + return childrenHandleMouseEvent(&LLView::handleScrollWheel, x, y, clicks, false); +} + +LLView* LLView::childrenHandleScrollHWheel(S32 x, S32 y, S32 clicks) +{ + return childrenHandleMouseEvent(&LLView::handleScrollHWheel, x, y, clicks, false); +} + +// Called during downward traversal +LLView* LLView::childrenHandleKey(KEY key, MASK mask) +{ + return childrenHandleCharEvent("Key", &LLView::handleKey, key, mask); +} + +// Called during downward traversal +LLView* LLView::childrenHandleKeyUp(KEY key, MASK mask) +{ + return childrenHandleCharEvent("Key Up", &LLView::handleKeyUp, key, mask); +} + +// Called during downward traversal +LLView* LLView::childrenHandleUnicodeChar(llwchar uni_char) +{ + return childrenHandleCharEvent("Unicode character", &LLView::handleUnicodeCharWithDummyMask, + uni_char, MASK_NONE); +} + +LLView* LLView::childrenHandleMouseDown(S32 x, S32 y, MASK mask) +{ + return childrenHandleMouseEvent(&LLView::handleMouseDown, x, y, mask); +} + +LLView* LLView::childrenHandleRightMouseDown(S32 x, S32 y, MASK mask) +{ + return childrenHandleMouseEvent(&LLView::handleRightMouseDown, x, y, mask); +} + +LLView* LLView::childrenHandleMiddleMouseDown(S32 x, S32 y, MASK mask) +{ + return childrenHandleMouseEvent(&LLView::handleMiddleMouseDown, x, y, mask); +} + +LLView* LLView::childrenHandleDoubleClick(S32 x, S32 y, MASK mask) +{ + return childrenHandleMouseEvent(&LLView::handleDoubleClick, x, y, mask); +} + +LLView* LLView::childrenHandleMouseUp(S32 x, S32 y, MASK mask) +{ + return childrenHandleMouseEvent(&LLView::handleMouseUp, x, y, mask); +} + +LLView* LLView::childrenHandleRightMouseUp(S32 x, S32 y, MASK mask) +{ + return childrenHandleMouseEvent(&LLView::handleRightMouseUp, x, y, mask); +} + +LLView* LLView::childrenHandleMiddleMouseUp(S32 x, S32 y, MASK mask) +{ + return childrenHandleMouseEvent(&LLView::handleMiddleMouseUp, x, y, mask); +} + +void LLView::draw() +{ + drawChildren(); +} + +void LLView::drawChildren() +{ + if (!mChildList.empty()) + { + LLView* rootp = LLUI::getInstance()->getRootView(); + ++sDepth; + + for (child_list_reverse_iter_t child_iter = mChildList.rbegin(); child_iter != mChildList.rend();) // ++child_iter) + { + child_list_reverse_iter_t child = child_iter++; + LLView *viewp = *child; + + if (viewp == NULL) + { + continue; + } + + if (viewp->getVisible() && viewp->getRect().isValid()) + { + LLRect screen_rect = viewp->calcScreenRect(); + if ( rootp->getLocalRect().overlaps(screen_rect) && sDirtyRect.overlaps(screen_rect)) + { + LLUI::pushMatrix(); + { + LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom); + // flag the fact we are in draw here, in case overridden draw() method attempts to remove this widget + viewp->mInDraw = true; + viewp->draw(); + viewp->mInDraw = false; + + if (sDebugRects) + { + viewp->drawDebugRect(); + + // Check for bogus rectangle + if (!getRect().isValid()) + { + LL_WARNS() << "Bogus rectangle for " << getName() << " with " << mRect << LL_ENDL; + } + } + } + LLUI::popMatrix(); + } + } + + } + --sDepth; + } +} + +void LLView::dirtyRect() +{ + LLView* child = getParent(); + LLView* parent = child ? child->getParent() : NULL; + LLView* cur = this; + while (child && parent && parent->getParent()) + { //find third to top-most view + cur = child; + child = parent; + parent = parent->getParent(); + } + + if (!sIsRectDirty) + { + sDirtyRect = cur->calcScreenRect(); + sIsRectDirty = true; + } + else + { + sDirtyRect.unionWith(cur->calcScreenRect()); + } +} + +//Draw a box for debugging. +void LLView::drawDebugRect() +{ + std::set::iterator preview_iter = std::find(sPreviewHighlightedElements.begin(), sPreviewHighlightedElements.end(), this); // figure out if it's a previewed element + + LLUI::pushMatrix(); + { + // drawing solids requires texturing be disabled + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + if (getUseBoundingRect()) + { + LLUI::translate((F32)mBoundingRect.mLeft - (F32)mRect.mLeft, (F32)mBoundingRect.mBottom - (F32)mRect.mBottom); + } + + LLRect debug_rect = getUseBoundingRect() ? mBoundingRect : mRect; + + // draw red rectangle for the border + LLColor4 border_color(0.25f, 0.25f, 0.25f, 1.f); + if(preview_iter != sPreviewHighlightedElements.end()) + { + if(LLView::sPreviewClickedElement && this == sPreviewClickedElement) + { + border_color = LLColor4::red; + } + else + { + static LLUIColor scroll_highlighted_color = LLUIColorTable::instance().getColor("ScrollHighlightedColor"); + border_color = scroll_highlighted_color; + } + } + else + { + border_color.mV[sDepth%3] = 1.f; + } + + gGL.color4fv( border_color.mV ); + + gGL.begin(LLRender::LINES); + gGL.vertex2i(0, debug_rect.getHeight() - 1); + gGL.vertex2i(0, 0); + + gGL.vertex2i(0, 0); + gGL.vertex2i(debug_rect.getWidth() - 1, 0); + + gGL.vertex2i(debug_rect.getWidth() - 1, 0); + gGL.vertex2i(debug_rect.getWidth() - 1, debug_rect.getHeight() - 1); + + gGL.vertex2i(debug_rect.getWidth() - 1, debug_rect.getHeight() - 1); + gGL.vertex2i(0, debug_rect.getHeight() - 1); + gGL.end(); + + // Draw the name if it's not a leaf node or not in editing or preview mode + if (mChildList.size() + && preview_iter == sPreviewHighlightedElements.end() + && sDebugRectsShowNames) + { + S32 x, y; + gGL.color4fv( border_color.mV ); + + x = debug_rect.getWidth() / 2; + + S32 rect_height = debug_rect.getHeight(); + S32 lines = rect_height / LINE_HEIGHT + 1; + + S32 depth = 0; + LLView * viewp = this; + while (NULL != viewp) + { + viewp = viewp->getParent(); + depth++; + } + + y = rect_height - LINE_HEIGHT * (depth % lines + 1); + + std::string debug_text = llformat("%s (%d x %d)", getName().c_str(), + debug_rect.getWidth(), debug_rect.getHeight()); + LLFontGL::getFontSansSerifSmall()->renderUTF8(debug_text, 0, (F32)x, (F32)y, border_color, + LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); + } + } + LLUI::popMatrix(); +} + +void LLView::drawChild(LLView* childp, S32 x_offset, S32 y_offset, bool force_draw) +{ + if (childp && childp->getParent() == this) + { + ++sDepth; + + if ((childp->getVisible() && childp->getRect().isValid()) + || force_draw) + { + gGL.matrixMode(LLRender::MM_MODELVIEW); + LLUI::pushMatrix(); + { + LLUI::translate((F32)childp->getRect().mLeft + x_offset, (F32)childp->getRect().mBottom + y_offset); + childp->draw(); + } + LLUI::popMatrix(); + } + + --sDepth; + } +} + + +void LLView::reshape(S32 width, S32 height, bool called_from_parent) +{ + // compute how much things changed and apply reshape logic to children + S32 delta_width = width - getRect().getWidth(); + S32 delta_height = height - getRect().getHeight(); + + if (delta_width || delta_height || sForceReshape) + { + // adjust our rectangle + mRect.mRight = getRect().mLeft + width; + mRect.mTop = getRect().mBottom + height; + + // move child views according to reshape flags + for (LLView* viewp : mChildList) + { + if (viewp != NULL) + { + LLRect child_rect( viewp->mRect ); + + if (viewp->followsRight() && viewp->followsLeft()) + { + child_rect.mRight += delta_width; + } + else if (viewp->followsRight()) + { + child_rect.mLeft += delta_width; + child_rect.mRight += delta_width; + } + else if (viewp->followsLeft()) + { + // left is 0, don't need to adjust coords + } + else + { + // BUG what to do when we don't follow anyone? + // for now, same as followsLeft + } + + if (viewp->followsTop() && viewp->followsBottom()) + { + child_rect.mTop += delta_height; + } + else if (viewp->followsTop()) + { + child_rect.mTop += delta_height; + child_rect.mBottom += delta_height; + } + else if (viewp->followsBottom()) + { + // bottom is 0, so don't need to adjust coords + } + else + { + // BUG what to do when we don't follow? + // for now, same as bottom + } + + S32 delta_x = child_rect.mLeft - viewp->getRect().mLeft; + S32 delta_y = child_rect.mBottom - viewp->getRect().mBottom; + viewp->translate( delta_x, delta_y ); + if (child_rect.getWidth() != viewp->getRect().getWidth() + || child_rect.getHeight() != viewp->getRect().getHeight() + || sForceReshape) + { + viewp->reshape(child_rect.getWidth(), child_rect.getHeight()); + } + } + } + } + + if (!called_from_parent) + { + if (mParentView) + { + mParentView->reshape(mParentView->getRect().getWidth(), mParentView->getRect().getHeight(), false); + } + } + + updateBoundingRect(); +} + +LLRect LLView::calcBoundingRect() +{ + LLRect local_bounding_rect = LLRect::null; + + for (LLView* childp : mChildList) + { + // ignore invisible and "top" children when calculating bounding rect + // such as combobox popups + if (!childp->getVisible() || childp == gFocusMgr.getTopCtrl()) + { + continue; + } + + LLRect child_bounding_rect = childp->getBoundingRect(); + + if (local_bounding_rect.isEmpty()) + { + // start out with bounding rect equal to first visible child's bounding rect + local_bounding_rect = child_bounding_rect; + } + else + { + // accumulate non-null children rectangles + if (!child_bounding_rect.isEmpty()) + { + local_bounding_rect.unionWith(child_bounding_rect); + } + } + } + + // convert to parent-relative coordinates + local_bounding_rect.translate(mRect.mLeft, mRect.mBottom); + return local_bounding_rect; +} + + +void LLView::updateBoundingRect() +{ + if (isDead()) return; + + LLRect cur_rect = mBoundingRect; + + if (getUseBoundingRect()) + { + mBoundingRect = calcBoundingRect(); + } + else + { + mBoundingRect = mRect; + } + + // give parent view a chance to resize, in case we just moved, for example + if (getParent() && getParent()->getUseBoundingRect()) + { + getParent()->updateBoundingRect(); + } + + if (mBoundingRect != cur_rect) + { + dirtyRect(); + } + +} + +LLRect LLView::calcScreenRect() const +{ + LLRect screen_rect; + localPointToScreen(0, 0, &screen_rect.mLeft, &screen_rect.mBottom); + localPointToScreen(getRect().getWidth(), getRect().getHeight(), &screen_rect.mRight, &screen_rect.mTop); + return screen_rect; +} + +LLRect LLView::calcScreenBoundingRect() const +{ + LLRect screen_rect; + // get bounding rect, if used + LLRect bounding_rect = getUseBoundingRect() ? mBoundingRect : mRect; + + // convert to local coordinates, as defined by mRect + bounding_rect.translate(-mRect.mLeft, -mRect.mBottom); + + localPointToScreen(bounding_rect.mLeft, bounding_rect.mBottom, &screen_rect.mLeft, &screen_rect.mBottom); + localPointToScreen(bounding_rect.mRight, bounding_rect.mTop, &screen_rect.mRight, &screen_rect.mTop); + return screen_rect; +} + +LLRect LLView::getLocalBoundingRect() const +{ + LLRect local_bounding_rect = getBoundingRect(); + local_bounding_rect.translate(-mRect.mLeft, -mRect.mBottom); + + return local_bounding_rect; +} + + +LLRect LLView::getLocalRect() const +{ + LLRect local_rect(0, getRect().getHeight(), getRect().getWidth(), 0); + return local_rect; +} + +LLRect LLView::getLocalSnapRect() const +{ + LLRect local_snap_rect = getSnapRect(); + local_snap_rect.translate(-getRect().mLeft, -getRect().mBottom); + return local_snap_rect; +} + +bool LLView::hasAncestor(const LLView* parentp) const +{ + if (!parentp) + { + return false; + } + + LLView* viewp = getParent(); + while(viewp) + { + if (viewp == parentp) + { + return true; + } + viewp = viewp->getParent(); + } + + return false; +} + +//----------------------------------------------------------------------------- + +bool LLView::childHasKeyboardFocus( const std::string& childname ) const +{ + LLView *focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); + + while (focus != NULL) + { + if (focus->getName() == childname) + { + return true; + } + + focus = focus->getParent(); + } + + return false; +} + +//----------------------------------------------------------------------------- + +bool LLView::hasChild(const std::string& childname, bool recurse) const +{ + return findChildView(childname, recurse) != NULL; +} + +//----------------------------------------------------------------------------- +// getChildView() +//----------------------------------------------------------------------------- +LLView* LLView::getChildView(const std::string& name, bool recurse) const +{ + return getChild(name, recurse); +} + +LLView* LLView::findChildView(const std::string& name, bool recurse) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + + // Look for direct children *first* + for (LLView* childp : mChildList) + { + llassert(childp); + if (childp->getName() == name) + { + return childp; + } + } + if (recurse) + { + // Look inside each child as well. + for (LLView* childp : mChildList) + { + llassert(childp); + LLView* viewp = childp->findChildView(name, recurse); + if ( viewp ) + { + return viewp; + } + } + } + return NULL; +} + +bool LLView::parentPointInView(S32 x, S32 y, EHitTestType type) const +{ + return (getUseBoundingRect() && type == HIT_TEST_USE_BOUNDING_RECT) + ? mBoundingRect.pointInRect( x, y ) + : mRect.pointInRect( x, y ); +} + +bool LLView::pointInView(S32 x, S32 y, EHitTestType type) const +{ + return (getUseBoundingRect() && type == HIT_TEST_USE_BOUNDING_RECT) + ? mBoundingRect.pointInRect( x + mRect.mLeft, y + mRect.mBottom ) + : mRect.localPointInRect( x, y ); +} + +bool LLView::blockMouseEvent(S32 x, S32 y) const +{ + return mMouseOpaque && pointInView(x, y, HIT_TEST_IGNORE_BOUNDING_RECT); +} + +// virtual +void LLView::screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const +{ + *local_x = screen_x - getRect().mLeft; + *local_y = screen_y - getRect().mBottom; + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + *local_x -= cur->getRect().mLeft; + *local_y -= cur->getRect().mBottom; + } +} + +void LLView::localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const +{ + *screen_x = local_x; + *screen_y = local_y; + + const LLView* cur = this; + do + { + LLRect cur_rect = cur->getRect(); + *screen_x += cur_rect.mLeft; + *screen_y += cur_rect.mBottom; + cur = cur->mParentView; + } + while( cur ); +} + +void LLView::screenRectToLocal(const LLRect& screen, LLRect* local) const +{ + *local = screen; + local->translate( -getRect().mLeft, -getRect().mBottom ); + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + local->translate( -cur->getRect().mLeft, -cur->getRect().mBottom ); + } +} + +void LLView::localRectToScreen(const LLRect& local, LLRect* screen) const +{ + *screen = local; + screen->translate( getRect().mLeft, getRect().mBottom ); + + const LLView* cur = this; + while( cur->mParentView ) + { + cur = cur->mParentView; + screen->translate( cur->getRect().mLeft, cur->getRect().mBottom ); + } +} + +LLView* LLView::getRootView() +{ + LLView* view = this; + while( view->mParentView ) + { + view = view->mParentView; + } + return view; +} + +LLView* LLView::findPrevSibling(LLView* child) +{ + child_list_t::iterator prev_it = std::find(mChildList.begin(), mChildList.end(), child); + if (prev_it != mChildList.end() && prev_it != mChildList.begin()) + { + return *(--prev_it); + } + return NULL; +} + +LLView* LLView::findNextSibling(LLView* child) +{ + child_list_t::iterator next_it = std::find(mChildList.begin(), mChildList.end(), child); + if (next_it != mChildList.end()) + { + next_it++; + } + + return (next_it != mChildList.end()) ? *next_it : NULL; +} + + +LLCoordGL getNeededTranslation(const LLRect& input, const LLRect& constraint, S32 min_overlap_pixels) +{ + LLCoordGL delta; + + const S32 KEEP_ONSCREEN_PIXELS_WIDTH = llmin(min_overlap_pixels, input.getWidth()); + const S32 KEEP_ONSCREEN_PIXELS_HEIGHT = llmin(min_overlap_pixels, input.getHeight()); + + if (KEEP_ONSCREEN_PIXELS_WIDTH <= constraint.getWidth() && + KEEP_ONSCREEN_PIXELS_HEIGHT <= constraint.getHeight()) + { + if (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH < constraint.mLeft) + { + delta.mX = constraint.mLeft - (input.mRight - KEEP_ONSCREEN_PIXELS_WIDTH); + } + else if (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH > constraint.mRight) + { + delta.mX = constraint.mRight - (input.mLeft + KEEP_ONSCREEN_PIXELS_WIDTH); + } + + if (input.mTop > constraint.mTop) + { + delta.mY = constraint.mTop - input.mTop; + } + else if (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT < constraint.mBottom) + { + delta.mY = constraint.mBottom - (input.mTop - KEEP_ONSCREEN_PIXELS_HEIGHT); + } + } + + return delta; +} + +// Moves the view so that it is entirely inside of constraint. +// If the view will not fit because it's too big, aligns with the top and left. +// (Why top and left? That's where the drag bars are for floaters.) +bool LLView::translateIntoRect(const LLRect& constraint, S32 min_overlap_pixels) +{ + return translateRectIntoRect(getRect(), constraint, min_overlap_pixels); +} + +bool LLView::translateRectIntoRect(const LLRect& rect, const LLRect& constraint, S32 min_overlap_pixels) +{ + LLCoordGL translation = getNeededTranslation(rect, constraint, min_overlap_pixels); + + if (translation.mX != 0 || translation.mY != 0) + { + translate(translation.mX, translation.mY); + return true; + } + + return false; +} + +// move this view into "inside" but not onto "exclude" +// NOTE: if this view is already contained in "inside", we ignore the "exclude" rect +bool LLView::translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, S32 min_overlap_pixels) +{ + LLCoordGL translation = getNeededTranslation(getRect(), inside, min_overlap_pixels); + + if (translation.mX != 0 || translation.mY != 0) + { + // translate ourselves into constraint rect + translate(translation.mX, translation.mY); + + // do we overlap with exclusion area? + // keep moving in the same direction to the other side of the exclusion rect + if (exclude.overlaps(getRect())) + { + // moving right + if (translation.mX > 0) + { + translate(exclude.mRight - getRect().mLeft, 0); + } + // moving left + else if (translation.mX < 0) + { + translate(exclude.mLeft - getRect().mRight, 0); + } + + // moving up + if (translation.mY > 0) + { + translate(0, exclude.mTop - getRect().mBottom); + } + // moving down + else if (translation.mY < 0) + { + translate(0, exclude.mBottom - getRect().mTop); + } + } + + return true; + } + return false; +} + + +void LLView::centerWithin(const LLRect& bounds) +{ + S32 left = bounds.mLeft + (bounds.getWidth() - getRect().getWidth()) / 2; + S32 bottom = bounds.mBottom + (bounds.getHeight() - getRect().getHeight()) / 2; + + translate( left - getRect().mLeft, bottom - getRect().mBottom ); +} + +bool LLView::localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, const LLView* other_view) const +{ + const LLView* cur_view = this; + const LLView* root_view = NULL; + + while (cur_view) + { + if (cur_view == other_view) + { + *other_x = x; + *other_y = y; + return true; + } + + x += cur_view->getRect().mLeft; + y += cur_view->getRect().mBottom; + + cur_view = cur_view->getParent(); + root_view = cur_view; + } + + // assuming common root between two views, chase other_view's parents up to root + cur_view = other_view; + while (cur_view) + { + x -= cur_view->getRect().mLeft; + y -= cur_view->getRect().mBottom; + + cur_view = cur_view->getParent(); + + if (cur_view == root_view) + { + *other_x = x; + *other_y = y; + return true; + } + } + + *other_x = x; + *other_y = y; + return false; +} + +bool LLView::localRectToOtherView( const LLRect& local, LLRect* other, const LLView* other_view ) const +{ + LLRect cur_rect = local; + const LLView* cur_view = this; + const LLView* root_view = NULL; + + while (cur_view) + { + if (cur_view == other_view) + { + *other = cur_rect; + return true; + } + + cur_rect.translate(cur_view->getRect().mLeft, cur_view->getRect().mBottom); + + cur_view = cur_view->getParent(); + root_view = cur_view; + } + + // assuming common root between two views, chase other_view's parents up to root + cur_view = other_view; + while (cur_view) + { + cur_rect.translate(-cur_view->getRect().mLeft, -cur_view->getRect().mBottom); + + cur_view = cur_view->getParent(); + + if (cur_view == root_view) + { + *other = cur_rect; + return true; + } + } + + *other = cur_rect; + return false; +} + + +class CompareByTabOrder +{ +public: + CompareByTabOrder(const LLView::child_tab_order_t& order, S32 default_tab_group = 0) + : mTabOrder(order), + mDefaultTabGroup(default_tab_group) + {} + virtual ~CompareByTabOrder() {} + + // This method compares two LLViews by the tab order specified in the comparator object. The + // code for this is a little convoluted because each argument can have four states: + // 1) not a control, 2) a control but not in the tab order, 3) a control in the tab order, 4) null + bool operator() (const LLView* const a, const LLView* const b) const + { + S32 a_group = 0, b_group = 0; + if(!a) return false; + if(!b) return true; + + LLView::child_tab_order_const_iter_t a_found = mTabOrder.find(a), b_found = mTabOrder.find(b); + if(a_found != mTabOrder.end()) + { + a_group = a_found->second; + } + if(b_found != mTabOrder.end()) + { + b_group = b_found->second; + } + + if(a_group < mDefaultTabGroup && b_group >= mDefaultTabGroup) return true; + if(b_group < mDefaultTabGroup && a_group >= mDefaultTabGroup) return false; + return a_group > b_group; // sort correctly if they're both on the same side of the default tab groupreturn a > b; + } +private: + // ok to store a reference, as this should only be allocated on stack during view query operations + const LLView::child_tab_order_t& mTabOrder; + const S32 mDefaultTabGroup; +}; + +class SortByTabOrder : public LLQuerySorter, public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(SortByTabOrder); + /*virtual*/ void sort(LLView * parent, LLView::child_list_t &children) const override + { + children.sort(CompareByTabOrder(parent->getTabOrder(), parent->getDefaultTabGroup())); + } +}; + +// static +const LLViewQuery & LLView::getTabOrderQuery() +{ + static LLViewQuery query; + if(query.getPreFilters().size() == 0) { + query.addPreFilter(LLVisibleFilter::getInstance()); + query.addPreFilter(LLEnabledFilter::getInstance()); + query.addPreFilter(LLTabStopFilter::getInstance()); + query.addPostFilter(LLLeavesFilter::getInstance()); + query.setSorter(SortByTabOrder::getInstance()); + } + return query; +} + +// This class is only used internally by getFocusRootsQuery below. +class LLFocusRootsFilter : public LLQueryFilter, public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLFocusRootsFilter); + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override + { + return filterResult_t(view->isCtrl() && view->isFocusRoot(), !view->isFocusRoot()); + } +}; + +// static +const LLViewQuery & LLView::getFocusRootsQuery() +{ + static LLViewQuery query; + if(query.getPreFilters().size() == 0) { + query.addPreFilter(LLVisibleFilter::getInstance()); + query.addPreFilter(LLEnabledFilter::getInstance()); + query.addPreFilter(LLFocusRootsFilter::getInstance()); + query.addPostFilter(LLRootsFilter::getInstance()); + } + return query; +} + + +void LLView::setShape(const LLRect& new_rect, bool by_user) +{ + if (new_rect != getRect()) + { + handleReshape(new_rect, by_user); + } +} + +void LLView::handleReshape(const LLRect& new_rect, bool by_user) +{ + reshape(new_rect.getWidth(), new_rect.getHeight()); + translate(new_rect.mLeft - getRect().mLeft, new_rect.mBottom - getRect().mBottom); +} + +LLView* LLView::findSnapRect(LLRect& new_rect, const LLCoordGL& mouse_dir, + LLView::ESnapType snap_type, S32 threshold, S32 padding) +{ + new_rect = mRect; + LLView* snap_view = NULL; + + if (!mParentView) + { + return NULL; + } + + S32 delta_x = 0; + S32 delta_y = 0; + if (mouse_dir.mX >= 0) + { + S32 new_right = mRect.mRight; + LLView* view = findSnapEdge(new_right, mouse_dir, SNAP_RIGHT, snap_type, threshold, padding); + delta_x = new_right - mRect.mRight; + snap_view = view ? view : snap_view; + } + + if (mouse_dir.mX <= 0) + { + S32 new_left = mRect.mLeft; + LLView* view = findSnapEdge(new_left, mouse_dir, SNAP_LEFT, snap_type, threshold, padding); + delta_x = new_left - mRect.mLeft; + snap_view = view ? view : snap_view; + } + + if (mouse_dir.mY >= 0) + { + S32 new_top = mRect.mTop; + LLView* view = findSnapEdge(new_top, mouse_dir, SNAP_TOP, snap_type, threshold, padding); + delta_y = new_top - mRect.mTop; + snap_view = view ? view : snap_view; + } + + if (mouse_dir.mY <= 0) + { + S32 new_bottom = mRect.mBottom; + LLView* view = findSnapEdge(new_bottom, mouse_dir, SNAP_BOTTOM, snap_type, threshold, padding); + delta_y = new_bottom - mRect.mBottom; + snap_view = view ? view : snap_view; + } + + new_rect.translate(delta_x, delta_y); + return snap_view; +} + +LLView* LLView::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding) +{ + LLRect snap_rect = getSnapRect(); + S32 snap_pos = 0; + switch(snap_edge) + { + case SNAP_LEFT: + snap_pos = snap_rect.mLeft; + break; + case SNAP_RIGHT: + snap_pos = snap_rect.mRight; + break; + case SNAP_TOP: + snap_pos = snap_rect.mTop; + break; + case SNAP_BOTTOM: + snap_pos = snap_rect.mBottom; + break; + } + + if (!mParentView) + { + new_edge_val = snap_pos; + return NULL; + } + + LLView* snap_view = NULL; + + // If the view is near the edge of its parent, snap it to + // the edge. + LLRect test_rect = snap_rect; + test_rect.stretch(padding); + + S32 x_threshold = threshold; + S32 y_threshold = threshold; + + LLRect parent_local_snap_rect = mParentView->getLocalSnapRect(); + + if (snap_type == SNAP_PARENT || snap_type == SNAP_PARENT_AND_SIBLINGS) + { + switch(snap_edge) + { + case SNAP_RIGHT: + if (llabs(parent_local_snap_rect.mRight - test_rect.mRight) <= x_threshold + && (parent_local_snap_rect.mRight - test_rect.mRight) * mouse_dir.mX >= 0) + { + snap_pos = parent_local_snap_rect.mRight - padding; + snap_view = mParentView; + x_threshold = llabs(parent_local_snap_rect.mRight - test_rect.mRight); + } + break; + case SNAP_LEFT: + if (llabs(test_rect.mLeft - parent_local_snap_rect.mLeft) <= x_threshold + && test_rect.mLeft * mouse_dir.mX <= 0) + { + snap_pos = parent_local_snap_rect.mLeft + padding; + snap_view = mParentView; + x_threshold = llabs(test_rect.mLeft - parent_local_snap_rect.mLeft); + } + break; + case SNAP_BOTTOM: + if (llabs(test_rect.mBottom - parent_local_snap_rect.mBottom) <= y_threshold + && test_rect.mBottom * mouse_dir.mY <= 0) + { + snap_pos = parent_local_snap_rect.mBottom + padding; + snap_view = mParentView; + y_threshold = llabs(test_rect.mBottom - parent_local_snap_rect.mBottom); + } + break; + case SNAP_TOP: + if (llabs(parent_local_snap_rect.mTop - test_rect.mTop) <= y_threshold && (parent_local_snap_rect.mTop - test_rect.mTop) * mouse_dir.mY >= 0) + { + snap_pos = parent_local_snap_rect.mTop - padding; + snap_view = mParentView; + y_threshold = llabs(parent_local_snap_rect.mTop - test_rect.mTop); + } + break; + default: + LL_ERRS() << "Invalid snap edge" << LL_ENDL; + } + } + + if (snap_type == SNAP_SIBLINGS || snap_type == SNAP_PARENT_AND_SIBLINGS) + { + for ( child_list_const_iter_t child_it = mParentView->getChildList()->begin(); + child_it != mParentView->getChildList()->end(); ++child_it) + { + LLView* siblingp = *child_it; + + if (!canSnapTo(siblingp)) continue; + + LLRect sibling_rect = siblingp->getSnapRect(); + + switch(snap_edge) + { + case SNAP_RIGHT: + if (llabs(test_rect.mRight - sibling_rect.mLeft) <= x_threshold + && (test_rect.mRight - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mLeft - padding; + snap_view = siblingp; + x_threshold = llabs(test_rect.mRight - sibling_rect.mLeft); + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mTop - (test_rect.mBottom - padding)) <= y_threshold + || llabs(sibling_rect.mBottom - (test_rect.mTop + padding)) <= x_threshold) + { + if (llabs(test_rect.mRight - sibling_rect.mRight) <= x_threshold + && (test_rect.mRight - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mRight; + snap_view = siblingp; + x_threshold = llabs(test_rect.mRight - sibling_rect.mRight); + } + } + break; + case SNAP_LEFT: + if (llabs(test_rect.mLeft - sibling_rect.mRight) <= x_threshold + && (test_rect.mLeft - sibling_rect.mRight) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mRight + padding; + snap_view = siblingp; + x_threshold = llabs(test_rect.mLeft - sibling_rect.mRight); + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mTop - (test_rect.mBottom - padding)) <= y_threshold + || llabs(sibling_rect.mBottom - (test_rect.mTop + padding)) <= y_threshold) + { + if (llabs(test_rect.mLeft - sibling_rect.mLeft) <= x_threshold + && (test_rect.mLeft - sibling_rect.mLeft) * mouse_dir.mX <= 0) + { + snap_pos = sibling_rect.mLeft; + snap_view = siblingp; + x_threshold = llabs(test_rect.mLeft - sibling_rect.mLeft); + } + } + break; + case SNAP_BOTTOM: + if (llabs(test_rect.mBottom - sibling_rect.mTop) <= y_threshold + && (test_rect.mBottom - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mTop + padding; + snap_view = siblingp; + y_threshold = llabs(test_rect.mBottom - sibling_rect.mTop); + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mRight - (test_rect.mLeft - padding)) <= x_threshold + || llabs(sibling_rect.mLeft - (test_rect.mRight + padding)) <= x_threshold) + { + if (llabs(test_rect.mBottom - sibling_rect.mBottom) <= y_threshold + && (test_rect.mBottom - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mBottom; + snap_view = siblingp; + y_threshold = llabs(test_rect.mBottom - sibling_rect.mBottom); + } + } + break; + case SNAP_TOP: + if (llabs(test_rect.mTop - sibling_rect.mBottom) <= y_threshold + && (test_rect.mTop - sibling_rect.mBottom) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mBottom - padding; + snap_view = siblingp; + y_threshold = llabs(test_rect.mTop - sibling_rect.mBottom); + } + // if snapped with sibling along other axis, check for shared edge + else if (llabs(sibling_rect.mRight - (test_rect.mLeft - padding)) <= x_threshold + || llabs(sibling_rect.mLeft - (test_rect.mRight + padding)) <= x_threshold) + { + if (llabs(test_rect.mTop - sibling_rect.mTop) <= y_threshold + && (test_rect.mTop - sibling_rect.mTop) * mouse_dir.mY <= 0) + { + snap_pos = sibling_rect.mTop; + snap_view = siblingp; + y_threshold = llabs(test_rect.mTop - sibling_rect.mTop); + } + } + break; + default: + LL_ERRS() << "Invalid snap edge" << LL_ENDL; + } + } + } + + new_edge_val = snap_pos; + return snap_view; +} + +//----------------------------------------------------------------------------- +// Listener dispatch functions +//----------------------------------------------------------------------------- + + +LLControlVariable *LLView::findControl(const std::string& name) +{ + // parse the name to locate which group it belongs to + std::size_t key_pos= name.find("."); + if(key_pos!= std::string::npos ) + { + std::string control_group_key = name.substr(0, key_pos); + LLControlVariable* control; + // check if it's in the control group that name indicated + if(LLUI::getInstance()->mSettingGroups[control_group_key]) + { + control = LLUI::getInstance()->mSettingGroups[control_group_key]->getControl(name); + if (control) + { + return control; + } + } + } + + LLControlGroup& control_group = LLUI::getInstance()->getControlControlGroup(name); + return control_group.getControl(name); +} + +void LLView::initFromParams(const LLView::Params& params) +{ + LLRect required_rect = getRequiredRect(); + + S32 width = llmax(getRect().getWidth(), required_rect.getWidth()); + S32 height = llmax(getRect().getHeight(), required_rect.getHeight()); + + reshape(width, height); + + // call virtual methods with most recent data + // use getters because these values might not come through parameter block + setEnabled(getEnabled()); + setVisible(getVisible()); + + if (!params.name().empty()) + { + setName(params.name()); + } + + mLayout = params.layout(); +} + +void LLView::parseFollowsFlags(const LLView::Params& params) +{ + // preserve follows flags set by code if user did not override + if (!params.follows.isProvided()) + { + return; + } + + // interpret either string or bitfield version of follows + if (params.follows.string.isChosen()) + { + setFollows(FOLLOWS_NONE); + + std::string follows = params.follows.string; + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("|"); + tokenizer tokens(follows, sep); + tokenizer::iterator token_iter = tokens.begin(); + + while(token_iter != tokens.end()) + { + const std::string& token_str = *token_iter; + if (token_str == "left") + { + setFollowsLeft(); + } + else if (token_str == "right") + { + setFollowsRight(); + } + else if (token_str == "top") + { + setFollowsTop(); + } + else if (token_str == "bottom") + { + setFollowsBottom(); + } + else if (token_str == "all") + { + setFollowsAll(); + } + ++token_iter; + } + } + else if (params.follows.flags.isChosen()) + { + setFollows(params.follows.flags); + } +} + + +// static +//LLFontGL::HAlign LLView::selectFontHAlign(LLXMLNodePtr node) +//{ +// LLFontGL::HAlign gl_hfont_align = LLFontGL::LEFT; +// +// if (node->hasAttribute("halign")) +// { +// std::string horizontal_align_name; +// node->getAttributeString("halign", horizontal_align_name); +// gl_hfont_align = LLFontGL::hAlignFromName(horizontal_align_name); +// } +// return gl_hfont_align; +//} + +// Return the rectangle of the last-constructed child, +// if present and a first-class widget (eg, not a close box or drag handle) +// Returns true if found +static bool get_last_child_rect(LLView* parent, LLRect *rect) +{ + if (!parent) return false; + + LLView::child_list_t::const_iterator itor = + parent->getChildList()->begin(); + for (;itor != parent->getChildList()->end(); ++itor) + { + LLView *last_view = (*itor); + if (last_view->getFromXUI()) + { + *rect = last_view->getRect(); + return true; + } + } + return false; +} + +//static +void LLView::applyXUILayout(LLView::Params& p, LLView* parent, LLRect layout_rect) +{ + if (!parent) return; + + const S32 VPAD = 4; + const S32 MIN_WIDGET_HEIGHT = 10; + + // *NOTE: This will confuse export of floater/panel coordinates unless + // the default is also "topleft". JC + if (p.layout().empty()) + { + p.layout = parent->getLayout(); + } + + if (layout_rect.isEmpty()) + { + layout_rect = parent->getLocalRect(); + } + + // overwrite uninitialized rect params, using context + LLRect default_rect = parent->getLocalRect(); + + bool layout_topleft = (p.layout() == "topleft"); + + // convert negative or centered coordinates to parent relative values + // Note: some of this logic matches the logic in TypedParam::setValueFromBlock() + if (p.rect.left.isProvided()) + { + p.rect.left = p.rect.left + ((p.rect.left >= 0) ? layout_rect.mLeft : layout_rect.mRight); + } + if (p.rect.right.isProvided()) + { + p.rect.right = p.rect.right + ((p.rect.right >= 0) ? layout_rect.mLeft : layout_rect.mRight); + } + if (p.rect.bottom.isProvided()) + { + p.rect.bottom = p.rect.bottom + ((p.rect.bottom >= 0) ? layout_rect.mBottom : layout_rect.mTop); + if (layout_topleft) + { + //invert top to bottom + p.rect.bottom = layout_rect.mBottom + layout_rect.mTop - p.rect.bottom; + } + } + if (p.rect.top.isProvided()) + { + p.rect.top = p.rect.top + ((p.rect.top >= 0) ? layout_rect.mBottom : layout_rect.mTop); + if (layout_topleft) + { + //invert top to bottom + p.rect.top = layout_rect.mBottom + layout_rect.mTop - p.rect.top; + } + } + + // DEPRECATE: automatically fall back to height of MIN_WIDGET_HEIGHT pixels + if (!p.rect.height.isProvided() && !p.rect.top.isProvided() && p.rect.height == 0) + { + p.rect.height = MIN_WIDGET_HEIGHT; + } + + default_rect.translate(0, default_rect.getHeight()); + + // If there was a recently constructed child, use its rectangle + get_last_child_rect(parent, &default_rect); + + if (layout_topleft) + { + // Invert the sense of bottom_delta for topleft layout + if (p.bottom_delta.isProvided()) + { + p.bottom_delta = -p.bottom_delta; + } + else if (p.top_pad.isProvided()) + { + p.bottom_delta = -(p.rect.height + p.top_pad); + } + else if (p.top_delta.isProvided()) + { + p.bottom_delta = + -(p.top_delta + p.rect.height - default_rect.getHeight()); + } + else if (!p.left_delta.isProvided() + && !p.left_pad.isProvided()) + { + // set default position is just below last rect + p.bottom_delta.set(-(p.rect.height + VPAD), false); + } + else + { + p.bottom_delta.set(0, false); + } + + // default to same left edge + if (!p.left_delta.isProvided()) + { + p.left_delta.set(0, false); + } + if (p.left_pad.isProvided()) + { + // left_pad is based on prior widget's right edge + p.left_delta.set(p.left_pad + default_rect.getWidth(), false); + } + + default_rect.translate(p.left_delta, p.bottom_delta); + } + else + { + // set default position is just below last rect + if (!p.bottom_delta.isProvided()) + { + p.bottom_delta.set(-(p.rect.height + VPAD), false); + } + if (!p.left_delta.isProvided()) + { + p.left_delta.set(0, false); + } + default_rect.translate(p.left_delta, p.bottom_delta); + } + + // this handles case where *both* x and x_delta are provided + // ignore x in favor of default x + x_delta + if (p.bottom_delta.isProvided()) p.rect.bottom.set(0, false); + if (p.left_delta.isProvided()) p.rect.left.set(0, false); + + // selectively apply rectangle defaults, making sure that + // params are not flagged as having been "provided" + // as rect params are overconstrained and rely on provided flags + if (!p.rect.left.isProvided()) + { + p.rect.left.set(default_rect.mLeft, false); + //HACK: get around the fact that setting a rect param component value won't invalidate the existing rect object value + p.rect.paramChanged(p.rect.left, true); + } + if (!p.rect.bottom.isProvided()) + { + p.rect.bottom.set(default_rect.mBottom, false); + p.rect.paramChanged(p.rect.bottom, true); + } + if (!p.rect.top.isProvided()) + { + p.rect.top.set(default_rect.mTop, false); + p.rect.paramChanged(p.rect.top, true); + } + if (!p.rect.right.isProvided()) + { + p.rect.right.set(default_rect.mRight, false); + p.rect.paramChanged(p.rect.right, true); + + } + if (!p.rect.width.isProvided()) + { + p.rect.width.set(default_rect.getWidth(), false); + p.rect.paramChanged(p.rect.width, true); + } + if (!p.rect.height.isProvided()) + { + p.rect.height.set(default_rect.getHeight(), false); + p.rect.paramChanged(p.rect.height, true); + } +} + +static S32 invert_vertical(S32 y, LLView* parent) +{ + if (y < 0) + { + // already based on top-left, just invert + return -y; + } + else if (parent) + { + // use parent to flip coordinate + S32 parent_height = parent->getRect().getHeight(); + return parent_height - y; + } + else + { + LL_WARNS() << "Attempting to convert layout to top-left with no parent" << LL_ENDL; + return y; + } +} + +// Assumes that input is in bottom-left coordinates, hence must call +// _before_ convert_coords_to_top_left(). +static void convert_to_relative_layout(LLView::Params& p, LLView* parent) +{ + // Use setupParams to get the final widget rectangle + // according to our wacky layout rules. + LLView::Params final = p; + LLView::applyXUILayout(final, parent); + // Must actually extract the rectangle to get consistent + // right = left+width, top = bottom+height + LLRect final_rect = final.rect; + + // We prefer to write out top edge instead of bottom, regardless + // of whether we use relative positioning + bool converted_top = false; + + // Look for a last rectangle + LLRect last_rect; + if (get_last_child_rect(parent, &last_rect)) + { + // ...we have a previous widget to compare to + const S32 EDGE_THRESHOLD_PIXELS = 4; + S32 left_pad = final_rect.mLeft - last_rect.mRight; + S32 left_delta = final_rect.mLeft - last_rect.mLeft; + S32 top_pad = final_rect.mTop - last_rect.mBottom; + S32 top_delta = final_rect.mTop - last_rect.mTop; + // If my left edge is almost the same, or my top edge is + // almost the same... + if (llabs(left_delta) <= EDGE_THRESHOLD_PIXELS + || llabs(top_delta) <= EDGE_THRESHOLD_PIXELS) + { + // ...use relative positioning + // prefer top_pad if widgets are stacking vertically + // (coordinate system is still bottom-left here) + if (top_pad < 0) + { + p.top_pad = top_pad; + p.top_delta.setProvided(false); + } + else + { + p.top_pad.setProvided(false); + p.top_delta = top_delta; + } + // null out other vertical specifiers + p.rect.top.setProvided(false); + p.rect.bottom.setProvided(false); + p.bottom_delta.setProvided(false); + converted_top = true; + + // prefer left_pad if widgets are stacking horizontally + if (left_pad > 0) + { + p.left_pad = left_pad; + p.left_delta.setProvided(false); + } + else + { + p.left_pad.setProvided(false); + p.left_delta = left_delta; + } + p.rect.left.setProvided(false); + p.rect.right.setProvided(false); + } + } + + if (!converted_top) + { + // ...this is the first widget, or one that wasn't aligned + // prefer top/left specification + p.rect.top = final_rect.mTop; + p.rect.bottom.setProvided(false); + p.bottom_delta.setProvided(false); + p.top_pad.setProvided(false); + p.top_delta.setProvided(false); + } +} + +static void convert_coords_to_top_left(LLView::Params& p, LLView* parent) +{ + // Convert the coordinate system to be top-left based. + if (p.rect.top.isProvided()) + { + p.rect.top = invert_vertical(p.rect.top, parent); + } + if (p.rect.bottom.isProvided()) + { + p.rect.bottom = invert_vertical(p.rect.bottom, parent); + } + if (p.top_pad.isProvided()) + { + p.top_pad = -p.top_pad; + } + if (p.top_delta.isProvided()) + { + p.top_delta = -p.top_delta; + } + if (p.bottom_delta.isProvided()) + { + p.bottom_delta = -p.bottom_delta; + } + p.layout = "topleft"; +} + +//static +void LLView::setupParamsForExport(Params& p, LLView* parent) +{ + // Don't convert if already top-left based + if (p.layout() == "topleft") + { + return; + } + + // heuristic: Many of our floaters and panels were bulk-exported. + // These specify exactly bottom/left and height/width. + // Others were done by hand using bottom_delta and/or left_delta. + // Some rely on not specifying left to mean align with left edge. + // Try to convert both to use relative layout, but using top-left + // coordinates. + // Avoid rectangles where top/bottom/left/right was specified. + if (p.rect.height.isProvided() && p.rect.width.isProvided()) + { + if (p.rect.bottom.isProvided() && p.rect.left.isProvided()) + { + // standard bulk export, convert it + convert_to_relative_layout(p, parent); + } + else if (p.rect.bottom.isProvided() && p.left_delta.isProvided()) + { + // hand layout with left_delta + convert_to_relative_layout(p, parent); + } + else if (p.bottom_delta.isProvided()) + { + // hand layout with bottom_delta + // don't check for p.rect.left or p.left_delta because sometimes + // this layout doesn't set it for widgets that are left-aligned + convert_to_relative_layout(p, parent); + } + } + + convert_coords_to_top_left(p, parent); +} + +LLView::tree_iterator_t LLView::beginTreeDFS() +{ + return tree_iterator_t(this, + boost::bind(boost::mem_fn(&LLView::beginChild), _1), + boost::bind(boost::mem_fn(&LLView::endChild), _1)); +} + +LLView::tree_iterator_t LLView::endTreeDFS() +{ + // an empty iterator is an "end" iterator + return tree_iterator_t(); +} + +LLView::tree_post_iterator_t LLView::beginTreeDFSPost() +{ + return tree_post_iterator_t(this, + boost::bind(boost::mem_fn(&LLView::beginChild), _1), + boost::bind(boost::mem_fn(&LLView::endChild), _1)); +} + +LLView::tree_post_iterator_t LLView::endTreeDFSPost() +{ + // an empty iterator is an "end" iterator + return tree_post_iterator_t(); +} + +LLView::bfs_tree_iterator_t LLView::beginTreeBFS() +{ + return bfs_tree_iterator_t(this, + boost::bind(boost::mem_fn(&LLView::beginChild), _1), + boost::bind(boost::mem_fn(&LLView::endChild), _1)); +} + +LLView::bfs_tree_iterator_t LLView::endTreeBFS() +{ + // an empty iterator is an "end" iterator + return bfs_tree_iterator_t(); +} + + +LLView::root_to_view_iterator_t LLView::beginRootToView() +{ + return root_to_view_iterator_t(this, boost::bind(&LLView::getParent, _1)); +} + +LLView::root_to_view_iterator_t LLView::endRootToView() +{ + return root_to_view_iterator_t(); +} + + +// only create maps on demand, as they incur heap allocation/deallocation cost +// when a view is constructed/deconstructed +LLView& LLView::getDefaultWidgetContainer() const +{ + if (!mDefaultWidgets) + { + LLView::Params p; + p.name = "default widget container"; + p.visible = false; // ensures default widgets can't steal focus, etc. + mDefaultWidgets = new LLView(p); + } + return *mDefaultWidgets; +} + +S32 LLView::notifyParent(const LLSD& info) +{ + LLView* parent = getParent(); + if(parent) + return parent->notifyParent(info); + return 0; +} +bool LLView::notifyChildren(const LLSD& info) +{ + bool ret = false; + for (LLView* childp : mChildList) + { + ret = ret || childp->notifyChildren(info); + } + return ret; +} + +// convenient accessor for draw context +const LLViewDrawContext& LLView::getDrawContext() +{ + return LLViewDrawContext::getCurrentContext(); +} + +const LLViewDrawContext& LLViewDrawContext::getCurrentContext() +{ + static LLViewDrawContext default_context; + + if (sDrawContextStack.empty()) + return default_context; + + return *sDrawContextStack.back(); +} + +LLSD LLView::getInfo(void) +{ + LLSD info; + addInfo(info); + return info; +} + +void LLView::addInfo(LLSD & info) +{ + info["path"] = getPathname(); + info["class"] = typeid(*this).name(); + info["visible"] = getVisible(); + info["visible_chain"] = isInVisibleChain(); + info["enabled"] = getEnabled(); + info["enabled_chain"] = isInEnabledChain(); + info["available"] = isAvailable(); + LLRect rect(calcScreenRect()); + info["rect"] = LLSDMap("left", rect.mLeft)("top", rect.mTop) + ("right", rect.mRight)("bottom", rect.mBottom); +} diff --git a/indra/llui/llview.h b/indra/llui/llview.h index ddb0a3dbbf..3af748dda6 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -1,737 +1,737 @@ -/** - * @file llview.h - * @brief Container for other views, anything that draws. - * - * $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$ - */ - -#ifndef LL_LLVIEW_H -#define LL_LLVIEW_H - -// A view is an area in a window that can draw. It might represent -// the HUD or a dialog box or a button. It can also contain sub-views -// and child widgets - -#include "stdtypes.h" -#include "llcoord.h" -#include "llfontgl.h" -#include "llhandle.h" -#include "llmortician.h" -#include "llmousehandler.h" -#include "llstring.h" -#include "llrect.h" -#include "llui.h" -#include "lluistring.h" -#include "llviewquery.h" -#include "lluistring.h" -#include "llcursortypes.h" -#include "lluictrlfactory.h" -#include "lltreeiterators.h" -#include "llfocusmgr.h" - -#include -#include -#include - -class LLSD; - -const U32 FOLLOWS_NONE = 0x00; -const U32 FOLLOWS_LEFT = 0x01; -const U32 FOLLOWS_RIGHT = 0x02; -const U32 FOLLOWS_TOP = 0x10; -const U32 FOLLOWS_BOTTOM = 0x20; -const U32 FOLLOWS_ALL = 0x33; - -const bool MOUSE_OPAQUE = true; -const bool NOT_MOUSE_OPAQUE = false; - -const U32 GL_NAME_UI_RESERVED = 2; - - -// maintains render state during traversal of UI tree -class LLViewDrawContext -{ -public: - F32 mAlpha; - - LLViewDrawContext(F32 alpha = 1.f) - : mAlpha(alpha) - { - if (!sDrawContextStack.empty()) - { - LLViewDrawContext* context_top = sDrawContextStack.back(); - // merge with top of stack - mAlpha *= context_top->mAlpha; - } - sDrawContextStack.push_back(this); - } - - ~LLViewDrawContext() - { - sDrawContextStack.pop_back(); - } - - static const LLViewDrawContext& getCurrentContext(); - -private: - static std::vector sDrawContextStack; -}; - -class LLView -: public LLMouseHandler, // handles mouse events - public LLFocusableElement, // handles keyboard events - public LLMortician, // lazy deletion - public LLHandleProvider // passes out weak references to self -{ -public: - - enum EOrientation { HORIZONTAL, VERTICAL, ORIENTATION_COUNT }; - - struct Follows : public LLInitParam::ChoiceBlock - { - Alternative string; - Alternative flags; - - Follows(); - }; - - struct Params : public LLInitParam::Block - { - Mandatory name; - - Optional enabled, - visible, - mouse_opaque, - use_bounding_rect, - from_xui, - focus_root; - - Optional tab_group, - default_tab_group; - Optional tool_tip; - - Optional sound_flags; - Optional follows; - Optional hover_cursor; - - Optional layout; - Optional rect; - - // Historical bottom-left layout used bottom_delta and left_delta - // for relative positioning. New layout "topleft" prefers specifying - // based on top edge. - Optional bottom_delta, // from last bottom to my bottom - top_pad, // from last bottom to my top - top_delta, // from last top to my top - left_pad, // from last right to my left - left_delta; // from last left to my left - - //FIXME: get parent context involved in parsing traversal - Ignored needs_translate, // cue for translation tools - xmlns, // xml namespace - xmlns_xsi, // xml namespace - xsi_schemaLocation, // xml schema - xsi_type; // xml schema type - - Params(); - }; - - // most widgets are valid children of LLView - typedef LLDefaultChildRegistry child_registry_t; - - void initFromParams(const LLView::Params&); - -protected: - LLView(const LLView::Params&); - friend class LLUICtrlFactory; - -private: - // widgets in general are not copyable - LLView(const LLView& other); -public: -//#if LL_DEBUG - static bool sIsDrawing; -//#endif - enum ESoundFlags - { - SILENT = 0, - MOUSE_DOWN = 1, - MOUSE_UP = 2 - }; - - enum ESnapType - { - SNAP_PARENT, - SNAP_SIBLINGS, - SNAP_PARENT_AND_SIBLINGS - }; - - enum ESnapEdge - { - SNAP_LEFT, - SNAP_TOP, - SNAP_RIGHT, - SNAP_BOTTOM - }; - - typedef std::list child_list_t; - typedef child_list_t::iterator child_list_iter_t; - typedef child_list_t::const_iterator child_list_const_iter_t; - typedef child_list_t::reverse_iterator child_list_reverse_iter_t; - typedef child_list_t::const_reverse_iterator child_list_const_reverse_iter_t; - - typedef std::pair tab_order_pair_t; - // this structure primarily sorts by the tab group, secondarily by the insertion ordinal (lastly by the value of the pointer) - typedef std::map child_tab_order_t; - typedef child_tab_order_t::iterator child_tab_order_iter_t; - typedef child_tab_order_t::const_iterator child_tab_order_const_iter_t; - typedef child_tab_order_t::reverse_iterator child_tab_order_reverse_iter_t; - typedef child_tab_order_t::const_reverse_iterator child_tab_order_const_reverse_iter_t; - - virtual ~LLView(); - - // Some UI widgets need to be added as controls. Others need to - // be added as regular view children. isCtrl should return true - // if a widget needs to be added as a ctrl - virtual bool isCtrl() const; - - virtual bool isPanel() const; - - // - // MANIPULATORS - // - void setMouseOpaque( bool b ) { mMouseOpaque = b; } - bool getMouseOpaque() const { return mMouseOpaque; } - void setToolTip( const LLStringExplicit& msg ); - bool setToolTipArg( const LLStringExplicit& key, const LLStringExplicit& text ); - void setToolTipArgs( const LLStringUtil::format_map_t& args ); - - virtual void setRect(const LLRect &rect); - void setFollows(U32 flags) { mReshapeFlags = flags; } - - // deprecated, use setFollows() with FOLLOWS_LEFT | FOLLOWS_TOP, etc. - void setFollowsNone() { mReshapeFlags = FOLLOWS_NONE; } - void setFollowsLeft() { mReshapeFlags |= FOLLOWS_LEFT; } - void setFollowsTop() { mReshapeFlags |= FOLLOWS_TOP; } - void setFollowsRight() { mReshapeFlags |= FOLLOWS_RIGHT; } - void setFollowsBottom() { mReshapeFlags |= FOLLOWS_BOTTOM; } - void setFollowsAll() { mReshapeFlags |= FOLLOWS_ALL; } - - void setSoundFlags(U8 flags) { mSoundFlags = flags; } - void setName(std::string name) { mName = name; } - void setUseBoundingRect( bool use_bounding_rect ); - bool getUseBoundingRect() const; - - ECursorType getHoverCursor() { return mHoverCursor; } - - static F32 getTooltipTimeout(); - virtual const std::string getToolTip() const; - virtual const std::string& getText() const { return LLStringUtil::null; } - virtual const LLFontGL* getFont() const { return nullptr; } - - void sendChildToFront(LLView* child); - void sendChildToBack(LLView* child); - - virtual bool addChild(LLView* view, S32 tab_group = 0); - - // implemented in terms of addChild() - bool addChildInBack(LLView* view, S32 tab_group = 0); - - // remove the specified child from the view, and set it's parent to NULL. - virtual void removeChild(LLView* view); - - virtual bool postBuild() { return true; } - - const child_tab_order_t& getTabOrder() const { return mTabOrder; } - - void setDefaultTabGroup(S32 d) { mDefaultTabGroup = d; } - S32 getDefaultTabGroup() const { return mDefaultTabGroup; } - S32 getLastTabGroup() { return mLastTabGroup; } - - bool isInVisibleChain() const; - bool isInEnabledChain() const; - - void setFocusRoot(bool b) { mIsFocusRoot = b; } - bool isFocusRoot() const { return mIsFocusRoot; } - virtual bool canFocusChildren() const; - - bool focusNextRoot(); - bool focusPrevRoot(); - - // Normally we want the app menus to get priority on accelerated keys - // However, sometimes we want to give specific views a first chance - // iat handling them. (eg. the script editor) - virtual bool hasAccelerators() const { return false; }; - - // delete all children. Override this function if you need to - // perform any extra clean up such as cached pointers to selected - // children, etc. - virtual void deleteAllChildren(); - - void setAllChildrenEnabled(bool b); - - virtual void setVisible(bool visible); - void setVisibleDirect(bool visible) { mVisible = visible; } - const bool& getVisible() const { return mVisible; } - virtual void setEnabled(bool enabled); - bool getEnabled() const { return mEnabled; } - /// 'available' in this context means 'visible and enabled': in other - /// words, can a user actually interact with this? - virtual bool isAvailable() const; - /// The static isAvailable() tests an LLView* that could be NULL. - static bool isAvailable(const LLView* view); - U8 getSoundFlags() const { return mSoundFlags; } - - virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); - - virtual void onVisibilityChange ( bool new_visibility ); - virtual void onUpdateScrollToChild(const LLUICtrl * cntrl); - - void pushVisible(bool visible) { mLastVisible = mVisible; setVisible(visible); } - void popVisible() { setVisible(mLastVisible); } - bool getLastVisible() const { return mLastVisible; } - - U32 getFollows() const { return mReshapeFlags; } - bool followsLeft() const { return mReshapeFlags & FOLLOWS_LEFT; } - bool followsRight() const { return mReshapeFlags & FOLLOWS_RIGHT; } - bool followsTop() const { return mReshapeFlags & FOLLOWS_TOP; } - bool followsBottom() const { return mReshapeFlags & FOLLOWS_BOTTOM; } - bool followsAll() const { return mReshapeFlags & FOLLOWS_ALL; } - - const LLRect& getRect() const { return mRect; } - const LLRect& getBoundingRect() const { return mBoundingRect; } - LLRect getLocalBoundingRect() const; - LLRect calcScreenRect() const; - LLRect calcScreenBoundingRect() const; - LLRect getLocalRect() const; - virtual LLRect getSnapRect() const; - LLRect getLocalSnapRect() const; - - std::string getLayout() { return mLayout; } - - // Override and return required size for this object. 0 for width/height means don't care. - virtual LLRect getRequiredRect(); - LLRect calcBoundingRect(); - void updateBoundingRect(); - - LLView* getRootView(); - LLView* getParent() const { return mParentView; } - LLView* getFirstChild() const { return (mChildList.empty()) ? NULL : *(mChildList.begin()); } - LLView* findPrevSibling(LLView* child); - LLView* findNextSibling(LLView* child); - S32 getChildCount() const { return (S32)mChildList.size(); } - template void sortChildren(_Pr3 _Pred) { mChildList.sort(_Pred); } - bool hasAncestor(const LLView* parentp) const; - bool hasChild(const std::string& childname, bool recurse = false) const; - bool childHasKeyboardFocus( const std::string& childname ) const; - - // these iterators are used for collapsing various tree traversals into for loops - typedef LLTreeDFSIter tree_iterator_t; - tree_iterator_t beginTreeDFS(); - tree_iterator_t endTreeDFS(); - - typedef LLTreeDFSPostIter tree_post_iterator_t; - tree_post_iterator_t beginTreeDFSPost(); - tree_post_iterator_t endTreeDFSPost(); - - typedef LLTreeBFSIter bfs_tree_iterator_t; - bfs_tree_iterator_t beginTreeBFS(); - bfs_tree_iterator_t endTreeBFS(); - - - typedef LLTreeDownIter root_to_view_iterator_t; - root_to_view_iterator_t beginRootToView(); - root_to_view_iterator_t endRootToView(); - - // - // UTILITIES - // - - // Default behavior is to use reshape flags to resize child views - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - virtual void translate( S32 x, S32 y ); - void setOrigin( S32 x, S32 y ) { mRect.translate( x - mRect.mLeft, y - mRect.mBottom ); } - bool translateIntoRect( const LLRect& constraint, S32 min_overlap_pixels = S32_MAX); - bool translateRectIntoRect( const LLRect& rect, const LLRect& constraint, S32 min_overlap_pixels = S32_MAX); - bool translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, S32 min_overlap_pixels = S32_MAX); - void centerWithin(const LLRect& bounds); - - void setShape(const LLRect& new_rect, bool by_user = false); - virtual LLView* findSnapRect(LLRect& new_rect, const LLCoordGL& mouse_dir, LLView::ESnapType snap_type, S32 threshold, S32 padding = 0); - virtual LLView* findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding = 0); - virtual bool canSnapTo(const LLView* other_view); - virtual void setSnappedTo(const LLView* snap_view); - - // inherited from LLFocusableElement - /* virtual */ bool handleKey(KEY key, MASK mask, bool called_from_parent); - /* virtual */ bool handleKeyUp(KEY key, MASK mask, bool called_from_parent); - /* virtual */ bool handleUnicodeChar(llwchar uni_char, 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); - - virtual void draw(); - - void parseFollowsFlags(const LLView::Params& params); - - // Some widgets, like close box buttons, don't need to be saved - bool getFromXUI() const { return mFromXUI; } - void setFromXUI(bool b) { mFromXUI = b; } - - typedef enum e_hit_test_type - { - HIT_TEST_USE_BOUNDING_RECT, - HIT_TEST_IGNORE_BOUNDING_RECT - }EHitTestType; - - bool parentPointInView(S32 x, S32 y, EHitTestType type = HIT_TEST_USE_BOUNDING_RECT) const; - bool pointInView(S32 x, S32 y, EHitTestType type = HIT_TEST_USE_BOUNDING_RECT) const; - bool blockMouseEvent(S32 x, S32 y) const; - - // See LLMouseHandler virtuals for screenPointToLocal and localPointToScreen - bool localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, const LLView* other_view) const; - bool localRectToOtherView( const LLRect& local, LLRect* other, const LLView* other_view ) const; - void screenRectToLocal( const LLRect& screen, LLRect* local ) const; - void localRectToScreen( const LLRect& local, LLRect* screen ) const; - - LLControlVariable *findControl(const std::string& name); - - const child_list_t* getChildList() const { return &mChildList; } - child_list_const_iter_t beginChild() const { return mChildList.begin(); } - child_list_const_iter_t endChild() const { return mChildList.end(); } - - // LLMouseHandler functions - // Default behavior is to pass events to children - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMiddleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ bool handleScrollHWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - - /*virtual*/ const std::string& getName() const; - /*virtual*/ void onMouseCaptureLost(); - /*virtual*/ bool hasMouseCapture(); - /*virtual*/ void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const; - /*virtual*/ void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const; - - virtual LLView* childFromPoint(S32 x, S32 y, bool recur=false); - - // view-specific handlers - virtual void onMouseEnter(S32 x, S32 y, MASK mask); - virtual void onMouseLeave(S32 x, S32 y, MASK mask); - - std::string getPathname() const; - // static method handles NULL pointer too - static std::string getPathname(const LLView*); - - template T* findChild(const std::string& name, bool recurse = true) const - { - LLView* child = findChildView(name, recurse); - T* result = dynamic_cast(child); - return result; - } - - template T* getChild(const std::string& name, bool recurse = true) const; - - template T& getChildRef(const std::string& name, bool recurse = true) const - { - return *getChild(name, recurse); - } - - virtual LLView* getChildView(const std::string& name, bool recurse = true) const; - virtual LLView* findChildView(const std::string& name, bool recurse = true) const; - - template T* getDefaultWidget(const std::string& name) const - { - LLView* widgetp = getDefaultWidgetContainer().findChildView(name); - return dynamic_cast(widgetp); - } - - template T* getParentByType() const - { - LLView* parent = getParent(); - while(parent) - { - if (dynamic_cast(parent)) - { - return static_cast(parent); - } - parent = parent->getParent(); - } - return NULL; - } - - ////////////////////////////////////////////// - // statics - ////////////////////////////////////////////// - //static LLFontGL::HAlign selectFontHAlign(LLXMLNodePtr node); - - // focuses the item in the list after the currently-focused item, wrapping if necessary - static bool focusNext(LLView::child_list_t & result); - // focuses the item in the list before the currently-focused item, wrapping if necessary - static bool focusPrev(LLView::child_list_t & result); - - // returns query for iterating over controls in tab order - static const LLViewQuery & getTabOrderQuery(); - // return query for iterating over focus roots in tab order - static const LLViewQuery & getFocusRootsQuery(); - - static LLWindow* getWindow(void) { return LLUI::getInstance()->mWindow; } - - // Set up params after XML load before calling new(), - // usually to adjust layout. - static void applyXUILayout(Params& p, LLView* parent, LLRect layout_rect = LLRect()); - - // For re-export of floaters and panels, convert the coordinate system - // to be top-left based. - static void setupParamsForExport(Params& p, LLView* parent); - - //virtual bool addChildFromParam(const LLInitParam::BaseBlock& params) { return true; } - virtual bool handleKeyHere(KEY key, MASK mask); - virtual bool handleKeyUpHere(KEY key, MASK mask); - virtual bool handleUnicodeCharHere(llwchar uni_char); - - virtual void handleReshape(const LLRect& rect, bool by_user); - virtual void dirtyRect(); - - //send custom notification to LLView parent - virtual S32 notifyParent(const LLSD& info); - - //send custom notification to all view childrend - // return true if _any_ children return true. otherwise false. - virtual bool notifyChildren(const LLSD& info); - - //send custom notification to current view - virtual S32 notify(const LLSD& info) { return 0;}; - - static const LLViewDrawContext& getDrawContext(); - - // Returns useful information about this ui widget. - LLSD getInfo(void); - -protected: - void drawDebugRect(); - void drawChild(LLView* childp, S32 x_offset = 0, S32 y_offset = 0, bool force_draw = false); - void drawChildren(); - bool visibleAndContains(S32 local_x, S32 local_Y); - bool visibleEnabledAndContains(S32 local_x, S32 local_y); - void logMouseEvent(); - - LLView* childrenHandleKey(KEY key, MASK mask); - LLView* childrenHandleKeyUp(KEY key, MASK mask); - LLView* childrenHandleUnicodeChar(llwchar uni_char); - LLView* childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, - EDragAndDropType type, - void* data, - EAcceptance* accept, - std::string& tooltip_msg); - - LLView* childrenHandleHover(S32 x, S32 y, MASK mask); - LLView* childrenHandleMouseUp(S32 x, S32 y, MASK mask); - LLView* childrenHandleMouseDown(S32 x, S32 y, MASK mask); - LLView* childrenHandleMiddleMouseUp(S32 x, S32 y, MASK mask); - LLView* childrenHandleMiddleMouseDown(S32 x, S32 y, MASK mask); - LLView* childrenHandleDoubleClick(S32 x, S32 y, MASK mask); - LLView* childrenHandleScrollWheel(S32 x, S32 y, S32 clicks); - LLView* childrenHandleScrollHWheel(S32 x, S32 y, S32 clicks); - LLView* childrenHandleRightMouseDown(S32 x, S32 y, MASK mask); - LLView* childrenHandleRightMouseUp(S32 x, S32 y, MASK mask); - LLView* childrenHandleToolTip(S32 x, S32 y, MASK mask); - - ECursorType mHoverCursor; - - virtual void addInfo(LLSD & info); -private: - - template - LLView* childrenHandleMouseEvent(const METHOD& method, S32 x, S32 y, XDATA extra, bool allow_mouse_block = true); - - template - LLView* childrenHandleCharEvent(const std::string& desc, const METHOD& method, - CHARTYPE c, MASK mask); - - // adapter to blur distinction between handleKey() and handleUnicodeChar() - // for childrenHandleCharEvent() - bool handleUnicodeCharWithDummyMask(llwchar uni_char, MASK /* dummy */, bool from_parent) - { - return handleUnicodeChar(uni_char, from_parent); - } - - LLView* mParentView; - child_list_t mChildList; - - // location in pixels, relative to surrounding structure, bottom,left=0,0 - bool mVisible; - LLRect mRect; - LLRect mBoundingRect; - - std::string mLayout; - std::string mName; - - U32 mReshapeFlags; - - child_tab_order_t mTabOrder; - S32 mDefaultTabGroup; - S32 mLastTabGroup; - - bool mEnabled; // Enabled means "accepts input that has an effect on the state of the application." - // A disabled view, for example, may still have a scrollbar that responds to mouse events. - bool mMouseOpaque; // Opaque views handle all mouse events that are over their rect. - LLUIString mToolTipMsg; // isNull() is true if none. - - U8 mSoundFlags; - bool mFromXUI; - - bool mIsFocusRoot; - bool mUseBoundingRect; // hit test against bounding rectangle that includes all child elements - - bool mLastVisible; - - bool mInDraw; - - static LLWindow* sWindow; // All root views must know about their window. - - typedef std::map default_widget_map_t; - // allocate this map no demand, as it is rarely needed - mutable LLView* mDefaultWidgets; - - LLView& getDefaultWidgetContainer() const; - - // This allows special mouse-event targeting logic for testing. - typedef boost::function DrilldownFunc; - static DrilldownFunc sDrilldown; - -public: - // This is the only public accessor to alter sDrilldown. This is not - // an accident. The intended usage pattern is like: - // { - // LLView::TemporaryDrilldownFunc scoped_func(myfunctor); - // // ... test with myfunctor ... - // } // exiting block restores original LLView::sDrilldown - class TemporaryDrilldownFunc: public boost::noncopyable - { - public: - TemporaryDrilldownFunc(const DrilldownFunc& func): - mOldDrilldown(sDrilldown) - { - sDrilldown = func; - } - - ~TemporaryDrilldownFunc() - { - sDrilldown = mOldDrilldown; - } - - private: - DrilldownFunc mOldDrilldown; - }; - - // Depth in view hierarchy during rendering - static S32 sDepth; - - // Draw debug rectangles around widgets to help with alignment and spacing - static bool sDebugRects; - - // Show hexadecimal byte values of unicode symbols in a tooltip - static bool sDebugUnicode; - - // Show camera position and direction in Camera Controls floater - static bool sDebugCamera; - - static bool sIsRectDirty; - static LLRect sDirtyRect; - - // Draw widget names and sizes when drawing debug rectangles, turning this - // off is useful to make the rectangles themselves easier to see. - static bool sDebugRectsShowNames; - - static bool sDebugKeys; - static bool sDebugMouseHandling; - static std::string sMouseHandlerMessage; - static S32 sSelectID; - static std::set sPreviewHighlightedElements; // DEV-16869 - static bool sHighlightingDiffs; // DEV-16869 - static LLView* sPreviewClickedElement; // DEV-16869 - static bool sDrawPreviewHighlights; - static S32 sLastLeftXML; - static S32 sLastBottomXML; - static bool sForceReshape; -}; - -namespace LLInitParam -{ -template<> -struct TypeValues : public LLInitParam::TypeValuesHelper -{ - static void declareValues(); -}; -} - -template T* LLView::getChild(const std::string& name, bool recurse) const -{ - LLView* child = findChildView(name, recurse); - T* result = dynamic_cast(child); - if (!result) - { - // did we find *something* with that name? - if (child) - { - LL_WARNS() << "Found child named \"" << name << "\" but of wrong type " << typeid(*child).name() << ", expecting " << typeid(T*).name() << LL_ENDL; - } - result = getDefaultWidget(name); - if (!result) - { - result = LLUICtrlFactory::getDefaultWidget(name); - if (!result) - { - LL_ERRS() << "Failed to create dummy " << typeid(T).name() << LL_ENDL; - } - - // *NOTE: You cannot call mFoo = getChild("bar") - // in a floater or panel constructor. The widgets will not - // be ready. Instead, put it in postBuild(). - LL_WARNS() << "Making dummy " << typeid(T).name() << " named \"" << name << "\" in " << getName() << LL_ENDL; - - getDefaultWidgetContainer().addChild(result); - } - } - return result; -} - -// Compiler optimization - don't generate these specializations inline, -// require explicit specialization. See llbutton.cpp for an example. -#ifndef LLVIEW_CPP -extern template class LLView* LLView::getChild( - const std::string& name, bool recurse) const; -#endif - -#endif //LL_LLVIEW_H +/** + * @file llview.h + * @brief Container for other views, anything that draws. + * + * $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$ + */ + +#ifndef LL_LLVIEW_H +#define LL_LLVIEW_H + +// A view is an area in a window that can draw. It might represent +// the HUD or a dialog box or a button. It can also contain sub-views +// and child widgets + +#include "stdtypes.h" +#include "llcoord.h" +#include "llfontgl.h" +#include "llhandle.h" +#include "llmortician.h" +#include "llmousehandler.h" +#include "llstring.h" +#include "llrect.h" +#include "llui.h" +#include "lluistring.h" +#include "llviewquery.h" +#include "lluistring.h" +#include "llcursortypes.h" +#include "lluictrlfactory.h" +#include "lltreeiterators.h" +#include "llfocusmgr.h" + +#include +#include +#include + +class LLSD; + +const U32 FOLLOWS_NONE = 0x00; +const U32 FOLLOWS_LEFT = 0x01; +const U32 FOLLOWS_RIGHT = 0x02; +const U32 FOLLOWS_TOP = 0x10; +const U32 FOLLOWS_BOTTOM = 0x20; +const U32 FOLLOWS_ALL = 0x33; + +const bool MOUSE_OPAQUE = true; +const bool NOT_MOUSE_OPAQUE = false; + +const U32 GL_NAME_UI_RESERVED = 2; + + +// maintains render state during traversal of UI tree +class LLViewDrawContext +{ +public: + F32 mAlpha; + + LLViewDrawContext(F32 alpha = 1.f) + : mAlpha(alpha) + { + if (!sDrawContextStack.empty()) + { + LLViewDrawContext* context_top = sDrawContextStack.back(); + // merge with top of stack + mAlpha *= context_top->mAlpha; + } + sDrawContextStack.push_back(this); + } + + ~LLViewDrawContext() + { + sDrawContextStack.pop_back(); + } + + static const LLViewDrawContext& getCurrentContext(); + +private: + static std::vector sDrawContextStack; +}; + +class LLView +: public LLMouseHandler, // handles mouse events + public LLFocusableElement, // handles keyboard events + public LLMortician, // lazy deletion + public LLHandleProvider // passes out weak references to self +{ +public: + + enum EOrientation { HORIZONTAL, VERTICAL, ORIENTATION_COUNT }; + + struct Follows : public LLInitParam::ChoiceBlock + { + Alternative string; + Alternative flags; + + Follows(); + }; + + struct Params : public LLInitParam::Block + { + Mandatory name; + + Optional enabled, + visible, + mouse_opaque, + use_bounding_rect, + from_xui, + focus_root; + + Optional tab_group, + default_tab_group; + Optional tool_tip; + + Optional sound_flags; + Optional follows; + Optional hover_cursor; + + Optional layout; + Optional rect; + + // Historical bottom-left layout used bottom_delta and left_delta + // for relative positioning. New layout "topleft" prefers specifying + // based on top edge. + Optional bottom_delta, // from last bottom to my bottom + top_pad, // from last bottom to my top + top_delta, // from last top to my top + left_pad, // from last right to my left + left_delta; // from last left to my left + + //FIXME: get parent context involved in parsing traversal + Ignored needs_translate, // cue for translation tools + xmlns, // xml namespace + xmlns_xsi, // xml namespace + xsi_schemaLocation, // xml schema + xsi_type; // xml schema type + + Params(); + }; + + // most widgets are valid children of LLView + typedef LLDefaultChildRegistry child_registry_t; + + void initFromParams(const LLView::Params&); + +protected: + LLView(const LLView::Params&); + friend class LLUICtrlFactory; + +private: + // widgets in general are not copyable + LLView(const LLView& other); +public: +//#if LL_DEBUG + static bool sIsDrawing; +//#endif + enum ESoundFlags + { + SILENT = 0, + MOUSE_DOWN = 1, + MOUSE_UP = 2 + }; + + enum ESnapType + { + SNAP_PARENT, + SNAP_SIBLINGS, + SNAP_PARENT_AND_SIBLINGS + }; + + enum ESnapEdge + { + SNAP_LEFT, + SNAP_TOP, + SNAP_RIGHT, + SNAP_BOTTOM + }; + + typedef std::list child_list_t; + typedef child_list_t::iterator child_list_iter_t; + typedef child_list_t::const_iterator child_list_const_iter_t; + typedef child_list_t::reverse_iterator child_list_reverse_iter_t; + typedef child_list_t::const_reverse_iterator child_list_const_reverse_iter_t; + + typedef std::pair tab_order_pair_t; + // this structure primarily sorts by the tab group, secondarily by the insertion ordinal (lastly by the value of the pointer) + typedef std::map child_tab_order_t; + typedef child_tab_order_t::iterator child_tab_order_iter_t; + typedef child_tab_order_t::const_iterator child_tab_order_const_iter_t; + typedef child_tab_order_t::reverse_iterator child_tab_order_reverse_iter_t; + typedef child_tab_order_t::const_reverse_iterator child_tab_order_const_reverse_iter_t; + + virtual ~LLView(); + + // Some UI widgets need to be added as controls. Others need to + // be added as regular view children. isCtrl should return true + // if a widget needs to be added as a ctrl + virtual bool isCtrl() const; + + virtual bool isPanel() const; + + // + // MANIPULATORS + // + void setMouseOpaque( bool b ) { mMouseOpaque = b; } + bool getMouseOpaque() const { return mMouseOpaque; } + void setToolTip( const LLStringExplicit& msg ); + bool setToolTipArg( const LLStringExplicit& key, const LLStringExplicit& text ); + void setToolTipArgs( const LLStringUtil::format_map_t& args ); + + virtual void setRect(const LLRect &rect); + void setFollows(U32 flags) { mReshapeFlags = flags; } + + // deprecated, use setFollows() with FOLLOWS_LEFT | FOLLOWS_TOP, etc. + void setFollowsNone() { mReshapeFlags = FOLLOWS_NONE; } + void setFollowsLeft() { mReshapeFlags |= FOLLOWS_LEFT; } + void setFollowsTop() { mReshapeFlags |= FOLLOWS_TOP; } + void setFollowsRight() { mReshapeFlags |= FOLLOWS_RIGHT; } + void setFollowsBottom() { mReshapeFlags |= FOLLOWS_BOTTOM; } + void setFollowsAll() { mReshapeFlags |= FOLLOWS_ALL; } + + void setSoundFlags(U8 flags) { mSoundFlags = flags; } + void setName(std::string name) { mName = name; } + void setUseBoundingRect( bool use_bounding_rect ); + bool getUseBoundingRect() const; + + ECursorType getHoverCursor() { return mHoverCursor; } + + static F32 getTooltipTimeout(); + virtual const std::string getToolTip() const; + virtual const std::string& getText() const { return LLStringUtil::null; } + virtual const LLFontGL* getFont() const { return nullptr; } + + void sendChildToFront(LLView* child); + void sendChildToBack(LLView* child); + + virtual bool addChild(LLView* view, S32 tab_group = 0); + + // implemented in terms of addChild() + bool addChildInBack(LLView* view, S32 tab_group = 0); + + // remove the specified child from the view, and set it's parent to NULL. + virtual void removeChild(LLView* view); + + virtual bool postBuild() { return true; } + + const child_tab_order_t& getTabOrder() const { return mTabOrder; } + + void setDefaultTabGroup(S32 d) { mDefaultTabGroup = d; } + S32 getDefaultTabGroup() const { return mDefaultTabGroup; } + S32 getLastTabGroup() { return mLastTabGroup; } + + bool isInVisibleChain() const; + bool isInEnabledChain() const; + + void setFocusRoot(bool b) { mIsFocusRoot = b; } + bool isFocusRoot() const { return mIsFocusRoot; } + virtual bool canFocusChildren() const; + + bool focusNextRoot(); + bool focusPrevRoot(); + + // Normally we want the app menus to get priority on accelerated keys + // However, sometimes we want to give specific views a first chance + // iat handling them. (eg. the script editor) + virtual bool hasAccelerators() const { return false; }; + + // delete all children. Override this function if you need to + // perform any extra clean up such as cached pointers to selected + // children, etc. + virtual void deleteAllChildren(); + + void setAllChildrenEnabled(bool b); + + virtual void setVisible(bool visible); + void setVisibleDirect(bool visible) { mVisible = visible; } + const bool& getVisible() const { return mVisible; } + virtual void setEnabled(bool enabled); + bool getEnabled() const { return mEnabled; } + /// 'available' in this context means 'visible and enabled': in other + /// words, can a user actually interact with this? + virtual bool isAvailable() const; + /// The static isAvailable() tests an LLView* that could be NULL. + static bool isAvailable(const LLView* view); + U8 getSoundFlags() const { return mSoundFlags; } + + virtual bool setLabelArg( const std::string& key, const LLStringExplicit& text ); + + virtual void onVisibilityChange ( bool new_visibility ); + virtual void onUpdateScrollToChild(const LLUICtrl * cntrl); + + void pushVisible(bool visible) { mLastVisible = mVisible; setVisible(visible); } + void popVisible() { setVisible(mLastVisible); } + bool getLastVisible() const { return mLastVisible; } + + U32 getFollows() const { return mReshapeFlags; } + bool followsLeft() const { return mReshapeFlags & FOLLOWS_LEFT; } + bool followsRight() const { return mReshapeFlags & FOLLOWS_RIGHT; } + bool followsTop() const { return mReshapeFlags & FOLLOWS_TOP; } + bool followsBottom() const { return mReshapeFlags & FOLLOWS_BOTTOM; } + bool followsAll() const { return mReshapeFlags & FOLLOWS_ALL; } + + const LLRect& getRect() const { return mRect; } + const LLRect& getBoundingRect() const { return mBoundingRect; } + LLRect getLocalBoundingRect() const; + LLRect calcScreenRect() const; + LLRect calcScreenBoundingRect() const; + LLRect getLocalRect() const; + virtual LLRect getSnapRect() const; + LLRect getLocalSnapRect() const; + + std::string getLayout() { return mLayout; } + + // Override and return required size for this object. 0 for width/height means don't care. + virtual LLRect getRequiredRect(); + LLRect calcBoundingRect(); + void updateBoundingRect(); + + LLView* getRootView(); + LLView* getParent() const { return mParentView; } + LLView* getFirstChild() const { return (mChildList.empty()) ? NULL : *(mChildList.begin()); } + LLView* findPrevSibling(LLView* child); + LLView* findNextSibling(LLView* child); + S32 getChildCount() const { return (S32)mChildList.size(); } + template void sortChildren(_Pr3 _Pred) { mChildList.sort(_Pred); } + bool hasAncestor(const LLView* parentp) const; + bool hasChild(const std::string& childname, bool recurse = false) const; + bool childHasKeyboardFocus( const std::string& childname ) const; + + // these iterators are used for collapsing various tree traversals into for loops + typedef LLTreeDFSIter tree_iterator_t; + tree_iterator_t beginTreeDFS(); + tree_iterator_t endTreeDFS(); + + typedef LLTreeDFSPostIter tree_post_iterator_t; + tree_post_iterator_t beginTreeDFSPost(); + tree_post_iterator_t endTreeDFSPost(); + + typedef LLTreeBFSIter bfs_tree_iterator_t; + bfs_tree_iterator_t beginTreeBFS(); + bfs_tree_iterator_t endTreeBFS(); + + + typedef LLTreeDownIter root_to_view_iterator_t; + root_to_view_iterator_t beginRootToView(); + root_to_view_iterator_t endRootToView(); + + // + // UTILITIES + // + + // Default behavior is to use reshape flags to resize child views + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + virtual void translate( S32 x, S32 y ); + void setOrigin( S32 x, S32 y ) { mRect.translate( x - mRect.mLeft, y - mRect.mBottom ); } + bool translateIntoRect( const LLRect& constraint, S32 min_overlap_pixels = S32_MAX); + bool translateRectIntoRect( const LLRect& rect, const LLRect& constraint, S32 min_overlap_pixels = S32_MAX); + bool translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, S32 min_overlap_pixels = S32_MAX); + void centerWithin(const LLRect& bounds); + + void setShape(const LLRect& new_rect, bool by_user = false); + virtual LLView* findSnapRect(LLRect& new_rect, const LLCoordGL& mouse_dir, LLView::ESnapType snap_type, S32 threshold, S32 padding = 0); + virtual LLView* findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding = 0); + virtual bool canSnapTo(const LLView* other_view); + virtual void setSnappedTo(const LLView* snap_view); + + // inherited from LLFocusableElement + /* virtual */ bool handleKey(KEY key, MASK mask, bool called_from_parent); + /* virtual */ bool handleKeyUp(KEY key, MASK mask, bool called_from_parent); + /* virtual */ bool handleUnicodeChar(llwchar uni_char, 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); + + virtual void draw(); + + void parseFollowsFlags(const LLView::Params& params); + + // Some widgets, like close box buttons, don't need to be saved + bool getFromXUI() const { return mFromXUI; } + void setFromXUI(bool b) { mFromXUI = b; } + + typedef enum e_hit_test_type + { + HIT_TEST_USE_BOUNDING_RECT, + HIT_TEST_IGNORE_BOUNDING_RECT + }EHitTestType; + + bool parentPointInView(S32 x, S32 y, EHitTestType type = HIT_TEST_USE_BOUNDING_RECT) const; + bool pointInView(S32 x, S32 y, EHitTestType type = HIT_TEST_USE_BOUNDING_RECT) const; + bool blockMouseEvent(S32 x, S32 y) const; + + // See LLMouseHandler virtuals for screenPointToLocal and localPointToScreen + bool localPointToOtherView( S32 x, S32 y, S32 *other_x, S32 *other_y, const LLView* other_view) const; + bool localRectToOtherView( const LLRect& local, LLRect* other, const LLView* other_view ) const; + void screenRectToLocal( const LLRect& screen, LLRect* local ) const; + void localRectToScreen( const LLRect& local, LLRect* screen ) const; + + LLControlVariable *findControl(const std::string& name); + + const child_list_t* getChildList() const { return &mChildList; } + child_list_const_iter_t beginChild() const { return mChildList.begin(); } + child_list_const_iter_t endChild() const { return mChildList.end(); } + + // LLMouseHandler functions + // Default behavior is to pass events to children + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMiddleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ bool handleScrollHWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + + /*virtual*/ const std::string& getName() const; + /*virtual*/ void onMouseCaptureLost(); + /*virtual*/ bool hasMouseCapture(); + /*virtual*/ void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const; + /*virtual*/ void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const; + + virtual LLView* childFromPoint(S32 x, S32 y, bool recur=false); + + // view-specific handlers + virtual void onMouseEnter(S32 x, S32 y, MASK mask); + virtual void onMouseLeave(S32 x, S32 y, MASK mask); + + std::string getPathname() const; + // static method handles NULL pointer too + static std::string getPathname(const LLView*); + + template T* findChild(const std::string& name, bool recurse = true) const + { + LLView* child = findChildView(name, recurse); + T* result = dynamic_cast(child); + return result; + } + + template T* getChild(const std::string& name, bool recurse = true) const; + + template T& getChildRef(const std::string& name, bool recurse = true) const + { + return *getChild(name, recurse); + } + + virtual LLView* getChildView(const std::string& name, bool recurse = true) const; + virtual LLView* findChildView(const std::string& name, bool recurse = true) const; + + template T* getDefaultWidget(const std::string& name) const + { + LLView* widgetp = getDefaultWidgetContainer().findChildView(name); + return dynamic_cast(widgetp); + } + + template T* getParentByType() const + { + LLView* parent = getParent(); + while(parent) + { + if (dynamic_cast(parent)) + { + return static_cast(parent); + } + parent = parent->getParent(); + } + return NULL; + } + + ////////////////////////////////////////////// + // statics + ////////////////////////////////////////////// + //static LLFontGL::HAlign selectFontHAlign(LLXMLNodePtr node); + + // focuses the item in the list after the currently-focused item, wrapping if necessary + static bool focusNext(LLView::child_list_t & result); + // focuses the item in the list before the currently-focused item, wrapping if necessary + static bool focusPrev(LLView::child_list_t & result); + + // returns query for iterating over controls in tab order + static const LLViewQuery & getTabOrderQuery(); + // return query for iterating over focus roots in tab order + static const LLViewQuery & getFocusRootsQuery(); + + static LLWindow* getWindow(void) { return LLUI::getInstance()->mWindow; } + + // Set up params after XML load before calling new(), + // usually to adjust layout. + static void applyXUILayout(Params& p, LLView* parent, LLRect layout_rect = LLRect()); + + // For re-export of floaters and panels, convert the coordinate system + // to be top-left based. + static void setupParamsForExport(Params& p, LLView* parent); + + //virtual bool addChildFromParam(const LLInitParam::BaseBlock& params) { return true; } + virtual bool handleKeyHere(KEY key, MASK mask); + virtual bool handleKeyUpHere(KEY key, MASK mask); + virtual bool handleUnicodeCharHere(llwchar uni_char); + + virtual void handleReshape(const LLRect& rect, bool by_user); + virtual void dirtyRect(); + + //send custom notification to LLView parent + virtual S32 notifyParent(const LLSD& info); + + //send custom notification to all view childrend + // return true if _any_ children return true. otherwise false. + virtual bool notifyChildren(const LLSD& info); + + //send custom notification to current view + virtual S32 notify(const LLSD& info) { return 0;}; + + static const LLViewDrawContext& getDrawContext(); + + // Returns useful information about this ui widget. + LLSD getInfo(void); + +protected: + void drawDebugRect(); + void drawChild(LLView* childp, S32 x_offset = 0, S32 y_offset = 0, bool force_draw = false); + void drawChildren(); + bool visibleAndContains(S32 local_x, S32 local_Y); + bool visibleEnabledAndContains(S32 local_x, S32 local_y); + void logMouseEvent(); + + LLView* childrenHandleKey(KEY key, MASK mask); + LLView* childrenHandleKeyUp(KEY key, MASK mask); + LLView* childrenHandleUnicodeChar(llwchar uni_char); + LLView* childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, + EDragAndDropType type, + void* data, + EAcceptance* accept, + std::string& tooltip_msg); + + LLView* childrenHandleHover(S32 x, S32 y, MASK mask); + LLView* childrenHandleMouseUp(S32 x, S32 y, MASK mask); + LLView* childrenHandleMouseDown(S32 x, S32 y, MASK mask); + LLView* childrenHandleMiddleMouseUp(S32 x, S32 y, MASK mask); + LLView* childrenHandleMiddleMouseDown(S32 x, S32 y, MASK mask); + LLView* childrenHandleDoubleClick(S32 x, S32 y, MASK mask); + LLView* childrenHandleScrollWheel(S32 x, S32 y, S32 clicks); + LLView* childrenHandleScrollHWheel(S32 x, S32 y, S32 clicks); + LLView* childrenHandleRightMouseDown(S32 x, S32 y, MASK mask); + LLView* childrenHandleRightMouseUp(S32 x, S32 y, MASK mask); + LLView* childrenHandleToolTip(S32 x, S32 y, MASK mask); + + ECursorType mHoverCursor; + + virtual void addInfo(LLSD & info); +private: + + template + LLView* childrenHandleMouseEvent(const METHOD& method, S32 x, S32 y, XDATA extra, bool allow_mouse_block = true); + + template + LLView* childrenHandleCharEvent(const std::string& desc, const METHOD& method, + CHARTYPE c, MASK mask); + + // adapter to blur distinction between handleKey() and handleUnicodeChar() + // for childrenHandleCharEvent() + bool handleUnicodeCharWithDummyMask(llwchar uni_char, MASK /* dummy */, bool from_parent) + { + return handleUnicodeChar(uni_char, from_parent); + } + + LLView* mParentView; + child_list_t mChildList; + + // location in pixels, relative to surrounding structure, bottom,left=0,0 + bool mVisible; + LLRect mRect; + LLRect mBoundingRect; + + std::string mLayout; + std::string mName; + + U32 mReshapeFlags; + + child_tab_order_t mTabOrder; + S32 mDefaultTabGroup; + S32 mLastTabGroup; + + bool mEnabled; // Enabled means "accepts input that has an effect on the state of the application." + // A disabled view, for example, may still have a scrollbar that responds to mouse events. + bool mMouseOpaque; // Opaque views handle all mouse events that are over their rect. + LLUIString mToolTipMsg; // isNull() is true if none. + + U8 mSoundFlags; + bool mFromXUI; + + bool mIsFocusRoot; + bool mUseBoundingRect; // hit test against bounding rectangle that includes all child elements + + bool mLastVisible; + + bool mInDraw; + + static LLWindow* sWindow; // All root views must know about their window. + + typedef std::map default_widget_map_t; + // allocate this map no demand, as it is rarely needed + mutable LLView* mDefaultWidgets; + + LLView& getDefaultWidgetContainer() const; + + // This allows special mouse-event targeting logic for testing. + typedef boost::function DrilldownFunc; + static DrilldownFunc sDrilldown; + +public: + // This is the only public accessor to alter sDrilldown. This is not + // an accident. The intended usage pattern is like: + // { + // LLView::TemporaryDrilldownFunc scoped_func(myfunctor); + // // ... test with myfunctor ... + // } // exiting block restores original LLView::sDrilldown + class TemporaryDrilldownFunc: public boost::noncopyable + { + public: + TemporaryDrilldownFunc(const DrilldownFunc& func): + mOldDrilldown(sDrilldown) + { + sDrilldown = func; + } + + ~TemporaryDrilldownFunc() + { + sDrilldown = mOldDrilldown; + } + + private: + DrilldownFunc mOldDrilldown; + }; + + // Depth in view hierarchy during rendering + static S32 sDepth; + + // Draw debug rectangles around widgets to help with alignment and spacing + static bool sDebugRects; + + // Show hexadecimal byte values of unicode symbols in a tooltip + static bool sDebugUnicode; + + // Show camera position and direction in Camera Controls floater + static bool sDebugCamera; + + static bool sIsRectDirty; + static LLRect sDirtyRect; + + // Draw widget names and sizes when drawing debug rectangles, turning this + // off is useful to make the rectangles themselves easier to see. + static bool sDebugRectsShowNames; + + static bool sDebugKeys; + static bool sDebugMouseHandling; + static std::string sMouseHandlerMessage; + static S32 sSelectID; + static std::set sPreviewHighlightedElements; // DEV-16869 + static bool sHighlightingDiffs; // DEV-16869 + static LLView* sPreviewClickedElement; // DEV-16869 + static bool sDrawPreviewHighlights; + static S32 sLastLeftXML; + static S32 sLastBottomXML; + static bool sForceReshape; +}; + +namespace LLInitParam +{ +template<> +struct TypeValues : public LLInitParam::TypeValuesHelper +{ + static void declareValues(); +}; +} + +template T* LLView::getChild(const std::string& name, bool recurse) const +{ + LLView* child = findChildView(name, recurse); + T* result = dynamic_cast(child); + if (!result) + { + // did we find *something* with that name? + if (child) + { + LL_WARNS() << "Found child named \"" << name << "\" but of wrong type " << typeid(*child).name() << ", expecting " << typeid(T*).name() << LL_ENDL; + } + result = getDefaultWidget(name); + if (!result) + { + result = LLUICtrlFactory::getDefaultWidget(name); + if (!result) + { + LL_ERRS() << "Failed to create dummy " << typeid(T).name() << LL_ENDL; + } + + // *NOTE: You cannot call mFoo = getChild("bar") + // in a floater or panel constructor. The widgets will not + // be ready. Instead, put it in postBuild(). + LL_WARNS() << "Making dummy " << typeid(T).name() << " named \"" << name << "\" in " << getName() << LL_ENDL; + + getDefaultWidgetContainer().addChild(result); + } + } + return result; +} + +// Compiler optimization - don't generate these specializations inline, +// require explicit specialization. See llbutton.cpp for an example. +#ifndef LLVIEW_CPP +extern template class LLView* LLView::getChild( + const std::string& name, bool recurse) const; +#endif + +#endif //LL_LLVIEW_H diff --git a/indra/llui/llviewborder.cpp b/indra/llui/llviewborder.cpp index 241192e21c..c1777c972c 100644 --- a/indra/llui/llviewborder.cpp +++ b/indra/llui/llviewborder.cpp @@ -1,270 +1,270 @@ -/** - * @file llviewborder.cpp - * - * $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 "llviewborder.h" -#include "llrender.h" -#include "llfocusmgr.h" -#include "lluictrlfactory.h" -#include "lluiimage.h" - -static LLDefaultChildRegistry::Register r("view_border"); - -void LLViewBorder::BevelValues::declareValues() -{ - declare("in", LLViewBorder::BEVEL_IN); - declare("out", LLViewBorder::BEVEL_OUT); - declare("bright", LLViewBorder::BEVEL_BRIGHT); - declare("none", LLViewBorder::BEVEL_NONE); -} - -void LLViewBorder::StyleValues::declareValues() -{ - declare("line", LLViewBorder::STYLE_LINE); - declare("texture", LLViewBorder::STYLE_TEXTURE); -} - -LLViewBorder::Params::Params() -: bevel_style("bevel_style", BEVEL_OUT), - render_style("border_style", STYLE_LINE), - border_thickness("border_thickness"), - highlight_light_color("highlight_light_color"), - highlight_dark_color("highlight_dark_color"), - shadow_light_color("shadow_light_color"), - shadow_dark_color("shadow_dark_color") -{ - addSynonym(border_thickness, "thickness"); - addSynonym(render_style, "style"); -} - - -LLViewBorder::LLViewBorder(const LLViewBorder::Params& p) -: LLView(p), - mTexture( NULL ), - mHasKeyboardFocus( false ), - mBorderWidth(p.border_thickness), - mHighlightLight(p.highlight_light_color()), - mHighlightDark(p.highlight_dark_color()), - mShadowLight(p.shadow_light_color()), - mShadowDark(p.shadow_dark_color()), - mBevel(p.bevel_style), - mStyle(p.render_style) -{} - -void LLViewBorder::setColors( const LLColor4& shadow_dark, const LLColor4& highlight_light ) -{ - mShadowDark = shadow_dark; - mHighlightLight = highlight_light; -} - -void LLViewBorder::setColorsExtended( const LLColor4& shadow_light, const LLColor4& shadow_dark, - const LLColor4& highlight_light, const LLColor4& highlight_dark ) -{ - mShadowDark = shadow_dark; - mShadowLight = shadow_light; - mHighlightLight = highlight_light; - mHighlightDark = highlight_dark; -} - -void LLViewBorder::setTexture( const LLUUID &image_id ) -{ - mTexture = LLUI::getUIImageByID(image_id); -} - - -void LLViewBorder::draw() -{ - if( STYLE_LINE == mStyle ) - { - if( 0 == mBorderWidth ) - { - // no visible border - } - else - if( 1 == mBorderWidth ) - { - drawOnePixelLines(); - } - else - if( 2 == mBorderWidth ) - { - drawTwoPixelLines(); - } - else - { - llassert( false ); // not implemented - } - } - - LLView::draw(); -} - -void LLViewBorder::drawOnePixelLines() -{ - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - LLColor4 top_color = mHighlightLight.get(); - LLColor4 bottom_color = mHighlightLight.get(); - switch( mBevel ) - { - case BEVEL_OUT: - top_color = mHighlightLight.get(); - bottom_color = mShadowDark.get(); - break; - case BEVEL_IN: - top_color = mShadowDark.get(); - bottom_color = mHighlightLight.get(); - break; - case BEVEL_NONE: - // use defaults - break; - default: - llassert(0); - } - - if( mHasKeyboardFocus ) - { - top_color = gFocusMgr.getFocusColor(); - bottom_color = top_color; - - LLUI::setLineWidth(lerp(1.f, 3.f, gFocusMgr.getFocusFlashAmt())); - } - - S32 left = 0; - S32 top = getRect().getHeight(); - S32 right = getRect().getWidth(); - S32 bottom = 0; - - gGL.color4fv( top_color.mV ); - gl_line_2d(left, bottom, left, top); - gl_line_2d(left, top, right, top); - - gGL.color4fv( bottom_color.mV ); - gl_line_2d(right, top, right, bottom); - gl_line_2d(left, bottom, right, bottom); - - LLUI::setLineWidth(1.f); -} - -void LLViewBorder::drawTwoPixelLines() -{ - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - LLColor4 focus_color = gFocusMgr.getFocusColor(); - - LLColor4 top_in_color; - LLColor4 top_out_color; - LLColor4 bottom_in_color; - LLColor4 bottom_out_color; - - switch( mBevel ) - { - case BEVEL_OUT: - top_in_color = mHighlightLight.get(); - top_out_color = mHighlightDark.get(); - bottom_in_color = mShadowLight.get(); - bottom_out_color = mShadowDark.get(); - break; - case BEVEL_IN: - top_in_color = mShadowDark.get(); - top_out_color = mShadowLight.get(); - bottom_in_color = mHighlightDark.get(); - bottom_out_color = mHighlightLight.get(); - break; - case BEVEL_BRIGHT: - top_in_color = mHighlightLight.get(); - top_out_color = mHighlightLight.get(); - bottom_in_color = mHighlightLight.get(); - bottom_out_color = mHighlightLight.get(); - break; - case BEVEL_NONE: - top_in_color = mShadowDark.get(); - top_out_color = mShadowDark.get(); - bottom_in_color = mShadowDark.get(); - bottom_out_color = mShadowDark.get(); - // use defaults - break; - default: - llassert(0); - } - - if( mHasKeyboardFocus ) - { - top_out_color = focus_color; - bottom_out_color = focus_color; - } - - S32 left = 0; - S32 top = getRect().getHeight(); - S32 right = getRect().getWidth(); - S32 bottom = 0; - - // draw borders - gGL.color3fv( top_out_color.mV ); - gl_line_2d(left, bottom, left, top-1); - gl_line_2d(left, top-1, right, top-1); - - gGL.color3fv( top_in_color.mV ); - gl_line_2d(left+1, bottom+1, left+1, top-2); - gl_line_2d(left+1, top-2, right-1, top-2); - - gGL.color3fv( bottom_out_color.mV ); - gl_line_2d(right-1, top-1, right-1, bottom); - gl_line_2d(left, bottom, right, bottom); - - gGL.color3fv( bottom_in_color.mV ); - gl_line_2d(right-2, top-2, right-2, bottom+1); - gl_line_2d(left+1, bottom+1, right-1, bottom+1); -} - -bool LLViewBorder::getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel& bevel_style) -{ - if (node->hasAttribute("bevel_style")) - { - std::string bevel_string; - node->getAttributeString("bevel_style", bevel_string); - LLStringUtil::toLower(bevel_string); - - if (bevel_string == "none") - { - bevel_style = LLViewBorder::BEVEL_NONE; - } - else if (bevel_string == "in") - { - bevel_style = LLViewBorder::BEVEL_IN; - } - else if (bevel_string == "out") - { - bevel_style = LLViewBorder::BEVEL_OUT; - } - else if (bevel_string == "bright") - { - bevel_style = LLViewBorder::BEVEL_BRIGHT; - } - return true; - } - return false; -} - +/** + * @file llviewborder.cpp + * + * $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 "llviewborder.h" +#include "llrender.h" +#include "llfocusmgr.h" +#include "lluictrlfactory.h" +#include "lluiimage.h" + +static LLDefaultChildRegistry::Register r("view_border"); + +void LLViewBorder::BevelValues::declareValues() +{ + declare("in", LLViewBorder::BEVEL_IN); + declare("out", LLViewBorder::BEVEL_OUT); + declare("bright", LLViewBorder::BEVEL_BRIGHT); + declare("none", LLViewBorder::BEVEL_NONE); +} + +void LLViewBorder::StyleValues::declareValues() +{ + declare("line", LLViewBorder::STYLE_LINE); + declare("texture", LLViewBorder::STYLE_TEXTURE); +} + +LLViewBorder::Params::Params() +: bevel_style("bevel_style", BEVEL_OUT), + render_style("border_style", STYLE_LINE), + border_thickness("border_thickness"), + highlight_light_color("highlight_light_color"), + highlight_dark_color("highlight_dark_color"), + shadow_light_color("shadow_light_color"), + shadow_dark_color("shadow_dark_color") +{ + addSynonym(border_thickness, "thickness"); + addSynonym(render_style, "style"); +} + + +LLViewBorder::LLViewBorder(const LLViewBorder::Params& p) +: LLView(p), + mTexture( NULL ), + mHasKeyboardFocus( false ), + mBorderWidth(p.border_thickness), + mHighlightLight(p.highlight_light_color()), + mHighlightDark(p.highlight_dark_color()), + mShadowLight(p.shadow_light_color()), + mShadowDark(p.shadow_dark_color()), + mBevel(p.bevel_style), + mStyle(p.render_style) +{} + +void LLViewBorder::setColors( const LLColor4& shadow_dark, const LLColor4& highlight_light ) +{ + mShadowDark = shadow_dark; + mHighlightLight = highlight_light; +} + +void LLViewBorder::setColorsExtended( const LLColor4& shadow_light, const LLColor4& shadow_dark, + const LLColor4& highlight_light, const LLColor4& highlight_dark ) +{ + mShadowDark = shadow_dark; + mShadowLight = shadow_light; + mHighlightLight = highlight_light; + mHighlightDark = highlight_dark; +} + +void LLViewBorder::setTexture( const LLUUID &image_id ) +{ + mTexture = LLUI::getUIImageByID(image_id); +} + + +void LLViewBorder::draw() +{ + if( STYLE_LINE == mStyle ) + { + if( 0 == mBorderWidth ) + { + // no visible border + } + else + if( 1 == mBorderWidth ) + { + drawOnePixelLines(); + } + else + if( 2 == mBorderWidth ) + { + drawTwoPixelLines(); + } + else + { + llassert( false ); // not implemented + } + } + + LLView::draw(); +} + +void LLViewBorder::drawOnePixelLines() +{ + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLColor4 top_color = mHighlightLight.get(); + LLColor4 bottom_color = mHighlightLight.get(); + switch( mBevel ) + { + case BEVEL_OUT: + top_color = mHighlightLight.get(); + bottom_color = mShadowDark.get(); + break; + case BEVEL_IN: + top_color = mShadowDark.get(); + bottom_color = mHighlightLight.get(); + break; + case BEVEL_NONE: + // use defaults + break; + default: + llassert(0); + } + + if( mHasKeyboardFocus ) + { + top_color = gFocusMgr.getFocusColor(); + bottom_color = top_color; + + LLUI::setLineWidth(lerp(1.f, 3.f, gFocusMgr.getFocusFlashAmt())); + } + + S32 left = 0; + S32 top = getRect().getHeight(); + S32 right = getRect().getWidth(); + S32 bottom = 0; + + gGL.color4fv( top_color.mV ); + gl_line_2d(left, bottom, left, top); + gl_line_2d(left, top, right, top); + + gGL.color4fv( bottom_color.mV ); + gl_line_2d(right, top, right, bottom); + gl_line_2d(left, bottom, right, bottom); + + LLUI::setLineWidth(1.f); +} + +void LLViewBorder::drawTwoPixelLines() +{ + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLColor4 focus_color = gFocusMgr.getFocusColor(); + + LLColor4 top_in_color; + LLColor4 top_out_color; + LLColor4 bottom_in_color; + LLColor4 bottom_out_color; + + switch( mBevel ) + { + case BEVEL_OUT: + top_in_color = mHighlightLight.get(); + top_out_color = mHighlightDark.get(); + bottom_in_color = mShadowLight.get(); + bottom_out_color = mShadowDark.get(); + break; + case BEVEL_IN: + top_in_color = mShadowDark.get(); + top_out_color = mShadowLight.get(); + bottom_in_color = mHighlightDark.get(); + bottom_out_color = mHighlightLight.get(); + break; + case BEVEL_BRIGHT: + top_in_color = mHighlightLight.get(); + top_out_color = mHighlightLight.get(); + bottom_in_color = mHighlightLight.get(); + bottom_out_color = mHighlightLight.get(); + break; + case BEVEL_NONE: + top_in_color = mShadowDark.get(); + top_out_color = mShadowDark.get(); + bottom_in_color = mShadowDark.get(); + bottom_out_color = mShadowDark.get(); + // use defaults + break; + default: + llassert(0); + } + + if( mHasKeyboardFocus ) + { + top_out_color = focus_color; + bottom_out_color = focus_color; + } + + S32 left = 0; + S32 top = getRect().getHeight(); + S32 right = getRect().getWidth(); + S32 bottom = 0; + + // draw borders + gGL.color3fv( top_out_color.mV ); + gl_line_2d(left, bottom, left, top-1); + gl_line_2d(left, top-1, right, top-1); + + gGL.color3fv( top_in_color.mV ); + gl_line_2d(left+1, bottom+1, left+1, top-2); + gl_line_2d(left+1, top-2, right-1, top-2); + + gGL.color3fv( bottom_out_color.mV ); + gl_line_2d(right-1, top-1, right-1, bottom); + gl_line_2d(left, bottom, right, bottom); + + gGL.color3fv( bottom_in_color.mV ); + gl_line_2d(right-2, top-2, right-2, bottom+1); + gl_line_2d(left+1, bottom+1, right-1, bottom+1); +} + +bool LLViewBorder::getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel& bevel_style) +{ + if (node->hasAttribute("bevel_style")) + { + std::string bevel_string; + node->getAttributeString("bevel_style", bevel_string); + LLStringUtil::toLower(bevel_string); + + if (bevel_string == "none") + { + bevel_style = LLViewBorder::BEVEL_NONE; + } + else if (bevel_string == "in") + { + bevel_style = LLViewBorder::BEVEL_IN; + } + else if (bevel_string == "out") + { + bevel_style = LLViewBorder::BEVEL_OUT; + } + else if (bevel_string == "bright") + { + bevel_style = LLViewBorder::BEVEL_BRIGHT; + } + return true; + } + return false; +} + diff --git a/indra/llui/llviewborder.h b/indra/llui/llviewborder.h index 6a2bcb4b20..be499bb534 100644 --- a/indra/llui/llviewborder.h +++ b/indra/llui/llviewborder.h @@ -1,110 +1,110 @@ -/** - * @file llviewborder.h - * @brief A customizable decorative border. Does not interact with mouse events. - * - * $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$ - */ - -#ifndef LL_LLVIEWBORDER_H -#define LL_LLVIEWBORDER_H - -#include "llview.h" - -class LLViewBorder : public LLView -{ -public: - typedef enum e_bevel { BEVEL_IN, BEVEL_OUT, BEVEL_BRIGHT, BEVEL_NONE } EBevel ; - typedef enum e_style { STYLE_LINE, STYLE_TEXTURE } EStyle; - - struct BevelValues - : public LLInitParam::TypeValuesHelper - { - static void declareValues(); - }; - - struct StyleValues - : public LLInitParam::TypeValuesHelper - { - static void declareValues(); - }; - - struct Params : public LLInitParam::Block - { - Optional bevel_style; - Optional render_style; - Optional border_thickness; - - Optional highlight_light_color, - highlight_dark_color, - shadow_light_color, - shadow_dark_color; - - Params(); - }; -protected: - LLViewBorder(const Params&); - friend class LLUICtrlFactory; -public: - virtual void setValue(const LLSD& val) { setRect(LLRect(val)); } - - virtual bool isCtrl() const { return false; } - - // llview functionality - virtual void draw(); - - static bool getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel& bevel_style); - - void setBorderWidth(S32 width) { mBorderWidth = width; } - S32 getBorderWidth() const { return mBorderWidth; } - void setBevel(EBevel bevel) { mBevel = bevel; } - EBevel getBevel() const { return mBevel; } - void setColors( const LLColor4& shadow_dark, const LLColor4& highlight_light ); - void setColorsExtended( const LLColor4& shadow_light, const LLColor4& shadow_dark, - const LLColor4& highlight_light, const LLColor4& highlight_dark ); - void setTexture( const class LLUUID &image_id ); - - LLColor4 getHighlightLight() {return mHighlightLight.get();} - LLColor4 getShadowDark() {return mHighlightDark.get();} - - EStyle getStyle() const { return mStyle; } - - void setKeyboardFocusHighlight( bool b ) { mHasKeyboardFocus = b; } - -private: - void drawOnePixelLines(); - void drawTwoPixelLines(); - void drawTextures(); - - EBevel mBevel; - EStyle mStyle; - LLUIColor mHighlightLight; - LLUIColor mHighlightDark; - LLUIColor mShadowLight; - LLUIColor mShadowDark; - LLUIColor mBackgroundColor; - S32 mBorderWidth; - LLPointer mTexture; - bool mHasKeyboardFocus; -}; - -#endif // LL_LLVIEWBORDER_H - +/** + * @file llviewborder.h + * @brief A customizable decorative border. Does not interact with mouse events. + * + * $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$ + */ + +#ifndef LL_LLVIEWBORDER_H +#define LL_LLVIEWBORDER_H + +#include "llview.h" + +class LLViewBorder : public LLView +{ +public: + typedef enum e_bevel { BEVEL_IN, BEVEL_OUT, BEVEL_BRIGHT, BEVEL_NONE } EBevel ; + typedef enum e_style { STYLE_LINE, STYLE_TEXTURE } EStyle; + + struct BevelValues + : public LLInitParam::TypeValuesHelper + { + static void declareValues(); + }; + + struct StyleValues + : public LLInitParam::TypeValuesHelper + { + static void declareValues(); + }; + + struct Params : public LLInitParam::Block + { + Optional bevel_style; + Optional render_style; + Optional border_thickness; + + Optional highlight_light_color, + highlight_dark_color, + shadow_light_color, + shadow_dark_color; + + Params(); + }; +protected: + LLViewBorder(const Params&); + friend class LLUICtrlFactory; +public: + virtual void setValue(const LLSD& val) { setRect(LLRect(val)); } + + virtual bool isCtrl() const { return false; } + + // llview functionality + virtual void draw(); + + static bool getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel& bevel_style); + + void setBorderWidth(S32 width) { mBorderWidth = width; } + S32 getBorderWidth() const { return mBorderWidth; } + void setBevel(EBevel bevel) { mBevel = bevel; } + EBevel getBevel() const { return mBevel; } + void setColors( const LLColor4& shadow_dark, const LLColor4& highlight_light ); + void setColorsExtended( const LLColor4& shadow_light, const LLColor4& shadow_dark, + const LLColor4& highlight_light, const LLColor4& highlight_dark ); + void setTexture( const class LLUUID &image_id ); + + LLColor4 getHighlightLight() {return mHighlightLight.get();} + LLColor4 getShadowDark() {return mHighlightDark.get();} + + EStyle getStyle() const { return mStyle; } + + void setKeyboardFocusHighlight( bool b ) { mHasKeyboardFocus = b; } + +private: + void drawOnePixelLines(); + void drawTwoPixelLines(); + void drawTextures(); + + EBevel mBevel; + EStyle mStyle; + LLUIColor mHighlightLight; + LLUIColor mHighlightDark; + LLUIColor mShadowLight; + LLUIColor mShadowDark; + LLUIColor mBackgroundColor; + S32 mBorderWidth; + LLPointer mTexture; + bool mHasKeyboardFocus; +}; + +#endif // LL_LLVIEWBORDER_H + diff --git a/indra/llui/llviewereventrecorder.cpp b/indra/llui/llviewereventrecorder.cpp index 177cfb98ce..1bf3e3c43b 100644 --- a/indra/llui/llviewereventrecorder.cpp +++ b/indra/llui/llviewereventrecorder.cpp @@ -1,296 +1,296 @@ -/** - * @file llviewereventrecorder.cpp - * @brief Viewer event recording and playback support for mouse and keyboard events - * - * $LicenseInfo:firstyear=2013&license=viewerlgpl$ - * - * Copyright (c) 2013, 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 "llviewereventrecorder.h" -#include "llui.h" -#include "llleap.h" - -LLViewerEventRecorder::LLViewerEventRecorder() { - - clear(UNDEFINED); - logEvents = false; - // Remove any previous event log file - std::string old_log_ui_events_to_llsd_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife_Events_log.old"); - LLFile::remove(old_log_ui_events_to_llsd_file, ENOENT); - - - mLogFilename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife_Events_log.llsd"); - LLFile::rename(mLogFilename, old_log_ui_events_to_llsd_file, ENOENT); - -} - - -bool LLViewerEventRecorder::displayViewerEventRecorderMenuItems() { - return LLUI::getInstance()->mSettingGroups["config"]->getBOOL("ShowEventRecorderMenuItems"); -} - - -void LLViewerEventRecorder::setEventLoggingOn() { - if (! mLog.is_open()) { - mLog.open(mLogFilename.c_str(), std::ios_base::out); - } - logEvents=true; - LL_DEBUGS() << "LLViewerEventRecorder::setEventLoggingOn event logging turned on" << LL_ENDL; -} - -void LLViewerEventRecorder::setEventLoggingOff() { - logEvents=false; - mLog.flush(); - mLog.close(); - LL_DEBUGS() << "LLViewerEventRecorder::setEventLoggingOff event logging turned off" << LL_ENDL; -} - - - LLViewerEventRecorder::~LLViewerEventRecorder() { - if (mLog.is_open()) { - mLog.close(); - } -} - -void LLViewerEventRecorder::clear_xui() { - xui.clear(); -} - -void LLViewerEventRecorder::clear(S32 r) { - - xui.clear(); - - local_x=r; - local_y=r; - - global_x=r; - global_y=r; - - -} - -void LLViewerEventRecorder::setMouseLocalCoords(S32 x, S32 y) { - local_x=x; - local_y=y; -} - -void LLViewerEventRecorder::setMouseGlobalCoords(S32 x, S32 y) { - global_x=x; - global_y=y; -} - -void LLViewerEventRecorder::updateMouseEventInfo(S32 local_x, S32 local_y, S32 global_x, S32 global_y, std::string mName) { - - LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), xui); - if (! target_view) { - LL_DEBUGS() << "LLViewerEventRecorder::updateMouseEventInfo - xui path on file at moment is NOT valid - so DO NOT record these local coords" << LL_ENDL; - return; - } - LL_DEBUGS() << "LLViewerEventRecorder::updateMouseEventInfo b4 updatemouseeventinfo - local_x|global x "<< this->local_x << " " << this->global_x << "local/global y " << this->local_y << " " << this->global_y << " mname: " << mName << " xui: " << xui << LL_ENDL; - - - if (this->local_x < 1 && this->local_y<1 && local_x && local_y) { - this->local_x=local_x; - this->local_y=local_y; - } - this->global_x=global_x; - this->global_y=global_y; - - // ONLY record deepest xui path for hierarchy searches - or first/only xui for floaters/panels reached via mouse captor - and llmousehandler - if (mName!="" && mName!="/" && xui=="") { - // xui=std::string("/")+mName+xui; - //xui=mName+xui; - xui = mName; // TODO review confirm we never call with partial path - also cAN REMOVE CHECK FOR "" - ON OTHER HAND IT'S PRETTY HARMLESS - } - - LL_DEBUGS() << "LLViewerEventRecorder::updateMouseEventInfo after updatemouseeventinfo - local_x|global x "<< this->local_x << " " << this->global_x << "local/global y " << this->local_y << " " << this->global_y << " mname: " << mName << " xui: " << xui << LL_ENDL; -} - -void LLViewerEventRecorder::logVisibilityChange(std::string xui, std::string name, bool visibility, std::string event_subtype) { - - LLSD event=LLSD::emptyMap(); - - event.insert("event",LLSD(std::string("visibility"))); - - if (visibility) { - event.insert("visibility",LLSD(true)); - } else { - event.insert("visibility",LLSD(false)); - } - - if (event_subtype!="") { - event.insert("event_subtype", LLSD(event_subtype)); - } - - if(name!="") { - event.insert("name",LLSD(name)); - } - - if (xui!="") { - event.insert("path",LLSD(xui)); - } - - event.insert("timestamp",LLSD(LLDate::now().asString())); - recordEvent(event); -} - - -std::string LLViewerEventRecorder::get_xui() { - return xui; -} -void LLViewerEventRecorder::update_xui(std::string xui) { - if (xui!="" && this->xui=="" ) { - LL_DEBUGS() << "LLViewerEventRecorder::update_xui to " << xui << LL_ENDL; - this->xui=xui; - } else { - LL_DEBUGS() << "LLViewerEventRecorder::update_xui called with empty string" << LL_ENDL; - } -} - -void LLViewerEventRecorder::logKeyEvent(KEY key, MASK mask) { - - // NOTE: Event recording only logs keydown events - the viewer itself hides keyup events at a fairly low level in the code and does not appear to care about them anywhere - - LLSD event = LLSD::emptyMap(); - - event.insert("event",LLSD("type")); - - // keysym ...or - // keycode...or - // char - event.insert("keysym",LLSD(LLKeyboard::stringFromKey(key))); - - // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps - // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might - // break the test script and it would be useful to have more context to make these sorts of edits safer - - // TODO replace this with a call which extracts to an array of names of masks (just like vita expects during playback) - // This is looking more and more like an object is a good idea, for this part a handy method call to setMask(mask) would be nice :-) - // call the func - llkeyboard::llsdStringarrayFromMask - - LLSD key_mask=LLSD::emptyArray(); - - if (mask & MASK_CONTROL) { key_mask.append(LLSD("CTL")); } // Mac command key - has code of 0x1 in llcommon/indra_contstants - if (mask & MASK_ALT) { key_mask.append(LLSD("ALT")); } - if (mask & MASK_SHIFT) { key_mask.append(LLSD("SHIFT")); } - if (mask & MASK_MAC_CONTROL) { key_mask.append(LLSD("MAC_CONTROL")); } - - event.insert("mask",key_mask); - event.insert("timestamp",LLSD(LLDate::now().asString())); - - // Although vita has keyDown and keyUp requests it does not have type as a high-level concept - // (maybe it should) - instead it has a convenience method that generates the keydown and keyup events - // Here we will use "type" as our event type - - LL_DEBUGS() << "LLVIewerEventRecorder::logKeyEvent Serialized LLSD for event " << event.asString() << "\n" << LL_ENDL; - - - //LL_DEBUGS() << "[VITA] key_name: " << LLKeyboard::stringFromKey(key) << "mask: "<< mask << "handled by " << getName() << LL_ENDL; - LL_DEBUGS() << "LLVIewerEventRecorder::logKeyEvent key_name: " << LLKeyboard::stringFromKey(key) << "mask: "<< mask << LL_ENDL; - - - recordEvent(event); - -} - -void LLViewerEventRecorder::playbackRecording() { - - LLSD LeapCommand; - - // ivita sets this on startup, it also sends commands to the viewer to make start, stop, and playback menu items visible in viewer - LeapCommand =LLUI::getInstance()->mSettingGroups["config"]->getLLSD("LeapPlaybackEventsCommand"); - - LL_DEBUGS() << "[VITA] launching playback - leap command is: " << LLSDXMLStreamer(LeapCommand) << LL_ENDL; - LLLeap::create("", LeapCommand, false); // exception=false - -} - - -void LLViewerEventRecorder::recordEvent(LLSD event) { - LL_DEBUGS() << "LLViewerEventRecorder::recordEvent event written to log: " << LLSDXMLStreamer(event) << LL_ENDL; - mLog << event << std::endl; - -} -void LLViewerEventRecorder::logKeyUnicodeEvent(llwchar uni_char) { - if (! logEvents) return; - - // Note: keyUp is not captured since the viewer seems to not care about keyUp events - - LLSD event=LLSD::emptyMap(); - - event.insert("timestamp",LLSD(LLDate::now().asString())); - - - // keysym ...or - // keycode...or - // char - - LL_DEBUGS() << "Wrapped in conversion to wstring " << wstring_to_utf8str(LLWString( 1, uni_char)) << "\n" << LL_ENDL; - - event.insert("char", - LLSD( wstring_to_utf8str(LLWString( 1,uni_char)) ) - ); - - // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps - // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might - // break the test script and it would be useful to have more context to make these sorts of edits safer - - // TODO need to consider mask keys too? Doesn't seem possible - at least not easily at this point - - event.insert("event",LLSD("keyDown")); - - LL_DEBUGS() << "[VITA] unicode key: " << uni_char << LL_ENDL; - LL_DEBUGS() << "[VITA] dumpxml " << LLSDXMLStreamer(event) << "\n" << LL_ENDL; - - - recordEvent(event); - -} - -void LLViewerEventRecorder::logMouseEvent(std::string button_state,std::string button_name) -{ - if (! logEvents) return; - - LLSD event=LLSD::emptyMap(); - - event.insert("event",LLSD(std::string("mouse"+ button_state))); - event.insert("button",LLSD(button_name)); - if (xui!="") { - event.insert("path",LLSD(xui)); - } - - if (local_x>0 && local_y>0) { - event.insert("local_x",LLSD(local_x)); - event.insert("local_y",LLSD(local_y)); - } - - if (global_x>0 && global_y>0) { - event.insert("global_x",LLSD(global_x)); - event.insert("global_y",LLSD(global_y)); - } - event.insert("timestamp",LLSD(LLDate::now().asString())); - recordEvent(event); - - - clear(UNDEFINED); - - -} +/** + * @file llviewereventrecorder.cpp + * @brief Viewer event recording and playback support for mouse and keyboard events + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * + * Copyright (c) 2013, 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 "llviewereventrecorder.h" +#include "llui.h" +#include "llleap.h" + +LLViewerEventRecorder::LLViewerEventRecorder() { + + clear(UNDEFINED); + logEvents = false; + // Remove any previous event log file + std::string old_log_ui_events_to_llsd_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife_Events_log.old"); + LLFile::remove(old_log_ui_events_to_llsd_file, ENOENT); + + + mLogFilename = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife_Events_log.llsd"); + LLFile::rename(mLogFilename, old_log_ui_events_to_llsd_file, ENOENT); + +} + + +bool LLViewerEventRecorder::displayViewerEventRecorderMenuItems() { + return LLUI::getInstance()->mSettingGroups["config"]->getBOOL("ShowEventRecorderMenuItems"); +} + + +void LLViewerEventRecorder::setEventLoggingOn() { + if (! mLog.is_open()) { + mLog.open(mLogFilename.c_str(), std::ios_base::out); + } + logEvents=true; + LL_DEBUGS() << "LLViewerEventRecorder::setEventLoggingOn event logging turned on" << LL_ENDL; +} + +void LLViewerEventRecorder::setEventLoggingOff() { + logEvents=false; + mLog.flush(); + mLog.close(); + LL_DEBUGS() << "LLViewerEventRecorder::setEventLoggingOff event logging turned off" << LL_ENDL; +} + + + LLViewerEventRecorder::~LLViewerEventRecorder() { + if (mLog.is_open()) { + mLog.close(); + } +} + +void LLViewerEventRecorder::clear_xui() { + xui.clear(); +} + +void LLViewerEventRecorder::clear(S32 r) { + + xui.clear(); + + local_x=r; + local_y=r; + + global_x=r; + global_y=r; + + +} + +void LLViewerEventRecorder::setMouseLocalCoords(S32 x, S32 y) { + local_x=x; + local_y=y; +} + +void LLViewerEventRecorder::setMouseGlobalCoords(S32 x, S32 y) { + global_x=x; + global_y=y; +} + +void LLViewerEventRecorder::updateMouseEventInfo(S32 local_x, S32 local_y, S32 global_x, S32 global_y, std::string mName) { + + LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), xui); + if (! target_view) { + LL_DEBUGS() << "LLViewerEventRecorder::updateMouseEventInfo - xui path on file at moment is NOT valid - so DO NOT record these local coords" << LL_ENDL; + return; + } + LL_DEBUGS() << "LLViewerEventRecorder::updateMouseEventInfo b4 updatemouseeventinfo - local_x|global x "<< this->local_x << " " << this->global_x << "local/global y " << this->local_y << " " << this->global_y << " mname: " << mName << " xui: " << xui << LL_ENDL; + + + if (this->local_x < 1 && this->local_y<1 && local_x && local_y) { + this->local_x=local_x; + this->local_y=local_y; + } + this->global_x=global_x; + this->global_y=global_y; + + // ONLY record deepest xui path for hierarchy searches - or first/only xui for floaters/panels reached via mouse captor - and llmousehandler + if (mName!="" && mName!="/" && xui=="") { + // xui=std::string("/")+mName+xui; + //xui=mName+xui; + xui = mName; // TODO review confirm we never call with partial path - also cAN REMOVE CHECK FOR "" - ON OTHER HAND IT'S PRETTY HARMLESS + } + + LL_DEBUGS() << "LLViewerEventRecorder::updateMouseEventInfo after updatemouseeventinfo - local_x|global x "<< this->local_x << " " << this->global_x << "local/global y " << this->local_y << " " << this->global_y << " mname: " << mName << " xui: " << xui << LL_ENDL; +} + +void LLViewerEventRecorder::logVisibilityChange(std::string xui, std::string name, bool visibility, std::string event_subtype) { + + LLSD event=LLSD::emptyMap(); + + event.insert("event",LLSD(std::string("visibility"))); + + if (visibility) { + event.insert("visibility",LLSD(true)); + } else { + event.insert("visibility",LLSD(false)); + } + + if (event_subtype!="") { + event.insert("event_subtype", LLSD(event_subtype)); + } + + if(name!="") { + event.insert("name",LLSD(name)); + } + + if (xui!="") { + event.insert("path",LLSD(xui)); + } + + event.insert("timestamp",LLSD(LLDate::now().asString())); + recordEvent(event); +} + + +std::string LLViewerEventRecorder::get_xui() { + return xui; +} +void LLViewerEventRecorder::update_xui(std::string xui) { + if (xui!="" && this->xui=="" ) { + LL_DEBUGS() << "LLViewerEventRecorder::update_xui to " << xui << LL_ENDL; + this->xui=xui; + } else { + LL_DEBUGS() << "LLViewerEventRecorder::update_xui called with empty string" << LL_ENDL; + } +} + +void LLViewerEventRecorder::logKeyEvent(KEY key, MASK mask) { + + // NOTE: Event recording only logs keydown events - the viewer itself hides keyup events at a fairly low level in the code and does not appear to care about them anywhere + + LLSD event = LLSD::emptyMap(); + + event.insert("event",LLSD("type")); + + // keysym ...or + // keycode...or + // char + event.insert("keysym",LLSD(LLKeyboard::stringFromKey(key))); + + // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps + // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might + // break the test script and it would be useful to have more context to make these sorts of edits safer + + // TODO replace this with a call which extracts to an array of names of masks (just like vita expects during playback) + // This is looking more and more like an object is a good idea, for this part a handy method call to setMask(mask) would be nice :-) + // call the func - llkeyboard::llsdStringarrayFromMask + + LLSD key_mask=LLSD::emptyArray(); + + if (mask & MASK_CONTROL) { key_mask.append(LLSD("CTL")); } // Mac command key - has code of 0x1 in llcommon/indra_contstants + if (mask & MASK_ALT) { key_mask.append(LLSD("ALT")); } + if (mask & MASK_SHIFT) { key_mask.append(LLSD("SHIFT")); } + if (mask & MASK_MAC_CONTROL) { key_mask.append(LLSD("MAC_CONTROL")); } + + event.insert("mask",key_mask); + event.insert("timestamp",LLSD(LLDate::now().asString())); + + // Although vita has keyDown and keyUp requests it does not have type as a high-level concept + // (maybe it should) - instead it has a convenience method that generates the keydown and keyup events + // Here we will use "type" as our event type + + LL_DEBUGS() << "LLVIewerEventRecorder::logKeyEvent Serialized LLSD for event " << event.asString() << "\n" << LL_ENDL; + + + //LL_DEBUGS() << "[VITA] key_name: " << LLKeyboard::stringFromKey(key) << "mask: "<< mask << "handled by " << getName() << LL_ENDL; + LL_DEBUGS() << "LLVIewerEventRecorder::logKeyEvent key_name: " << LLKeyboard::stringFromKey(key) << "mask: "<< mask << LL_ENDL; + + + recordEvent(event); + +} + +void LLViewerEventRecorder::playbackRecording() { + + LLSD LeapCommand; + + // ivita sets this on startup, it also sends commands to the viewer to make start, stop, and playback menu items visible in viewer + LeapCommand =LLUI::getInstance()->mSettingGroups["config"]->getLLSD("LeapPlaybackEventsCommand"); + + LL_DEBUGS() << "[VITA] launching playback - leap command is: " << LLSDXMLStreamer(LeapCommand) << LL_ENDL; + LLLeap::create("", LeapCommand, false); // exception=false + +} + + +void LLViewerEventRecorder::recordEvent(LLSD event) { + LL_DEBUGS() << "LLViewerEventRecorder::recordEvent event written to log: " << LLSDXMLStreamer(event) << LL_ENDL; + mLog << event << std::endl; + +} +void LLViewerEventRecorder::logKeyUnicodeEvent(llwchar uni_char) { + if (! logEvents) return; + + // Note: keyUp is not captured since the viewer seems to not care about keyUp events + + LLSD event=LLSD::emptyMap(); + + event.insert("timestamp",LLSD(LLDate::now().asString())); + + + // keysym ...or + // keycode...or + // char + + LL_DEBUGS() << "Wrapped in conversion to wstring " << wstring_to_utf8str(LLWString( 1, uni_char)) << "\n" << LL_ENDL; + + event.insert("char", + LLSD( wstring_to_utf8str(LLWString( 1,uni_char)) ) + ); + + // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps + // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might + // break the test script and it would be useful to have more context to make these sorts of edits safer + + // TODO need to consider mask keys too? Doesn't seem possible - at least not easily at this point + + event.insert("event",LLSD("keyDown")); + + LL_DEBUGS() << "[VITA] unicode key: " << uni_char << LL_ENDL; + LL_DEBUGS() << "[VITA] dumpxml " << LLSDXMLStreamer(event) << "\n" << LL_ENDL; + + + recordEvent(event); + +} + +void LLViewerEventRecorder::logMouseEvent(std::string button_state,std::string button_name) +{ + if (! logEvents) return; + + LLSD event=LLSD::emptyMap(); + + event.insert("event",LLSD(std::string("mouse"+ button_state))); + event.insert("button",LLSD(button_name)); + if (xui!="") { + event.insert("path",LLSD(xui)); + } + + if (local_x>0 && local_y>0) { + event.insert("local_x",LLSD(local_x)); + event.insert("local_y",LLSD(local_y)); + } + + if (global_x>0 && global_y>0) { + event.insert("global_x",LLSD(global_x)); + event.insert("global_y",LLSD(global_y)); + } + event.insert("timestamp",LLSD(LLDate::now().asString())); + recordEvent(event); + + + clear(UNDEFINED); + + +} diff --git a/indra/llui/llviewquery.cpp b/indra/llui/llviewquery.cpp index 336d884553..4835a60e77 100644 --- a/indra/llui/llviewquery.cpp +++ b/indra/llui/llviewquery.cpp @@ -1,136 +1,136 @@ -/** - * @file llviewquery.cpp - * @brief Implementation of view query 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 "llview.h" -#include "lluictrl.h" -#include "llviewquery.h" - -void LLQuerySorter::sort(LLView * parent, viewList_t &children) const {} - -filterResult_t LLLeavesFilter::operator() (const LLView* const view, const viewList_t & children) const -{ - return filterResult_t(children.empty(), true); -} - -filterResult_t LLRootsFilter::operator() (const LLView* const view, const viewList_t & children) const -{ - return filterResult_t(true, false); -} - -filterResult_t LLVisibleFilter::operator() (const LLView* const view, const viewList_t & children) const -{ - return filterResult_t(view->getVisible(), view->getVisible()); -} -filterResult_t LLEnabledFilter::operator() (const LLView* const view, const viewList_t & children) const -{ - return filterResult_t(view->getEnabled(), view->getEnabled()); -} -filterResult_t LLTabStopFilter::operator() (const LLView* const view, const viewList_t & children) const -{ - return filterResult_t(view->isCtrl() && static_cast(view)->hasTabStop(), - view->canFocusChildren()); -} - -filterResult_t LLCtrlFilter::operator() (const LLView* const view, const viewList_t & children) const -{ - return filterResult_t(view->isCtrl(),true); -} - -// -// LLViewQuery -// - -viewList_t LLViewQuery::run(LLView* view) const -{ - viewList_t result; - - // prefilter gets immediate children of view - filterResult_t pre = runFilters(view, *view->getChildList(), mPreFilters); - if(!pre.first && !pre.second) - { - // not including ourselves or the children - // nothing more to do - return result; - } - - viewList_t filtered_children; - filterResult_t post(true, true); - if(pre.second) - { - // run filters on children - filterChildren(view, filtered_children); - // only run post filters if this element passed pre filters - // so if you failed to pass the pre filter, you can't filter out children in post - if (pre.first) - { - post = runFilters(view, filtered_children, mPostFilters); - } - } - - if(pre.first && post.first) - { - result.push_back(view); - } - - if(pre.second && post.second) - { - result.insert(result.end(), filtered_children.begin(), filtered_children.end()); - } - - return result; -} - -void LLViewQuery::filterChildren(LLView* parent_view, viewList_t & filtered_children) const -{ - LLView::child_list_t views(*(parent_view->getChildList())); - if (mSorterp) - { - mSorterp->sort(parent_view, views); // sort the children per the sorter - } - for(LLView::child_list_iter_t iter = views.begin(); - iter != views.end(); - iter++) - { - viewList_t indiv_children = this->run(*iter); - filtered_children.splice(filtered_children.end(), indiv_children); - } -} - -filterResult_t LLViewQuery::runFilters(LLView * view, const viewList_t children, const filterList_t filters) const -{ - filterResult_t result = filterResult_t(true, true); - for(filterList_const_iter_t iter = filters.begin(); - iter != filters.end(); - iter++) - { - filterResult_t filtered = (**iter)(view, children); - result.first = result.first && filtered.first; - result.second = result.second && filtered.second; - } - return result; -} +/** + * @file llviewquery.cpp + * @brief Implementation of view query 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 "llview.h" +#include "lluictrl.h" +#include "llviewquery.h" + +void LLQuerySorter::sort(LLView * parent, viewList_t &children) const {} + +filterResult_t LLLeavesFilter::operator() (const LLView* const view, const viewList_t & children) const +{ + return filterResult_t(children.empty(), true); +} + +filterResult_t LLRootsFilter::operator() (const LLView* const view, const viewList_t & children) const +{ + return filterResult_t(true, false); +} + +filterResult_t LLVisibleFilter::operator() (const LLView* const view, const viewList_t & children) const +{ + return filterResult_t(view->getVisible(), view->getVisible()); +} +filterResult_t LLEnabledFilter::operator() (const LLView* const view, const viewList_t & children) const +{ + return filterResult_t(view->getEnabled(), view->getEnabled()); +} +filterResult_t LLTabStopFilter::operator() (const LLView* const view, const viewList_t & children) const +{ + return filterResult_t(view->isCtrl() && static_cast(view)->hasTabStop(), + view->canFocusChildren()); +} + +filterResult_t LLCtrlFilter::operator() (const LLView* const view, const viewList_t & children) const +{ + return filterResult_t(view->isCtrl(),true); +} + +// +// LLViewQuery +// + +viewList_t LLViewQuery::run(LLView* view) const +{ + viewList_t result; + + // prefilter gets immediate children of view + filterResult_t pre = runFilters(view, *view->getChildList(), mPreFilters); + if(!pre.first && !pre.second) + { + // not including ourselves or the children + // nothing more to do + return result; + } + + viewList_t filtered_children; + filterResult_t post(true, true); + if(pre.second) + { + // run filters on children + filterChildren(view, filtered_children); + // only run post filters if this element passed pre filters + // so if you failed to pass the pre filter, you can't filter out children in post + if (pre.first) + { + post = runFilters(view, filtered_children, mPostFilters); + } + } + + if(pre.first && post.first) + { + result.push_back(view); + } + + if(pre.second && post.second) + { + result.insert(result.end(), filtered_children.begin(), filtered_children.end()); + } + + return result; +} + +void LLViewQuery::filterChildren(LLView* parent_view, viewList_t & filtered_children) const +{ + LLView::child_list_t views(*(parent_view->getChildList())); + if (mSorterp) + { + mSorterp->sort(parent_view, views); // sort the children per the sorter + } + for(LLView::child_list_iter_t iter = views.begin(); + iter != views.end(); + iter++) + { + viewList_t indiv_children = this->run(*iter); + filtered_children.splice(filtered_children.end(), indiv_children); + } +} + +filterResult_t LLViewQuery::runFilters(LLView * view, const viewList_t children, const filterList_t filters) const +{ + filterResult_t result = filterResult_t(true, true); + for(filterList_const_iter_t iter = filters.begin(); + iter != filters.end(); + iter++) + { + filterResult_t filtered = (**iter)(view, children); + result.first = result.first && filtered.first; + result.second = result.second && filtered.second; + } + return result; +} diff --git a/indra/llui/llviewquery.h b/indra/llui/llviewquery.h index 59c580c08d..c03a15e8f9 100644 --- a/indra/llui/llviewquery.h +++ b/indra/llui/llviewquery.h @@ -1,137 +1,137 @@ -/** - * @file llviewquery.h - * @brief Query algorithm for flattening and filtering the view hierarchy. - * - * $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$ - */ - -#ifndef LL_LLVIEWQUERY_H -#define LL_LLVIEWQUERY_H - -#include - -#include "llsingleton.h" -#include "llui.h" - -class LLView; - -typedef std::list viewList_t; -typedef std::pair filterResult_t; - -// Abstract base class for all query filters. -class LLQueryFilter -{ -public: - virtual ~LLQueryFilter() {}; - virtual filterResult_t operator() (const LLView* const view, const viewList_t & children) const = 0; -}; - -class LLQuerySorter -{ -public: - virtual ~LLQuerySorter() {}; - virtual void sort(LLView * parent, viewList_t &children) const; -}; - -class LLLeavesFilter : public LLQueryFilter, public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLLeavesFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; -}; - -class LLRootsFilter : public LLQueryFilter, public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLRootsFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; -}; - -class LLVisibleFilter : public LLQueryFilter, public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLVisibleFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; -}; - -class LLEnabledFilter : public LLQueryFilter, public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLEnabledFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; -}; - -class LLTabStopFilter : public LLQueryFilter, public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLTabStopFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; -}; - -class LLCtrlFilter : public LLQueryFilter, public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLCtrlFilter); - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; -}; - -template -class LLWidgetTypeFilter : public LLQueryFilter -{ - /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const - { - return filterResult_t(dynamic_cast(view) != NULL, true); - } - -}; - -// Algorithm for flattening -class LLViewQuery -{ -public: - typedef std::list filterList_t; - typedef filterList_t::iterator filterList_iter_t; - typedef filterList_t::const_iterator filterList_const_iter_t; - - LLViewQuery() : mPreFilters(), mPostFilters(), mSorterp() {} - virtual ~LLViewQuery() {} - - void addPreFilter(const LLQueryFilter* prefilter) { mPreFilters.push_back(prefilter); } - void addPostFilter(const LLQueryFilter* postfilter) { mPostFilters.push_back(postfilter); } - const filterList_t & getPreFilters() const { return mPreFilters; } - const filterList_t & getPostFilters() const { return mPostFilters; } - - void setSorter(const LLQuerySorter* sorter) { mSorterp = sorter; } - const LLQuerySorter* getSorter() const { return mSorterp; } - - viewList_t run(LLView * view) const; - // syntactic sugar - viewList_t operator () (LLView * view) const { return run(view); } - - // override this method to provide iteration over other types of children - virtual void filterChildren(LLView * view, viewList_t& filtered_children) const; - -private: - - filterResult_t runFilters(LLView * view, const viewList_t children, const filterList_t filters) const; - - filterList_t mPreFilters; - filterList_t mPostFilters; - const LLQuerySorter* mSorterp; -}; - - -#endif // LL_LLVIEWQUERY_H +/** + * @file llviewquery.h + * @brief Query algorithm for flattening and filtering the view hierarchy. + * + * $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$ + */ + +#ifndef LL_LLVIEWQUERY_H +#define LL_LLVIEWQUERY_H + +#include + +#include "llsingleton.h" +#include "llui.h" + +class LLView; + +typedef std::list viewList_t; +typedef std::pair filterResult_t; + +// Abstract base class for all query filters. +class LLQueryFilter +{ +public: + virtual ~LLQueryFilter() {}; + virtual filterResult_t operator() (const LLView* const view, const viewList_t & children) const = 0; +}; + +class LLQuerySorter +{ +public: + virtual ~LLQuerySorter() {}; + virtual void sort(LLView * parent, viewList_t &children) const; +}; + +class LLLeavesFilter : public LLQueryFilter, public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLLeavesFilter); + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; +}; + +class LLRootsFilter : public LLQueryFilter, public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLRootsFilter); + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; +}; + +class LLVisibleFilter : public LLQueryFilter, public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLVisibleFilter); + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; +}; + +class LLEnabledFilter : public LLQueryFilter, public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLEnabledFilter); + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; +}; + +class LLTabStopFilter : public LLQueryFilter, public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLTabStopFilter); + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; +}; + +class LLCtrlFilter : public LLQueryFilter, public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLCtrlFilter); + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const override; +}; + +template +class LLWidgetTypeFilter : public LLQueryFilter +{ + /*virtual*/ filterResult_t operator() (const LLView* const view, const viewList_t & children) const + { + return filterResult_t(dynamic_cast(view) != NULL, true); + } + +}; + +// Algorithm for flattening +class LLViewQuery +{ +public: + typedef std::list filterList_t; + typedef filterList_t::iterator filterList_iter_t; + typedef filterList_t::const_iterator filterList_const_iter_t; + + LLViewQuery() : mPreFilters(), mPostFilters(), mSorterp() {} + virtual ~LLViewQuery() {} + + void addPreFilter(const LLQueryFilter* prefilter) { mPreFilters.push_back(prefilter); } + void addPostFilter(const LLQueryFilter* postfilter) { mPostFilters.push_back(postfilter); } + const filterList_t & getPreFilters() const { return mPreFilters; } + const filterList_t & getPostFilters() const { return mPostFilters; } + + void setSorter(const LLQuerySorter* sorter) { mSorterp = sorter; } + const LLQuerySorter* getSorter() const { return mSorterp; } + + viewList_t run(LLView * view) const; + // syntactic sugar + viewList_t operator () (LLView * view) const { return run(view); } + + // override this method to provide iteration over other types of children + virtual void filterChildren(LLView * view, viewList_t& filtered_children) const; + +private: + + filterResult_t runFilters(LLView * view, const viewList_t children, const filterList_t filters) const; + + filterList_t mPreFilters; + filterList_t mPostFilters; + const LLQuerySorter* mSorterp; +}; + + +#endif // LL_LLVIEWQUERY_H diff --git a/indra/llui/llvirtualtrackball.cpp b/indra/llui/llvirtualtrackball.cpp index 78651ff3cb..8166afc89b 100644 --- a/indra/llui/llvirtualtrackball.cpp +++ b/indra/llui/llvirtualtrackball.cpp @@ -1,520 +1,520 @@ -/** -* @file LLVirtualTrackball.cpp -* @author Andrey Lihatskiy -* @brief Implementation for LLVirtualTrackball -* -* $LicenseInfo:firstyear=2001&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2018, 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$ -*/ - -// A control for positioning the sun and the moon in the celestial sphere. - -#include "linden_common.h" -#include "llvirtualtrackball.h" -#include "llstring.h" -#include "llrect.h" -#include "lluictrlfactory.h" -#include "llrender.h" - -// Globals -static LLDefaultChildRegistry::Register register_virtual_trackball("sun_moon_trackball"); - -const LLVector3 VectorZero(1.0f, 0.0f, 0.0f); - -LLVirtualTrackball::Params::Params() - : border("border"), - image_moon_back("image_moon_back"), - image_moon_front("image_moon_front"), - image_sphere("image_sphere"), - image_sun_back("image_sun_back"), - image_sun_front("image_sun_front"), - btn_rotate_top("button_rotate_top"), - btn_rotate_bottom("button_rotate_bottom"), - btn_rotate_left("button_rotate_left"), - btn_rotate_right("button_rotate_right"), - thumb_mode("thumb_mode"), - lbl_N("labelN"), - lbl_S("labelS"), - lbl_W("labelW"), - lbl_E("labelE"), - increment_angle_mouse("increment_angle_mouse", 0.5f), - increment_angle_btn("increment_angle_btn", 3.0f) -{ -} - -LLVirtualTrackball::LLVirtualTrackball(const LLVirtualTrackball::Params& p) - : LLUICtrl(p), - mImgMoonBack(p.image_moon_back), - mImgMoonFront(p.image_moon_front), - mImgSunBack(p.image_sun_back), - mImgSunFront(p.image_sun_front), - mImgSphere(p.image_sphere), - mThumbMode(p.thumb_mode() == "moon" ? ThumbMode::MOON : ThumbMode::SUN), - mIncrementMouse(DEG_TO_RAD * p.increment_angle_mouse()), - mIncrementBtn(DEG_TO_RAD * p.increment_angle_btn()) -{ - LLRect border_rect = getLocalRect(); - S32 centerX = border_rect.getCenterX(); - S32 centerY = border_rect.getCenterY(); - U32 btn_size = 32; // width & height - U32 axis_offset_lt = 16; // offset from the axis for left/top sides - U32 axis_offset_rb = btn_size - axis_offset_lt; // and for right/bottom - - LLViewBorder::Params border = p.border; - border.rect(border_rect); - mBorder = LLUICtrlFactory::create(border); - addChild(mBorder); - - - LLButton::Params btn_rt = p.btn_rotate_top; - btn_rt.rect(LLRect(centerX - axis_offset_lt, border_rect.mTop, centerX + axis_offset_rb, border_rect.mTop - btn_size)); - btn_rt.click_callback.function(boost::bind(&LLVirtualTrackball::onRotateTopClick, this)); - btn_rt.mouse_held_callback.function(boost::bind(&LLVirtualTrackball::onRotateTopClick, this)); - btn_rt.mouseenter_callback.function(boost::bind(&LLVirtualTrackball::onRotateTopMouseEnter, this)); - mBtnRotateTop = LLUICtrlFactory::create(btn_rt); - addChild(mBtnRotateTop); - - LLTextBox::Params lbl_N = p.lbl_N; - LLRect rect_N = btn_rt.rect; - //rect_N.translate(btn_rt.rect().getWidth(), 0); - lbl_N.rect = rect_N; - lbl_N.initial_value(lbl_N.label()); - mLabelN = LLUICtrlFactory::create(lbl_N); - addChild(mLabelN); - - - LLButton::Params btn_rr = p.btn_rotate_right; - btn_rr.rect(LLRect(border_rect.mRight - btn_size, centerY + axis_offset_lt, border_rect.mRight, centerY - axis_offset_rb)); - btn_rr.click_callback.function(boost::bind(&LLVirtualTrackball::onRotateRightClick, this)); - btn_rr.mouse_held_callback.function(boost::bind(&LLVirtualTrackball::onRotateRightClick, this)); - btn_rr.mouseenter_callback.function(boost::bind(&LLVirtualTrackball::onRotateRightMouseEnter, this)); - mBtnRotateRight = LLUICtrlFactory::create(btn_rr); - addChild(mBtnRotateRight); - - LLTextBox::Params lbl_E = p.lbl_E; - LLRect rect_E = btn_rr.rect; - //rect_E.translate(0, -1 * btn_rr.rect().getHeight()); - lbl_E.rect = rect_E; - lbl_E.initial_value(lbl_E.label()); - mLabelE = LLUICtrlFactory::create(lbl_E); - addChild(mLabelE); - - - LLButton::Params btn_rb = p.btn_rotate_bottom; - btn_rb.rect(LLRect(centerX - axis_offset_lt, border_rect.mBottom + btn_size, centerX + axis_offset_rb, border_rect.mBottom)); - btn_rb.click_callback.function(boost::bind(&LLVirtualTrackball::onRotateBottomClick, this)); - btn_rb.mouse_held_callback.function(boost::bind(&LLVirtualTrackball::onRotateBottomClick, this)); - btn_rb.mouseenter_callback.function(boost::bind(&LLVirtualTrackball::onRotateBottomMouseEnter, this)); - mBtnRotateBottom = LLUICtrlFactory::create(btn_rb); - addChild(mBtnRotateBottom); - - LLTextBox::Params lbl_S = p.lbl_S; - LLRect rect_S = btn_rb.rect; - //rect_S.translate(btn_rb.rect().getWidth(), 0); - lbl_S.rect = rect_S; - lbl_S.initial_value(lbl_S.label()); - mLabelS = LLUICtrlFactory::create(lbl_S); - addChild(mLabelS); - - - LLButton::Params btn_rl = p.btn_rotate_left; - btn_rl.rect(LLRect(border_rect.mLeft, centerY + axis_offset_lt, border_rect.mLeft + btn_size, centerY - axis_offset_rb)); - btn_rl.click_callback.function(boost::bind(&LLVirtualTrackball::onRotateLeftClick, this)); - btn_rl.mouse_held_callback.function(boost::bind(&LLVirtualTrackball::onRotateLeftClick, this)); - btn_rl.mouseenter_callback.function(boost::bind(&LLVirtualTrackball::onRotateLeftMouseEnter, this)); - mBtnRotateLeft = LLUICtrlFactory::create(btn_rl); - addChild(mBtnRotateLeft); - - LLTextBox::Params lbl_W = p.lbl_W; - LLRect rect_W = btn_rl.rect; - //rect_W.translate(0, -1* btn_rl.rect().getHeight()); - lbl_W.rect = rect_W; - lbl_W.initial_value(lbl_W.label()); - mLabelW = LLUICtrlFactory::create(lbl_W); - addChild(mLabelW); - - - LLPanel::Params touch_area; - touch_area.rect = LLRect(centerX - mImgSphere->getWidth() / 2, - centerY + mImgSphere->getHeight() / 2, - centerX + mImgSphere->getWidth() / 2, - centerY - mImgSphere->getHeight() / 2); - mTouchArea = LLUICtrlFactory::create(touch_area); - addChild(mTouchArea); -} - -LLVirtualTrackball::~LLVirtualTrackball() -{ -} - -bool LLVirtualTrackball::postBuild() -{ - return true; -} - - -void LLVirtualTrackball::drawThumb(S32 x, S32 y, ThumbMode mode, bool upperHemi) -{ - LLUIImage* thumb; - if (mode == ThumbMode::SUN) - { - if (upperHemi) - { - thumb = mImgSunFront; - } - else - { - thumb = mImgSunBack; - } - } - else - { - if (upperHemi) - { - thumb = mImgMoonFront; - } - else - { - thumb = mImgMoonBack; - } - } - thumb->draw(LLRect(x - thumb->getWidth() / 2, - y + thumb->getHeight() / 2, - x + thumb->getWidth() / 2, - y - thumb->getHeight() / 2)); -} - -bool LLVirtualTrackball::pointInTouchCircle(S32 x, S32 y) const -{ - S32 centerX = mTouchArea->getRect().getCenterX(); - S32 centerY = mTouchArea->getRect().getCenterY(); - - bool in_circle = pow(x - centerX, 2) + pow(y - centerY, 2) <= pow(mTouchArea->getRect().getWidth() / 2, 2); - return in_circle; -} - -void LLVirtualTrackball::draw() -{ - LLVector3 draw_point = VectorZero * mValue; - - S32 halfwidth = mTouchArea->getRect().getWidth() / 2; - S32 halfheight = mTouchArea->getRect().getHeight() / 2; - draw_point.mV[VX] = (draw_point.mV[VX] + 1.0) * halfwidth + mTouchArea->getRect().mLeft; - draw_point.mV[VY] = (draw_point.mV[VY] + 1.0) * halfheight + mTouchArea->getRect().mBottom; - bool upper_hemisphere = (draw_point.mV[VZ] >= 0.f); - - mImgSphere->draw(mTouchArea->getRect(), upper_hemisphere ? UI_VERTEX_COLOR : UI_VERTEX_COLOR % 0.5f); - drawThumb(draw_point.mV[VX], draw_point.mV[VY], mThumbMode, upper_hemisphere); - - - if (LLView::sDebugRects) - { - gGL.color4fv(LLColor4::red.mV); - gl_circle_2d(mTouchArea->getRect().getCenterX(), mTouchArea->getRect().getCenterY(), mImgSphere->getWidth() / 2, 60, false); - gl_circle_2d(draw_point.mV[VX], draw_point.mV[VY], mImgSunFront->getWidth() / 2, 12, false); - } - - // hide the direction labels when disabled - bool enabled = isInEnabledChain(); - mLabelN->setVisible(enabled); - mLabelE->setVisible(enabled); - mLabelS->setVisible(enabled); - mLabelW->setVisible(enabled); - - LLView::draw(); -} - -void LLVirtualTrackball::onRotateTopClick() -{ - if (getEnabled()) - { - LLQuaternion delta; - delta.setAngleAxis(mIncrementBtn, 1, 0, 0); - mValue *= delta; - setValueAndCommit(mValue); - - make_ui_sound("UISndClick"); - } -} - -void LLVirtualTrackball::onRotateBottomClick() -{ - if (getEnabled()) - { - LLQuaternion delta; - delta.setAngleAxis(mIncrementBtn, -1, 0, 0); - mValue *= delta; - setValueAndCommit(mValue); - - make_ui_sound("UISndClick"); - } -} - -void LLVirtualTrackball::onRotateLeftClick() -{ - if (getEnabled()) - { - LLQuaternion delta; - delta.setAngleAxis(mIncrementBtn, 0, 1, 0); - mValue *= delta; - setValueAndCommit(mValue); - - make_ui_sound("UISndClick"); - } -} - -void LLVirtualTrackball::onRotateRightClick() -{ - if (getEnabled()) - { - LLQuaternion delta; - delta.setAngleAxis(mIncrementBtn, 0, -1, 0); - mValue *= delta; - setValueAndCommit(mValue); - - make_ui_sound("UISndClick"); - } -} - -void LLVirtualTrackball::onRotateTopMouseEnter() -{ - mBtnRotateTop->setHighlight(true); -} - -void LLVirtualTrackball::onRotateBottomMouseEnter() -{ - mBtnRotateBottom->setHighlight(true); -} - -void LLVirtualTrackball::onRotateLeftMouseEnter() -{ - mBtnRotateLeft->setHighlight(true); -} - -void LLVirtualTrackball::onRotateRightMouseEnter() -{ - mBtnRotateRight->setHighlight(true); -} - -void LLVirtualTrackball::setValue(const LLSD& value) -{ - if (value.isArray() && value.size() == 4) - { - mValue.setValue(value); - } -} - -void LLVirtualTrackball::setRotation(const LLQuaternion &value) -{ - mValue = value; -} - -void LLVirtualTrackball::setValue(F32 x, F32 y, F32 z, F32 w) -{ - mValue.set(x, y, z, w); -} - -void LLVirtualTrackball::setValueAndCommit(const LLQuaternion &value) -{ - mValue = value; - onCommit(); -} - -LLSD LLVirtualTrackball::getValue() const -{ - return mValue.getValue(); -} - -LLQuaternion LLVirtualTrackball::getRotation() const -{ - return mValue; -} - -// static -void LLVirtualTrackball::getAzimuthAndElevation(const LLQuaternion &quat, F32 &azimuth, F32 &elevation) -{ - // LLQuaternion has own function to get azimuth, but it doesn't appear to return correct values (meant for 2d?) - LLVector3 point = VectorZero * quat; - - if (!is_approx_zero(point.mV[VX]) || !is_approx_zero(point.mV[VY])) - { - azimuth = atan2f(point.mV[VX], point.mV[VY]); - } - else - { - azimuth = 0; - } - - azimuth -= F_PI_BY_TWO; - - if (azimuth < 0) - { - azimuth += F_PI * 2; - } - - // while vector is '1', F32 is not sufficiently precise and we can get - // values like 1.0000012 which will result in -90deg angle instead of 90deg - F32 z = llclamp(point.mV[VZ], -1.f, 1.f); - elevation = asin(z); // because VectorZero's length is '1' -} - -// static -void LLVirtualTrackball::getAzimuthAndElevationDeg(const LLQuaternion &quat, F32 &azimuth, F32 &elevation) -{ - getAzimuthAndElevation(quat, azimuth, elevation); - azimuth *= RAD_TO_DEG; - elevation *= RAD_TO_DEG; -} - -bool LLVirtualTrackball::handleHover(S32 x, S32 y, MASK mask) -{ - if (hasMouseCapture()) - { - if (mDragMode == DRAG_SCROLL) - { // trackball (move to roll) mode - LLQuaternion delta; - - F32 rotX = x - mPrevX; - F32 rotY = y - mPrevY; - - if (abs(rotX) > 1) - { - F32 direction = (rotX < 0) ? -1 : 1; - delta.setAngleAxis(mIncrementMouse * abs(rotX), 0, direction, 0); // changing X - rotate around Y axis - mValue *= delta; - } - - if (abs(rotY) > 1) - { - F32 direction = (rotY < 0) ? 1 : -1; // reverse for Y (value increases from bottom to top) - delta.setAngleAxis(mIncrementMouse * abs(rotY), direction, 0, 0); // changing Y - rotate around X axis - mValue *= delta; - } - } - else - { // set on click mode - if (!pointInTouchCircle(x, y)) - { - return true; // don't drag outside the circle - } - - F32 radius = mTouchArea->getRect().getWidth() / 2; - F32 xx = x - mTouchArea->getRect().getCenterX(); - F32 yy = y - mTouchArea->getRect().getCenterY(); - F32 dist = sqrt(pow(xx, 2) + pow(yy, 2)); - - F32 azimuth = llclamp(acosf(xx / dist), 0.0f, F_PI); - F32 altitude = llclamp(acosf(dist / radius), 0.0f, F_PI_BY_TWO); - - if (yy < 0) - { - azimuth = F_TWO_PI - azimuth; - } - - LLVector3 draw_point = VectorZero * mValue; - if (draw_point.mV[VZ] >= 0.f) - { - if (is_approx_zero(altitude)) // don't change the hemisphere - { - altitude = F_APPROXIMATELY_ZERO; - } - altitude *= -1; - } - - mValue.setAngleAxis(altitude, 0, 1, 0); - LLQuaternion az_quat; - az_quat.setAngleAxis(azimuth, 0, 0, 1); - mValue *= az_quat; - } - - // we are doing a lot of F32 mathematical operations with loss of precision, - // re-normalize to compensate - mValue.normalize(); - - mPrevX = x; - mPrevY = y; - onCommit(); - } - return true; -} - -bool LLVirtualTrackball::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (hasMouseCapture()) - { - mPrevX = 0; - mPrevY = 0; - gFocusMgr.setMouseCapture(NULL); - make_ui_sound("UISndClickRelease"); - } - return LLView::handleMouseUp(x, y, mask); -} - -bool LLVirtualTrackball::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (pointInTouchCircle(x, y)) - { - mPrevX = x; - mPrevY = y; - gFocusMgr.setMouseCapture(this); - mDragMode = (mask == MASK_CONTROL) ? DRAG_SCROLL : DRAG_SET; - make_ui_sound("UISndClick"); - } - return LLView::handleMouseDown(x, y, mask); -} - -bool LLVirtualTrackball::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - if (pointInTouchCircle(x, y)) - { - - //make_ui_sound("UISndClick"); - } - return LLView::handleRightMouseDown(x, y, mask); -} - -bool LLVirtualTrackball::handleKeyHere(KEY key, MASK mask) -{ - bool handled = false; - switch (key) - { - case KEY_DOWN: - onRotateTopClick(); - handled = true; - break; - case KEY_LEFT: - onRotateRightClick(); - handled = true; - break; - case KEY_UP: - onRotateBottomClick(); - handled = true; - break; - case KEY_RIGHT: - onRotateLeftClick(); - handled = true; - break; - default: - break; - } - return handled; -} - +/** +* @file LLVirtualTrackball.cpp +* @author Andrey Lihatskiy +* @brief Implementation for LLVirtualTrackball +* +* $LicenseInfo:firstyear=2001&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2018, 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$ +*/ + +// A control for positioning the sun and the moon in the celestial sphere. + +#include "linden_common.h" +#include "llvirtualtrackball.h" +#include "llstring.h" +#include "llrect.h" +#include "lluictrlfactory.h" +#include "llrender.h" + +// Globals +static LLDefaultChildRegistry::Register register_virtual_trackball("sun_moon_trackball"); + +const LLVector3 VectorZero(1.0f, 0.0f, 0.0f); + +LLVirtualTrackball::Params::Params() + : border("border"), + image_moon_back("image_moon_back"), + image_moon_front("image_moon_front"), + image_sphere("image_sphere"), + image_sun_back("image_sun_back"), + image_sun_front("image_sun_front"), + btn_rotate_top("button_rotate_top"), + btn_rotate_bottom("button_rotate_bottom"), + btn_rotate_left("button_rotate_left"), + btn_rotate_right("button_rotate_right"), + thumb_mode("thumb_mode"), + lbl_N("labelN"), + lbl_S("labelS"), + lbl_W("labelW"), + lbl_E("labelE"), + increment_angle_mouse("increment_angle_mouse", 0.5f), + increment_angle_btn("increment_angle_btn", 3.0f) +{ +} + +LLVirtualTrackball::LLVirtualTrackball(const LLVirtualTrackball::Params& p) + : LLUICtrl(p), + mImgMoonBack(p.image_moon_back), + mImgMoonFront(p.image_moon_front), + mImgSunBack(p.image_sun_back), + mImgSunFront(p.image_sun_front), + mImgSphere(p.image_sphere), + mThumbMode(p.thumb_mode() == "moon" ? ThumbMode::MOON : ThumbMode::SUN), + mIncrementMouse(DEG_TO_RAD * p.increment_angle_mouse()), + mIncrementBtn(DEG_TO_RAD * p.increment_angle_btn()) +{ + LLRect border_rect = getLocalRect(); + S32 centerX = border_rect.getCenterX(); + S32 centerY = border_rect.getCenterY(); + U32 btn_size = 32; // width & height + U32 axis_offset_lt = 16; // offset from the axis for left/top sides + U32 axis_offset_rb = btn_size - axis_offset_lt; // and for right/bottom + + LLViewBorder::Params border = p.border; + border.rect(border_rect); + mBorder = LLUICtrlFactory::create(border); + addChild(mBorder); + + + LLButton::Params btn_rt = p.btn_rotate_top; + btn_rt.rect(LLRect(centerX - axis_offset_lt, border_rect.mTop, centerX + axis_offset_rb, border_rect.mTop - btn_size)); + btn_rt.click_callback.function(boost::bind(&LLVirtualTrackball::onRotateTopClick, this)); + btn_rt.mouse_held_callback.function(boost::bind(&LLVirtualTrackball::onRotateTopClick, this)); + btn_rt.mouseenter_callback.function(boost::bind(&LLVirtualTrackball::onRotateTopMouseEnter, this)); + mBtnRotateTop = LLUICtrlFactory::create(btn_rt); + addChild(mBtnRotateTop); + + LLTextBox::Params lbl_N = p.lbl_N; + LLRect rect_N = btn_rt.rect; + //rect_N.translate(btn_rt.rect().getWidth(), 0); + lbl_N.rect = rect_N; + lbl_N.initial_value(lbl_N.label()); + mLabelN = LLUICtrlFactory::create(lbl_N); + addChild(mLabelN); + + + LLButton::Params btn_rr = p.btn_rotate_right; + btn_rr.rect(LLRect(border_rect.mRight - btn_size, centerY + axis_offset_lt, border_rect.mRight, centerY - axis_offset_rb)); + btn_rr.click_callback.function(boost::bind(&LLVirtualTrackball::onRotateRightClick, this)); + btn_rr.mouse_held_callback.function(boost::bind(&LLVirtualTrackball::onRotateRightClick, this)); + btn_rr.mouseenter_callback.function(boost::bind(&LLVirtualTrackball::onRotateRightMouseEnter, this)); + mBtnRotateRight = LLUICtrlFactory::create(btn_rr); + addChild(mBtnRotateRight); + + LLTextBox::Params lbl_E = p.lbl_E; + LLRect rect_E = btn_rr.rect; + //rect_E.translate(0, -1 * btn_rr.rect().getHeight()); + lbl_E.rect = rect_E; + lbl_E.initial_value(lbl_E.label()); + mLabelE = LLUICtrlFactory::create(lbl_E); + addChild(mLabelE); + + + LLButton::Params btn_rb = p.btn_rotate_bottom; + btn_rb.rect(LLRect(centerX - axis_offset_lt, border_rect.mBottom + btn_size, centerX + axis_offset_rb, border_rect.mBottom)); + btn_rb.click_callback.function(boost::bind(&LLVirtualTrackball::onRotateBottomClick, this)); + btn_rb.mouse_held_callback.function(boost::bind(&LLVirtualTrackball::onRotateBottomClick, this)); + btn_rb.mouseenter_callback.function(boost::bind(&LLVirtualTrackball::onRotateBottomMouseEnter, this)); + mBtnRotateBottom = LLUICtrlFactory::create(btn_rb); + addChild(mBtnRotateBottom); + + LLTextBox::Params lbl_S = p.lbl_S; + LLRect rect_S = btn_rb.rect; + //rect_S.translate(btn_rb.rect().getWidth(), 0); + lbl_S.rect = rect_S; + lbl_S.initial_value(lbl_S.label()); + mLabelS = LLUICtrlFactory::create(lbl_S); + addChild(mLabelS); + + + LLButton::Params btn_rl = p.btn_rotate_left; + btn_rl.rect(LLRect(border_rect.mLeft, centerY + axis_offset_lt, border_rect.mLeft + btn_size, centerY - axis_offset_rb)); + btn_rl.click_callback.function(boost::bind(&LLVirtualTrackball::onRotateLeftClick, this)); + btn_rl.mouse_held_callback.function(boost::bind(&LLVirtualTrackball::onRotateLeftClick, this)); + btn_rl.mouseenter_callback.function(boost::bind(&LLVirtualTrackball::onRotateLeftMouseEnter, this)); + mBtnRotateLeft = LLUICtrlFactory::create(btn_rl); + addChild(mBtnRotateLeft); + + LLTextBox::Params lbl_W = p.lbl_W; + LLRect rect_W = btn_rl.rect; + //rect_W.translate(0, -1* btn_rl.rect().getHeight()); + lbl_W.rect = rect_W; + lbl_W.initial_value(lbl_W.label()); + mLabelW = LLUICtrlFactory::create(lbl_W); + addChild(mLabelW); + + + LLPanel::Params touch_area; + touch_area.rect = LLRect(centerX - mImgSphere->getWidth() / 2, + centerY + mImgSphere->getHeight() / 2, + centerX + mImgSphere->getWidth() / 2, + centerY - mImgSphere->getHeight() / 2); + mTouchArea = LLUICtrlFactory::create(touch_area); + addChild(mTouchArea); +} + +LLVirtualTrackball::~LLVirtualTrackball() +{ +} + +bool LLVirtualTrackball::postBuild() +{ + return true; +} + + +void LLVirtualTrackball::drawThumb(S32 x, S32 y, ThumbMode mode, bool upperHemi) +{ + LLUIImage* thumb; + if (mode == ThumbMode::SUN) + { + if (upperHemi) + { + thumb = mImgSunFront; + } + else + { + thumb = mImgSunBack; + } + } + else + { + if (upperHemi) + { + thumb = mImgMoonFront; + } + else + { + thumb = mImgMoonBack; + } + } + thumb->draw(LLRect(x - thumb->getWidth() / 2, + y + thumb->getHeight() / 2, + x + thumb->getWidth() / 2, + y - thumb->getHeight() / 2)); +} + +bool LLVirtualTrackball::pointInTouchCircle(S32 x, S32 y) const +{ + S32 centerX = mTouchArea->getRect().getCenterX(); + S32 centerY = mTouchArea->getRect().getCenterY(); + + bool in_circle = pow(x - centerX, 2) + pow(y - centerY, 2) <= pow(mTouchArea->getRect().getWidth() / 2, 2); + return in_circle; +} + +void LLVirtualTrackball::draw() +{ + LLVector3 draw_point = VectorZero * mValue; + + S32 halfwidth = mTouchArea->getRect().getWidth() / 2; + S32 halfheight = mTouchArea->getRect().getHeight() / 2; + draw_point.mV[VX] = (draw_point.mV[VX] + 1.0) * halfwidth + mTouchArea->getRect().mLeft; + draw_point.mV[VY] = (draw_point.mV[VY] + 1.0) * halfheight + mTouchArea->getRect().mBottom; + bool upper_hemisphere = (draw_point.mV[VZ] >= 0.f); + + mImgSphere->draw(mTouchArea->getRect(), upper_hemisphere ? UI_VERTEX_COLOR : UI_VERTEX_COLOR % 0.5f); + drawThumb(draw_point.mV[VX], draw_point.mV[VY], mThumbMode, upper_hemisphere); + + + if (LLView::sDebugRects) + { + gGL.color4fv(LLColor4::red.mV); + gl_circle_2d(mTouchArea->getRect().getCenterX(), mTouchArea->getRect().getCenterY(), mImgSphere->getWidth() / 2, 60, false); + gl_circle_2d(draw_point.mV[VX], draw_point.mV[VY], mImgSunFront->getWidth() / 2, 12, false); + } + + // hide the direction labels when disabled + bool enabled = isInEnabledChain(); + mLabelN->setVisible(enabled); + mLabelE->setVisible(enabled); + mLabelS->setVisible(enabled); + mLabelW->setVisible(enabled); + + LLView::draw(); +} + +void LLVirtualTrackball::onRotateTopClick() +{ + if (getEnabled()) + { + LLQuaternion delta; + delta.setAngleAxis(mIncrementBtn, 1, 0, 0); + mValue *= delta; + setValueAndCommit(mValue); + + make_ui_sound("UISndClick"); + } +} + +void LLVirtualTrackball::onRotateBottomClick() +{ + if (getEnabled()) + { + LLQuaternion delta; + delta.setAngleAxis(mIncrementBtn, -1, 0, 0); + mValue *= delta; + setValueAndCommit(mValue); + + make_ui_sound("UISndClick"); + } +} + +void LLVirtualTrackball::onRotateLeftClick() +{ + if (getEnabled()) + { + LLQuaternion delta; + delta.setAngleAxis(mIncrementBtn, 0, 1, 0); + mValue *= delta; + setValueAndCommit(mValue); + + make_ui_sound("UISndClick"); + } +} + +void LLVirtualTrackball::onRotateRightClick() +{ + if (getEnabled()) + { + LLQuaternion delta; + delta.setAngleAxis(mIncrementBtn, 0, -1, 0); + mValue *= delta; + setValueAndCommit(mValue); + + make_ui_sound("UISndClick"); + } +} + +void LLVirtualTrackball::onRotateTopMouseEnter() +{ + mBtnRotateTop->setHighlight(true); +} + +void LLVirtualTrackball::onRotateBottomMouseEnter() +{ + mBtnRotateBottom->setHighlight(true); +} + +void LLVirtualTrackball::onRotateLeftMouseEnter() +{ + mBtnRotateLeft->setHighlight(true); +} + +void LLVirtualTrackball::onRotateRightMouseEnter() +{ + mBtnRotateRight->setHighlight(true); +} + +void LLVirtualTrackball::setValue(const LLSD& value) +{ + if (value.isArray() && value.size() == 4) + { + mValue.setValue(value); + } +} + +void LLVirtualTrackball::setRotation(const LLQuaternion &value) +{ + mValue = value; +} + +void LLVirtualTrackball::setValue(F32 x, F32 y, F32 z, F32 w) +{ + mValue.set(x, y, z, w); +} + +void LLVirtualTrackball::setValueAndCommit(const LLQuaternion &value) +{ + mValue = value; + onCommit(); +} + +LLSD LLVirtualTrackball::getValue() const +{ + return mValue.getValue(); +} + +LLQuaternion LLVirtualTrackball::getRotation() const +{ + return mValue; +} + +// static +void LLVirtualTrackball::getAzimuthAndElevation(const LLQuaternion &quat, F32 &azimuth, F32 &elevation) +{ + // LLQuaternion has own function to get azimuth, but it doesn't appear to return correct values (meant for 2d?) + LLVector3 point = VectorZero * quat; + + if (!is_approx_zero(point.mV[VX]) || !is_approx_zero(point.mV[VY])) + { + azimuth = atan2f(point.mV[VX], point.mV[VY]); + } + else + { + azimuth = 0; + } + + azimuth -= F_PI_BY_TWO; + + if (azimuth < 0) + { + azimuth += F_PI * 2; + } + + // while vector is '1', F32 is not sufficiently precise and we can get + // values like 1.0000012 which will result in -90deg angle instead of 90deg + F32 z = llclamp(point.mV[VZ], -1.f, 1.f); + elevation = asin(z); // because VectorZero's length is '1' +} + +// static +void LLVirtualTrackball::getAzimuthAndElevationDeg(const LLQuaternion &quat, F32 &azimuth, F32 &elevation) +{ + getAzimuthAndElevation(quat, azimuth, elevation); + azimuth *= RAD_TO_DEG; + elevation *= RAD_TO_DEG; +} + +bool LLVirtualTrackball::handleHover(S32 x, S32 y, MASK mask) +{ + if (hasMouseCapture()) + { + if (mDragMode == DRAG_SCROLL) + { // trackball (move to roll) mode + LLQuaternion delta; + + F32 rotX = x - mPrevX; + F32 rotY = y - mPrevY; + + if (abs(rotX) > 1) + { + F32 direction = (rotX < 0) ? -1 : 1; + delta.setAngleAxis(mIncrementMouse * abs(rotX), 0, direction, 0); // changing X - rotate around Y axis + mValue *= delta; + } + + if (abs(rotY) > 1) + { + F32 direction = (rotY < 0) ? 1 : -1; // reverse for Y (value increases from bottom to top) + delta.setAngleAxis(mIncrementMouse * abs(rotY), direction, 0, 0); // changing Y - rotate around X axis + mValue *= delta; + } + } + else + { // set on click mode + if (!pointInTouchCircle(x, y)) + { + return true; // don't drag outside the circle + } + + F32 radius = mTouchArea->getRect().getWidth() / 2; + F32 xx = x - mTouchArea->getRect().getCenterX(); + F32 yy = y - mTouchArea->getRect().getCenterY(); + F32 dist = sqrt(pow(xx, 2) + pow(yy, 2)); + + F32 azimuth = llclamp(acosf(xx / dist), 0.0f, F_PI); + F32 altitude = llclamp(acosf(dist / radius), 0.0f, F_PI_BY_TWO); + + if (yy < 0) + { + azimuth = F_TWO_PI - azimuth; + } + + LLVector3 draw_point = VectorZero * mValue; + if (draw_point.mV[VZ] >= 0.f) + { + if (is_approx_zero(altitude)) // don't change the hemisphere + { + altitude = F_APPROXIMATELY_ZERO; + } + altitude *= -1; + } + + mValue.setAngleAxis(altitude, 0, 1, 0); + LLQuaternion az_quat; + az_quat.setAngleAxis(azimuth, 0, 0, 1); + mValue *= az_quat; + } + + // we are doing a lot of F32 mathematical operations with loss of precision, + // re-normalize to compensate + mValue.normalize(); + + mPrevX = x; + mPrevY = y; + onCommit(); + } + return true; +} + +bool LLVirtualTrackball::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (hasMouseCapture()) + { + mPrevX = 0; + mPrevY = 0; + gFocusMgr.setMouseCapture(NULL); + make_ui_sound("UISndClickRelease"); + } + return LLView::handleMouseUp(x, y, mask); +} + +bool LLVirtualTrackball::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (pointInTouchCircle(x, y)) + { + mPrevX = x; + mPrevY = y; + gFocusMgr.setMouseCapture(this); + mDragMode = (mask == MASK_CONTROL) ? DRAG_SCROLL : DRAG_SET; + make_ui_sound("UISndClick"); + } + return LLView::handleMouseDown(x, y, mask); +} + +bool LLVirtualTrackball::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (pointInTouchCircle(x, y)) + { + + //make_ui_sound("UISndClick"); + } + return LLView::handleRightMouseDown(x, y, mask); +} + +bool LLVirtualTrackball::handleKeyHere(KEY key, MASK mask) +{ + bool handled = false; + switch (key) + { + case KEY_DOWN: + onRotateTopClick(); + handled = true; + break; + case KEY_LEFT: + onRotateRightClick(); + handled = true; + break; + case KEY_UP: + onRotateBottomClick(); + handled = true; + break; + case KEY_RIGHT: + onRotateLeftClick(); + handled = true; + break; + default: + break; + } + return handled; +} + diff --git a/indra/llui/llvirtualtrackball.h b/indra/llui/llvirtualtrackball.h index 54ed6082ea..61a78b2398 100644 --- a/indra/llui/llvirtualtrackball.h +++ b/indra/llui/llvirtualtrackball.h @@ -1,163 +1,163 @@ -/** -* @file virtualtrackball.h -* @author Andrey Lihatskiy -* @brief Header file for LLVirtualTrackball -* -* $LicenseInfo:firstyear=2001&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2018, 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$ -*/ - -// A control for positioning the sun and the moon in the celestial sphere. - -#ifndef LL_LLVIRTUALTRACKBALL_H -#define LL_LLVIRTUALTRACKBALL_H - -#include "lluictrl.h" -#include "llpanel.h" -#include "lltextbox.h" -#include "llbutton.h" - -class LLVirtualTrackball - : public LLUICtrl -{ -public: - enum ThumbMode - { - SUN, - MOON - }; - enum DragMode - { - DRAG_SET, - DRAG_SCROLL - }; - - struct Params - : public LLInitParam::Block - { - Optional border; - Optional image_moon_back, - image_moon_front, - image_sphere, - image_sun_back, - image_sun_front; - - Optional thumb_mode; - Optional increment_angle_mouse, - increment_angle_btn; - - Optional lbl_N, - lbl_S, - lbl_W, - lbl_E; - - Optional btn_rotate_top, - btn_rotate_bottom, - btn_rotate_left, - btn_rotate_right; - - Params(); - }; - - - virtual ~LLVirtualTrackball(); - /*virtual*/ bool postBuild(); - - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleKeyHere(KEY key, MASK mask); - - virtual void draw(); - - virtual void setValue(const LLSD& value); - void setValue(F32 x, F32 y, F32 z, F32 w); - virtual LLSD getValue() const; - - void setRotation(const LLQuaternion &value); - LLQuaternion getRotation() const; - - static void getAzimuthAndElevation(const LLQuaternion &quat, F32 &azimuth, F32 &elevation); - static void getAzimuthAndElevationDeg(const LLQuaternion &quat, F32 &azimuth, F32 &elevation); - -protected: - friend class LLUICtrlFactory; - LLVirtualTrackball(const Params&); - void onEditChange(); - -protected: - LLTextBox* mNLabel; - LLTextBox* mELabel; - LLTextBox* mSLabel; - LLTextBox* mWLabel; - - LLButton* mBtnRotateTop; - LLButton* mBtnRotateBottom; - LLButton* mBtnRotateLeft; - LLButton* mBtnRotateRight; - - LLTextBox* mLabelN; - LLTextBox* mLabelS; - LLTextBox* mLabelW; - LLTextBox* mLabelE; - - LLPanel* mTouchArea; - LLViewBorder* mBorder; - -private: - void setValueAndCommit(const LLQuaternion &value); - void drawThumb(S32 x, S32 y, ThumbMode mode, bool upperHemi = true); - bool pointInTouchCircle(S32 x, S32 y) const; - - void onRotateTopClick(); - void onRotateBottomClick(); - void onRotateLeftClick(); - void onRotateRightClick(); - - void onRotateTopMouseEnter(); - void onRotateBottomMouseEnter(); - void onRotateLeftMouseEnter(); - void onRotateRightMouseEnter(); - - S32 mPrevX; - S32 mPrevY; - - LLUIImage* mImgMoonBack; - LLUIImage* mImgMoonFront; - LLUIImage* mImgSunBack; - LLUIImage* mImgSunFront; - LLUIImage* mImgBtnRotTop; - LLUIImage* mImgBtnRotLeft; - LLUIImage* mImgBtnRotRight; - LLUIImage* mImgBtnRotBottom; - LLUIImage* mImgSphere; - - LLQuaternion mValue; - ThumbMode mThumbMode; - DragMode mDragMode; - - F32 mIncrementMouse; - F32 mIncrementBtn; -}; - -#endif - +/** +* @file virtualtrackball.h +* @author Andrey Lihatskiy +* @brief Header file for LLVirtualTrackball +* +* $LicenseInfo:firstyear=2001&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2018, 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$ +*/ + +// A control for positioning the sun and the moon in the celestial sphere. + +#ifndef LL_LLVIRTUALTRACKBALL_H +#define LL_LLVIRTUALTRACKBALL_H + +#include "lluictrl.h" +#include "llpanel.h" +#include "lltextbox.h" +#include "llbutton.h" + +class LLVirtualTrackball + : public LLUICtrl +{ +public: + enum ThumbMode + { + SUN, + MOON + }; + enum DragMode + { + DRAG_SET, + DRAG_SCROLL + }; + + struct Params + : public LLInitParam::Block + { + Optional border; + Optional image_moon_back, + image_moon_front, + image_sphere, + image_sun_back, + image_sun_front; + + Optional thumb_mode; + Optional increment_angle_mouse, + increment_angle_btn; + + Optional lbl_N, + lbl_S, + lbl_W, + lbl_E; + + Optional btn_rotate_top, + btn_rotate_bottom, + btn_rotate_left, + btn_rotate_right; + + Params(); + }; + + + virtual ~LLVirtualTrackball(); + /*virtual*/ bool postBuild(); + + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleKeyHere(KEY key, MASK mask); + + virtual void draw(); + + virtual void setValue(const LLSD& value); + void setValue(F32 x, F32 y, F32 z, F32 w); + virtual LLSD getValue() const; + + void setRotation(const LLQuaternion &value); + LLQuaternion getRotation() const; + + static void getAzimuthAndElevation(const LLQuaternion &quat, F32 &azimuth, F32 &elevation); + static void getAzimuthAndElevationDeg(const LLQuaternion &quat, F32 &azimuth, F32 &elevation); + +protected: + friend class LLUICtrlFactory; + LLVirtualTrackball(const Params&); + void onEditChange(); + +protected: + LLTextBox* mNLabel; + LLTextBox* mELabel; + LLTextBox* mSLabel; + LLTextBox* mWLabel; + + LLButton* mBtnRotateTop; + LLButton* mBtnRotateBottom; + LLButton* mBtnRotateLeft; + LLButton* mBtnRotateRight; + + LLTextBox* mLabelN; + LLTextBox* mLabelS; + LLTextBox* mLabelW; + LLTextBox* mLabelE; + + LLPanel* mTouchArea; + LLViewBorder* mBorder; + +private: + void setValueAndCommit(const LLQuaternion &value); + void drawThumb(S32 x, S32 y, ThumbMode mode, bool upperHemi = true); + bool pointInTouchCircle(S32 x, S32 y) const; + + void onRotateTopClick(); + void onRotateBottomClick(); + void onRotateLeftClick(); + void onRotateRightClick(); + + void onRotateTopMouseEnter(); + void onRotateBottomMouseEnter(); + void onRotateLeftMouseEnter(); + void onRotateRightMouseEnter(); + + S32 mPrevX; + S32 mPrevY; + + LLUIImage* mImgMoonBack; + LLUIImage* mImgMoonFront; + LLUIImage* mImgSunBack; + LLUIImage* mImgSunFront; + LLUIImage* mImgBtnRotTop; + LLUIImage* mImgBtnRotLeft; + LLUIImage* mImgBtnRotRight; + LLUIImage* mImgBtnRotBottom; + LLUIImage* mImgSphere; + + LLQuaternion mValue; + ThumbMode mThumbMode; + DragMode mDragMode; + + F32 mIncrementMouse; + F32 mIncrementBtn; +}; + +#endif + diff --git a/indra/llui/llxuiparser.cpp b/indra/llui/llxuiparser.cpp index bfa91b3729..8fd85a89a1 100644 --- a/indra/llui/llxuiparser.cpp +++ b/indra/llui/llxuiparser.cpp @@ -1,1765 +1,1765 @@ -/** - * @file llxuiparser.cpp - * @brief Utility functions for handling XUI structures in XML - * - * $LicenseInfo:firstyear=2003&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 "llxuiparser.h" - -#include "llxmlnode.h" -#include "llfasttimer.h" -#ifdef LL_USESYSTEMLIBS -#include -#else -#include "expat/expat.h" -#endif - -#include -#include -#include -//#include -#include - -#include "lluicolor.h" -#include "v3math.h" -using namespace BOOST_SPIRIT_CLASSIC_NS; - -const S32 MAX_STRING_ATTRIBUTE_SIZE = 40; - -static LLInitParam::Parser::parser_read_func_map_t sXSDReadFuncs; -static LLInitParam::Parser::parser_write_func_map_t sXSDWriteFuncs; -static LLInitParam::Parser::parser_inspect_func_map_t sXSDInspectFuncs; - -static LLInitParam::Parser::parser_read_func_map_t sSimpleXUIReadFuncs; -static LLInitParam::Parser::parser_write_func_map_t sSimpleXUIWriteFuncs; -static LLInitParam::Parser::parser_inspect_func_map_t sSimpleXUIInspectFuncs; - -const char* NO_VALUE_MARKER = "no_value"; - -struct MaxOccursValues : public LLInitParam::TypeValuesHelper -{ - static void declareValues() - { - declare("unbounded", U32_MAX); - } -}; - -struct Occurs : public LLInitParam::Block -{ - Optional minOccurs; - Optional maxOccurs; - - Occurs() - : minOccurs("minOccurs", 0), - maxOccurs("maxOccurs", U32_MAX) - - {} -}; - -typedef enum -{ - USE_REQUIRED, - USE_OPTIONAL -} EUse; - -namespace LLInitParam -{ - template<> - struct TypeValues : public TypeValuesHelper - { - static void declareValues() - { - declare("required", USE_REQUIRED); - declare("optional", USE_OPTIONAL); - } - }; -} - -struct Element; -struct Group; -struct Sequence; - -struct All : public LLInitParam::Block -{ - Multiple< Lazy > elements; - - All() - : elements("element") - { - maxOccurs = 1; - } -}; - -struct Attribute : public LLInitParam::Block -{ - Mandatory name, - type; - Mandatory use; - - Attribute() - : name("name"), - type("type"), - use("use") - {} -}; - -struct Any : public LLInitParam::Block -{ - Optional _namespace; - - Any() - : _namespace("namespace") - {} -}; - -struct Choice : public LLInitParam::ChoiceBlock -{ - Alternative< Lazy > element; - Alternative< Lazy > group; - Alternative< Lazy > choice; - Alternative< Lazy > sequence; - Alternative< Lazy > any; - - Choice() - : element("element"), - group("group"), - choice("choice"), - sequence("sequence"), - any("any") - {} - -}; - -struct Sequence : public LLInitParam::ChoiceBlock -{ - Alternative< Lazy > element; - Alternative< Lazy > group; - Alternative< Lazy > choice; - Alternative< Lazy > sequence; - Alternative< Lazy > any; -}; - -struct GroupContents : public LLInitParam::ChoiceBlock -{ - Alternative all; - Alternative choice; - Alternative sequence; - - GroupContents() - : all("all"), - choice("choice"), - sequence("sequence") - {} -}; - -struct Group : public LLInitParam::Block -{ - Optional name, - ref; - - Group() - : name("name"), - ref("ref") - {} -}; - -struct Restriction : public LLInitParam::Block -{ -}; - -struct Extension : public LLInitParam::Block -{ -}; - -struct SimpleContent : public LLInitParam::ChoiceBlock -{ - Alternative restriction; - Alternative extension; - - SimpleContent() - : restriction("restriction"), - extension("extension") - {} -}; - -struct SimpleType : public LLInitParam::Block -{ - // TODO -}; - -struct ComplexContent : public LLInitParam::Block -{ - Optional mixed; - - ComplexContent() - : mixed("mixed", true) - {} -}; - -struct ComplexTypeContents : public LLInitParam::ChoiceBlock -{ - Alternative simple_content; - Alternative complex_content; - Alternative group; - Alternative all; - Alternative choice; - Alternative sequence; - - ComplexTypeContents() - : simple_content("simpleContent"), - complex_content("complexContent"), - group("group"), - all("all"), - choice("choice"), - sequence("sequence") - {} -}; - -struct ComplexType : public LLInitParam::Block -{ - Optional name; - Optional mixed; - - Multiple attribute; - Multiple< Lazy > elements; - - ComplexType() - : name("name"), - attribute("xs:attribute"), - elements("xs:element"), - mixed("mixed") - { - } -}; - -struct ElementContents : public LLInitParam::ChoiceBlock -{ - Alternative simpleType; - Alternative complexType; - - ElementContents() - : simpleType("simpleType"), - complexType("complexType") - {} -}; - -struct Element : public LLInitParam::Block -{ - Optional name, - ref, - type; - - Element() - : name("xs:name"), - ref("xs:ref"), - type("xs:type") - {} -}; - -struct Schema : public LLInitParam::Block -{ -private: - Mandatory targetNamespace, - xmlns, - xs; - -public: - Optional attributeFormDefault, - elementFormDefault; - - Mandatory root_element; - - void setNameSpace(const std::string& ns) {targetNamespace = ns; xmlns = ns;} - - Schema(const std::string& ns = LLStringUtil::null) - : attributeFormDefault("attributeFormDefault"), - elementFormDefault("elementFormDefault"), - xs("xmlns:xs"), - targetNamespace("targetNamespace"), - xmlns("xmlns"), - root_element("xs:element") - { - attributeFormDefault = "unqualified"; - elementFormDefault = "qualified"; - xs = "http://www.w3.org/2001/XMLSchema"; - if (!ns.empty()) - { - setNameSpace(ns); - }; - } -}; - -// -// LLXSDWriter -// -LLXSDWriter::LLXSDWriter() -: Parser(sXSDReadFuncs, sXSDWriteFuncs, sXSDInspectFuncs) -{ - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:boolean", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedByte", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedByte", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedShort", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedShort", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedInt", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:integer", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:float", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:double", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); - registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); -} - -LLXSDWriter::~LLXSDWriter() {} - -void LLXSDWriter::writeXSD(const std::string& type_name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace) -{ - Schema schema(xml_namespace); - - schema.root_element.name = type_name; - Choice& choice = schema.root_element.complexType.choice; - - choice.minOccurs = 0; - choice.maxOccurs = "unbounded"; - - mSchemaNode = node; - //node->setName("xs:schema"); - //node->createChild("attributeFormDefault", true)->setStringValue("unqualified"); - //node->createChild("elementFormDefault", true)->setStringValue("qualified"); - //node->createChild("targetNamespace", true)->setStringValue(xml_namespace); - //node->createChild("xmlns:xs", true)->setStringValue("http://www.w3.org/2001/XMLSchema"); - //node->createChild("xmlns", true)->setStringValue(xml_namespace); - - //node = node->createChild("xs:complexType", false); - //node->createChild("name", true)->setStringValue(type_name); - //node->createChild("mixed", true)->setStringValue("true"); - - //mAttributeNode = node; - //mElementNode = node->createChild("xs:choice", false); - //mElementNode->createChild("minOccurs", true)->setStringValue("0"); - //mElementNode->createChild("maxOccurs", true)->setStringValue("unbounded"); - block.inspectBlock(*this); - - // duplicate element choices - LLXMLNodeList children; - mElementNode->getChildren("xs:element", children, false); - for (LLXMLNodeList::iterator child_it = children.begin(); child_it != children.end(); ++child_it) - { - LLXMLNodePtr child_copy = child_it->second->deepCopy(); - std::string child_name; - child_copy->getAttributeString("name", child_name); - child_copy->setAttributeString("name", type_name + "." + child_name); - mElementNode->addChild(child_copy); - } - - LLXMLNodePtr element_declaration_node = mSchemaNode->createChild("xs:element", false); - element_declaration_node->createChild("name", true)->setStringValue(type_name); - element_declaration_node->createChild("type", true)->setStringValue(type_name); -} - -void LLXSDWriter::writeAttribute(const std::string& type, const Parser::name_stack_t& stack, S32 min_count, S32 max_count, const std::vector* possible_values) -{ - name_stack_t non_empty_names; - std::string attribute_name; - for (name_stack_t::const_iterator it = stack.begin(); - it != stack.end(); - ++it) - { - const std::string& name = it->first; - if (!name.empty()) - { - non_empty_names.push_back(*it); - } - } - - for (name_stack_t::const_iterator it = non_empty_names.begin(); - it != non_empty_names.end(); - ++it) - { - if (!attribute_name.empty()) - { - attribute_name += "."; - } - attribute_name += it->first; - } - - // only flag non-nested attributes as mandatory, nested attributes have variant syntax - // that can't be properly constrained in XSD - // e.g. vs - bool attribute_mandatory = min_count == 1 && max_count == 1 && non_empty_names.size() == 1; - - // don't bother supporting "Multiple" params as xml attributes - if (max_count <= 1) - { - // add compound attribute to root node - addAttributeToSchema(mAttributeNode, attribute_name, type, attribute_mandatory, possible_values); - } - - // now generated nested elements for compound attributes - if (non_empty_names.size() > 1 && !attribute_mandatory) - { - std::string element_name; - - // traverse all but last element, leaving that as an attribute name - name_stack_t::const_iterator end_it = non_empty_names.end(); - end_it--; - - for (name_stack_t::const_iterator it = non_empty_names.begin(); - it != end_it; - ++it) - { - if (it != non_empty_names.begin()) - { - element_name += "."; - } - element_name += it->first; - } - - std::string short_attribute_name = non_empty_names.back().first; - - LLXMLNodePtr complex_type_node; - - // find existing element node here, starting at tail of child list - if (mElementNode->mChildren.notNull()) - { - for(LLXMLNodePtr element = mElementNode->mChildren->tail; - element.notNull(); - element = element->mPrev) - { - std::string name; - if(element->getAttributeString("name", name) && name == element_name) - { - complex_type_node = element->mChildren->head; - break; - } - } - } - //create complex_type node - // - // - // - // - // - if(complex_type_node.isNull()) - { - complex_type_node = mElementNode->createChild("xs:element", false); - - complex_type_node->createChild("minOccurs", true)->setIntValue(min_count); - complex_type_node->createChild("maxOccurs", true)->setIntValue(max_count); - complex_type_node->createChild("name", true)->setStringValue(element_name); - complex_type_node = complex_type_node->createChild("xs:complexType", false); - } - - addAttributeToSchema(complex_type_node, short_attribute_name, type, false, possible_values); - } -} - -void LLXSDWriter::addAttributeToSchema(LLXMLNodePtr type_declaration_node, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector* possible_values) -{ - if (!attribute_name.empty()) - { - LLXMLNodePtr new_enum_type_node; - if (possible_values != NULL) - { - // custom attribute type, for example - // - // - // - // - // - // - new_enum_type_node = new LLXMLNode("xs:simpleType", false); - - LLXMLNodePtr restriction_node = new_enum_type_node->createChild("xs:restriction", false); - restriction_node->createChild("base", true)->setStringValue("xs:string"); - - for (std::vector::const_iterator it = possible_values->begin(); - it != possible_values->end(); - ++it) - { - LLXMLNodePtr enum_node = restriction_node->createChild("xs:enumeration", false); - enum_node->createChild("value", true)->setStringValue(*it); - } - } - - string_set_t& attributes_written = mAttributesWritten[type_declaration_node]; - - string_set_t::iterator found_it = attributes_written.lower_bound(attribute_name); - - // attribute not yet declared - if (found_it == attributes_written.end() || attributes_written.key_comp()(attribute_name, *found_it)) - { - attributes_written.insert(found_it, attribute_name); - - LLXMLNodePtr attribute_node = type_declaration_node->createChild("xs:attribute", false); - - // attribute name - attribute_node->createChild("name", true)->setStringValue(attribute_name); - - if (new_enum_type_node.notNull()) - { - attribute_node->addChild(new_enum_type_node); - } - else - { - // simple attribute type - attribute_node->createChild("type", true)->setStringValue(type); - } - - // required or optional - attribute_node->createChild("use", true)->setStringValue(mandatory ? "required" : "optional"); - } - // attribute exists...handle collision of same name attributes with potentially different types - else - { - LLXMLNodePtr attribute_declaration; - if (type_declaration_node.notNull()) - { - for(LLXMLNodePtr node = type_declaration_node->mChildren->tail; - node.notNull(); - node = node->mPrev) - { - std::string name; - if (node->getAttributeString("name", name) && name == attribute_name) - { - attribute_declaration = node; - break; - } - } - } - - bool new_type_is_enum = new_enum_type_node.notNull(); - bool existing_type_is_enum = !attribute_declaration->hasAttribute("type"); - - // either type is enum, revert to string in collision - // don't bother to check for enum equivalence - if (new_type_is_enum || existing_type_is_enum) - { - if (attribute_declaration->hasAttribute("type")) - { - attribute_declaration->setAttributeString("type", "xs:string"); - } - else - { - attribute_declaration->createChild("type", true)->setStringValue("xs:string"); - } - attribute_declaration->deleteChildren("xs:simpleType"); - } - else - { - // check for collision of different standard types - std::string existing_type; - attribute_declaration->getAttributeString("type", existing_type); - // if current type is not the same as the new type, revert to strnig - if (existing_type != type) - { - // ...than use most general type, string - attribute_declaration->setAttributeString("type", "string"); - } - } - } - } -} - -// -// LLXUIXSDWriter -// -void LLXUIXSDWriter::writeXSD(const std::string& type_name, const std::string& path, const LLInitParam::BaseBlock& block) -{ - std::string file_name(path); - file_name += type_name + ".xsd"; - LLXMLNodePtr root_nodep = new LLXMLNode(); - - LLXSDWriter::writeXSD(type_name, root_nodep, block, "http://www.lindenlab.com/xui"); - - // add includes for all possible children - const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name); - const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type); - - // add choices for valid children - if (widget_registryp) - { - // add include declarations for all valid children - for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); - it != widget_registryp->currentRegistrar().endItems(); - ++it) - { - std::string widget_name = it->first; - if (widget_name == type_name) - { - continue; - } - LLXMLNodePtr nodep = new LLXMLNode("xs:include", false); - nodep->createChild("schemaLocation", true)->setStringValue(widget_name + ".xsd"); - - // add to front of schema - mSchemaNode->addChild(nodep); - } - - for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); - it != widget_registryp->currentRegistrar().endItems(); - ++it) - { - std::string widget_name = it->first; - // - LLXMLNodePtr widget_node = mElementNode->createChild("xs:element", false); - widget_node->createChild("name", true)->setStringValue(widget_name); - widget_node->createChild("type", true)->setStringValue(widget_name); - } - } - - LLFILE* xsd_file = LLFile::fopen(file_name.c_str(), "w"); - LLXMLNode::writeHeaderToFile(xsd_file); - root_nodep->writeToFile(xsd_file); - fclose(xsd_file); -} - -static LLInitParam::Parser::parser_read_func_map_t sXUIReadFuncs; -static LLInitParam::Parser::parser_write_func_map_t sXUIWriteFuncs; -static LLInitParam::Parser::parser_inspect_func_map_t sXUIInspectFuncs; - -// -// LLXUIParser -// -LLXUIParser::LLXUIParser() -: Parser(sXUIReadFuncs, sXUIWriteFuncs, sXUIInspectFuncs), - mCurReadDepth(0) -{ - if (sXUIReadFuncs.empty()) - { - registerParserFuncs(readFlag, writeFlag); - registerParserFuncs(readBoolValue, writeBoolValue); - registerParserFuncs(readStringValue, writeStringValue); - registerParserFuncs(readU8Value, writeU8Value); - registerParserFuncs(readS8Value, writeS8Value); - registerParserFuncs(readU16Value, writeU16Value); - registerParserFuncs(readS16Value, writeS16Value); - registerParserFuncs(readU32Value, writeU32Value); - registerParserFuncs(readS32Value, writeS32Value); - registerParserFuncs(readF32Value, writeF32Value); - registerParserFuncs(readF64Value, writeF64Value); - registerParserFuncs(readVector3Value, writeVector3Value); - registerParserFuncs(readColor4Value, writeColor4Value); - registerParserFuncs(readUIColorValue, writeUIColorValue); - registerParserFuncs(readUUIDValue, writeUUIDValue); - registerParserFuncs(readSDValue, writeSDValue); - } -} - -static LLTrace::BlockTimerStatHandle FTM_PARSE_XUI("XUI Parsing"); -const LLXMLNodePtr DUMMY_NODE = new LLXMLNode(); - -void LLXUIParser::readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, const std::string& filename, bool silent) -{ - LL_RECORD_BLOCK_TIME(FTM_PARSE_XUI); - mNameStack.clear(); - mRootNodeName = node->getName()->mString; - mCurFileName = filename; - mCurReadDepth = 0; - setParseSilently(silent); - - if (node.isNull()) - { - parserWarning("Invalid node"); - } - else - { - readXUIImpl(node, block); - } -} - -bool LLXUIParser::readXUIImpl(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) -{ - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("."); - - bool values_parsed = false; - bool silent = mCurReadDepth > 0; - - if (nodep->getFirstChild().isNull() - && nodep->mAttributes.empty() - && nodep->getSanitizedValue().empty()) - { - // empty node, just parse as flag - mCurReadNode = DUMMY_NODE; - return block.submitValue(mNameStack, *this, silent); - } - - // submit attributes for current node - values_parsed |= readAttributes(nodep, block); - - // treat text contents of xml node as "value" parameter - std::string text_contents = nodep->getSanitizedValue(); - if (!text_contents.empty()) - { - mCurReadNode = nodep; - mNameStack.push_back(std::make_pair(std::string("value"), true)); - // child nodes are not necessarily valid parameters (could be a child widget) - // so don't complain once we've recursed - if (!block.submitValue(mNameStack, *this, true)) - { - mNameStack.pop_back(); - block.submitValue(mNameStack, *this, silent); - } - else - { - mNameStack.pop_back(); - } - } - - // then traverse children - // child node must start with last name of parent node (our "scope") - // for example: "" - // which equates to the following nesting: - // button - // param - // nested_param1 - // nested_param2 - // nested_param3 - mCurReadDepth++; - for(LLXMLNodePtr childp = nodep->getFirstChild(); childp.notNull();) - { - std::string child_name(childp->getName()->mString); - S32 num_tokens_pushed = 0; - - // for non "dotted" child nodes check to see if child node maps to another widget type - // and if not, treat as a child element of the current node - // e.g. will interpret as "button.rect" - // since there is no widget named "rect" - if (child_name.find(".") == std::string::npos) - { - mNameStack.push_back(std::make_pair(child_name, true)); - num_tokens_pushed++; - } - else - { - // parse out "dotted" name into individual tokens - tokenizer name_tokens(child_name, sep); - - tokenizer::iterator name_token_it = name_tokens.begin(); - if(name_token_it == name_tokens.end()) - { - childp = childp->getNextSibling(); - continue; - } - - // check for proper nesting - if (mNameStack.empty()) - { - if (*name_token_it != mRootNodeName) - { - childp = childp->getNextSibling(); - continue; - } - } - else if(mNameStack.back().first != *name_token_it) - { - childp = childp->getNextSibling(); - continue; - } - - // now ignore first token - ++name_token_it; - - // copy remaining tokens on to our running token list - for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) - { - mNameStack.push_back(std::make_pair(*token_to_push, true)); - num_tokens_pushed++; - } - } - - // recurse and visit children XML nodes - if(readXUIImpl(childp, block)) - { - // child node successfully parsed, remove from DOM - - values_parsed = true; - LLXMLNodePtr node_to_remove = childp; - childp = childp->getNextSibling(); - - nodep->deleteChild(node_to_remove); - } - else - { - childp = childp->getNextSibling(); - } - - while(num_tokens_pushed-- > 0) - { - mNameStack.pop_back(); - } - } - mCurReadDepth--; - return values_parsed; -} - -bool LLXUIParser::readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) -{ - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("."); - - bool any_parsed = false; - bool silent = mCurReadDepth > 0; - - for(LLXMLAttribList::const_iterator attribute_it = nodep->mAttributes.begin(); - attribute_it != nodep->mAttributes.end(); - ++attribute_it) - { - S32 num_tokens_pushed = 0; - std::string attribute_name(attribute_it->first->mString); - mCurReadNode = attribute_it->second; - - tokenizer name_tokens(attribute_name, sep); - // copy remaining tokens on to our running token list - for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) - { - mNameStack.push_back(std::make_pair(*token_to_push, true)); - num_tokens_pushed++; - } - - // child nodes are not necessarily valid attributes, so don't complain once we've recursed - any_parsed |= block.submitValue(mNameStack, *this, silent); - - while(num_tokens_pushed-- > 0) - { - mNameStack.pop_back(); - } - } - - return any_parsed; -} - -void LLXUIParser::writeXUIImpl(LLXMLNodePtr node, const LLInitParam::BaseBlock &block, const LLInitParam::predicate_rule_t rules, const LLInitParam::BaseBlock* diff_block) -{ - mWriteRootNode = node; - name_stack_t name_stack = Parser::name_stack_t(); - block.serializeBlock(*this, name_stack, rules, diff_block); - mOutNodes.clear(); -} - -// go from a stack of names to a specific XML node -LLXMLNodePtr LLXUIParser::getNode(name_stack_t& stack) -{ - LLXMLNodePtr out_node = mWriteRootNode; - - name_stack_t::iterator next_it = stack.begin(); - for (name_stack_t::iterator it = stack.begin(); - it != stack.end(); - it = next_it) - { - ++next_it; - bool force_new_node = false; - - if (it->first.empty()) - { - it->second = false; - continue; - } - - if (next_it != stack.end() && next_it->first.empty() && next_it->second) - { - force_new_node = true; - } - - - out_nodes_t::iterator found_it = mOutNodes.find(it->first); - - // node with this name not yet written - if (found_it == mOutNodes.end() || it->second || force_new_node) - { - // make an attribute if we are the last element on the name stack - bool is_attribute = next_it == stack.end(); - LLXMLNodePtr new_node = new LLXMLNode(it->first.c_str(), is_attribute); - out_node->addChild(new_node); - mOutNodes[it->first] = new_node; - out_node = new_node; - it->second = false; - } - else - { - out_node = found_it->second; - } - } - - return (out_node == mWriteRootNode ? LLXMLNodePtr(NULL) : out_node); -} - -bool LLXUIParser::readFlag(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode == DUMMY_NODE; -} - -bool LLXUIParser::writeFlag(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - // just create node - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - return node.notNull(); -} - -bool LLXUIParser::readBoolValue(Parser& parser, void* val_ptr) -{ - bool value; - LLXUIParser& self = static_cast(parser); - bool success = self.mCurReadNode->getBoolValue(1, &value); - *((bool*)val_ptr) = value; - return success; -} - -bool LLXUIParser::writeBoolValue(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setBoolValue(*((bool*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readStringValue(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - *((std::string*)val_ptr) = self.mCurReadNode->getSanitizedValue(); - return true; -} - -bool LLXUIParser::writeStringValue(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - const std::string* string_val = reinterpret_cast(val_ptr); - if (string_val->find('\n') != std::string::npos - || string_val->size() > MAX_STRING_ATTRIBUTE_SIZE) - { - // don't write strings with newlines into attributes - std::string attribute_name = node->getName()->mString; - LLXMLNodePtr parent_node = node->mParent; - parent_node->deleteChild(node); - // write results in text contents of node - if (attribute_name == "value") - { - // "value" is implicit, just write to parent - node = parent_node; - } - else - { - // create a child that is not an attribute, but with same name - node = parent_node->createChild(attribute_name.c_str(), false); - } - } - node->setStringValue(*string_val); - return true; - } - return false; -} - -bool LLXUIParser::readU8Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode->getByteValue(1, (U8*)val_ptr); -} - -bool LLXUIParser::writeU8Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setUnsignedValue(*((U8*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readS8Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - S32 value; - if(self.mCurReadNode->getIntValue(1, &value)) - { - *((S8*)val_ptr) = value; - return true; - } - return false; -} - -bool LLXUIParser::writeS8Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setIntValue(*((S8*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readU16Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - U32 value; - if(self.mCurReadNode->getUnsignedValue(1, &value)) - { - *((U16*)val_ptr) = value; - return true; - } - return false; -} - -bool LLXUIParser::writeU16Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setUnsignedValue(*((U16*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readS16Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - S32 value; - if(self.mCurReadNode->getIntValue(1, &value)) - { - *((S16*)val_ptr) = value; - return true; - } - return false; -} - -bool LLXUIParser::writeS16Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setIntValue(*((S16*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readU32Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode->getUnsignedValue(1, (U32*)val_ptr); -} - -bool LLXUIParser::writeU32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setUnsignedValue(*((U32*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readS32Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode->getIntValue(1, (S32*)val_ptr); -} - -bool LLXUIParser::writeS32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setIntValue(*((S32*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readF32Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode->getFloatValue(1, (F32*)val_ptr); -} - -bool LLXUIParser::writeF32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setFloatValue(*((F32*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readF64Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - return self.mCurReadNode->getDoubleValue(1, (F64*)val_ptr); -} - -bool LLXUIParser::writeF64Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setDoubleValue(*((F64*)val_ptr)); - return true; - } - return false; -} - -bool LLXUIParser::readVector3Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - LLVector3* vecp = (LLVector3*)val_ptr; - if(self.mCurReadNode->getFloatValue(3, vecp->mV) >= 3) - { - return true; - } - - return false; -} - -bool LLXUIParser::writeVector3Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - LLVector3 vector = *((LLVector3*)val_ptr); - node->setFloatValue(3, vector.mV); - return true; - } - return false; -} - -bool LLXUIParser::readColor4Value(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - LLColor4* colorp = (LLColor4*)val_ptr; - if(self.mCurReadNode->getFloatValue(4, colorp->mV) >= 3) - { - return true; - } - - return false; -} - -bool LLXUIParser::writeColor4Value(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - LLColor4 color = *((LLColor4*)val_ptr); - node->setFloatValue(4, color.mV); - return true; - } - return false; -} - -bool LLXUIParser::readUIColorValue(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - LLUIColor* param = (LLUIColor*)val_ptr; - LLColor4 color; - bool success = self.mCurReadNode->getFloatValue(4, color.mV) >= 3; - if (success) - { - param->set(color); - return true; - } - return false; -} - -bool LLXUIParser::writeUIColorValue(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - LLUIColor color = *((LLUIColor*)val_ptr); - //RN: don't write out the color that is represented by a function - // rely on param block exporting to get the reference to the color settings - if (color.isReference()) return false; - node->setFloatValue(4, color.get().mV); - return true; - } - return false; -} - -bool LLXUIParser::readUUIDValue(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - LLUUID temp_id; - // LLUUID::set is destructive, so use temporary value - if (temp_id.set(self.mCurReadNode->getSanitizedValue())) - { - *(LLUUID*)(val_ptr) = temp_id; - return true; - } - return false; -} - -bool LLXUIParser::writeUUIDValue(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - node->setStringValue(((LLUUID*)val_ptr)->asString()); - return true; - } - return false; -} - -bool LLXUIParser::readSDValue(Parser& parser, void* val_ptr) -{ - LLXUIParser& self = static_cast(parser); - *((LLSD*)val_ptr) = LLSD(self.mCurReadNode->getSanitizedValue()); - return true; -} - -bool LLXUIParser::writeSDValue(Parser& parser, const void* val_ptr, name_stack_t& stack) -{ - LLXUIParser& self = static_cast(parser); - - LLXMLNodePtr node = self.getNode(stack); - if (node.notNull()) - { - std::string string_val = ((LLSD*)val_ptr)->asString(); - if (string_val.find('\n') != std::string::npos || string_val.size() > MAX_STRING_ATTRIBUTE_SIZE) - { - // don't write strings with newlines into attributes - std::string attribute_name = node->getName()->mString; - LLXMLNodePtr parent_node = node->mParent; - parent_node->deleteChild(node); - // write results in text contents of node - if (attribute_name == "value") - { - // "value" is implicit, just write to parent - node = parent_node; - } - else - { - node = parent_node->createChild(attribute_name.c_str(), false); - } - } - - node->setStringValue(string_val); - return true; - } - return false; -} - -/*virtual*/ std::string LLXUIParser::getCurrentElementName() -{ - std::string full_name; - for (name_stack_t::iterator it = mNameStack.begin(); - it != mNameStack.end(); - ++it) - { - full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." - } - - return full_name; -} - -void LLXUIParser::parserWarning(const std::string& message) -{ - std::string warning_msg = llformat("%s:\t%s(%d)", message.c_str(), mCurFileName.c_str(), mCurReadNode->getLineNumber()); - Parser::parserWarning(warning_msg); -} - -void LLXUIParser::parserError(const std::string& message) -{ - std::string error_msg = llformat("%s:\t%s(%d)", message.c_str(), mCurFileName.c_str(), mCurReadNode->getLineNumber()); - Parser::parserError(error_msg); -} - - -// -// LLSimpleXUIParser -// - -struct ScopedFile -{ - ScopedFile( const std::string& filename, const char* accessmode ) - { - mFile = LLFile::fopen(filename, accessmode); - } - - ~ScopedFile() - { - fclose(mFile); - mFile = NULL; - } - - S32 getRemainingBytes() - { - if (!isOpen()) return 0; - - S32 cur_pos = ftell(mFile); - fseek(mFile, 0L, SEEK_END); - S32 file_size = ftell(mFile); - fseek(mFile, cur_pos, SEEK_SET); - return file_size - cur_pos; - } - - bool isOpen() { return mFile != NULL; } - - LLFILE* mFile; -}; -LLSimpleXUIParser::LLSimpleXUIParser(LLSimpleXUIParser::element_start_callback_t element_cb) -: Parser(sSimpleXUIReadFuncs, sSimpleXUIWriteFuncs, sSimpleXUIInspectFuncs), - mCurReadDepth(0), - mElementCB(element_cb) -{ - if (sSimpleXUIReadFuncs.empty()) - { - registerParserFuncs(readFlag); - registerParserFuncs(readBoolValue); - registerParserFuncs(readStringValue); - registerParserFuncs(readU8Value); - registerParserFuncs(readS8Value); - registerParserFuncs(readU16Value); - registerParserFuncs(readS16Value); - registerParserFuncs(readU32Value); - registerParserFuncs(readS32Value); - registerParserFuncs(readF32Value); - registerParserFuncs(readF64Value); - registerParserFuncs(readColor4Value); - registerParserFuncs(readUIColorValue); - registerParserFuncs(readUUIDValue); - registerParserFuncs(readSDValue); - } -} - -LLSimpleXUIParser::~LLSimpleXUIParser() -{ -} - - -bool LLSimpleXUIParser::readXUI(const std::string& filename, LLInitParam::BaseBlock& block, bool silent) -{ - LL_RECORD_BLOCK_TIME(FTM_PARSE_XUI); - - mParser = XML_ParserCreate(NULL); - XML_SetUserData(mParser, this); - XML_SetElementHandler( mParser, startElementHandler, endElementHandler); - XML_SetCharacterDataHandler( mParser, characterDataHandler); - - mOutputStack.push_back(std::make_pair(&block, 0)); - mNameStack.clear(); - mCurFileName = filename; - mCurReadDepth = 0; - setParseSilently(silent); - - ScopedFile file(filename, "rb"); - if( !file.isOpen() ) - { - LL_WARNS("ReadXUI") << "Unable to open file " << filename << LL_ENDL; - XML_ParserFree( mParser ); - return false; - } - - S32 bytes_read = 0; - - S32 buffer_size = file.getRemainingBytes(); - void* buffer = XML_GetBuffer(mParser, buffer_size); - if( !buffer ) - { - LL_WARNS("ReadXUI") << "Unable to allocate XML buffer while reading file " << filename << LL_ENDL; - XML_ParserFree( mParser ); - return false; - } - - bytes_read = (S32)fread(buffer, 1, buffer_size, file.mFile); - if( bytes_read <= 0 ) - { - LL_WARNS("ReadXUI") << "Error while reading file " << filename << LL_ENDL; - XML_ParserFree( mParser ); - return false; - } - - mEmptyLeafNode.push_back(false); - - if( !XML_ParseBuffer(mParser, bytes_read, true ) ) - { - LL_WARNS("ReadXUI") << "Error while parsing file " << filename << LL_ENDL; - XML_ParserFree( mParser ); - return false; - } - - mEmptyLeafNode.pop_back(); - - XML_ParserFree( mParser ); - return true; -} - -void LLSimpleXUIParser::startElementHandler(void *userData, const char *name, const char **atts) -{ - LLSimpleXUIParser* self = reinterpret_cast(userData); - self->startElement(name, atts); -} - -void LLSimpleXUIParser::endElementHandler(void *userData, const char *name) -{ - LLSimpleXUIParser* self = reinterpret_cast(userData); - self->endElement(name); -} - -void LLSimpleXUIParser::characterDataHandler(void *userData, const char *s, int len) -{ - LLSimpleXUIParser* self = reinterpret_cast(userData); - self->characterData(s, len); -} - -void LLSimpleXUIParser::characterData(const char *s, int len) -{ - mTextContents += std::string(s, len); -} - -void LLSimpleXUIParser::startElement(const char *name, const char **atts) -{ - processText(); - - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("."); - - if (mElementCB) - { - LLInitParam::BaseBlock* blockp = mElementCB(*this, name); - if (blockp) - { - mOutputStack.push_back(std::make_pair(blockp, 0)); - } - } - - mOutputStack.back().second++; - S32 num_tokens_pushed = 0; - std::string child_name(name); - - if (mOutputStack.back().second == 1) - { // root node for this block - mScope.push_back(child_name); - } - else - { // compound attribute - if (child_name.find(".") == std::string::npos) - { - mNameStack.push_back(std::make_pair(child_name, true)); - num_tokens_pushed++; - mScope.push_back(child_name); - } - else - { - // parse out "dotted" name into individual tokens - tokenizer name_tokens(child_name, sep); - - tokenizer::iterator name_token_it = name_tokens.begin(); - if(name_token_it == name_tokens.end()) - { - return; - } - - // check for proper nesting - if(!mScope.empty() && *name_token_it != mScope.back()) - { - return; - } - - // now ignore first token - ++name_token_it; - - // copy remaining tokens on to our running token list - for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) - { - mNameStack.push_back(std::make_pair(*token_to_push, true)); - num_tokens_pushed++; - } - mScope.push_back(mNameStack.back().first); - } - } - - // parent node is not empty - mEmptyLeafNode.back() = false; - // we are empty if we have no attributes - mEmptyLeafNode.push_back(atts[0] == NULL); - - mTokenSizeStack.push_back(num_tokens_pushed); - readAttributes(atts); - -} - -void LLSimpleXUIParser::endElement(const char *name) -{ - bool has_text = processText(); - - // no text, attributes, or children - if (!has_text && mEmptyLeafNode.back()) - { - // submit this as a valueless name (even though there might be text contents we haven't seen yet) - mCurAttributeValueBegin = NO_VALUE_MARKER; - mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); - } - - if (--mOutputStack.back().second == 0) - { - if (mOutputStack.empty()) - { - LL_ERRS("ReadXUI") << "Parameter block output stack popped while empty." << LL_ENDL; - } - mOutputStack.pop_back(); - } - - S32 num_tokens_to_pop = mTokenSizeStack.back(); - mTokenSizeStack.pop_back(); - while(num_tokens_to_pop-- > 0) - { - mNameStack.pop_back(); - } - mScope.pop_back(); - mEmptyLeafNode.pop_back(); -} - -bool LLSimpleXUIParser::readAttributes(const char **atts) -{ - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("."); - - bool any_parsed = false; - for(S32 i = 0; atts[i] && atts[i+1]; i += 2 ) - { - std::string attribute_name(atts[i]); - mCurAttributeValueBegin = atts[i+1]; - - S32 num_tokens_pushed = 0; - tokenizer name_tokens(attribute_name, sep); - // copy remaining tokens on to our running token list - for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) - { - mNameStack.push_back(std::make_pair(*token_to_push, true)); - num_tokens_pushed++; - } - - // child nodes are not necessarily valid attributes, so don't complain once we've recursed - any_parsed |= mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); - - while(num_tokens_pushed-- > 0) - { - mNameStack.pop_back(); - } - } - return any_parsed; -} - -bool LLSimpleXUIParser::processText() -{ - if (!mTextContents.empty()) - { - LLStringUtil::trim(mTextContents); - if (!mTextContents.empty()) - { - mNameStack.push_back(std::make_pair(std::string("value"), true)); - mCurAttributeValueBegin = mTextContents.c_str(); - mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); - mNameStack.pop_back(); - } - mTextContents.clear(); - return true; - } - return false; -} - -/*virtual*/ std::string LLSimpleXUIParser::getCurrentElementName() -{ - std::string full_name; - for (name_stack_t::iterator it = mNameStack.begin(); - it != mNameStack.end(); - ++it) - { - full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." - } - - return full_name; -} - -void LLSimpleXUIParser::parserWarning(const std::string& message) -{ - std::string warning_msg = llformat("%s:\t%s", message.c_str(), mCurFileName.c_str()); - Parser::parserWarning(warning_msg); -} - -void LLSimpleXUIParser::parserError(const std::string& message) -{ - std::string error_msg = llformat("%s:\t%s", message.c_str(), mCurFileName.c_str()); - Parser::parserError(error_msg); -} - -bool LLSimpleXUIParser::readFlag(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return self.mCurAttributeValueBegin == NO_VALUE_MARKER; -} - -bool LLSimpleXUIParser::readBoolValue(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - if (!strcmp(self.mCurAttributeValueBegin, "true")) - { - *((bool*)val_ptr) = true; - return true; - } - else if (!strcmp(self.mCurAttributeValueBegin, "false")) - { - *((bool*)val_ptr) = false; - return true; - } - - return false; -} - -bool LLSimpleXUIParser::readStringValue(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - *((std::string*)val_ptr) = self.mCurAttributeValueBegin; - return true; -} - -bool LLSimpleXUIParser::readU8Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U8*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readS8Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S8*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readU16Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U16*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readS16Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S16*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readU32Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U32*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readS32Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S32*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readF32Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F32*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readF64Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F64*)val_ptr)]).full; -} - -bool LLSimpleXUIParser::readColor4Value(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - LLColor4 value; - - if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) - { - *(LLColor4*)(val_ptr) = value; - return true; - } - return false; -} - -bool LLSimpleXUIParser::readUIColorValue(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - LLColor4 value; - LLUIColor* colorp = (LLUIColor*)val_ptr; - - if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) - { - colorp->set(value); - return true; - } - return false; -} - -bool LLSimpleXUIParser::readUUIDValue(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - LLUUID temp_id; - // LLUUID::set is destructive, so use temporary value - if (temp_id.set(std::string(self.mCurAttributeValueBegin))) - { - *(LLUUID*)(val_ptr) = temp_id; - return true; - } - return false; -} - -bool LLSimpleXUIParser::readSDValue(Parser& parser, void* val_ptr) -{ - LLSimpleXUIParser& self = static_cast(parser); - *((LLSD*)val_ptr) = LLSD(self.mCurAttributeValueBegin); - return true; -} +/** + * @file llxuiparser.cpp + * @brief Utility functions for handling XUI structures in XML + * + * $LicenseInfo:firstyear=2003&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 "llxuiparser.h" + +#include "llxmlnode.h" +#include "llfasttimer.h" +#ifdef LL_USESYSTEMLIBS +#include +#else +#include "expat/expat.h" +#endif + +#include +#include +#include +//#include +#include + +#include "lluicolor.h" +#include "v3math.h" +using namespace BOOST_SPIRIT_CLASSIC_NS; + +const S32 MAX_STRING_ATTRIBUTE_SIZE = 40; + +static LLInitParam::Parser::parser_read_func_map_t sXSDReadFuncs; +static LLInitParam::Parser::parser_write_func_map_t sXSDWriteFuncs; +static LLInitParam::Parser::parser_inspect_func_map_t sXSDInspectFuncs; + +static LLInitParam::Parser::parser_read_func_map_t sSimpleXUIReadFuncs; +static LLInitParam::Parser::parser_write_func_map_t sSimpleXUIWriteFuncs; +static LLInitParam::Parser::parser_inspect_func_map_t sSimpleXUIInspectFuncs; + +const char* NO_VALUE_MARKER = "no_value"; + +struct MaxOccursValues : public LLInitParam::TypeValuesHelper +{ + static void declareValues() + { + declare("unbounded", U32_MAX); + } +}; + +struct Occurs : public LLInitParam::Block +{ + Optional minOccurs; + Optional maxOccurs; + + Occurs() + : minOccurs("minOccurs", 0), + maxOccurs("maxOccurs", U32_MAX) + + {} +}; + +typedef enum +{ + USE_REQUIRED, + USE_OPTIONAL +} EUse; + +namespace LLInitParam +{ + template<> + struct TypeValues : public TypeValuesHelper + { + static void declareValues() + { + declare("required", USE_REQUIRED); + declare("optional", USE_OPTIONAL); + } + }; +} + +struct Element; +struct Group; +struct Sequence; + +struct All : public LLInitParam::Block +{ + Multiple< Lazy > elements; + + All() + : elements("element") + { + maxOccurs = 1; + } +}; + +struct Attribute : public LLInitParam::Block +{ + Mandatory name, + type; + Mandatory use; + + Attribute() + : name("name"), + type("type"), + use("use") + {} +}; + +struct Any : public LLInitParam::Block +{ + Optional _namespace; + + Any() + : _namespace("namespace") + {} +}; + +struct Choice : public LLInitParam::ChoiceBlock +{ + Alternative< Lazy > element; + Alternative< Lazy > group; + Alternative< Lazy > choice; + Alternative< Lazy > sequence; + Alternative< Lazy > any; + + Choice() + : element("element"), + group("group"), + choice("choice"), + sequence("sequence"), + any("any") + {} + +}; + +struct Sequence : public LLInitParam::ChoiceBlock +{ + Alternative< Lazy > element; + Alternative< Lazy > group; + Alternative< Lazy > choice; + Alternative< Lazy > sequence; + Alternative< Lazy > any; +}; + +struct GroupContents : public LLInitParam::ChoiceBlock +{ + Alternative all; + Alternative choice; + Alternative sequence; + + GroupContents() + : all("all"), + choice("choice"), + sequence("sequence") + {} +}; + +struct Group : public LLInitParam::Block +{ + Optional name, + ref; + + Group() + : name("name"), + ref("ref") + {} +}; + +struct Restriction : public LLInitParam::Block +{ +}; + +struct Extension : public LLInitParam::Block +{ +}; + +struct SimpleContent : public LLInitParam::ChoiceBlock +{ + Alternative restriction; + Alternative extension; + + SimpleContent() + : restriction("restriction"), + extension("extension") + {} +}; + +struct SimpleType : public LLInitParam::Block +{ + // TODO +}; + +struct ComplexContent : public LLInitParam::Block +{ + Optional mixed; + + ComplexContent() + : mixed("mixed", true) + {} +}; + +struct ComplexTypeContents : public LLInitParam::ChoiceBlock +{ + Alternative simple_content; + Alternative complex_content; + Alternative group; + Alternative all; + Alternative choice; + Alternative sequence; + + ComplexTypeContents() + : simple_content("simpleContent"), + complex_content("complexContent"), + group("group"), + all("all"), + choice("choice"), + sequence("sequence") + {} +}; + +struct ComplexType : public LLInitParam::Block +{ + Optional name; + Optional mixed; + + Multiple attribute; + Multiple< Lazy > elements; + + ComplexType() + : name("name"), + attribute("xs:attribute"), + elements("xs:element"), + mixed("mixed") + { + } +}; + +struct ElementContents : public LLInitParam::ChoiceBlock +{ + Alternative simpleType; + Alternative complexType; + + ElementContents() + : simpleType("simpleType"), + complexType("complexType") + {} +}; + +struct Element : public LLInitParam::Block +{ + Optional name, + ref, + type; + + Element() + : name("xs:name"), + ref("xs:ref"), + type("xs:type") + {} +}; + +struct Schema : public LLInitParam::Block +{ +private: + Mandatory targetNamespace, + xmlns, + xs; + +public: + Optional attributeFormDefault, + elementFormDefault; + + Mandatory root_element; + + void setNameSpace(const std::string& ns) {targetNamespace = ns; xmlns = ns;} + + Schema(const std::string& ns = LLStringUtil::null) + : attributeFormDefault("attributeFormDefault"), + elementFormDefault("elementFormDefault"), + xs("xmlns:xs"), + targetNamespace("targetNamespace"), + xmlns("xmlns"), + root_element("xs:element") + { + attributeFormDefault = "unqualified"; + elementFormDefault = "qualified"; + xs = "http://www.w3.org/2001/XMLSchema"; + if (!ns.empty()) + { + setNameSpace(ns); + }; + } +}; + +// +// LLXSDWriter +// +LLXSDWriter::LLXSDWriter() +: Parser(sXSDReadFuncs, sXSDWriteFuncs, sXSDInspectFuncs) +{ + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:boolean", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedByte", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedByte", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedShort", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:signedShort", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:unsignedInt", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:integer", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:float", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:double", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); + registerInspectFunc(boost::bind(&LLXSDWriter::writeAttribute, this, "xs:string", _1, _2, _3, _4)); +} + +LLXSDWriter::~LLXSDWriter() {} + +void LLXSDWriter::writeXSD(const std::string& type_name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace) +{ + Schema schema(xml_namespace); + + schema.root_element.name = type_name; + Choice& choice = schema.root_element.complexType.choice; + + choice.minOccurs = 0; + choice.maxOccurs = "unbounded"; + + mSchemaNode = node; + //node->setName("xs:schema"); + //node->createChild("attributeFormDefault", true)->setStringValue("unqualified"); + //node->createChild("elementFormDefault", true)->setStringValue("qualified"); + //node->createChild("targetNamespace", true)->setStringValue(xml_namespace); + //node->createChild("xmlns:xs", true)->setStringValue("http://www.w3.org/2001/XMLSchema"); + //node->createChild("xmlns", true)->setStringValue(xml_namespace); + + //node = node->createChild("xs:complexType", false); + //node->createChild("name", true)->setStringValue(type_name); + //node->createChild("mixed", true)->setStringValue("true"); + + //mAttributeNode = node; + //mElementNode = node->createChild("xs:choice", false); + //mElementNode->createChild("minOccurs", true)->setStringValue("0"); + //mElementNode->createChild("maxOccurs", true)->setStringValue("unbounded"); + block.inspectBlock(*this); + + // duplicate element choices + LLXMLNodeList children; + mElementNode->getChildren("xs:element", children, false); + for (LLXMLNodeList::iterator child_it = children.begin(); child_it != children.end(); ++child_it) + { + LLXMLNodePtr child_copy = child_it->second->deepCopy(); + std::string child_name; + child_copy->getAttributeString("name", child_name); + child_copy->setAttributeString("name", type_name + "." + child_name); + mElementNode->addChild(child_copy); + } + + LLXMLNodePtr element_declaration_node = mSchemaNode->createChild("xs:element", false); + element_declaration_node->createChild("name", true)->setStringValue(type_name); + element_declaration_node->createChild("type", true)->setStringValue(type_name); +} + +void LLXSDWriter::writeAttribute(const std::string& type, const Parser::name_stack_t& stack, S32 min_count, S32 max_count, const std::vector* possible_values) +{ + name_stack_t non_empty_names; + std::string attribute_name; + for (name_stack_t::const_iterator it = stack.begin(); + it != stack.end(); + ++it) + { + const std::string& name = it->first; + if (!name.empty()) + { + non_empty_names.push_back(*it); + } + } + + for (name_stack_t::const_iterator it = non_empty_names.begin(); + it != non_empty_names.end(); + ++it) + { + if (!attribute_name.empty()) + { + attribute_name += "."; + } + attribute_name += it->first; + } + + // only flag non-nested attributes as mandatory, nested attributes have variant syntax + // that can't be properly constrained in XSD + // e.g. vs + bool attribute_mandatory = min_count == 1 && max_count == 1 && non_empty_names.size() == 1; + + // don't bother supporting "Multiple" params as xml attributes + if (max_count <= 1) + { + // add compound attribute to root node + addAttributeToSchema(mAttributeNode, attribute_name, type, attribute_mandatory, possible_values); + } + + // now generated nested elements for compound attributes + if (non_empty_names.size() > 1 && !attribute_mandatory) + { + std::string element_name; + + // traverse all but last element, leaving that as an attribute name + name_stack_t::const_iterator end_it = non_empty_names.end(); + end_it--; + + for (name_stack_t::const_iterator it = non_empty_names.begin(); + it != end_it; + ++it) + { + if (it != non_empty_names.begin()) + { + element_name += "."; + } + element_name += it->first; + } + + std::string short_attribute_name = non_empty_names.back().first; + + LLXMLNodePtr complex_type_node; + + // find existing element node here, starting at tail of child list + if (mElementNode->mChildren.notNull()) + { + for(LLXMLNodePtr element = mElementNode->mChildren->tail; + element.notNull(); + element = element->mPrev) + { + std::string name; + if(element->getAttributeString("name", name) && name == element_name) + { + complex_type_node = element->mChildren->head; + break; + } + } + } + //create complex_type node + // + // + // + // + // + if(complex_type_node.isNull()) + { + complex_type_node = mElementNode->createChild("xs:element", false); + + complex_type_node->createChild("minOccurs", true)->setIntValue(min_count); + complex_type_node->createChild("maxOccurs", true)->setIntValue(max_count); + complex_type_node->createChild("name", true)->setStringValue(element_name); + complex_type_node = complex_type_node->createChild("xs:complexType", false); + } + + addAttributeToSchema(complex_type_node, short_attribute_name, type, false, possible_values); + } +} + +void LLXSDWriter::addAttributeToSchema(LLXMLNodePtr type_declaration_node, const std::string& attribute_name, const std::string& type, bool mandatory, const std::vector* possible_values) +{ + if (!attribute_name.empty()) + { + LLXMLNodePtr new_enum_type_node; + if (possible_values != NULL) + { + // custom attribute type, for example + // + // + // + // + // + // + new_enum_type_node = new LLXMLNode("xs:simpleType", false); + + LLXMLNodePtr restriction_node = new_enum_type_node->createChild("xs:restriction", false); + restriction_node->createChild("base", true)->setStringValue("xs:string"); + + for (std::vector::const_iterator it = possible_values->begin(); + it != possible_values->end(); + ++it) + { + LLXMLNodePtr enum_node = restriction_node->createChild("xs:enumeration", false); + enum_node->createChild("value", true)->setStringValue(*it); + } + } + + string_set_t& attributes_written = mAttributesWritten[type_declaration_node]; + + string_set_t::iterator found_it = attributes_written.lower_bound(attribute_name); + + // attribute not yet declared + if (found_it == attributes_written.end() || attributes_written.key_comp()(attribute_name, *found_it)) + { + attributes_written.insert(found_it, attribute_name); + + LLXMLNodePtr attribute_node = type_declaration_node->createChild("xs:attribute", false); + + // attribute name + attribute_node->createChild("name", true)->setStringValue(attribute_name); + + if (new_enum_type_node.notNull()) + { + attribute_node->addChild(new_enum_type_node); + } + else + { + // simple attribute type + attribute_node->createChild("type", true)->setStringValue(type); + } + + // required or optional + attribute_node->createChild("use", true)->setStringValue(mandatory ? "required" : "optional"); + } + // attribute exists...handle collision of same name attributes with potentially different types + else + { + LLXMLNodePtr attribute_declaration; + if (type_declaration_node.notNull()) + { + for(LLXMLNodePtr node = type_declaration_node->mChildren->tail; + node.notNull(); + node = node->mPrev) + { + std::string name; + if (node->getAttributeString("name", name) && name == attribute_name) + { + attribute_declaration = node; + break; + } + } + } + + bool new_type_is_enum = new_enum_type_node.notNull(); + bool existing_type_is_enum = !attribute_declaration->hasAttribute("type"); + + // either type is enum, revert to string in collision + // don't bother to check for enum equivalence + if (new_type_is_enum || existing_type_is_enum) + { + if (attribute_declaration->hasAttribute("type")) + { + attribute_declaration->setAttributeString("type", "xs:string"); + } + else + { + attribute_declaration->createChild("type", true)->setStringValue("xs:string"); + } + attribute_declaration->deleteChildren("xs:simpleType"); + } + else + { + // check for collision of different standard types + std::string existing_type; + attribute_declaration->getAttributeString("type", existing_type); + // if current type is not the same as the new type, revert to strnig + if (existing_type != type) + { + // ...than use most general type, string + attribute_declaration->setAttributeString("type", "string"); + } + } + } + } +} + +// +// LLXUIXSDWriter +// +void LLXUIXSDWriter::writeXSD(const std::string& type_name, const std::string& path, const LLInitParam::BaseBlock& block) +{ + std::string file_name(path); + file_name += type_name + ".xsd"; + LLXMLNodePtr root_nodep = new LLXMLNode(); + + LLXSDWriter::writeXSD(type_name, root_nodep, block, "http://www.lindenlab.com/xui"); + + // add includes for all possible children + const std::type_info* type = *LLWidgetTypeRegistry::instance().getValue(type_name); + const widget_registry_t* widget_registryp = LLChildRegistryRegistry::instance().getValue(type); + + // add choices for valid children + if (widget_registryp) + { + // add include declarations for all valid children + for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); + it != widget_registryp->currentRegistrar().endItems(); + ++it) + { + std::string widget_name = it->first; + if (widget_name == type_name) + { + continue; + } + LLXMLNodePtr nodep = new LLXMLNode("xs:include", false); + nodep->createChild("schemaLocation", true)->setStringValue(widget_name + ".xsd"); + + // add to front of schema + mSchemaNode->addChild(nodep); + } + + for (widget_registry_t::Registrar::registry_map_t::const_iterator it = widget_registryp->currentRegistrar().beginItems(); + it != widget_registryp->currentRegistrar().endItems(); + ++it) + { + std::string widget_name = it->first; + // + LLXMLNodePtr widget_node = mElementNode->createChild("xs:element", false); + widget_node->createChild("name", true)->setStringValue(widget_name); + widget_node->createChild("type", true)->setStringValue(widget_name); + } + } + + LLFILE* xsd_file = LLFile::fopen(file_name.c_str(), "w"); + LLXMLNode::writeHeaderToFile(xsd_file); + root_nodep->writeToFile(xsd_file); + fclose(xsd_file); +} + +static LLInitParam::Parser::parser_read_func_map_t sXUIReadFuncs; +static LLInitParam::Parser::parser_write_func_map_t sXUIWriteFuncs; +static LLInitParam::Parser::parser_inspect_func_map_t sXUIInspectFuncs; + +// +// LLXUIParser +// +LLXUIParser::LLXUIParser() +: Parser(sXUIReadFuncs, sXUIWriteFuncs, sXUIInspectFuncs), + mCurReadDepth(0) +{ + if (sXUIReadFuncs.empty()) + { + registerParserFuncs(readFlag, writeFlag); + registerParserFuncs(readBoolValue, writeBoolValue); + registerParserFuncs(readStringValue, writeStringValue); + registerParserFuncs(readU8Value, writeU8Value); + registerParserFuncs(readS8Value, writeS8Value); + registerParserFuncs(readU16Value, writeU16Value); + registerParserFuncs(readS16Value, writeS16Value); + registerParserFuncs(readU32Value, writeU32Value); + registerParserFuncs(readS32Value, writeS32Value); + registerParserFuncs(readF32Value, writeF32Value); + registerParserFuncs(readF64Value, writeF64Value); + registerParserFuncs(readVector3Value, writeVector3Value); + registerParserFuncs(readColor4Value, writeColor4Value); + registerParserFuncs(readUIColorValue, writeUIColorValue); + registerParserFuncs(readUUIDValue, writeUUIDValue); + registerParserFuncs(readSDValue, writeSDValue); + } +} + +static LLTrace::BlockTimerStatHandle FTM_PARSE_XUI("XUI Parsing"); +const LLXMLNodePtr DUMMY_NODE = new LLXMLNode(); + +void LLXUIParser::readXUI(LLXMLNodePtr node, LLInitParam::BaseBlock& block, const std::string& filename, bool silent) +{ + LL_RECORD_BLOCK_TIME(FTM_PARSE_XUI); + mNameStack.clear(); + mRootNodeName = node->getName()->mString; + mCurFileName = filename; + mCurReadDepth = 0; + setParseSilently(silent); + + if (node.isNull()) + { + parserWarning("Invalid node"); + } + else + { + readXUIImpl(node, block); + } +} + +bool LLXUIParser::readXUIImpl(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + bool values_parsed = false; + bool silent = mCurReadDepth > 0; + + if (nodep->getFirstChild().isNull() + && nodep->mAttributes.empty() + && nodep->getSanitizedValue().empty()) + { + // empty node, just parse as flag + mCurReadNode = DUMMY_NODE; + return block.submitValue(mNameStack, *this, silent); + } + + // submit attributes for current node + values_parsed |= readAttributes(nodep, block); + + // treat text contents of xml node as "value" parameter + std::string text_contents = nodep->getSanitizedValue(); + if (!text_contents.empty()) + { + mCurReadNode = nodep; + mNameStack.push_back(std::make_pair(std::string("value"), true)); + // child nodes are not necessarily valid parameters (could be a child widget) + // so don't complain once we've recursed + if (!block.submitValue(mNameStack, *this, true)) + { + mNameStack.pop_back(); + block.submitValue(mNameStack, *this, silent); + } + else + { + mNameStack.pop_back(); + } + } + + // then traverse children + // child node must start with last name of parent node (our "scope") + // for example: "" + // which equates to the following nesting: + // button + // param + // nested_param1 + // nested_param2 + // nested_param3 + mCurReadDepth++; + for(LLXMLNodePtr childp = nodep->getFirstChild(); childp.notNull();) + { + std::string child_name(childp->getName()->mString); + S32 num_tokens_pushed = 0; + + // for non "dotted" child nodes check to see if child node maps to another widget type + // and if not, treat as a child element of the current node + // e.g. will interpret as "button.rect" + // since there is no widget named "rect" + if (child_name.find(".") == std::string::npos) + { + mNameStack.push_back(std::make_pair(child_name, true)); + num_tokens_pushed++; + } + else + { + // parse out "dotted" name into individual tokens + tokenizer name_tokens(child_name, sep); + + tokenizer::iterator name_token_it = name_tokens.begin(); + if(name_token_it == name_tokens.end()) + { + childp = childp->getNextSibling(); + continue; + } + + // check for proper nesting + if (mNameStack.empty()) + { + if (*name_token_it != mRootNodeName) + { + childp = childp->getNextSibling(); + continue; + } + } + else if(mNameStack.back().first != *name_token_it) + { + childp = childp->getNextSibling(); + continue; + } + + // now ignore first token + ++name_token_it; + + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + } + + // recurse and visit children XML nodes + if(readXUIImpl(childp, block)) + { + // child node successfully parsed, remove from DOM + + values_parsed = true; + LLXMLNodePtr node_to_remove = childp; + childp = childp->getNextSibling(); + + nodep->deleteChild(node_to_remove); + } + else + { + childp = childp->getNextSibling(); + } + + while(num_tokens_pushed-- > 0) + { + mNameStack.pop_back(); + } + } + mCurReadDepth--; + return values_parsed; +} + +bool LLXUIParser::readAttributes(LLXMLNodePtr nodep, LLInitParam::BaseBlock& block) +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + bool any_parsed = false; + bool silent = mCurReadDepth > 0; + + for(LLXMLAttribList::const_iterator attribute_it = nodep->mAttributes.begin(); + attribute_it != nodep->mAttributes.end(); + ++attribute_it) + { + S32 num_tokens_pushed = 0; + std::string attribute_name(attribute_it->first->mString); + mCurReadNode = attribute_it->second; + + tokenizer name_tokens(attribute_name, sep); + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + + // child nodes are not necessarily valid attributes, so don't complain once we've recursed + any_parsed |= block.submitValue(mNameStack, *this, silent); + + while(num_tokens_pushed-- > 0) + { + mNameStack.pop_back(); + } + } + + return any_parsed; +} + +void LLXUIParser::writeXUIImpl(LLXMLNodePtr node, const LLInitParam::BaseBlock &block, const LLInitParam::predicate_rule_t rules, const LLInitParam::BaseBlock* diff_block) +{ + mWriteRootNode = node; + name_stack_t name_stack = Parser::name_stack_t(); + block.serializeBlock(*this, name_stack, rules, diff_block); + mOutNodes.clear(); +} + +// go from a stack of names to a specific XML node +LLXMLNodePtr LLXUIParser::getNode(name_stack_t& stack) +{ + LLXMLNodePtr out_node = mWriteRootNode; + + name_stack_t::iterator next_it = stack.begin(); + for (name_stack_t::iterator it = stack.begin(); + it != stack.end(); + it = next_it) + { + ++next_it; + bool force_new_node = false; + + if (it->first.empty()) + { + it->second = false; + continue; + } + + if (next_it != stack.end() && next_it->first.empty() && next_it->second) + { + force_new_node = true; + } + + + out_nodes_t::iterator found_it = mOutNodes.find(it->first); + + // node with this name not yet written + if (found_it == mOutNodes.end() || it->second || force_new_node) + { + // make an attribute if we are the last element on the name stack + bool is_attribute = next_it == stack.end(); + LLXMLNodePtr new_node = new LLXMLNode(it->first.c_str(), is_attribute); + out_node->addChild(new_node); + mOutNodes[it->first] = new_node; + out_node = new_node; + it->second = false; + } + else + { + out_node = found_it->second; + } + } + + return (out_node == mWriteRootNode ? LLXMLNodePtr(NULL) : out_node); +} + +bool LLXUIParser::readFlag(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode == DUMMY_NODE; +} + +bool LLXUIParser::writeFlag(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + // just create node + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + return node.notNull(); +} + +bool LLXUIParser::readBoolValue(Parser& parser, void* val_ptr) +{ + bool value; + LLXUIParser& self = static_cast(parser); + bool success = self.mCurReadNode->getBoolValue(1, &value); + *((bool*)val_ptr) = value; + return success; +} + +bool LLXUIParser::writeBoolValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setBoolValue(*((bool*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readStringValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + *((std::string*)val_ptr) = self.mCurReadNode->getSanitizedValue(); + return true; +} + +bool LLXUIParser::writeStringValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + const std::string* string_val = reinterpret_cast(val_ptr); + if (string_val->find('\n') != std::string::npos + || string_val->size() > MAX_STRING_ATTRIBUTE_SIZE) + { + // don't write strings with newlines into attributes + std::string attribute_name = node->getName()->mString; + LLXMLNodePtr parent_node = node->mParent; + parent_node->deleteChild(node); + // write results in text contents of node + if (attribute_name == "value") + { + // "value" is implicit, just write to parent + node = parent_node; + } + else + { + // create a child that is not an attribute, but with same name + node = parent_node->createChild(attribute_name.c_str(), false); + } + } + node->setStringValue(*string_val); + return true; + } + return false; +} + +bool LLXUIParser::readU8Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getByteValue(1, (U8*)val_ptr); +} + +bool LLXUIParser::writeU8Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setUnsignedValue(*((U8*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readS8Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + S32 value; + if(self.mCurReadNode->getIntValue(1, &value)) + { + *((S8*)val_ptr) = value; + return true; + } + return false; +} + +bool LLXUIParser::writeS8Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setIntValue(*((S8*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readU16Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + U32 value; + if(self.mCurReadNode->getUnsignedValue(1, &value)) + { + *((U16*)val_ptr) = value; + return true; + } + return false; +} + +bool LLXUIParser::writeU16Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setUnsignedValue(*((U16*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readS16Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + S32 value; + if(self.mCurReadNode->getIntValue(1, &value)) + { + *((S16*)val_ptr) = value; + return true; + } + return false; +} + +bool LLXUIParser::writeS16Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setIntValue(*((S16*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readU32Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getUnsignedValue(1, (U32*)val_ptr); +} + +bool LLXUIParser::writeU32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setUnsignedValue(*((U32*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readS32Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getIntValue(1, (S32*)val_ptr); +} + +bool LLXUIParser::writeS32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setIntValue(*((S32*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readF32Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getFloatValue(1, (F32*)val_ptr); +} + +bool LLXUIParser::writeF32Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setFloatValue(*((F32*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readF64Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + return self.mCurReadNode->getDoubleValue(1, (F64*)val_ptr); +} + +bool LLXUIParser::writeF64Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setDoubleValue(*((F64*)val_ptr)); + return true; + } + return false; +} + +bool LLXUIParser::readVector3Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + LLVector3* vecp = (LLVector3*)val_ptr; + if(self.mCurReadNode->getFloatValue(3, vecp->mV) >= 3) + { + return true; + } + + return false; +} + +bool LLXUIParser::writeVector3Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + LLVector3 vector = *((LLVector3*)val_ptr); + node->setFloatValue(3, vector.mV); + return true; + } + return false; +} + +bool LLXUIParser::readColor4Value(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + LLColor4* colorp = (LLColor4*)val_ptr; + if(self.mCurReadNode->getFloatValue(4, colorp->mV) >= 3) + { + return true; + } + + return false; +} + +bool LLXUIParser::writeColor4Value(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + LLColor4 color = *((LLColor4*)val_ptr); + node->setFloatValue(4, color.mV); + return true; + } + return false; +} + +bool LLXUIParser::readUIColorValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + LLUIColor* param = (LLUIColor*)val_ptr; + LLColor4 color; + bool success = self.mCurReadNode->getFloatValue(4, color.mV) >= 3; + if (success) + { + param->set(color); + return true; + } + return false; +} + +bool LLXUIParser::writeUIColorValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + LLUIColor color = *((LLUIColor*)val_ptr); + //RN: don't write out the color that is represented by a function + // rely on param block exporting to get the reference to the color settings + if (color.isReference()) return false; + node->setFloatValue(4, color.get().mV); + return true; + } + return false; +} + +bool LLXUIParser::readUUIDValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + LLUUID temp_id; + // LLUUID::set is destructive, so use temporary value + if (temp_id.set(self.mCurReadNode->getSanitizedValue())) + { + *(LLUUID*)(val_ptr) = temp_id; + return true; + } + return false; +} + +bool LLXUIParser::writeUUIDValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + node->setStringValue(((LLUUID*)val_ptr)->asString()); + return true; + } + return false; +} + +bool LLXUIParser::readSDValue(Parser& parser, void* val_ptr) +{ + LLXUIParser& self = static_cast(parser); + *((LLSD*)val_ptr) = LLSD(self.mCurReadNode->getSanitizedValue()); + return true; +} + +bool LLXUIParser::writeSDValue(Parser& parser, const void* val_ptr, name_stack_t& stack) +{ + LLXUIParser& self = static_cast(parser); + + LLXMLNodePtr node = self.getNode(stack); + if (node.notNull()) + { + std::string string_val = ((LLSD*)val_ptr)->asString(); + if (string_val.find('\n') != std::string::npos || string_val.size() > MAX_STRING_ATTRIBUTE_SIZE) + { + // don't write strings with newlines into attributes + std::string attribute_name = node->getName()->mString; + LLXMLNodePtr parent_node = node->mParent; + parent_node->deleteChild(node); + // write results in text contents of node + if (attribute_name == "value") + { + // "value" is implicit, just write to parent + node = parent_node; + } + else + { + node = parent_node->createChild(attribute_name.c_str(), false); + } + } + + node->setStringValue(string_val); + return true; + } + return false; +} + +/*virtual*/ std::string LLXUIParser::getCurrentElementName() +{ + std::string full_name; + for (name_stack_t::iterator it = mNameStack.begin(); + it != mNameStack.end(); + ++it) + { + full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." + } + + return full_name; +} + +void LLXUIParser::parserWarning(const std::string& message) +{ + std::string warning_msg = llformat("%s:\t%s(%d)", message.c_str(), mCurFileName.c_str(), mCurReadNode->getLineNumber()); + Parser::parserWarning(warning_msg); +} + +void LLXUIParser::parserError(const std::string& message) +{ + std::string error_msg = llformat("%s:\t%s(%d)", message.c_str(), mCurFileName.c_str(), mCurReadNode->getLineNumber()); + Parser::parserError(error_msg); +} + + +// +// LLSimpleXUIParser +// + +struct ScopedFile +{ + ScopedFile( const std::string& filename, const char* accessmode ) + { + mFile = LLFile::fopen(filename, accessmode); + } + + ~ScopedFile() + { + fclose(mFile); + mFile = NULL; + } + + S32 getRemainingBytes() + { + if (!isOpen()) return 0; + + S32 cur_pos = ftell(mFile); + fseek(mFile, 0L, SEEK_END); + S32 file_size = ftell(mFile); + fseek(mFile, cur_pos, SEEK_SET); + return file_size - cur_pos; + } + + bool isOpen() { return mFile != NULL; } + + LLFILE* mFile; +}; +LLSimpleXUIParser::LLSimpleXUIParser(LLSimpleXUIParser::element_start_callback_t element_cb) +: Parser(sSimpleXUIReadFuncs, sSimpleXUIWriteFuncs, sSimpleXUIInspectFuncs), + mCurReadDepth(0), + mElementCB(element_cb) +{ + if (sSimpleXUIReadFuncs.empty()) + { + registerParserFuncs(readFlag); + registerParserFuncs(readBoolValue); + registerParserFuncs(readStringValue); + registerParserFuncs(readU8Value); + registerParserFuncs(readS8Value); + registerParserFuncs(readU16Value); + registerParserFuncs(readS16Value); + registerParserFuncs(readU32Value); + registerParserFuncs(readS32Value); + registerParserFuncs(readF32Value); + registerParserFuncs(readF64Value); + registerParserFuncs(readColor4Value); + registerParserFuncs(readUIColorValue); + registerParserFuncs(readUUIDValue); + registerParserFuncs(readSDValue); + } +} + +LLSimpleXUIParser::~LLSimpleXUIParser() +{ +} + + +bool LLSimpleXUIParser::readXUI(const std::string& filename, LLInitParam::BaseBlock& block, bool silent) +{ + LL_RECORD_BLOCK_TIME(FTM_PARSE_XUI); + + mParser = XML_ParserCreate(NULL); + XML_SetUserData(mParser, this); + XML_SetElementHandler( mParser, startElementHandler, endElementHandler); + XML_SetCharacterDataHandler( mParser, characterDataHandler); + + mOutputStack.push_back(std::make_pair(&block, 0)); + mNameStack.clear(); + mCurFileName = filename; + mCurReadDepth = 0; + setParseSilently(silent); + + ScopedFile file(filename, "rb"); + if( !file.isOpen() ) + { + LL_WARNS("ReadXUI") << "Unable to open file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + S32 bytes_read = 0; + + S32 buffer_size = file.getRemainingBytes(); + void* buffer = XML_GetBuffer(mParser, buffer_size); + if( !buffer ) + { + LL_WARNS("ReadXUI") << "Unable to allocate XML buffer while reading file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + bytes_read = (S32)fread(buffer, 1, buffer_size, file.mFile); + if( bytes_read <= 0 ) + { + LL_WARNS("ReadXUI") << "Error while reading file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + mEmptyLeafNode.push_back(false); + + if( !XML_ParseBuffer(mParser, bytes_read, true ) ) + { + LL_WARNS("ReadXUI") << "Error while parsing file " << filename << LL_ENDL; + XML_ParserFree( mParser ); + return false; + } + + mEmptyLeafNode.pop_back(); + + XML_ParserFree( mParser ); + return true; +} + +void LLSimpleXUIParser::startElementHandler(void *userData, const char *name, const char **atts) +{ + LLSimpleXUIParser* self = reinterpret_cast(userData); + self->startElement(name, atts); +} + +void LLSimpleXUIParser::endElementHandler(void *userData, const char *name) +{ + LLSimpleXUIParser* self = reinterpret_cast(userData); + self->endElement(name); +} + +void LLSimpleXUIParser::characterDataHandler(void *userData, const char *s, int len) +{ + LLSimpleXUIParser* self = reinterpret_cast(userData); + self->characterData(s, len); +} + +void LLSimpleXUIParser::characterData(const char *s, int len) +{ + mTextContents += std::string(s, len); +} + +void LLSimpleXUIParser::startElement(const char *name, const char **atts) +{ + processText(); + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + if (mElementCB) + { + LLInitParam::BaseBlock* blockp = mElementCB(*this, name); + if (blockp) + { + mOutputStack.push_back(std::make_pair(blockp, 0)); + } + } + + mOutputStack.back().second++; + S32 num_tokens_pushed = 0; + std::string child_name(name); + + if (mOutputStack.back().second == 1) + { // root node for this block + mScope.push_back(child_name); + } + else + { // compound attribute + if (child_name.find(".") == std::string::npos) + { + mNameStack.push_back(std::make_pair(child_name, true)); + num_tokens_pushed++; + mScope.push_back(child_name); + } + else + { + // parse out "dotted" name into individual tokens + tokenizer name_tokens(child_name, sep); + + tokenizer::iterator name_token_it = name_tokens.begin(); + if(name_token_it == name_tokens.end()) + { + return; + } + + // check for proper nesting + if(!mScope.empty() && *name_token_it != mScope.back()) + { + return; + } + + // now ignore first token + ++name_token_it; + + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_token_it; token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + mScope.push_back(mNameStack.back().first); + } + } + + // parent node is not empty + mEmptyLeafNode.back() = false; + // we are empty if we have no attributes + mEmptyLeafNode.push_back(atts[0] == NULL); + + mTokenSizeStack.push_back(num_tokens_pushed); + readAttributes(atts); + +} + +void LLSimpleXUIParser::endElement(const char *name) +{ + bool has_text = processText(); + + // no text, attributes, or children + if (!has_text && mEmptyLeafNode.back()) + { + // submit this as a valueless name (even though there might be text contents we haven't seen yet) + mCurAttributeValueBegin = NO_VALUE_MARKER; + mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); + } + + if (--mOutputStack.back().second == 0) + { + if (mOutputStack.empty()) + { + LL_ERRS("ReadXUI") << "Parameter block output stack popped while empty." << LL_ENDL; + } + mOutputStack.pop_back(); + } + + S32 num_tokens_to_pop = mTokenSizeStack.back(); + mTokenSizeStack.pop_back(); + while(num_tokens_to_pop-- > 0) + { + mNameStack.pop_back(); + } + mScope.pop_back(); + mEmptyLeafNode.pop_back(); +} + +bool LLSimpleXUIParser::readAttributes(const char **atts) +{ + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + + bool any_parsed = false; + for(S32 i = 0; atts[i] && atts[i+1]; i += 2 ) + { + std::string attribute_name(atts[i]); + mCurAttributeValueBegin = atts[i+1]; + + S32 num_tokens_pushed = 0; + tokenizer name_tokens(attribute_name, sep); + // copy remaining tokens on to our running token list + for(tokenizer::iterator token_to_push = name_tokens.begin(); token_to_push != name_tokens.end(); ++token_to_push) + { + mNameStack.push_back(std::make_pair(*token_to_push, true)); + num_tokens_pushed++; + } + + // child nodes are not necessarily valid attributes, so don't complain once we've recursed + any_parsed |= mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); + + while(num_tokens_pushed-- > 0) + { + mNameStack.pop_back(); + } + } + return any_parsed; +} + +bool LLSimpleXUIParser::processText() +{ + if (!mTextContents.empty()) + { + LLStringUtil::trim(mTextContents); + if (!mTextContents.empty()) + { + mNameStack.push_back(std::make_pair(std::string("value"), true)); + mCurAttributeValueBegin = mTextContents.c_str(); + mOutputStack.back().first->submitValue(mNameStack, *this, mParseSilently); + mNameStack.pop_back(); + } + mTextContents.clear(); + return true; + } + return false; +} + +/*virtual*/ std::string LLSimpleXUIParser::getCurrentElementName() +{ + std::string full_name; + for (name_stack_t::iterator it = mNameStack.begin(); + it != mNameStack.end(); + ++it) + { + full_name += it->first + "."; // build up dotted names: "button.param.nestedparam." + } + + return full_name; +} + +void LLSimpleXUIParser::parserWarning(const std::string& message) +{ + std::string warning_msg = llformat("%s:\t%s", message.c_str(), mCurFileName.c_str()); + Parser::parserWarning(warning_msg); +} + +void LLSimpleXUIParser::parserError(const std::string& message) +{ + std::string error_msg = llformat("%s:\t%s", message.c_str(), mCurFileName.c_str()); + Parser::parserError(error_msg); +} + +bool LLSimpleXUIParser::readFlag(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return self.mCurAttributeValueBegin == NO_VALUE_MARKER; +} + +bool LLSimpleXUIParser::readBoolValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + if (!strcmp(self.mCurAttributeValueBegin, "true")) + { + *((bool*)val_ptr) = true; + return true; + } + else if (!strcmp(self.mCurAttributeValueBegin, "false")) + { + *((bool*)val_ptr) = false; + return true; + } + + return false; +} + +bool LLSimpleXUIParser::readStringValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + *((std::string*)val_ptr) = self.mCurAttributeValueBegin; + return true; +} + +bool LLSimpleXUIParser::readU8Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U8*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readS8Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S8*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readU16Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U16*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readS16Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S16*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readU32Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, uint_p[assign_a(*(U32*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readS32Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, int_p[assign_a(*(S32*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readF32Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F32*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readF64Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + return parse(self.mCurAttributeValueBegin, real_p[assign_a(*(F64*)val_ptr)]).full; +} + +bool LLSimpleXUIParser::readColor4Value(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + LLColor4 value; + + if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) + { + *(LLColor4*)(val_ptr) = value; + return true; + } + return false; +} + +bool LLSimpleXUIParser::readUIColorValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + LLColor4 value; + LLUIColor* colorp = (LLUIColor*)val_ptr; + + if (parse(self.mCurAttributeValueBegin, real_p[assign_a(value.mV[0])] >> real_p[assign_a(value.mV[1])] >> real_p[assign_a(value.mV[2])] >> real_p[assign_a(value.mV[3])], space_p).full) + { + colorp->set(value); + return true; + } + return false; +} + +bool LLSimpleXUIParser::readUUIDValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + LLUUID temp_id; + // LLUUID::set is destructive, so use temporary value + if (temp_id.set(std::string(self.mCurAttributeValueBegin))) + { + *(LLUUID*)(val_ptr) = temp_id; + return true; + } + return false; +} + +bool LLSimpleXUIParser::readSDValue(Parser& parser, void* val_ptr) +{ + LLSimpleXUIParser& self = static_cast(parser); + *((LLSD*)val_ptr) = LLSD(self.mCurAttributeValueBegin); + return true; +} diff --git a/indra/llui/llxyvector.h b/indra/llui/llxyvector.h index 9dbd5808eb..bc41213c13 100644 --- a/indra/llui/llxyvector.h +++ b/indra/llui/llxyvector.h @@ -1,122 +1,122 @@ -/** -* @file llxyvector.h -* @author Andrey Lihatskiy -* @brief Header file for LLXYVector -* -* $LicenseInfo:firstyear=2001&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2018, 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$ -*/ - -// A control that allows to set two related vector magnitudes by manipulating a single vector on a plane. - -#ifndef LL_LLXYVECTOR_H -#define LL_LLXYVECTOR_H - -#include "lluictrl.h" -#include "llpanel.h" -#include "lltextbox.h" -#include "lllineeditor.h" - -class LLXYVector - : public LLUICtrl -{ -public: - struct Params - : public LLInitParam::Block - { - Optional x_entry; - Optional y_entry; - Optional touch_area; - Optional border; - Optional edit_bar_height; - Optional padding; - Optional label_width; - Optional min_val_x; - Optional max_val_x; - Optional increment_x; - Optional min_val_y; - Optional max_val_y; - Optional increment_y; - Optional arrow_color; - Optional ghost_color; - Optional area_color; - Optional grid_color; - Optional logarithmic; - - Params(); - }; - - - virtual ~LLXYVector(); - /*virtual*/ bool postBuild(); - - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - - virtual void draw(); - - virtual void setValue(const LLSD& value); - void setValue(F32 x, F32 y); - virtual LLSD getValue() const; - -protected: - friend class LLUICtrlFactory; - LLXYVector(const Params&); - void onEditChange(); - -protected: - LLTextBox* mXLabel; - LLTextBox* mYLabel; - LLLineEditor* mXEntry; - LLLineEditor* mYEntry; - LLPanel* mTouchArea; - LLViewBorder* mBorder; - -private: - void update(); - void setValueAndCommit(F32 x, F32 y); - - F32 mValueX; - F32 mValueY; - - F32 mMinValueX; - F32 mMaxValueX; - F32 mIncrementX; - F32 mMinValueY; - F32 mMaxValueY; - F32 mIncrementY; - - U32 mGhostX; - U32 mGhostY; - - LLUIColor mArrowColor; - LLUIColor mGhostColor; - LLUIColor mAreaColor; - LLUIColor mGridColor; - - bool mLogarithmic; - F32 mLogScaleX; - F32 mLogScaleY; -}; - -#endif - +/** +* @file llxyvector.h +* @author Andrey Lihatskiy +* @brief Header file for LLXYVector +* +* $LicenseInfo:firstyear=2001&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2018, 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$ +*/ + +// A control that allows to set two related vector magnitudes by manipulating a single vector on a plane. + +#ifndef LL_LLXYVECTOR_H +#define LL_LLXYVECTOR_H + +#include "lluictrl.h" +#include "llpanel.h" +#include "lltextbox.h" +#include "lllineeditor.h" + +class LLXYVector + : public LLUICtrl +{ +public: + struct Params + : public LLInitParam::Block + { + Optional x_entry; + Optional y_entry; + Optional touch_area; + Optional border; + Optional edit_bar_height; + Optional padding; + Optional label_width; + Optional min_val_x; + Optional max_val_x; + Optional increment_x; + Optional min_val_y; + Optional max_val_y; + Optional increment_y; + Optional arrow_color; + Optional ghost_color; + Optional area_color; + Optional grid_color; + Optional logarithmic; + + Params(); + }; + + + virtual ~LLXYVector(); + /*virtual*/ bool postBuild(); + + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + + virtual void draw(); + + virtual void setValue(const LLSD& value); + void setValue(F32 x, F32 y); + virtual LLSD getValue() const; + +protected: + friend class LLUICtrlFactory; + LLXYVector(const Params&); + void onEditChange(); + +protected: + LLTextBox* mXLabel; + LLTextBox* mYLabel; + LLLineEditor* mXEntry; + LLLineEditor* mYEntry; + LLPanel* mTouchArea; + LLViewBorder* mBorder; + +private: + void update(); + void setValueAndCommit(F32 x, F32 y); + + F32 mValueX; + F32 mValueY; + + F32 mMinValueX; + F32 mMaxValueX; + F32 mIncrementX; + F32 mMinValueY; + F32 mMaxValueY; + F32 mIncrementY; + + U32 mGhostX; + U32 mGhostY; + + LLUIColor mArrowColor; + LLUIColor mGhostColor; + LLUIColor mAreaColor; + LLUIColor mGridColor; + + bool mLogarithmic; + F32 mLogScaleX; + F32 mLogScaleY; +}; + +#endif + diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index 3f0b230a4f..088cdd675b 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -1,935 +1,935 @@ -/** - * @file llurlentry_test.cpp - * @author Martin Reddy - * @brief Unit tests for LLUrlEntry objects - * - * $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 "../llurlentry.h" -#include "../lluictrl.h" -//#include "llurlentry_stub.cpp" -#include "lltut.h" -#include "../lluicolortable.h" -#include "../llrender/lluiimage.h" -#include "../llmessage/llexperiencecache.h" - -#include - -#if LL_WINDOWS -// because something pulls in window and lldxdiag dependencies which in turn need wbemuuid.lib - #pragma comment(lib, "wbemuuid.lib") -#endif - - -// namespace LLExperienceCache -// { -// const LLSD& get( const LLUUID& key) -// { -// static LLSD boo; -// return boo; -// } -// -// void get( const LLUUID& key, callback_slot_t slot ){} -// -// } - -/*==========================================================================*| -typedef std::map settings_map_t; -settings_map_t LLUI::sSettingGroups; - -bool LLControlGroup::getBOOL(const std::string& name) -{ - return false; -} - -LLUIColor LLUIColorTable::getColor(const std::string& name, const LLColor4& default_color) const -{ - return LLUIColor(); -} - -LLUIColor::LLUIColor() : mColorPtr(NULL) {} - -LLUIImage::LLUIImage(const std::string& name, LLPointer image) -{ -} - -LLUIImage::~LLUIImage() -{ -} - -//virtual -S32 LLUIImage::getWidth() const -{ - return 0; -} - -//virtual -S32 LLUIImage::getHeight() const -{ - return 0; -} -|*==========================================================================*/ - -namespace tut -{ - struct LLUrlEntryData - { - }; - - typedef test_group factory; - typedef factory::object object; -} - -namespace -{ - tut::factory tf("LLUrlEntry"); -} - -namespace tut -{ - void testRegex(const std::string &testname, LLUrlEntryBase &entry, - const char *text, const std::string &expected) - { - boost::regex regex = entry.getPattern(); - std::string url = ""; - boost::cmatch result; - bool found = boost::regex_search(text, result, regex); - if (found) - { - S32 start = static_cast(result[0].first - text); - S32 end = static_cast(result[0].second - text); - url = entry.getUrl(std::string(text+start, end-start)); - } - ensure_equals(testname, url, expected); - } - - void dummyCallback(const std::string &url, const std::string &label, const std::string& icon) - { - } - - void testLabel(const std::string &testname, LLUrlEntryBase &entry, - const char *text, const std::string &expected) - { - boost::regex regex = entry.getPattern(); - std::string label = ""; - boost::cmatch result; - bool found = boost::regex_search(text, result, regex); - if (found) - { - S32 start = static_cast(result[0].first - text); - S32 end = static_cast(result[0].second - text); - std::string url = std::string(text+start, end-start); - label = entry.getLabel(url, boost::bind(dummyCallback, _1, _2, _3)); - } - ensure_equals(testname, label, expected); - } - - void testLocation(const std::string &testname, LLUrlEntryBase &entry, - const char *text, const std::string &expected) - { - boost::regex regex = entry.getPattern(); - std::string location = ""; - boost::cmatch result; - bool found = boost::regex_search(text, result, regex); - if (found) - { - S32 start = static_cast(result[0].first - text); - S32 end = static_cast(result[0].second - text); - std::string url = std::string(text+start, end-start); - location = entry.getLocation(url); - } - ensure_equals(testname, location, expected); - } - - - template<> template<> - void object::test<1>() - { - // - // test LLUrlEntryHTTP - standard http Urls - // - LLUrlEntryHTTP url; - - testRegex("no valid url", url, - "htp://slurl.com/", - ""); - - testRegex("simple http (1)", url, - "http://slurl.com/", - "http://slurl.com/"); - - testRegex("simple http (2)", url, - "http://slurl.com", - "http://slurl.com"); - - testRegex("simple http (3)", url, - "http://slurl.com/about.php", - "http://slurl.com/about.php"); - - testRegex("simple https", url, - "https://slurl.com/about.php", - "https://slurl.com/about.php"); - - testRegex("http in text (1)", url, - "XX http://slurl.com/ XX", - "http://slurl.com/"); - - testRegex("http in text (2)", url, - "XX http://slurl.com/about.php XX", - "http://slurl.com/about.php"); - - testRegex("https in text", url, - "XX https://slurl.com/about.php XX", - "https://slurl.com/about.php"); - - testRegex("two http urls", url, - "XX http://slurl.com/about.php http://secondlife.com/ XX", - "http://slurl.com/about.php"); - - testRegex("http url with port and username", url, - "XX http://nobody@slurl.com:80/about.php http://secondlife.com/ XX", - "http://nobody@slurl.com:80/about.php"); - - testRegex("http url with port, username, and query string", url, - "XX http://nobody@slurl.com:80/about.php?title=hi%20there http://secondlife.com/ XX", - "http://nobody@slurl.com:80/about.php?title=hi%20there"); - - // note: terminating commas will be removed by LLUrlRegistry:findUrl() - testRegex("http url with commas in middle and terminating", url, - "XX http://slurl.com/?title=Hi,There, XX", - "http://slurl.com/?title=Hi,There,"); - - // note: terminating periods will be removed by LLUrlRegistry:findUrl() - testRegex("http url with periods in middle and terminating", url, - "XX http://slurl.com/index.php. XX", - "http://slurl.com/index.php."); - - // DEV-19842: Closing parenthesis ")" breaks urls - testRegex("http url with brackets (1)", url, - "XX http://en.wikipedia.org/wiki/JIRA_(software) XX", - "http://en.wikipedia.org/wiki/JIRA_(software)"); - - // DEV-19842: Closing parenthesis ")" breaks urls - testRegex("http url with brackets (2)", url, - "XX http://jira.secondlife.com/secure/attachment/17990/eggy+avs+in+1.21.0+(93713)+public+nightly.jpg XX", - "http://jira.secondlife.com/secure/attachment/17990/eggy+avs+in+1.21.0+(93713)+public+nightly.jpg"); - - // DEV-10353: URLs in chat log terminated incorrectly when newline in chat - testRegex("http url with newlines", url, - "XX\nhttp://www.secondlife.com/\nXX", - "http://www.secondlife.com/"); - - testRegex("http url without tld shouldn't be decorated (1)", url, - "http://test", - ""); - - testRegex("http url without tld shouldn't be decorated (2)", url, - "http://test .com", - ""); - } - - template<> template<> - void object::test<2>() - { - // - // test LLUrlEntryHTTPLabel - wiki-style http Urls with labels - // - LLUrlEntryHTTPLabel url; - - testRegex("invalid wiki url [1]", url, - "[http://www.example.org]", - ""); - - testRegex("invalid wiki url [2]", url, - "[http://www.example.org", - ""); - - testRegex("invalid wiki url [3]", url, - "[http://www.example.org Label", - ""); - - testRegex("example.org with label (spaces)", url, - "[http://www.example.org Text]", - "http://www.example.org"); - - testRegex("example.org with label (tabs)", url, - "[http://www.example.org\t Text]", - "http://www.example.org"); - - testRegex("SL http URL with label", url, - "[http://www.secondlife.com/ Second Life]", - "http://www.secondlife.com/"); - - testRegex("SL https URL with label", url, - "XXX [https://www.secondlife.com/ Second Life] YYY", - "https://www.secondlife.com/"); - - testRegex("SL http URL with label", url, - "[http://www.secondlife.com/?test=Hi%20There Second Life]", - "http://www.secondlife.com/?test=Hi%20There"); - } - - template<> template<> - void object::test<3>() - { - // - // test LLUrlEntrySLURL - second life URLs - // - LLUrlEntrySLURL url; - - testRegex("no valid slurl [1]", url, - "htp://slurl.com/secondlife/Ahern/50/50/50/", - ""); - - testRegex("no valid slurl [2]", url, - "http://slurl.com/secondlife/", - ""); - - testRegex("no valid slurl [3]", url, - "hhtp://slurl.com/secondlife/Ahern/50/FOO/50/", - ""); - - testRegex("Ahern (50,50,50) [1]", url, - "http://slurl.com/secondlife/Ahern/50/50/50/", - "http://slurl.com/secondlife/Ahern/50/50/50/"); - - testRegex("Ahern (50,50,50) [2]", url, - "XXX http://slurl.com/secondlife/Ahern/50/50/50/ XXX", - "http://slurl.com/secondlife/Ahern/50/50/50/"); - - testRegex("Ahern (50,50,50) [3]", url, - "XXX http://slurl.com/secondlife/Ahern/50/50/50 XXX", - "http://slurl.com/secondlife/Ahern/50/50/50"); - - testRegex("Ahern (50,50,50) multicase", url, - "XXX http://SLUrl.com/SecondLife/Ahern/50/50/50/ XXX", - "http://SLUrl.com/SecondLife/Ahern/50/50/50/"); - - testRegex("Ahern (50,50) [1]", url, - "XXX http://slurl.com/secondlife/Ahern/50/50/ XXX", - "http://slurl.com/secondlife/Ahern/50/50/"); - - testRegex("Ahern (50,50) [2]", url, - "XXX http://slurl.com/secondlife/Ahern/50/50 XXX", - "http://slurl.com/secondlife/Ahern/50/50"); - - testRegex("Ahern (50)", url, - "XXX http://slurl.com/secondlife/Ahern/50 XXX", - "http://slurl.com/secondlife/Ahern/50"); - - testRegex("Ahern", url, - "XXX http://slurl.com/secondlife/Ahern/ XXX", - "http://slurl.com/secondlife/Ahern/"); - - testRegex("Ahern SLURL with title", url, - "XXX http://slurl.com/secondlife/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE! XXX", - "http://slurl.com/secondlife/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE!"); - - testRegex("Ahern SLURL with msg", url, - "XXX http://slurl.com/secondlife/Ahern/50/50/50/?msg=Your%20text%20here. XXX", - "http://slurl.com/secondlife/Ahern/50/50/50/?msg=Your%20text%20here."); - - // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat - testRegex("SLURL with brackets", url, - "XXX http://slurl.com/secondlife/Burning%20Life%20(Hyper)/27/210/30 XXX", - "http://slurl.com/secondlife/Burning%20Life%20(Hyper)/27/210/30"); - - // DEV-35459: SLURLs and teleport Links not parsed properly - testRegex("SLURL with quote", url, - "XXX http://slurl.com/secondlife/A'ksha%20Oasis/41/166/701 XXX", - "http://slurl.com/secondlife/A%27ksha%20Oasis/41/166/701"); - } - - template<> template<> - void object::test<4>() - { - // - // test LLUrlEntryAgent - secondlife://app/agent Urls - // - LLUrlEntryAgent url; - - testRegex("Invalid Agent Url", url, - "secondlife:///app/agent/0e346d8b-4433-4d66-XXXX-fd37083abc4c/about", - ""); - - testRegex("Agent Url ", url, - "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", - "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - - testRegex("Agent Url in text", url, - "XXX secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about XXX", - "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - - testRegex("Agent Url multicase", url, - "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About XXX", - "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About"); - - testRegex("Agent Url alternate command", url, - "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar", - "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar"); - - testRegex("Standalone Agent Url ", url, - "x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", - "x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - - testRegex("Standalone Agent Url Multicase with Text", url, - "M x-grid-location-info://lincoln.lindenlab.com/app/AGENT/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about M", - "x-grid-location-info://lincoln.lindenlab.com/app/AGENT/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - } - - template<> template<> - void object::test<5>() - { - // - // test LLUrlEntryGroup - secondlife://app/group Urls - // - LLUrlEntryGroup url; - - testRegex("Invalid Group Url", url, - "secondlife:///app/group/00005ff3-4044-c79f-XXXX-fb28ae0df991/about", - ""); - - testRegex("Group Url ", url, - "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about", - "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about"); - - testRegex("Group Url ", url, - "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect", - "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect"); - - testRegex("Group Url in text", url, - "XXX secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about XXX", - "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about"); - - testRegex("Group Url multicase", url, - "XXX secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About XXX", - "secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About"); - - testRegex("Standalone Group Url ", url, - "x-grid-location-info://lincoln.lindenlab.com/app/group/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", - "x-grid-location-info://lincoln.lindenlab.com/app/group/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - - testRegex("Standalone Group Url Multicase ith Text", url, - "M x-grid-location-info://lincoln.lindenlab.com/app/GROUP/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about M", - "x-grid-location-info://lincoln.lindenlab.com/app/GROUP/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - - } - - template<> template<> - void object::test<6>() - { - // - // test LLUrlEntryPlace - secondlife:// URLs - // - LLUrlEntryPlace url; - - testRegex("no valid slurl [1]", url, - "secondlife://Ahern/FOO/50/", - ""); - - testRegex("Ahern (50,50,50) [1]", url, - "secondlife://Ahern/50/50/50/", - "secondlife://Ahern/50/50/50/"); - - testRegex("Ahern (50,50,50) [2]", url, - "XXX secondlife://Ahern/50/50/50/ XXX", - "secondlife://Ahern/50/50/50/"); - - testRegex("Ahern (50,50,50) [3]", url, - "XXX secondlife://Ahern/50/50/50 XXX", - "secondlife://Ahern/50/50/50"); - - testRegex("Ahern (50,50,50) multicase", url, - "XXX SecondLife://Ahern/50/50/50/ XXX", - "SecondLife://Ahern/50/50/50/"); - - testRegex("Ahern (50,50) [1]", url, - "XXX secondlife://Ahern/50/50/ XXX", - "secondlife://Ahern/50/50/"); - - testRegex("Ahern (50,50) [2]", url, - "XXX secondlife://Ahern/50/50 XXX", - "secondlife://Ahern/50/50"); - - // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat - testRegex("SLURL with brackets", url, - "XXX secondlife://Burning%20Life%20(Hyper)/27/210/30 XXX", - "secondlife://Burning%20Life%20(Hyper)/27/210/30"); - - // DEV-35459: SLURLs and teleport Links not parsed properly - testRegex("SLURL with quote", url, - "XXX secondlife://A'ksha%20Oasis/41/166/701 XXX", - "secondlife://A%27ksha%20Oasis/41/166/701"); - - testRegex("Standalone All Hands (50,50) [2] with text", url, - "XXX x-grid-location-info://lincoln.lindenlab.com/region/All%20Hands/50/50/50 XXX", - "x-grid-location-info://lincoln.lindenlab.com/region/All%20Hands/50/50/50"); - } - - template<> template<> - void object::test<7>() - { - // - // test LLUrlEntryParcel - secondlife://app/parcel Urls - // - LLUrlEntryParcel url; - - testRegex("Invalid Classified Url", url, - "secondlife:///app/parcel/0000060e-4b39-e00b-XXXX-d98b1934e3a8/about", - ""); - - testRegex("Classified Url ", url, - "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about", - "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about"); - - testRegex("Classified Url in text", url, - "XXX secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about XXX", - "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about"); - - testRegex("Classified Url multicase", url, - "XXX secondlife:///APP/Parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/About XXX", - "secondlife:///APP/Parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/About"); - } - template<> template<> - void object::test<8>() - { - // - // test LLUrlEntryTeleport - secondlife://app/teleport URLs - // - LLUrlEntryTeleport url; - - testRegex("no valid teleport [1]", url, - "http://slurl.com/secondlife/Ahern/50/50/50/", - ""); - - testRegex("no valid teleport [2]", url, - "secondlife:///app/teleport/", - ""); - - testRegex("no valid teleport [3]", url, - "second-life:///app/teleport/Ahern/50/50/50/", - ""); - - testRegex("no valid teleport [3]", url, - "hhtp://slurl.com/secondlife/Ahern/50/FOO/50/", - ""); - - testRegex("Ahern (50,50,50) [1]", url, - "secondlife:///app/teleport/Ahern/50/50/50/", - "secondlife:///app/teleport/Ahern/50/50/50/"); - - testRegex("Ahern (50,50,50) [2]", url, - "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX", - "secondlife:///app/teleport/Ahern/50/50/50/"); - - testRegex("Ahern (50,50,50) [3]", url, - "XXX secondlife:///app/teleport/Ahern/50/50/50 XXX", - "secondlife:///app/teleport/Ahern/50/50/50"); - - testRegex("Ahern (50,50,50) multicase", url, - "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX", - "secondlife:///app/teleport/Ahern/50/50/50/"); - - testRegex("Ahern (50,50) [1]", url, - "XXX secondlife:///app/teleport/Ahern/50/50/ XXX", - "secondlife:///app/teleport/Ahern/50/50/"); - - testRegex("Ahern (50,50) [2]", url, - "XXX secondlife:///app/teleport/Ahern/50/50 XXX", - "secondlife:///app/teleport/Ahern/50/50"); - - testRegex("Ahern (50)", url, - "XXX secondlife:///app/teleport/Ahern/50 XXX", - "secondlife:///app/teleport/Ahern/50"); - - testRegex("Ahern", url, - "XXX secondlife:///app/teleport/Ahern/ XXX", - "secondlife:///app/teleport/Ahern/"); - - testRegex("Ahern teleport with title", url, - "XXX secondlife:///app/teleport/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE! XXX", - "secondlife:///app/teleport/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE!"); - - testRegex("Ahern teleport with msg", url, - "XXX secondlife:///app/teleport/Ahern/50/50/50/?msg=Your%20text%20here. XXX", - "secondlife:///app/teleport/Ahern/50/50/50/?msg=Your%20text%20here."); - - // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat - testRegex("Teleport with brackets", url, - "XXX secondlife:///app/teleport/Burning%20Life%20(Hyper)/27/210/30 XXX", - "secondlife:///app/teleport/Burning%20Life%20(Hyper)/27/210/30"); - - // DEV-35459: SLURLs and teleport Links not parsed properly - testRegex("Teleport url with quote", url, - "XXX secondlife:///app/teleport/A'ksha%20Oasis/41/166/701 XXX", - "secondlife:///app/teleport/A%27ksha%20Oasis/41/166/701"); - - testRegex("Standalone All Hands", url, - "XXX x-grid-location-info://lincoln.lindenlab.com/app/teleport/All%20Hands/50/50/50 XXX", - "x-grid-location-info://lincoln.lindenlab.com/app/teleport/All%20Hands/50/50/50"); - } - - template<> template<> - void object::test<9>() - { - // - // test LLUrlEntrySL - general secondlife:// URLs - // - LLUrlEntrySL url; - - testRegex("no valid slapp [1]", url, - "http:///app/", - ""); - - testRegex("valid slapp [1]", url, - "secondlife:///app/", - "secondlife:///app/"); - - testRegex("valid slapp [2]", url, - "secondlife:///app/teleport/Ahern/50/50/50/", - "secondlife:///app/teleport/Ahern/50/50/50/"); - - testRegex("valid slapp [3]", url, - "secondlife:///app/foo", - "secondlife:///app/foo"); - - testRegex("valid slapp [4]", url, - "secondlife:///APP/foo?title=Hi%20There", - "secondlife:///APP/foo?title=Hi%20There"); - - testRegex("valid slapp [5]", url, - "secondlife://host/app/", - "secondlife://host/app/"); - - testRegex("valid slapp [6]", url, - "secondlife://host:8080/foo/bar", - "secondlife://host:8080/foo/bar"); - } - - template<> template<> - void object::test<10>() - { - // - // test LLUrlEntrySLLabel - general secondlife:// URLs with labels - // - LLUrlEntrySLLabel url; - - testRegex("invalid wiki url [1]", url, - "[secondlife:///app/]", - ""); - - testRegex("invalid wiki url [2]", url, - "[secondlife:///app/", - ""); - - testRegex("invalid wiki url [3]", url, - "[secondlife:///app/ Label", - ""); - - testRegex("agent slurl with label (spaces)", url, - "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about Text]", - "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - - testRegex("agent slurl with label (tabs)", url, - "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about\t Text]", - "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - - testRegex("agent slurl with label", url, - "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about FirstName LastName]", - "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - - testRegex("teleport slurl with label", url, - "XXX [secondlife:///app/teleport/Ahern/50/50/50/ Teleport to Ahern] YYY", - "secondlife:///app/teleport/Ahern/50/50/50/"); - } - - template<> template<> - void object::test<11>() - { - // - // test LLUrlEntryNoLink - turn off hyperlinking - // - LLUrlEntryNoLink url; - - testRegex(" [1]", url, - "google.com", - "google.com"); - - testRegex(" [2]", url, - "google.com", - ""); - - testRegex(" [3]", url, - "google.com", - ""); - - testRegex(" [4]", url, - "Hello World", - "Hello World"); - - testRegex(" [5]", url, - "My Object", - "My Object"); - } - - template<> template<> - void object::test<12>() - { - // - // test LLUrlEntryRegion - secondlife:///app/region/ URLs - // - LLUrlEntryRegion url; - - // Regex tests. - testRegex("no valid region", url, - "secondlife:///app/region/", - ""); - - testRegex("invalid coords", url, - "secondlife:///app/region/Korea2/a/b/c", - "secondlife:///app/region/Korea2/"); // don't count invalid coords - - testRegex("Ahern (50,50,50) [1]", url, - "secondlife:///app/region/Ahern/50/50/50/", - "secondlife:///app/region/Ahern/50/50/50/"); - - testRegex("Ahern (50,50,50) [2]", url, - "XXX secondlife:///app/region/Ahern/50/50/50/ XXX", - "secondlife:///app/region/Ahern/50/50/50/"); - - testRegex("Ahern (50,50,50) [3]", url, - "XXX secondlife:///app/region/Ahern/50/50/50 XXX", - "secondlife:///app/region/Ahern/50/50/50"); - - testRegex("Ahern (50,50,50) multicase", url, - "XXX secondlife:///app/region/Ahern/50/50/50/ XXX", - "secondlife:///app/region/Ahern/50/50/50/"); - - testRegex("Ahern (50,50) [1]", url, - "XXX secondlife:///app/region/Ahern/50/50/ XXX", - "secondlife:///app/region/Ahern/50/50/"); - - testRegex("Ahern (50,50) [2]", url, - "XXX secondlife:///app/region/Ahern/50/50 XXX", - "secondlife:///app/region/Ahern/50/50"); - - // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat - testRegex("Region with brackets", url, - "XXX secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30 XXX", - "secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30"); - - // Rendering tests. - testLabel("Render /app/region/Ahern/50/50/50/", url, - "secondlife:///app/region/Ahern/50/50/50/", - "Ahern (50,50,50)"); - - testLabel("Render /app/region/Ahern/50/50/50", url, - "secondlife:///app/region/Ahern/50/50/50", - "Ahern (50,50,50)"); - - testLabel("Render /app/region/Ahern/50/50/", url, - "secondlife:///app/region/Ahern/50/50/", - "Ahern (50,50)"); - - testLabel("Render /app/region/Ahern/50/50", url, - "secondlife:///app/region/Ahern/50/50", - "Ahern (50,50)"); - - testLabel("Render /app/region/Ahern/50/", url, - "secondlife:///app/region/Ahern/50/", - "Ahern (50)"); - - testLabel("Render /app/region/Ahern/50", url, - "secondlife:///app/region/Ahern/50", - "Ahern (50)"); - - testLabel("Render /app/region/Ahern/", url, - "secondlife:///app/region/Ahern/", - "Ahern"); - - testLabel("Render /app/region/Ahern/ within context", url, - "XXX secondlife:///app/region/Ahern/ XXX", - "Ahern"); - - testLabel("Render /app/region/Ahern", url, - "secondlife:///app/region/Ahern", - "Ahern"); - - testLabel("Render /app/region/Ahern within context", url, - "XXX secondlife:///app/region/Ahern XXX", - "Ahern"); - - testLabel("Render /app/region/Product%20Engine/", url, - "secondlife:///app/region/Product%20Engine/", - "Product Engine"); - - testLabel("Render /app/region/Product%20Engine", url, - "secondlife:///app/region/Product%20Engine", - "Product Engine"); - - // Location parsing texts. - testLocation("Location /app/region/Ahern/50/50/50/", url, - "secondlife:///app/region/Ahern/50/50/50/", - "Ahern"); - - testLocation("Location /app/region/Product%20Engine", url, - "secondlife:///app/region/Product%20Engine", - "Product Engine"); - } - - template<> template<> - void object::test<13>() - { - // - // test LLUrlEntryemail - general emails - // - LLUrlEntryEmail url; - - // Regex tests. - testRegex("match e-mail addresses", url, - "test@lindenlab.com", - "mailto:test@lindenlab.com"); - - testRegex("match e-mail addresses with mailto: prefix", url, - "mailto:test@lindenlab.com", - "mailto:test@lindenlab.com"); - - testRegex("match e-mail addresses with different domains", url, - "test@foo.org.us", - "mailto:test@foo.org.us"); - - testRegex("match e-mail addresses with different domains", url, - "test@foo.bar", - "mailto:test@foo.bar"); - - testRegex("don't match incorrect e-mail addresses", url, - "test @foo.com", - ""); - - testRegex("don't match incorrect e-mail addresses", url, - "test@ foo.com", - ""); - } - - template<> template<> - void object::test<14>() - { - // - // test LLUrlEntrySimpleSecondlifeURL - http://*.secondlife.com/* and http://*lindenlab.com/* urls - // - LLUrlEntrySecondlifeURL url; - - testRegex("match urls with protocol", url, - "this url should match http://lindenlab.com/products/second-life", - "http://lindenlab.com/products/second-life"); - - testRegex("match urls with protocol", url, - "search something https://marketplace.secondlife.com/products/search on marketplace and test the https", - "https://marketplace.secondlife.com/products/search"); - - testRegex("match HTTPS urls with port", url, - "let's specify some port https://secondlife.com:888/status", - "https://secondlife.com:888/status"); - - testRegex("don't match HTTP urls with port", url, - "let's specify some port for HTTP http://secondlife.com:888/status", - ""); - - testRegex("don't match urls w/o protocol", url, - "looks like an url something www.marketplace.secondlife.com/products but no https prefix", - ""); - - testRegex("but with a protocol www is fine", url, - "so let's add a protocol https://www.marketplace.secondlife.com:8888/products", - "https://www.marketplace.secondlife.com:8888/products"); - - testRegex("don't match urls w/o protocol", url, - "and even no www something secondlife.com/status", - ""); - } - - template<> template<> - void object::test<15>() - { - // - // test LLUrlEntrySimpleSecondlifeURL - http://*.secondlife.com and http://*lindenlab.com urls - // - - LLUrlEntrySimpleSecondlifeURL url; - - testRegex("match urls with a protocol", url, - "this url should match http://lindenlab.com", - "http://lindenlab.com"); - - testRegex("match urls with a protocol", url, - "search something https://marketplace.secondlife.com on marketplace and test the https", - "https://marketplace.secondlife.com"); - - testRegex("don't match urls w/o protocol", url, - "looks like an url something www.marketplace.secondlife.com but no https prefix", - ""); - - testRegex("but with a protocol www is fine", url, - "so let's add a protocol http://www.marketplace.secondlife.com", - "http://www.marketplace.secondlife.com"); - - testRegex("don't match urls w/o protocol", url, - "and even no www something lindenlab.com", - ""); - } - - template<> template<> - void object::test<16>() - { - // - // test LLUrlEntryIPv6 - // - LLUrlEntryIPv6 url; - - // Regex tests. - testRegex("match urls with a protocol", url, - "this url should match http://[::1]", - "http://[::1]"); - - testRegex("match urls with a protocol and query", url, - "this url should match http://[::1]/file.mp3", - "http://[::1]/file.mp3"); - - testRegex("match urls with a protocol", url, - "this url should match http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]", - "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]"); - - testRegex("match urls with port", url, - "let's specify some port http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]:8080", - "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]:8080"); - - testRegex("don't match urls w/o protocol", url, - "looks like an url something [2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d] but no https prefix", - ""); - - testRegex("don't match incorrect urls", url, - "http://[ 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d ]", - ""); - } -} +/** + * @file llurlentry_test.cpp + * @author Martin Reddy + * @brief Unit tests for LLUrlEntry objects + * + * $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 "../llurlentry.h" +#include "../lluictrl.h" +//#include "llurlentry_stub.cpp" +#include "lltut.h" +#include "../lluicolortable.h" +#include "../llrender/lluiimage.h" +#include "../llmessage/llexperiencecache.h" + +#include + +#if LL_WINDOWS +// because something pulls in window and lldxdiag dependencies which in turn need wbemuuid.lib + #pragma comment(lib, "wbemuuid.lib") +#endif + + +// namespace LLExperienceCache +// { +// const LLSD& get( const LLUUID& key) +// { +// static LLSD boo; +// return boo; +// } +// +// void get( const LLUUID& key, callback_slot_t slot ){} +// +// } + +/*==========================================================================*| +typedef std::map settings_map_t; +settings_map_t LLUI::sSettingGroups; + +bool LLControlGroup::getBOOL(const std::string& name) +{ + return false; +} + +LLUIColor LLUIColorTable::getColor(const std::string& name, const LLColor4& default_color) const +{ + return LLUIColor(); +} + +LLUIColor::LLUIColor() : mColorPtr(NULL) {} + +LLUIImage::LLUIImage(const std::string& name, LLPointer image) +{ +} + +LLUIImage::~LLUIImage() +{ +} + +//virtual +S32 LLUIImage::getWidth() const +{ + return 0; +} + +//virtual +S32 LLUIImage::getHeight() const +{ + return 0; +} +|*==========================================================================*/ + +namespace tut +{ + struct LLUrlEntryData + { + }; + + typedef test_group factory; + typedef factory::object object; +} + +namespace +{ + tut::factory tf("LLUrlEntry"); +} + +namespace tut +{ + void testRegex(const std::string &testname, LLUrlEntryBase &entry, + const char *text, const std::string &expected) + { + boost::regex regex = entry.getPattern(); + std::string url = ""; + boost::cmatch result; + bool found = boost::regex_search(text, result, regex); + if (found) + { + S32 start = static_cast(result[0].first - text); + S32 end = static_cast(result[0].second - text); + url = entry.getUrl(std::string(text+start, end-start)); + } + ensure_equals(testname, url, expected); + } + + void dummyCallback(const std::string &url, const std::string &label, const std::string& icon) + { + } + + void testLabel(const std::string &testname, LLUrlEntryBase &entry, + const char *text, const std::string &expected) + { + boost::regex regex = entry.getPattern(); + std::string label = ""; + boost::cmatch result; + bool found = boost::regex_search(text, result, regex); + if (found) + { + S32 start = static_cast(result[0].first - text); + S32 end = static_cast(result[0].second - text); + std::string url = std::string(text+start, end-start); + label = entry.getLabel(url, boost::bind(dummyCallback, _1, _2, _3)); + } + ensure_equals(testname, label, expected); + } + + void testLocation(const std::string &testname, LLUrlEntryBase &entry, + const char *text, const std::string &expected) + { + boost::regex regex = entry.getPattern(); + std::string location = ""; + boost::cmatch result; + bool found = boost::regex_search(text, result, regex); + if (found) + { + S32 start = static_cast(result[0].first - text); + S32 end = static_cast(result[0].second - text); + std::string url = std::string(text+start, end-start); + location = entry.getLocation(url); + } + ensure_equals(testname, location, expected); + } + + + template<> template<> + void object::test<1>() + { + // + // test LLUrlEntryHTTP - standard http Urls + // + LLUrlEntryHTTP url; + + testRegex("no valid url", url, + "htp://slurl.com/", + ""); + + testRegex("simple http (1)", url, + "http://slurl.com/", + "http://slurl.com/"); + + testRegex("simple http (2)", url, + "http://slurl.com", + "http://slurl.com"); + + testRegex("simple http (3)", url, + "http://slurl.com/about.php", + "http://slurl.com/about.php"); + + testRegex("simple https", url, + "https://slurl.com/about.php", + "https://slurl.com/about.php"); + + testRegex("http in text (1)", url, + "XX http://slurl.com/ XX", + "http://slurl.com/"); + + testRegex("http in text (2)", url, + "XX http://slurl.com/about.php XX", + "http://slurl.com/about.php"); + + testRegex("https in text", url, + "XX https://slurl.com/about.php XX", + "https://slurl.com/about.php"); + + testRegex("two http urls", url, + "XX http://slurl.com/about.php http://secondlife.com/ XX", + "http://slurl.com/about.php"); + + testRegex("http url with port and username", url, + "XX http://nobody@slurl.com:80/about.php http://secondlife.com/ XX", + "http://nobody@slurl.com:80/about.php"); + + testRegex("http url with port, username, and query string", url, + "XX http://nobody@slurl.com:80/about.php?title=hi%20there http://secondlife.com/ XX", + "http://nobody@slurl.com:80/about.php?title=hi%20there"); + + // note: terminating commas will be removed by LLUrlRegistry:findUrl() + testRegex("http url with commas in middle and terminating", url, + "XX http://slurl.com/?title=Hi,There, XX", + "http://slurl.com/?title=Hi,There,"); + + // note: terminating periods will be removed by LLUrlRegistry:findUrl() + testRegex("http url with periods in middle and terminating", url, + "XX http://slurl.com/index.php. XX", + "http://slurl.com/index.php."); + + // DEV-19842: Closing parenthesis ")" breaks urls + testRegex("http url with brackets (1)", url, + "XX http://en.wikipedia.org/wiki/JIRA_(software) XX", + "http://en.wikipedia.org/wiki/JIRA_(software)"); + + // DEV-19842: Closing parenthesis ")" breaks urls + testRegex("http url with brackets (2)", url, + "XX http://jira.secondlife.com/secure/attachment/17990/eggy+avs+in+1.21.0+(93713)+public+nightly.jpg XX", + "http://jira.secondlife.com/secure/attachment/17990/eggy+avs+in+1.21.0+(93713)+public+nightly.jpg"); + + // DEV-10353: URLs in chat log terminated incorrectly when newline in chat + testRegex("http url with newlines", url, + "XX\nhttp://www.secondlife.com/\nXX", + "http://www.secondlife.com/"); + + testRegex("http url without tld shouldn't be decorated (1)", url, + "http://test", + ""); + + testRegex("http url without tld shouldn't be decorated (2)", url, + "http://test .com", + ""); + } + + template<> template<> + void object::test<2>() + { + // + // test LLUrlEntryHTTPLabel - wiki-style http Urls with labels + // + LLUrlEntryHTTPLabel url; + + testRegex("invalid wiki url [1]", url, + "[http://www.example.org]", + ""); + + testRegex("invalid wiki url [2]", url, + "[http://www.example.org", + ""); + + testRegex("invalid wiki url [3]", url, + "[http://www.example.org Label", + ""); + + testRegex("example.org with label (spaces)", url, + "[http://www.example.org Text]", + "http://www.example.org"); + + testRegex("example.org with label (tabs)", url, + "[http://www.example.org\t Text]", + "http://www.example.org"); + + testRegex("SL http URL with label", url, + "[http://www.secondlife.com/ Second Life]", + "http://www.secondlife.com/"); + + testRegex("SL https URL with label", url, + "XXX [https://www.secondlife.com/ Second Life] YYY", + "https://www.secondlife.com/"); + + testRegex("SL http URL with label", url, + "[http://www.secondlife.com/?test=Hi%20There Second Life]", + "http://www.secondlife.com/?test=Hi%20There"); + } + + template<> template<> + void object::test<3>() + { + // + // test LLUrlEntrySLURL - second life URLs + // + LLUrlEntrySLURL url; + + testRegex("no valid slurl [1]", url, + "htp://slurl.com/secondlife/Ahern/50/50/50/", + ""); + + testRegex("no valid slurl [2]", url, + "http://slurl.com/secondlife/", + ""); + + testRegex("no valid slurl [3]", url, + "hhtp://slurl.com/secondlife/Ahern/50/FOO/50/", + ""); + + testRegex("Ahern (50,50,50) [1]", url, + "http://slurl.com/secondlife/Ahern/50/50/50/", + "http://slurl.com/secondlife/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [2]", url, + "XXX http://slurl.com/secondlife/Ahern/50/50/50/ XXX", + "http://slurl.com/secondlife/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [3]", url, + "XXX http://slurl.com/secondlife/Ahern/50/50/50 XXX", + "http://slurl.com/secondlife/Ahern/50/50/50"); + + testRegex("Ahern (50,50,50) multicase", url, + "XXX http://SLUrl.com/SecondLife/Ahern/50/50/50/ XXX", + "http://SLUrl.com/SecondLife/Ahern/50/50/50/"); + + testRegex("Ahern (50,50) [1]", url, + "XXX http://slurl.com/secondlife/Ahern/50/50/ XXX", + "http://slurl.com/secondlife/Ahern/50/50/"); + + testRegex("Ahern (50,50) [2]", url, + "XXX http://slurl.com/secondlife/Ahern/50/50 XXX", + "http://slurl.com/secondlife/Ahern/50/50"); + + testRegex("Ahern (50)", url, + "XXX http://slurl.com/secondlife/Ahern/50 XXX", + "http://slurl.com/secondlife/Ahern/50"); + + testRegex("Ahern", url, + "XXX http://slurl.com/secondlife/Ahern/ XXX", + "http://slurl.com/secondlife/Ahern/"); + + testRegex("Ahern SLURL with title", url, + "XXX http://slurl.com/secondlife/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE! XXX", + "http://slurl.com/secondlife/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE!"); + + testRegex("Ahern SLURL with msg", url, + "XXX http://slurl.com/secondlife/Ahern/50/50/50/?msg=Your%20text%20here. XXX", + "http://slurl.com/secondlife/Ahern/50/50/50/?msg=Your%20text%20here."); + + // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat + testRegex("SLURL with brackets", url, + "XXX http://slurl.com/secondlife/Burning%20Life%20(Hyper)/27/210/30 XXX", + "http://slurl.com/secondlife/Burning%20Life%20(Hyper)/27/210/30"); + + // DEV-35459: SLURLs and teleport Links not parsed properly + testRegex("SLURL with quote", url, + "XXX http://slurl.com/secondlife/A'ksha%20Oasis/41/166/701 XXX", + "http://slurl.com/secondlife/A%27ksha%20Oasis/41/166/701"); + } + + template<> template<> + void object::test<4>() + { + // + // test LLUrlEntryAgent - secondlife://app/agent Urls + // + LLUrlEntryAgent url; + + testRegex("Invalid Agent Url", url, + "secondlife:///app/agent/0e346d8b-4433-4d66-XXXX-fd37083abc4c/about", + ""); + + testRegex("Agent Url ", url, + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("Agent Url in text", url, + "XXX secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about XXX", + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("Agent Url multicase", url, + "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About XXX", + "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About"); + + testRegex("Agent Url alternate command", url, + "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar", + "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar"); + + testRegex("Standalone Agent Url ", url, + "x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", + "x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("Standalone Agent Url Multicase with Text", url, + "M x-grid-location-info://lincoln.lindenlab.com/app/AGENT/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about M", + "x-grid-location-info://lincoln.lindenlab.com/app/AGENT/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + } + + template<> template<> + void object::test<5>() + { + // + // test LLUrlEntryGroup - secondlife://app/group Urls + // + LLUrlEntryGroup url; + + testRegex("Invalid Group Url", url, + "secondlife:///app/group/00005ff3-4044-c79f-XXXX-fb28ae0df991/about", + ""); + + testRegex("Group Url ", url, + "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about", + "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about"); + + testRegex("Group Url ", url, + "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect", + "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect"); + + testRegex("Group Url in text", url, + "XXX secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about XXX", + "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about"); + + testRegex("Group Url multicase", url, + "XXX secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About XXX", + "secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About"); + + testRegex("Standalone Group Url ", url, + "x-grid-location-info://lincoln.lindenlab.com/app/group/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", + "x-grid-location-info://lincoln.lindenlab.com/app/group/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("Standalone Group Url Multicase ith Text", url, + "M x-grid-location-info://lincoln.lindenlab.com/app/GROUP/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about M", + "x-grid-location-info://lincoln.lindenlab.com/app/GROUP/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + } + + template<> template<> + void object::test<6>() + { + // + // test LLUrlEntryPlace - secondlife:// URLs + // + LLUrlEntryPlace url; + + testRegex("no valid slurl [1]", url, + "secondlife://Ahern/FOO/50/", + ""); + + testRegex("Ahern (50,50,50) [1]", url, + "secondlife://Ahern/50/50/50/", + "secondlife://Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [2]", url, + "XXX secondlife://Ahern/50/50/50/ XXX", + "secondlife://Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [3]", url, + "XXX secondlife://Ahern/50/50/50 XXX", + "secondlife://Ahern/50/50/50"); + + testRegex("Ahern (50,50,50) multicase", url, + "XXX SecondLife://Ahern/50/50/50/ XXX", + "SecondLife://Ahern/50/50/50/"); + + testRegex("Ahern (50,50) [1]", url, + "XXX secondlife://Ahern/50/50/ XXX", + "secondlife://Ahern/50/50/"); + + testRegex("Ahern (50,50) [2]", url, + "XXX secondlife://Ahern/50/50 XXX", + "secondlife://Ahern/50/50"); + + // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat + testRegex("SLURL with brackets", url, + "XXX secondlife://Burning%20Life%20(Hyper)/27/210/30 XXX", + "secondlife://Burning%20Life%20(Hyper)/27/210/30"); + + // DEV-35459: SLURLs and teleport Links not parsed properly + testRegex("SLURL with quote", url, + "XXX secondlife://A'ksha%20Oasis/41/166/701 XXX", + "secondlife://A%27ksha%20Oasis/41/166/701"); + + testRegex("Standalone All Hands (50,50) [2] with text", url, + "XXX x-grid-location-info://lincoln.lindenlab.com/region/All%20Hands/50/50/50 XXX", + "x-grid-location-info://lincoln.lindenlab.com/region/All%20Hands/50/50/50"); + } + + template<> template<> + void object::test<7>() + { + // + // test LLUrlEntryParcel - secondlife://app/parcel Urls + // + LLUrlEntryParcel url; + + testRegex("Invalid Classified Url", url, + "secondlife:///app/parcel/0000060e-4b39-e00b-XXXX-d98b1934e3a8/about", + ""); + + testRegex("Classified Url ", url, + "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about", + "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about"); + + testRegex("Classified Url in text", url, + "XXX secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about XXX", + "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about"); + + testRegex("Classified Url multicase", url, + "XXX secondlife:///APP/Parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/About XXX", + "secondlife:///APP/Parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/About"); + } + template<> template<> + void object::test<8>() + { + // + // test LLUrlEntryTeleport - secondlife://app/teleport URLs + // + LLUrlEntryTeleport url; + + testRegex("no valid teleport [1]", url, + "http://slurl.com/secondlife/Ahern/50/50/50/", + ""); + + testRegex("no valid teleport [2]", url, + "secondlife:///app/teleport/", + ""); + + testRegex("no valid teleport [3]", url, + "second-life:///app/teleport/Ahern/50/50/50/", + ""); + + testRegex("no valid teleport [3]", url, + "hhtp://slurl.com/secondlife/Ahern/50/FOO/50/", + ""); + + testRegex("Ahern (50,50,50) [1]", url, + "secondlife:///app/teleport/Ahern/50/50/50/", + "secondlife:///app/teleport/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [2]", url, + "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX", + "secondlife:///app/teleport/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [3]", url, + "XXX secondlife:///app/teleport/Ahern/50/50/50 XXX", + "secondlife:///app/teleport/Ahern/50/50/50"); + + testRegex("Ahern (50,50,50) multicase", url, + "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX", + "secondlife:///app/teleport/Ahern/50/50/50/"); + + testRegex("Ahern (50,50) [1]", url, + "XXX secondlife:///app/teleport/Ahern/50/50/ XXX", + "secondlife:///app/teleport/Ahern/50/50/"); + + testRegex("Ahern (50,50) [2]", url, + "XXX secondlife:///app/teleport/Ahern/50/50 XXX", + "secondlife:///app/teleport/Ahern/50/50"); + + testRegex("Ahern (50)", url, + "XXX secondlife:///app/teleport/Ahern/50 XXX", + "secondlife:///app/teleport/Ahern/50"); + + testRegex("Ahern", url, + "XXX secondlife:///app/teleport/Ahern/ XXX", + "secondlife:///app/teleport/Ahern/"); + + testRegex("Ahern teleport with title", url, + "XXX secondlife:///app/teleport/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE! XXX", + "secondlife:///app/teleport/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE!"); + + testRegex("Ahern teleport with msg", url, + "XXX secondlife:///app/teleport/Ahern/50/50/50/?msg=Your%20text%20here. XXX", + "secondlife:///app/teleport/Ahern/50/50/50/?msg=Your%20text%20here."); + + // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat + testRegex("Teleport with brackets", url, + "XXX secondlife:///app/teleport/Burning%20Life%20(Hyper)/27/210/30 XXX", + "secondlife:///app/teleport/Burning%20Life%20(Hyper)/27/210/30"); + + // DEV-35459: SLURLs and teleport Links not parsed properly + testRegex("Teleport url with quote", url, + "XXX secondlife:///app/teleport/A'ksha%20Oasis/41/166/701 XXX", + "secondlife:///app/teleport/A%27ksha%20Oasis/41/166/701"); + + testRegex("Standalone All Hands", url, + "XXX x-grid-location-info://lincoln.lindenlab.com/app/teleport/All%20Hands/50/50/50 XXX", + "x-grid-location-info://lincoln.lindenlab.com/app/teleport/All%20Hands/50/50/50"); + } + + template<> template<> + void object::test<9>() + { + // + // test LLUrlEntrySL - general secondlife:// URLs + // + LLUrlEntrySL url; + + testRegex("no valid slapp [1]", url, + "http:///app/", + ""); + + testRegex("valid slapp [1]", url, + "secondlife:///app/", + "secondlife:///app/"); + + testRegex("valid slapp [2]", url, + "secondlife:///app/teleport/Ahern/50/50/50/", + "secondlife:///app/teleport/Ahern/50/50/50/"); + + testRegex("valid slapp [3]", url, + "secondlife:///app/foo", + "secondlife:///app/foo"); + + testRegex("valid slapp [4]", url, + "secondlife:///APP/foo?title=Hi%20There", + "secondlife:///APP/foo?title=Hi%20There"); + + testRegex("valid slapp [5]", url, + "secondlife://host/app/", + "secondlife://host/app/"); + + testRegex("valid slapp [6]", url, + "secondlife://host:8080/foo/bar", + "secondlife://host:8080/foo/bar"); + } + + template<> template<> + void object::test<10>() + { + // + // test LLUrlEntrySLLabel - general secondlife:// URLs with labels + // + LLUrlEntrySLLabel url; + + testRegex("invalid wiki url [1]", url, + "[secondlife:///app/]", + ""); + + testRegex("invalid wiki url [2]", url, + "[secondlife:///app/", + ""); + + testRegex("invalid wiki url [3]", url, + "[secondlife:///app/ Label", + ""); + + testRegex("agent slurl with label (spaces)", url, + "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about Text]", + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("agent slurl with label (tabs)", url, + "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about\t Text]", + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("agent slurl with label", url, + "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about FirstName LastName]", + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("teleport slurl with label", url, + "XXX [secondlife:///app/teleport/Ahern/50/50/50/ Teleport to Ahern] YYY", + "secondlife:///app/teleport/Ahern/50/50/50/"); + } + + template<> template<> + void object::test<11>() + { + // + // test LLUrlEntryNoLink - turn off hyperlinking + // + LLUrlEntryNoLink url; + + testRegex(" [1]", url, + "google.com", + "google.com"); + + testRegex(" [2]", url, + "google.com", + ""); + + testRegex(" [3]", url, + "google.com", + ""); + + testRegex(" [4]", url, + "Hello World", + "Hello World"); + + testRegex(" [5]", url, + "My Object", + "My Object"); + } + + template<> template<> + void object::test<12>() + { + // + // test LLUrlEntryRegion - secondlife:///app/region/ URLs + // + LLUrlEntryRegion url; + + // Regex tests. + testRegex("no valid region", url, + "secondlife:///app/region/", + ""); + + testRegex("invalid coords", url, + "secondlife:///app/region/Korea2/a/b/c", + "secondlife:///app/region/Korea2/"); // don't count invalid coords + + testRegex("Ahern (50,50,50) [1]", url, + "secondlife:///app/region/Ahern/50/50/50/", + "secondlife:///app/region/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [2]", url, + "XXX secondlife:///app/region/Ahern/50/50/50/ XXX", + "secondlife:///app/region/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [3]", url, + "XXX secondlife:///app/region/Ahern/50/50/50 XXX", + "secondlife:///app/region/Ahern/50/50/50"); + + testRegex("Ahern (50,50,50) multicase", url, + "XXX secondlife:///app/region/Ahern/50/50/50/ XXX", + "secondlife:///app/region/Ahern/50/50/50/"); + + testRegex("Ahern (50,50) [1]", url, + "XXX secondlife:///app/region/Ahern/50/50/ XXX", + "secondlife:///app/region/Ahern/50/50/"); + + testRegex("Ahern (50,50) [2]", url, + "XXX secondlife:///app/region/Ahern/50/50 XXX", + "secondlife:///app/region/Ahern/50/50"); + + // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat + testRegex("Region with brackets", url, + "XXX secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30 XXX", + "secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30"); + + // Rendering tests. + testLabel("Render /app/region/Ahern/50/50/50/", url, + "secondlife:///app/region/Ahern/50/50/50/", + "Ahern (50,50,50)"); + + testLabel("Render /app/region/Ahern/50/50/50", url, + "secondlife:///app/region/Ahern/50/50/50", + "Ahern (50,50,50)"); + + testLabel("Render /app/region/Ahern/50/50/", url, + "secondlife:///app/region/Ahern/50/50/", + "Ahern (50,50)"); + + testLabel("Render /app/region/Ahern/50/50", url, + "secondlife:///app/region/Ahern/50/50", + "Ahern (50,50)"); + + testLabel("Render /app/region/Ahern/50/", url, + "secondlife:///app/region/Ahern/50/", + "Ahern (50)"); + + testLabel("Render /app/region/Ahern/50", url, + "secondlife:///app/region/Ahern/50", + "Ahern (50)"); + + testLabel("Render /app/region/Ahern/", url, + "secondlife:///app/region/Ahern/", + "Ahern"); + + testLabel("Render /app/region/Ahern/ within context", url, + "XXX secondlife:///app/region/Ahern/ XXX", + "Ahern"); + + testLabel("Render /app/region/Ahern", url, + "secondlife:///app/region/Ahern", + "Ahern"); + + testLabel("Render /app/region/Ahern within context", url, + "XXX secondlife:///app/region/Ahern XXX", + "Ahern"); + + testLabel("Render /app/region/Product%20Engine/", url, + "secondlife:///app/region/Product%20Engine/", + "Product Engine"); + + testLabel("Render /app/region/Product%20Engine", url, + "secondlife:///app/region/Product%20Engine", + "Product Engine"); + + // Location parsing texts. + testLocation("Location /app/region/Ahern/50/50/50/", url, + "secondlife:///app/region/Ahern/50/50/50/", + "Ahern"); + + testLocation("Location /app/region/Product%20Engine", url, + "secondlife:///app/region/Product%20Engine", + "Product Engine"); + } + + template<> template<> + void object::test<13>() + { + // + // test LLUrlEntryemail - general emails + // + LLUrlEntryEmail url; + + // Regex tests. + testRegex("match e-mail addresses", url, + "test@lindenlab.com", + "mailto:test@lindenlab.com"); + + testRegex("match e-mail addresses with mailto: prefix", url, + "mailto:test@lindenlab.com", + "mailto:test@lindenlab.com"); + + testRegex("match e-mail addresses with different domains", url, + "test@foo.org.us", + "mailto:test@foo.org.us"); + + testRegex("match e-mail addresses with different domains", url, + "test@foo.bar", + "mailto:test@foo.bar"); + + testRegex("don't match incorrect e-mail addresses", url, + "test @foo.com", + ""); + + testRegex("don't match incorrect e-mail addresses", url, + "test@ foo.com", + ""); + } + + template<> template<> + void object::test<14>() + { + // + // test LLUrlEntrySimpleSecondlifeURL - http://*.secondlife.com/* and http://*lindenlab.com/* urls + // + LLUrlEntrySecondlifeURL url; + + testRegex("match urls with protocol", url, + "this url should match http://lindenlab.com/products/second-life", + "http://lindenlab.com/products/second-life"); + + testRegex("match urls with protocol", url, + "search something https://marketplace.secondlife.com/products/search on marketplace and test the https", + "https://marketplace.secondlife.com/products/search"); + + testRegex("match HTTPS urls with port", url, + "let's specify some port https://secondlife.com:888/status", + "https://secondlife.com:888/status"); + + testRegex("don't match HTTP urls with port", url, + "let's specify some port for HTTP http://secondlife.com:888/status", + ""); + + testRegex("don't match urls w/o protocol", url, + "looks like an url something www.marketplace.secondlife.com/products but no https prefix", + ""); + + testRegex("but with a protocol www is fine", url, + "so let's add a protocol https://www.marketplace.secondlife.com:8888/products", + "https://www.marketplace.secondlife.com:8888/products"); + + testRegex("don't match urls w/o protocol", url, + "and even no www something secondlife.com/status", + ""); + } + + template<> template<> + void object::test<15>() + { + // + // test LLUrlEntrySimpleSecondlifeURL - http://*.secondlife.com and http://*lindenlab.com urls + // + + LLUrlEntrySimpleSecondlifeURL url; + + testRegex("match urls with a protocol", url, + "this url should match http://lindenlab.com", + "http://lindenlab.com"); + + testRegex("match urls with a protocol", url, + "search something https://marketplace.secondlife.com on marketplace and test the https", + "https://marketplace.secondlife.com"); + + testRegex("don't match urls w/o protocol", url, + "looks like an url something www.marketplace.secondlife.com but no https prefix", + ""); + + testRegex("but with a protocol www is fine", url, + "so let's add a protocol http://www.marketplace.secondlife.com", + "http://www.marketplace.secondlife.com"); + + testRegex("don't match urls w/o protocol", url, + "and even no www something lindenlab.com", + ""); + } + + template<> template<> + void object::test<16>() + { + // + // test LLUrlEntryIPv6 + // + LLUrlEntryIPv6 url; + + // Regex tests. + testRegex("match urls with a protocol", url, + "this url should match http://[::1]", + "http://[::1]"); + + testRegex("match urls with a protocol and query", url, + "this url should match http://[::1]/file.mp3", + "http://[::1]/file.mp3"); + + testRegex("match urls with a protocol", url, + "this url should match http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]", + "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]"); + + testRegex("match urls with port", url, + "let's specify some port http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]:8080", + "http://[2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d]:8080"); + + testRegex("don't match urls w/o protocol", url, + "looks like an url something [2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d] but no https prefix", + ""); + + testRegex("don't match incorrect urls", url, + "http://[ 2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d ]", + ""); + } +} -- cgit v1.2.3