diff options
Diffstat (limited to 'indra/llui')
167 files changed, 33583 insertions, 18882 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 5de8dc76af..ce068618e2 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -3,7 +3,6 @@ project(llui) include(00-Common) -include(LLAudio) include(LLCommon) include(LLImage) include(LLMath) @@ -12,9 +11,9 @@ include(LLRender) include(LLWindow) include(LLVFS) include(LLXML) +include(LLXUIXML) include_directories( - ${LLAUDIO_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} ${LLIMAGE_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} @@ -23,55 +22,90 @@ include_directories( ${LLWINDOW_INCLUDE_DIRS} ${LLVFS_INCLUDE_DIRS} ${LLXML_INCLUDE_DIRS} + ${LLXUIXML_INCLUDE_DIRS} ) set(llui_SOURCE_FILES - llalertdialog.cpp + llaccordionctrl.cpp + llaccordionctrltab.cpp llbutton.cpp llcheckboxctrl.cpp llclipboard.cpp llcombobox.cpp + llconsole.cpp + llcontainerview.cpp llctrlselectioninterface.cpp + lldockablefloater.cpp + lldockcontrol.cpp lldraghandle.cpp lleditmenuhandler.cpp + llf32uictrl.cpp + llfiltereditor.cpp + llflatlistview.cpp llfloater.cpp + llfloaterreg.cpp + llfloaterreglistener.cpp + llflyoutbutton.cpp llfocusmgr.cpp llfunctorregistry.cpp lliconctrl.cpp llkeywords.cpp + lllayoutstack.cpp lllineeditor.cpp + lllocalcliprect.cpp + llmenubutton.cpp llmenugl.cpp llmodaldialog.cpp + llmultifloater.cpp llmultislider.cpp llmultisliderctrl.cpp llnotifications.cpp + llnotificationslistener.cpp + llnotificationsutil.cpp llpanel.cpp llprogressbar.cpp llradiogroup.cpp llresizebar.cpp llresizehandle.cpp llresmgr.cpp - llrootview.cpp + llrngwriter.cpp llscrollbar.cpp llscrollcontainer.cpp llscrollingpanellist.cpp + llscrolllistcell.cpp + llscrolllistcolumn.cpp llscrolllistctrl.cpp + llscrolllistitem.cpp + llsdparam.cpp + llsearcheditor.cpp llslider.cpp llsliderctrl.cpp llspinctrl.cpp + llstatbar.cpp + llstatgraph.cpp + llstatview.cpp llstyle.cpp lltabcontainer.cpp - lltabcontainervertical.cpp + lltextbase.cpp lltextbox.cpp lltexteditor.cpp lltextparser.cpp + lltransutil.cpp + lltoggleablemenu.cpp + lltooltip.cpp llui.cpp + lluicolortable.cpp lluictrl.cpp lluictrlfactory.cpp + lluiimage.cpp lluistring.cpp - lluitrans.cpp llundo.cpp + llurlaction.cpp + llurlentry.cpp + llurlmatch.cpp + llurlregistry.cpp llviewborder.cpp + llviewmodel.cpp llview.cpp llviewquery.cpp ) @@ -79,58 +113,93 @@ set(llui_SOURCE_FILES set(llui_HEADER_FILES CMakeLists.txt - llalertdialog.h + llaccordionctrl.h + llaccordionctrltab.h llbutton.h llcallbackmap.h llcheckboxctrl.h llclipboard.h llcombobox.h + llconsole.h + llcontainerview.h llctrlselectioninterface.h lldraghandle.h + lldockablefloater.h + lldockcontrol.h lleditmenuhandler.h + llf32uictrl.h + llfiltereditor.h + llflatlistview.h llfloater.h + llfloaterreg.h + llfloaterreglistener.h + llflyoutbutton.h llfocusmgr.h llfunctorregistry.h - llhtmlhelp.h + llhandle.h + llhelp.h lliconctrl.h llkeywords.h + lllayoutstack.h + lllazyvalue.h lllineeditor.h - llmemberlistener.h + lllocalcliprect.h + llmenubutton.h llmenugl.h llmodaldialog.h + llmultifloater.h llmultisliderctrl.h llmultislider.h + llnotificationptr.h llnotifications.h + llnotificationslistener.h + llnotificationsutil.h llpanel.h llprogressbar.h llradiogroup.h llresizebar.h llresizehandle.h llresmgr.h - llrootview.h + llrngwriter.h + llsearcheditor.h llscrollbar.h llscrollcontainer.h llscrollingpanellist.h + llscrolllistcell.h + llscrolllistcolumn.h llscrolllistctrl.h + llscrolllistitem.h + llsdparam.h llsliderctrl.h llslider.h llspinctrl.h + llstatbar.h + llstatgraph.h + llstatview.h llstyle.h lltabcontainer.h - lltabcontainervertical.h + lltextbase.h lltextbox.h lltexteditor.h lltextparser.h + lltoggleablemenu.h + lltooltip.h + lltransutil.h + lluicolortable.h lluiconstants.h lluictrlfactory.h lluictrl.h lluifwd.h llui.h + lluiimage.h lluistring.h - lluitrans.h - lluixmltags.h llundo.h + llurlaction.h + llurlentry.h + llurlmatch.h + llurlregistry.h llviewborder.h + llviewmodel.h llview.h llviewquery.h ) @@ -144,11 +213,21 @@ add_library (llui ${llui_SOURCE_FILES}) # Libraries on which this library depends, needed for Linux builds # Sort by high-level to low-level target_link_libraries(llui - llrender - llwindow - llimage - llvfs # ugh, just for LLDir - llxml - llcommon # must be after llimage, llwindow, llrender - llmath + ${LLMESSAGE_LIBRARIES} + ${LLRENDER_LIBRARIES} + ${LLWINDOW_LIBRARIES} + ${LLIMAGE_LIBRARIES} + ${LLVFS_LIBRARIES} # ugh, just for LLDir + ${LLXUIXML_LIBRARIES} + ${LLXML_LIBRARIES} + ${LLMATH_LIBRARIES} + ${LLCOMMON_LIBRARIES} # must be after llimage, llwindow, llrender ) + +# Add tests +include(LLAddBuildTest) +SET(llui_TEST_SOURCE_FILES + llurlmatch.cpp + llurlentry.cpp + ) +LL_ADD_PROJECT_UNIT_TESTS(llui "${llui_TEST_SOURCE_FILES}") diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp new file mode 100644 index 0000000000..b5e870228a --- /dev/null +++ b/indra/llui/llaccordionctrl.cpp @@ -0,0 +1,578 @@ +/** + * @file llaccordionctrl.cpp + * @brief Accordion panel implementation + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#include "linden_common.h" + +#include "llaccordionctrl.h" +#include "llaccordionctrltab.h" + +#include "lluictrlfactory.h" // builds floaters from XML + +#include "llwindow.h" +#include "llfocusmgr.h" +#include "lllocalcliprect.h" + +#include "boost/bind.hpp" + +static const S32 DRAGGER_BAR_MARGIN = 4; +static const S32 DRAGGER_BAR_HEIGHT = 5; +static const S32 BORDER_MARGIN = 2; +static const S32 PARENT_BORDER_MARGIN = 5; + +static const S32 panel_delta = DRAGGER_BAR_MARGIN; // Distanse between two panels + +static const S32 HORIZONTAL_MULTIPLE = 8; +static const S32 VERTICAL_MULTIPLE = 16; +static const F32 MIN_AUTO_SCROLL_RATE = 120.f; +static const F32 MAX_AUTO_SCROLL_RATE = 500.f; +static const F32 AUTO_SCROLL_RATE_ACCEL = 120.f; + + +// LLAccordionCtrl =================================================================| + +static LLDefaultChildRegistry::Register<LLAccordionCtrl> t2("accordion"); + + +LLAccordionCtrl::LLAccordionCtrl(const Params& params):LLPanel(params) + , mFitParent(params.fit_parent) +{ + mSingleExpansion = params.single_expansion; + if(mFitParent && !mSingleExpansion) + { + llinfos << "fit_parent works best when combined with single_expansion" << llendl; + } +} + +LLAccordionCtrl::LLAccordionCtrl() : LLPanel() +{ + mSingleExpansion = false; + mFitParent = false; + LLUICtrlFactory::getInstance()->buildPanel(this, "accordion_parent.xml"); +} + +//--------------------------------------------------------------------------------- +void LLAccordionCtrl::draw() +{ + LLRect local_rect(0, getRect().getHeight(), getRect().getWidth(), 0); + + LLLocalClipRect clip(local_rect); + + LLPanel::draw(); +} + + +//--------------------------------------------------------------------------------- +BOOL LLAccordionCtrl::postBuild() +{ + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + + LLRect scroll_rect; + scroll_rect.setOriginAndSize( + getRect().getWidth() - scrollbar_size, + 1, + scrollbar_size, + getRect().getHeight() - 1); + + + LLScrollbar::Params sbparams; + sbparams.name("scrollable vertical"); + sbparams.rect(scroll_rect); + sbparams.orientation(LLScrollbar::VERTICAL); + sbparams.doc_size(mInnerRect.getHeight()); + sbparams.doc_pos(0); + sbparams.page_size(mInnerRect.getHeight()); + sbparams.step_size(VERTICAL_MULTIPLE); + sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM); + sbparams.change_callback(boost::bind(&LLAccordionCtrl::onScrollPosChangeCallback, this, _1, _2)); + + mScrollbar = LLUICtrlFactory::create<LLScrollbar> (sbparams); + LLView::addChild( mScrollbar ); + mScrollbar->setVisible( false ); + mScrollbar->setFollowsRight(); + mScrollbar->setFollowsTop(); + mScrollbar->setFollowsBottom(); + + //if it was created from xml... + std::vector<LLUICtrl*> accordion_tabs; + for(child_list_const_iter_t it = getChildList()->begin(); + getChildList()->end() != it; ++it) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(*it); + if(accordion_tab == NULL) + continue; + if(std::find(mAccordionTabs.begin(),mAccordionTabs.end(),accordion_tab) == mAccordionTabs.end()) + { + accordion_tabs.push_back(accordion_tab); + } + } + + for(std::vector<LLUICtrl*>::reverse_iterator it = accordion_tabs.rbegin();it!=accordion_tabs.rend();++it) + addCollapsibleCtrl(*it); + + arrange (); + + if(mSingleExpansion) + { + if(!mAccordionTabs[0]->getDisplayChildren()) + mAccordionTabs[0]->setDisplayChildren(true); + for(size_t i=1;i<mAccordionTabs.size();++i) + { + if(mAccordionTabs[i]->getDisplayChildren()) + mAccordionTabs[i]->setDisplayChildren(false); + } + } + + return TRUE; +} + + +//--------------------------------------------------------------------------------- +LLAccordionCtrl::~LLAccordionCtrl() +{ + mAccordionTabs.clear(); +} + +//--------------------------------------------------------------------------------- + +void LLAccordionCtrl::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + // adjust our rectangle + LLRect rcLocal = getRect(); + rcLocal.mRight = rcLocal.mLeft + width; + rcLocal.mTop = rcLocal.mBottom + height; + + setRect(rcLocal); + + arrange(); +} + +//--------------------------------------------------------------------------------- +BOOL LLAccordionCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + return LLPanel::handleRightMouseDown(x, y, mask); +} + +//--------------------------------------------------------------------------------- +void LLAccordionCtrl::shiftAccordionTabs(S16 panel_num, S32 delta) +{ + for(size_t i = panel_num; i < mAccordionTabs.size(); i++ ) + { + ctrlShiftVertical(mAccordionTabs[i],delta); + } +} + + +//--------------------------------------------------------------------------------- +void LLAccordionCtrl::onCollapseCtrlCloseOpen(S16 panel_num) +{ + if(mSingleExpansion) + { + for(size_t i=0;i<mAccordionTabs.size();++i) + { + if(i==panel_num) + continue; + if(mAccordionTabs[i]->getDisplayChildren()) + mAccordionTabs[i]->setDisplayChildren(false); + } + + } + arrange(); +} + +void LLAccordionCtrl::show_hide_scrollbar(S32 width, S32 height) +{ + calcRecuiredHeight(); + if(getRecuiredHeight() > height ) + showScrollbar(width,height); + else + hideScrollbar(width,height); +} + +void LLAccordionCtrl::showScrollbar(S32 width, S32 height) +{ + bool was_visible = mScrollbar->getVisible(); + + mScrollbar->setVisible(true); + + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + + ctrlSetLeftTopAndSize(mScrollbar + ,width-scrollbar_size - PARENT_BORDER_MARGIN/2 + ,height-PARENT_BORDER_MARGIN + ,scrollbar_size + ,height-2*PARENT_BORDER_MARGIN); + + mScrollbar->setPageSize(height); + mScrollbar->setDocParams(mInnerRect.getHeight(),mScrollbar->getDocPos()); + + if(was_visible) + { + S32 scroll_pos = llmin(mScrollbar->getDocPos(), getRecuiredHeight() - height - 1); + mScrollbar->setDocPos(scroll_pos); + } +} + +void LLAccordionCtrl::hideScrollbar( S32 width, S32 height ) +{ + if(mScrollbar->getVisible() == false) + return; + mScrollbar->setVisible(false); + + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + + S32 panel_width = width - 2*BORDER_MARGIN; + + //reshape all accordeons and shift all draggers + for(size_t i=0;i<mAccordionTabs.size();++i) + { + LLRect panel_rect = mAccordionTabs[i]->getRect(); + ctrlSetLeftTopAndSize(mAccordionTabs[i],panel_rect.mLeft,panel_rect.mTop,panel_width,panel_rect.getHeight()); + } + + mScrollbar->setDocPos(0); + + if(mAccordionTabs.size()>0) + { + S32 panel_top = height - BORDER_MARGIN; // Top coordinate of the first panel + S32 diff = panel_top - mAccordionTabs[0]->getRect().mTop; + shiftAccordionTabs(0,diff); + } +} + + +//--------------------------------------------------------------------------------- +S32 LLAccordionCtrl::calcRecuiredHeight() +{ + S32 rec_height = 0; + + std::vector<LLAccordionCtrlTab*>::iterator panel; + for(panel=mAccordionTabs.begin(); panel!=mAccordionTabs.end(); ++panel) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(*panel); + if(accordion_tab && accordion_tab->getVisible()) + { + rec_height += accordion_tab->getRect().getHeight(); + } + } + + mInnerRect.setLeftTopAndSize(0,rec_height + BORDER_MARGIN*2,getRect().getWidth(),rec_height + BORDER_MARGIN); + + return mInnerRect.getHeight(); +} + +//--------------------------------------------------------------------------------- +void LLAccordionCtrl::ctrlSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S32 width, S32 height) +{ + if(!panel) + return; + LLRect panel_rect = panel->getRect(); + panel_rect.setLeftTopAndSize( left, top, width, height); + panel->reshape( width, height, 1); + panel->setRect(panel_rect); +} + +void LLAccordionCtrl::ctrlShiftVertical(LLView* panel,S32 delta) +{ + if(!panel) + return; + panel->translate(0,delta); +} + +//--------------------------------------------------------------------------------- + +void LLAccordionCtrl::addCollapsibleCtrl(LLView* view) +{ + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(view); + if(!accordion_tab) + return; + if(std::find(getChildList()->begin(),getChildList()->end(),accordion_tab) == getChildList()->end()) + addChild(accordion_tab); + mAccordionTabs.push_back(accordion_tab); + + accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLAccordionCtrl::onCollapseCtrlCloseOpen, this, mAccordionTabs.size() - 1) ); + +} + + +void LLAccordionCtrl::arrange() +{ + if( mAccordionTabs.size() == 0) + { + //We do not arrange if we do not have what should be arranged + return; + } + + //Calculate params + S32 panel_left = BORDER_MARGIN; // Margin from left side of Splitter + S32 panel_top = getRect().getHeight() - BORDER_MARGIN; // Top coordinate of the first panel + S32 panel_width = getRect().getWidth() - 4; // Top coordinate of the first panel + + + if(mAccordionTabs.size() == 1) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[0]); + + LLRect panel_rect = accordion_tab->getRect(); + + S32 panel_height = getRect().getHeight() - 2*BORDER_MARGIN; + + ctrlSetLeftTopAndSize(accordion_tab,panel_rect.mLeft,panel_top,panel_width,panel_height); + + show_hide_scrollbar(getRect().getWidth(),getRect().getHeight()); + return; + + } + + for(size_t i = 0; i < mAccordionTabs.size(); i++ ) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); + + if(accordion_tab->getVisible() == false) //skip hidden accordion tabs + continue; + + if(!accordion_tab->isExpanded() ) + { + ctrlSetLeftTopAndSize(mAccordionTabs[i], panel_left, panel_top, panel_width, accordion_tab->getRect().getHeight()); + panel_top-=mAccordionTabs[i]->getRect().getHeight(); + } + else + { + S32 panel_height = accordion_tab->getRect().getHeight(); + + if(mFitParent) + { + // all expanded tabs will have equal height + panel_height = calcExpandedTabHeight(i, panel_top); + ctrlSetLeftTopAndSize(accordion_tab, panel_left, panel_top, panel_width, panel_height); + + // try to make accordion tab fit accordion view height. + // Accordion View should implement getRequiredRect() and provide valid height + S32 optimal_height = accordion_tab->getAccordionView()->getRequiredRect().getHeight(); + optimal_height += accordion_tab->getHeaderHeight() + 2 * BORDER_MARGIN; + if(optimal_height < panel_height) + { + panel_height = optimal_height; + } + + // minimum tab height is equal to header height + if(mAccordionTabs[i]->getHeaderHeight() > panel_height) + { + panel_height = mAccordionTabs[i]->getHeaderHeight(); + } + } + + ctrlSetLeftTopAndSize(mAccordionTabs[i], panel_left, panel_top, panel_width, panel_height); + panel_top-=panel_height; + + } + } + + show_hide_scrollbar(getRect().getWidth(),getRect().getHeight()); + + updateLayout(getRect().getWidth(),getRect().getHeight()); + +} + +//--------------------------------------------------------------------------------- + +BOOL LLAccordionCtrl::handleScrollWheel ( S32 x, S32 y, S32 clicks ) +{ + if(LLPanel::handleScrollWheel(x,y,clicks)) + return TRUE; + if( mScrollbar->getVisible() && mScrollbar->handleScrollWheel( 0, 0, clicks ) ) + return TRUE; + return false; + +} + +BOOL LLAccordionCtrl::handleKeyHere (KEY key, MASK mask) +{ + if( mScrollbar->getVisible() && mScrollbar->handleKeyHere( key,mask ) ) + return TRUE; + return LLPanel::handleKeyHere(key,mask); +} + +void LLAccordionCtrl::updateLayout (S32 width, S32 height) +{ + S32 panel_top = height - BORDER_MARGIN ; + if(mScrollbar->getVisible()) + panel_top+=mScrollbar->getDocPos(); + + S32 panel_width = width - 2*BORDER_MARGIN; + + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + if(mScrollbar->getVisible()) + panel_width-=scrollbar_size; + + //set sizes for first panels and dragbars + for(size_t i=0;i<mAccordionTabs.size();++i) + { + if(!mAccordionTabs[i]->getVisible()) + continue; + LLRect panel_rect = mAccordionTabs[i]->getRect(); + ctrlSetLeftTopAndSize(mAccordionTabs[i],panel_rect.mLeft,panel_top,panel_width,panel_rect.getHeight()); + panel_top-=panel_rect.getHeight(); + } +} + +void LLAccordionCtrl::onScrollPosChangeCallback(S32, LLScrollbar*) +{ + updateLayout(getRect().getWidth(),getRect().getHeight()); +} +void LLAccordionCtrl::onOpen (const LLSD& key) +{ + for(size_t i=0;i<mAccordionTabs.size();++i) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); + LLPanel* panel = dynamic_cast<LLPanel*>(accordion_tab->getAccordionView()); + if(panel!=NULL) + { + panel->onOpen(key); + } + } +} +S32 LLAccordionCtrl::notifyParent(const LLSD& info) +{ + if(info.has("action")) + { + std::string str_action = info["action"]; + if(str_action == "size_changes") + { + // + arrange(); + return 1; + } + else if(str_action == "select_next") + { + for(size_t i=0;i<mAccordionTabs.size();++i) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); + if(accordion_tab->hasFocus()) + { + while(++i<mAccordionTabs.size()) + { + if(mAccordionTabs[i]->getVisible()) + break; + } + if(i<mAccordionTabs.size()) + { + accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); + accordion_tab->notify(LLSD().with("action","select_first")); + return 1; + } + break; + } + } + return 0; + } + else if(str_action == "select_prev") + { + for(size_t i=0;i<mAccordionTabs.size();++i) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); + if(accordion_tab->hasFocus() && i>0) + { + while(i>0) + { + if(mAccordionTabs[--i]->getVisible()) + break; + } + + accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); + accordion_tab->notify(LLSD().with("action","select_last")); + return 1; + } + } + return 0; + } + } + else if (info.has("scrollToShowRect")) + { + LLRect screen_rc, local_rc; + screen_rc.setValue(info["scrollToShowRect"]); + screenRectToLocal(screen_rc, &local_rc); + + // Translate to parent coordinatess to check if we are in visible rectangle + local_rc.translate( getRect().mLeft, getRect().mBottom ); + + if ( !getRect().contains (local_rc) ) + { + // Back to local coords and calculate position for scroller + S32 bottom = mScrollbar->getDocPos() - local_rc.mBottom + getRect().mBottom; + S32 top = mScrollbar->getDocPos() - local_rc.mTop + getRect().mTop; + + S32 scroll_pos = llclamp(mScrollbar->getDocPos(), + bottom, // min vertical scroll + top); // max vertical scroll + + mScrollbar->setDocPos( scroll_pos ); + } + return 1; + } + return LLPanel::notifyParent(info); +} +void LLAccordionCtrl::reset () +{ + if(mScrollbar) + mScrollbar->setDocPos(0); +} + +S32 LLAccordionCtrl::calcExpandedTabHeight(S32 tab_index /* = 0 */, S32 available_height /* = 0 */) +{ + if(tab_index < 0) + { + return available_height; + } + + S32 collapsed_tabs_height = 0; + S32 num_expanded = 0; + + for(size_t n = tab_index; n < mAccordionTabs.size(); ++n) + { + if(!mAccordionTabs[n]->isExpanded()) + { + collapsed_tabs_height += mAccordionTabs[n]->getHeaderHeight(); + } + else + { + ++num_expanded; + } + } + + if(0 == num_expanded) + { + return available_height; + } + + S32 expanded_tab_height = available_height - collapsed_tabs_height - BORDER_MARGIN; // top BORDER_MARGIN is added in arrange(), here we add bottom BORDER_MARGIN + expanded_tab_height /= num_expanded; + return expanded_tab_height; +} diff --git a/indra/llui/llaccordionctrl.h b/indra/llui/llaccordionctrl.h new file mode 100644 index 0000000000..4cb0f38281 --- /dev/null +++ b/indra/llui/llaccordionctrl.h @@ -0,0 +1,123 @@ +/** + * @file LLAccordionCtrl.h + * @brief Accordion Panel implementation + * + * $LicenseInfo:firstyear=2004&license=viewergpl$ + * + * Copyright (c) 2004-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_ACCORDIONCTRL_H +#define LL_ACCORDIONCTRL_H + +#include "llpanel.h" +#include "llscrollbar.h" + +#include <vector> +#include <algorithm> +#include <string> + +class LLAccordionCtrlTab; + +class LLAccordionCtrl: public LLPanel +{ +private: + + std::vector<LLAccordionCtrlTab*> 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: + struct Params + : public LLInitParam::Block<Params, LLPanel::Params> + { + Optional<bool> 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*/ + + Params() + : single_expansion("single_expansion",false) + , fit_parent("fit_parent", false) + {}; + }; + + 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); + // + + // Call reshape after changing splitter's size + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + void addCollapsibleCtrl(LLView* view); + void arrange(); + + + void draw(); + + void onScrollPosChangeCallback(S32, LLScrollbar*); + + void onOpen (const LLSD& key); + S32 notifyParent(const LLSD& info); + + void reset (); + +private: + // 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); + +private: + LLRect mInnerRect; + LLScrollbar* mScrollbar; + bool mSingleExpansion; + bool mFitParent; +}; + + +#endif // LL_LLSPLITTER_H diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp new file mode 100644 index 0000000000..4bfe44135a --- /dev/null +++ b/indra/llui/llaccordionctrltab.cpp @@ -0,0 +1,600 @@ +/** + * @file LLAccordionCtrlTab.cpp + * @brief Collapsible control implementation + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lluictrl.h" + +#include "llaccordionctrltab.h" + +#include "lltextbox.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 = 20; +static const S32 HEADER_IMAGE_LEFT_OFFSET = 5; +static const S32 HEADER_TEXT_LEFT_OFFSET = 30; + +static LLDefaultChildRegistry::Register<LLAccordionCtrlTab> t1("accordion_tab"); + +class LLAccordionCtrlTab::LLAccordionCtrlTabHeader : public LLUICtrl +{ +public: + friend class LLUICtrlFactory; + + struct Params : public LLInitParam::Block<Params, LLAccordionCtrlTab::Params> + { + 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(); + + void setTitle(const std::string& title); + + 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); +private: + + LLTextBox* mHeaderTextbox; + + // Overlay images (arrows) + LLPointer<LLUIImage> mImageCollapsed; + LLPointer<LLUIImage> mImageExpanded; + LLPointer<LLUIImage> mImageCollapsedPressed; + LLPointer<LLUIImage> mImageExpandedPressed; + + // Background images + LLPointer<LLUIImage> mImageHeader; + LLPointer<LLUIImage> mImageHeaderOver; + LLPointer<LLUIImage> mImageHeaderPressed; + LLPointer<LLUIImage> mImageHeaderFocused; + + LLUIColor mHeaderBGColor; + + bool mNeedsHighlight; +}; + +LLAccordionCtrlTab::LLAccordionCtrlTabHeader::Params::Params() +{ +} + +LLAccordionCtrlTab::LLAccordionCtrlTabHeader::LLAccordionCtrlTabHeader( + const LLAccordionCtrlTabHeader::Params& p) +: LLUICtrl(p) +, mHeaderBGColor(p.header_bg_color()) +,mNeedsHighlight(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; + mHeaderTextbox = LLUICtrlFactory::create<LLTextBox>(textboxParams); + addChild(mHeaderTextbox); +} + +LLAccordionCtrlTab::LLAccordionCtrlTabHeader::~LLAccordionCtrlTabHeader() +{ +} + +BOOL LLAccordionCtrlTab::LLAccordionCtrlTabHeader::postBuild() +{ + return TRUE; +} + +void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::setTitle(const std::string& title) +{ + if(mHeaderTextbox) + mHeaderTextbox->setText(title); +} + +void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::draw() +{ + S32 width = getRect().getWidth(); + S32 height = getRect().getHeight(); + + gl_rect_2d(0,0,width - 1 ,height - 1,mHeaderBGColor.get(),true); + + LLAccordionCtrlTab* parent = dynamic_cast<LLAccordionCtrlTab*>(getParent()); + bool collapsible = (parent && parent->getCollapsible()); + bool expanded = (parent && parent->getDisplayChildren()); + + // Handle overlay images, if needed + // Only show green "focus" background image if the accordion is open, + // because the user's mental model of focus is that it goes away after + // the accordion is closed. + if (getParent()->hasFocus() + /*&& !(collapsible && !expanded)*/ // WHY?? + ) + { + mImageHeaderFocused->draw(0,0,width,height); + } + else + { + mImageHeader->draw(0,0,width,height); + } + + if(mNeedsHighlight) + { + mImageHeaderOver->draw(0,0,width,height); + } + + + if(collapsible) + { + LLPointer<LLUIImage> overlay_image; + if(expanded) + { + overlay_image = mImageExpanded; + } + else + { + overlay_image = mImageCollapsed; + } + overlay_image->draw(HEADER_IMAGE_LEFT_OFFSET, + (height - overlay_image->getHeight()) / 2); + } + + LLUICtrl::draw(); +} + +void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */) +{ + S32 header_height = mHeaderTextbox->getTextPixelHeight(); + + LLRect textboxRect(HEADER_TEXT_LEFT_OFFSET,(height+header_height)/2 ,width,(height-header_height)/2); + mHeaderTextbox->reshape(textboxRect.getWidth(), textboxRect.getHeight()); + mHeaderTextbox->setRect(textboxRect); +} + +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; +} +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); +} + + +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") +{ + 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) +{ + mStoredOpenCloseState = false; + mWasStateStored = false; + + mDropdownBGColor = LLColor4::white; + LLAccordionCtrlTabHeader::Params headerParams; + headerParams.name(DD_HEADER_NAME); + headerParams.title(p.title); + mHeader = LLUICtrlFactory::create<LLAccordionCtrlTabHeader>(headerParams); + addChild(mHeader, 1); + + 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); + + for(child_list_const_iter_t it = getChildList()->begin(); + getChildList()->end() != it; ++it) + { + LLView* child = *it; + if(DD_HEADER_NAME == child->getName()) + continue; + + child->setVisible(getDisplayChildren()); + } +} + +void LLAccordionCtrlTab::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */) +{ + LLRect headerRect; + + LLUICtrl::reshape(width, height, TRUE); + + headerRect.setLeftTopAndSize( + 0,height,width,HEADER_HEIGHT); + mHeader->setRect(headerRect); + mHeader->reshape(headerRect.getWidth(), headerRect.getHeight()); + + for(child_list_const_iter_t it = getChildList()->begin(); + getChildList()->end() != it; ++it) + { + LLView* child = *it; + if(DD_HEADER_NAME == child->getName()) + continue; + if(!child->getVisible()) + continue; + + LLRect childRect = child->getRect(); + S32 childWidth = width - getPaddingLeft() - getPaddingRight(); + S32 childHeight = height - getHeaderHeight() - getPaddingTop() - getPaddingBottom(); + + child->reshape(childWidth,childHeight); + + childRect.setLeftTopAndSize( + getPaddingLeft(), + childHeight + getPaddingBottom(), + childWidth, + childHeight); + + child->setRect(childRect); + + break;//suppose that there is only one panel + } + +} + +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()); + } +} + +BOOL LLAccordionCtrlTab::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if(mCollapsible && mHeaderVisible && mCanOpenClose) + { + if(y >= (getRect().getHeight() - HEADER_HEIGHT) ) + { + LLAccordionCtrlTabHeader* header = getChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); + header->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()); + } + + return res; +} + +void LLAccordionCtrlTab::setAccordionView(LLView* panel) +{ + addChild(panel,0); +} + + +LLView* LLAccordionCtrlTab::getAccordionView() +{ + for(child_list_const_iter_t it = getChildList()->begin(); + getChildList()->end() != it; ++it) + { + LLView* child = *it; + if(DD_HEADER_NAME == child->getName()) + continue; + if(!child->getVisible()) + continue; + return child; + } + return NULL; +} + + +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); +}; + +//vurtual +BOOL LLAccordionCtrlTab::postBuild() +{ + mHeader->setVisible(mHeaderVisible); + return LLUICtrl::postBuild(); +} +bool LLAccordionCtrlTab::notifyChildren (const LLSD& info) +{ + if(info.has("action")) + { + std::string str_action = info["action"]; + if(str_action == "store_state") + { + storeOpenCloseState(); + return true; + } + if(str_action == "restore_state") + { + restoreOpenCloseState(); + return true; + } + } + return LLUICtrl::notifyChildren(info); +} + +S32 LLAccordionCtrlTab::notifyParent(const LLSD& info) +{ + if(info.has("action")) + { + std::string str_action = info["action"]; + if(str_action == "size_changes") + { + // + S32 height = info["height"]; + height = llmax(height,10) + HEADER_HEIGHT + getPaddingTop() + getPaddingBottom(); + + mExpandedHeight = height; + + if(isExpanded()) + { + LLRect panel_rect = getRect(); + panel_rect.setLeftTopAndSize( panel_rect.mLeft, panel_rect.mTop, panel_rect.getWidth(), height); + reshape(getRect().getWidth(),height); + setRect(panel_rect); + } + + //LLAccordionCtrl should rearrange accodion tab if one of accordion change its size + getParent()->notifyParent(info); + return 1; + } + else if(str_action == "select_prev") + { + showAndFocusHeader(); + return 1; + } + } + return LLUICtrl::notifyParent(info); +} + +S32 LLAccordionCtrlTab::notify(const LLSD& info) +{ + if(info.has("action")) + { + std::string str_action = info["action"]; + if(str_action == "select_first") + { + showAndFocusHeader(); + return 1; + } + else if( str_action == "select_last" ) + { + if(getDisplayChildren() == false) + { + showAndFocusHeader(); + } + else + { + LLView* view = getAccordionView(); + if(view) + view->notify(LLSD().with("action","select_last")); + } + } + } + return 0; +} + +BOOL LLAccordionCtrlTab::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + LLAccordionCtrlTabHeader* header = getChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); + if( !header->hasFocus() ) + return LLUICtrl::handleKey(key, mask, called_from_parent); + + if ( (key == KEY_ADD || key == KEY_RIGHT)&& mask == MASK_NONE) + { + if(getDisplayChildren() == false) + { + changeOpenClose(getDisplayChildren()); + return TRUE; + } + } + if ( (key == KEY_SUBTRACT || key == KEY_LEFT)&& mask == MASK_NONE) + { + if(getDisplayChildren() == true) + { + changeOpenClose(getDisplayChildren()); + return TRUE; + } + } + + if ( key == KEY_DOWN && mask == MASK_NONE) + { + //if collapsed go to the next accordion + if(getDisplayChildren() == false) + //we processing notifyParent so let call parent directly + getParent()->notifyParent(LLSD().with("action","select_next")); + else + { + getAccordionView()->notify(LLSD().with("action","select_first")); + } + return TRUE; + } + + if ( key == KEY_UP && mask == MASK_NONE) + { + //go to the previous accordion + + //we processing notifyParent so let call parent directly + getParent()->notifyParent(LLSD().with("action","select_prev")); + return TRUE; + } + + return LLUICtrl::handleKey(key, mask, called_from_parent); +} + +void LLAccordionCtrlTab::showAndFocusHeader() +{ + LLAccordionCtrlTabHeader* header = getChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME); + header->setFocus(true); + + LLRect screen_rc; + LLRect selected_rc = header->getRect(); + localRectToScreen(selected_rc, &screen_rc); + 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; +} diff --git a/indra/llui/llaccordionctrltab.h b/indra/llui/llaccordionctrltab.h new file mode 100644 index 0000000000..b200d43438 --- /dev/null +++ b/indra/llui/llaccordionctrltab.h @@ -0,0 +1,191 @@ +/** + * @file LLAccordionCtrlTab.h + * @brief Collapsible box control implementation + * + * $LicenseInfo:firstyear=2004&license=viewergpl$ + * + * Copyright (c) 2004-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_ACCORDIONCTRLTAB_H_ +#define LL_ACCORDIONCTRLTAB_H_ + +#include <string> +#include "llrect.h" + +class LLUICtrl; +class LLUICtrlFactory; +class LLUIImage; +class LLButton; +class LLTextBox; + + + +// 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<Params, LLUICtrl::Params> + { + Optional<bool> display_children, //expanded or collapsed after initialization + collapsible; + + Optional<std::string> title; + + Optional<S32> header_height, + min_width, + min_height; + + // Overlay images (arrows on the left) + Mandatory<LLUIImage*> header_expand_img, + header_expand_img_pressed, + header_collapse_img, + header_collapse_img_pressed; + + // Background images for the accordion tabs + Mandatory<LLUIImage*> header_image, + header_image_over, + header_image_pressed, + header_image_focused; + + Optional<LLUIColor> header_bg_color, + header_text_color, + dropdown_bg_color; + + Optional<bool> header_visible; + + Optional<S32> padding_left; + Optional<S32> padding_right; + Optional<S32> padding_top; + Optional<S32> 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(); + + 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;}; + + virtual BOOL postBuild(); + + S32 notifyParent(const LLSD& info); + S32 notify(const LLSD& info); + bool notifyChildren(const LLSD& info); + + 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); + + // 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 addChild(LLView* child, S32 tab_group); + + bool isExpanded() { 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(); + +private: + + + + class LLAccordionCtrlTabHeader; + LLAccordionCtrlTabHeader* mHeader; //Header + + bool mDisplayChildren; //Expanded/collapsed + bool mCollapsible; + bool mHeaderVisible; + + bool mCanOpenClose; + + S32 mPaddingLeft; + S32 mPaddingRight; + S32 mPaddingTop; + S32 mPaddingBottom; + + bool mStoredOpenCloseState; + bool mWasStateStored; + + + LLUIColor mDropdownBGColor; +}; + +#endif diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index 2c2c1c25d8..9ce8ce8d55 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -32,6 +32,7 @@ #include "linden_common.h" +#define LLBUTTON_CPP #include "llbutton.h" // Linden library includes @@ -39,189 +40,213 @@ #include "llstring.h" // Project includes -#include "llhtmlhelp.h" #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 "llhelp.h" +#include "lldockablefloater.h" -static LLRegisterWidget<LLButton> r("button"); +static LLDefaultChildRegistry::Register<LLButton> r("button"); + +// Compiler optimization, generate extern template +template class LLButton* LLView::getChild<class LLButton>( + const std::string& name, BOOL recurse) const; // globals loaded from settings.xml -S32 LLBUTTON_ORIG_H_PAD = 6; // Pre-zoomable UI S32 LLBUTTON_H_PAD = 0; -S32 LLBUTTON_V_PAD = 0; S32 BTN_HEIGHT_SMALL= 0; S32 BTN_HEIGHT = 0; -S32 BTN_GRID = 12; -S32 BORDER_SIZE = 1; - -LLButton::LLButton( const std::string& name, const LLRect& rect, const std::string& control_name, void (*click_callback)(void*), void *callback_data) -: LLUICtrl(name, rect, TRUE, NULL, NULL), - mClickedCallback( click_callback ), - mMouseDownCallback( NULL ), - mMouseUpCallback( NULL ), - mHeldDownCallback( NULL ), - mGLFont( NULL ), - mMouseDownFrame( 0 ), - mHeldDownDelay( 0.5f ), // seconds until held-down callback is called - mHeldDownFrameDelay( 0 ), - mImageUnselected( NULL ), - mImageSelected( NULL ), - mImageHoverSelected( NULL ), - mImageHoverUnselected( NULL ), - mImageDisabled( NULL ), - mImageDisabledSelected( NULL ), - mToggleState( FALSE ), - mIsToggle( FALSE ), - mScaleImage( TRUE ), - mDropShadowedText( TRUE ), - mBorderEnabled( FALSE ), - mFlashing( FALSE ), - mHAlign( LLFontGL::HCENTER ), - mLeftHPad( LLBUTTON_H_PAD ), - mRightHPad( LLBUTTON_H_PAD ), - mHoverGlowStrength(0.15f), - mCurGlowStrength(0.f), - mNeedsHighlight(FALSE), - mCommitOnReturn(TRUE), - mImagep( NULL ) +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), + 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")), + 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"), + highlight_color("highlight_color"), + image_color("image_color"), + image_color_disabled("image_color_disabled"), + image_overlay_color("image_overlay_color", LLColor4::white), + flash_color("flash_color"), + pad_right("pad_right", LLUI::sSettingGroups["config"]->getS32("ButtonHPad")), + pad_left("pad_left", LLUI::sSettingGroups["config"]->getS32("ButtonHPad")), + 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) { - mUnselectedLabel = name; - mSelectedLabel = name; - - setImageUnselected(std::string("button_enabled_32x128.tga")); - setImageSelected(std::string("button_enabled_selected_32x128.tga")); - setImageDisabled(std::string("button_disabled_32x128.tga")); - setImageDisabledSelected(std::string("button_disabled_32x128.tga")); - - mImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); - mDisabledImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); - - init(click_callback, callback_data, NULL, control_name); + addSynonym(is_toggle, "toggle"); + held_down_delay.seconds = 0.5f; + initial_value.set(LLSD(false), false); } -LLButton::LLButton(const std::string& name, const LLRect& rect, - const std::string &unselected_image_name, - const std::string &selected_image_name, - const std::string& control_name, - void (*click_callback)(void*), - void *callback_data, - const LLFontGL *font, - const std::string& unselected_label, - const std::string& selected_label ) -: LLUICtrl(name, rect, TRUE, NULL, NULL), - mClickedCallback( click_callback ), - mMouseDownCallback( NULL ), - mMouseUpCallback( NULL ), - mHeldDownCallback( NULL ), - mGLFont( NULL ), - mMouseDownFrame( 0 ), - mHeldDownDelay( 0.5f ), // seconds until held-down callback is called - mHeldDownFrameDelay( 0 ), - mImageUnselected( NULL ), - mImageSelected( NULL ), - mImageHoverSelected( NULL ), - mImageHoverUnselected( NULL ), - mImageDisabled( NULL ), - mImageDisabledSelected( NULL ), - mToggleState( FALSE ), - mIsToggle( FALSE ), - mScaleImage( TRUE ), - mDropShadowedText( TRUE ), +LLButton::LLButton(const LLButton::Params& p) +: LLUICtrl(p), + mMouseDownFrame(0), + mMouseHeldDownCount(0), mBorderEnabled( FALSE ), mFlashing( FALSE ), - mHAlign( LLFontGL::HCENTER ), - mLeftHPad( LLBUTTON_H_PAD ), - mRightHPad( LLBUTTON_H_PAD ), - mHoverGlowStrength(0.25f), mCurGlowStrength(0.f), mNeedsHighlight(FALSE), - mCommitOnReturn(TRUE), - mImagep( NULL ) -{ - mUnselectedLabel = unselected_label; - mSelectedLabel = selected_label; + 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), + 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()), + mHighlightColor(p.highlight_color()), + mImageColor(p.image_color()), + mFlashBgColor(p.flash_color()), + mDisabledImageColor(p.image_color_disabled()), + mImageOverlay(p.image_overlay()), + mImageOverlayColor(p.image_overlay_color()), + mImageOverlayAlignment(LLFontGL::hAlignFromName(p.image_overlay_alignment)), + mIsToggle(p.is_toggle), + mScaleImage(p.scale_image), + mDropShadowedText(p.label_shadow), + mAutoResize(p.auto_resize), + mUseEllipses( p.use_ellipses ), + 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), + mFadeWhenDisabled(FALSE), + mForcePressedState(false), + mLastDrawCharsCount(0), + mMouseDownSignal(NULL), + mMouseUpSignal(NULL), + mHeldDownSignal(NULL) - // by default, disabled color is same as enabled - mImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); - mDisabledImageColor = LLUI::sColorsGroup->getColor( "ButtonImageColor" ); +{ + static LLUICachedControl<S32> llbutton_orig_h_pad ("UIButtonOrigHPad", 0); + static Params default_params(LLUICtrlFactory::getDefaultParams<LLButton>()); - if( unselected_image_name != "" ) + if (!p.label_selected.isProvided()) { - // user-specified image - don't use fixed borders unless requested - setImageUnselected(unselected_image_name); - setImageDisabled(unselected_image_name); - - mDisabledImageColor.mV[VALPHA] = 0.5f; - mScaleImage = FALSE; + mSelectedLabel = mUnselectedLabel; } - else + + // Hack to make sure there is space for at least one character + if (getRect().getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(std::string(" "))) { - setImageUnselected(std::string("button_enabled_32x128.tga")); - setImageDisabled(std::string("button_disabled_32x128.tga")); + // Use old defaults + mLeftHPad = llbutton_orig_h_pad; + mRightHPad = llbutton_orig_h_pad; } + + mMouseDownTimer.stop(); - if( selected_image_name != "" ) + // if custom unselected button image provided... + if (p.image_unselected != default_params.image_unselected) { - // user-specified image - don't use fixed borders unless requested - setImageSelected(selected_image_name); - setImageDisabledSelected(selected_image_name); + //...fade it out for disabled image by default... + if (p.image_disabled() == default_params.image_disabled() ) + { + mImageDisabled = p.image_unselected; + mFadeWhenDisabled = TRUE; + } - mDisabledImageColor.mV[VALPHA] = 0.5f; - mScaleImage = FALSE; + if (p.image_pressed_selected == default_params.image_pressed_selected) + { + mImagePressedSelected = mImageUnselected; + } } - else + + // if custom selected button image provided... + if (p.image_selected != default_params.image_selected) { - setImageSelected(std::string("button_enabled_selected_32x128.tga")); - setImageDisabledSelected(std::string("button_disabled_32x128.tga")); - } + //...fade it out for disabled image by default... + if (p.image_disabled_selected() == default_params.image_disabled_selected()) + { + mImageDisabledSelected = p.image_selected; + mFadeWhenDisabled = TRUE; + } - init(click_callback, callback_data, font, control_name); -} + if (p.image_pressed == default_params.image_pressed) + { + mImagePressed = mImageSelected; + } + } -void LLButton::init(void (*click_callback)(void*), void *callback_data, const LLFontGL* font, const std::string& control_name) -{ - mGLFont = ( font ? font : LLFontGL::getFontSansSerif()); + if (!p.image_pressed.isProvided()) + { + mImagePressed = mImageSelected; + } - // Hack to make sure there is space for at least one character - if (getRect().getWidth() - (mRightHPad + mLeftHPad) < mGLFont->getWidth(std::string(" "))) + if (!p.image_pressed_selected.isProvided()) { - // Use old defaults - mLeftHPad = LLBUTTON_ORIG_H_PAD; - mRightHPad = LLBUTTON_ORIG_H_PAD; + mImagePressedSelected = mImageUnselected; } - mCallbackUserData = callback_data; - mMouseDownTimer.stop(); - - setControlName(control_name, NULL); - - mUnselectedLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelColor" ) ); - mSelectedLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelSelectedColor" ) ); - mDisabledLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelDisabledColor" ) ); - mDisabledSelectedLabelColor = ( LLUI::sColorsGroup->getColor( "ButtonLabelSelectedDisabledColor" ) ); - mHighlightColor = ( LLUI::sColorsGroup->getColor( "ButtonUnselectedFgColor" ) ); - mUnselectedBgColor = ( LLUI::sColorsGroup->getColor( "ButtonUnselectedBgColor" ) ); - mSelectedBgColor = ( LLUI::sColorsGroup->getColor( "ButtonSelectedBgColor" ) ); - mFlashBgColor = ( LLUI::sColorsGroup->getColor( "ButtonFlashBgColor" ) ); - - mImageOverlayAlignment = LLFontGL::HCENTER; - mImageOverlayColor = LLColor4::white; + if (mImageUnselected.isNull()) + { + llwarns << "Button: " << getName() << " with no image!" << llendl; + } + + 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)); + } } LLButton::~LLButton() { - if( hasMouseCapture() ) - { - gFocusMgr.setMouseCapture( NULL ); - } + delete mMouseDownSignal; + delete mMouseUpSignal; + delete mHeldDownSignal; } // HACK: Committing a button is the same as instantly clicking it. @@ -229,19 +254,12 @@ LLButton::~LLButton() void LLButton::onCommit() { // WARNING: Sometimes clicking a button destroys the floater or - // panel containing it. Therefore we need to call mClickedCallback + // panel containing it. Therefore we need to call LLUICtrl::onCommit() // LAST, otherwise this becomes deleted memory. - LLUICtrl::onCommit(); - if (mMouseDownCallback) - { - (*mMouseDownCallback)(mCallbackUserData); - } + if (mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); - if (mMouseUpCallback) - { - (*mMouseUpCallback)(mCallbackUserData); - } + if (mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); if (getSoundFlags() & MOUSE_DOWN) { @@ -259,14 +277,54 @@ void LLButton::onCommit() } // do this last, as it can result in destroying this button - if (mClickedCallback) - { - (*mClickedCallback)( mCallbackUserData ); - } + LLUICtrl::onCommit(); } +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 compatability 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(); + return TRUE; +} BOOL LLButton::handleUnicodeCharHere(llwchar uni_char) { BOOL handled = FALSE; @@ -278,10 +336,8 @@ BOOL LLButton::handleUnicodeCharHere(llwchar uni_char) toggleState(); } - if (mClickedCallback) - { - (*mClickedCallback)( mCallbackUserData ); - } + LLUICtrl::onCommit(); + handled = TRUE; } return handled; @@ -299,10 +355,7 @@ BOOL LLButton::handleKeyHere(KEY key, MASK mask ) handled = TRUE; - if (mClickedCallback) - { - (*mClickedCallback)( mCallbackUserData ); - } + LLUICtrl::onCommit(); } return handled; } @@ -310,27 +363,35 @@ BOOL LLButton::handleKeyHere(KEY key, MASK mask ) BOOL LLButton::handleMouseDown(S32 x, S32 y, MASK mask) { - // Route future Mouse messages here preemptively. (Release on mouse up.) - gFocusMgr.setMouseCapture( this ); - - if (hasTabStop() && !getIsChrome()) + if (!childrenHandleMouseDown(x, y, mask)) { - setFocus(TRUE); - } + // Route future Mouse messages here preemptively. (Release on mouse up.) + gFocusMgr.setMouseCapture( this ); - if (mMouseDownCallback) - { - (*mMouseDownCallback)(mCallbackUserData); - } + if (hasTabStop() && !getIsChrome()) + { + setFocus(TRUE); + } - mMouseDownTimer.start(); - mMouseDownFrame = (S32) LLFrameTimer::getFrameCount(); - - if (getSoundFlags() & MOUSE_DOWN) - { - make_ui_sound("UISndClick"); - } + /* + * 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); + + if(mMouseDownSignal) (*mMouseDownSignal)(this, LLSD()); + + mMouseDownTimer.start(); + mMouseDownFrame = (S32) LLFrameTimer::getFrameCount(); + mMouseHeldDownCount = 0; + + if (getSoundFlags() & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } + } return TRUE; } @@ -343,14 +404,17 @@ BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask) // 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); + // Regardless of where mouseup occurs, handle callback - if (mMouseUpCallback) - { - (*mMouseUpCallback)(mCallbackUserData); - } + if(mMouseUpSignal) (*mMouseUpSignal)(this, LLSD()); - mMouseDownTimer.stop(); - mMouseDownTimer.reset(); + resetMouseDownTimer(); // 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 @@ -366,38 +430,106 @@ BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask) toggleState(); } - if (mClickedCallback) - { - (*mClickedCallback)( mCallbackUserData ); - } + LLUICtrl::onCommit(); } } + else + { + childrenHandleMouseUp(x, y, mask); + } return TRUE; } - -BOOL LLButton::handleHover(S32 x, S32 y, MASK mask) +BOOL LLButton::handleRightMouseDown(S32 x, S32 y, MASK mask) { - LLMouseHandler* other_captor = gFocusMgr.getMouseCapture(); - mNeedsHighlight = other_captor == NULL || - other_captor == this || - // this following bit is to support modal dialogs - (other_captor->isView() && hasAncestor((LLView*)other_captor)); - - if (mMouseDownTimer.getStarted() && NULL != mHeldDownCallback) + if (!childrenHandleRightMouseDown(x, y, mask)) { - F32 elapsed = getHeldDownTime(); - if( mHeldDownDelay <= elapsed && mHeldDownFrameDelay <= (S32)LLFrameTimer::getFrameCount() - mMouseDownFrame) + // Route future Mouse messages here preemptively. (Release on mouse up.) + gFocusMgr.setMouseCapture( this ); + + if (hasTabStop() && !getIsChrome()) { - mHeldDownCallback( mCallbackUserData ); + 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) +{ // We only handle the click if the click both started and ended within us - getWindow()->setCursor(UI_CURSOR_ARROW); - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + 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::onMouseEnter(S32 x, S32 y, MASK mask) +{ + LLUICtrl::onMouseEnter(x, y, mask); + + if (isInEnabledChain()) + mNeedsHighlight = 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 (!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); + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; + } return TRUE; } @@ -405,16 +537,20 @@ BOOL LLButton::handleHover(S32 x, S32 y, MASK mask) // virtual void LLButton::draw() { - BOOL flash = FALSE; + F32 alpha = getDrawContext().mAlpha; + bool flash = FALSE; + static LLUICachedControl<F32> button_flash_rate("ButtonFlashRate", 0); + static LLUICachedControl<S32> button_flash_count("ButtonFlashCount", 0); + if( mFlashing ) { F32 elapsed = mFlashingTimer.getElapsedTimeF32(); - S32 flash_count = S32(elapsed * LLUI::sConfigGroup->getF32("ButtonFlashRate") * 2.f); + S32 flash_count = S32(elapsed * button_flash_rate * 2.f); // flash on or off? - flash = (flash_count % 2 == 0) || flash_count > S32((F32)LLUI::sConfigGroup->getS32("ButtonFlashCount") * 2.f); + flash = (flash_count % 2 == 0) || flash_count > S32((F32)button_flash_count * 2.f); } - BOOL pressed_by_keyboard = FALSE; + bool pressed_by_keyboard = FALSE; if (hasFocus()) { pressed_by_keyboard = gKeyboard->getKeyDown(' ') || (mCommitOnReturn && gKeyboard->getKeyDown(KEY_RETURN)); @@ -423,26 +559,34 @@ void LLButton::draw() // Unselected image assignments S32 local_mouse_x; S32 local_mouse_y; - LLUI::getCursorPositionLocal(this, &local_mouse_x, &local_mouse_y); + LLUI::getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); - BOOL pressed = pressed_by_keyboard - || (hasMouseCapture() && pointInView(local_mouse_x, local_mouse_y)) - || mToggleState; + bool enabled = isInEnabledChain(); + + bool pressed = pressed_by_keyboard + || (hasMouseCapture() && pointInView(local_mouse_x, local_mouse_y)) + || mForcePressedState; + bool selected = getToggleState(); - BOOL use_glow_effect = FALSE; + bool use_glow_effect = FALSE; LLColor4 glow_color = LLColor4::white; LLRender::eBlendType glow_type = LLRender::BT_ADD_WITH_ALPHA; - if ( mNeedsHighlight ) + LLUIImage* imagep = NULL; + if (pressed) { - if (pressed) + imagep = selected ? mImagePressedSelected : mImagePressed; + } + else if ( mNeedsHighlight ) + { + if (selected) { if (mImageHoverSelected) { - mImagep = mImageHoverSelected; + imagep = mImageHoverSelected; } else { - mImagep = mImageSelected; + imagep = mImageSelected; use_glow_effect = TRUE; } } @@ -450,32 +594,18 @@ void LLButton::draw() { if (mImageHoverUnselected) { - mImagep = mImageHoverUnselected; + imagep = mImageHoverUnselected; } else { - mImagep = mImageUnselected; + imagep = mImageUnselected; use_glow_effect = TRUE; } } } - else if ( pressed ) - { - mImagep = mImageSelected; - } - else - { - mImagep = mImageUnselected; - } - - if (mFlashing) + else { - use_glow_effect = TRUE; - glow_type = LLRender::BT_ALPHA; // blend the glow - if (mNeedsHighlight) // highlighted AND flashing - glow_color = (glow_color*0.5f + mFlashBgColor*0.5f) % 2.0f; // average between flash and highlight colour, with sum of the opacity - else - glow_color = mFlashBgColor; + imagep = selected ? mImageSelected : mImageUnselected; } // Override if more data is available @@ -485,19 +615,31 @@ void LLButton::draw() // disabled but checked if (!mImageDisabledSelected.isNull() && - ( (getEnabled() && getTentative()) - || (!getEnabled() && pressed ) ) ) + ( (enabled && getTentative()) + || (!enabled && selected ) ) ) { - mImagep = mImageDisabledSelected; + imagep = mImageDisabledSelected; } else if (!mImageDisabled.isNull() - && !getEnabled() - && !pressed) + && !enabled + && !selected) { - mImagep = mImageDisabled; + imagep = mImageDisabled; } - if (mNeedsHighlight && !mImagep) + if (mFlashing) + { + LLColor4 flash_color = mFlashBgColor.get(); + use_glow_effect = TRUE; + glow_type = LLRender::BT_ALPHA; // blend the glow + + if (mNeedsHighlight) // highlighted AND flashing + glow_color = (glow_color*0.5f + flash_color*0.5f) % 2.0f; // average between flash and highlight colour, with sum of the opacity + else + glow_color = flash_color; + } + + if (mNeedsHighlight && !imagep) { use_glow_effect = TRUE; } @@ -506,60 +648,46 @@ void LLButton::draw() LLColor4 label_color; // label changes when button state changes, not when pressed - if ( getEnabled() ) + if ( enabled ) { - if ( mToggleState ) + if ( getToggleState() ) { - label_color = mSelectedLabelColor; + label_color = mSelectedLabelColor.get(); } else { - label_color = mUnselectedLabelColor; + label_color = mUnselectedLabelColor.get(); } } else { - if ( mToggleState ) + if ( getToggleState() ) { - label_color = mDisabledSelectedLabelColor; + label_color = mDisabledSelectedLabelColor.get(); } else { - label_color = mDisabledLabelColor; + label_color = mDisabledLabelColor.get(); } } // Unselected label assignments LLWString label; - if( mToggleState ) + if( getToggleState() ) { - if( getEnabled() || mDisabledSelectedLabel.empty() ) - { - label = mSelectedLabel; - } - else - { - label = mDisabledSelectedLabel; - } + label = mSelectedLabel; } else { - if( getEnabled() || mDisabledLabel.empty() ) - { - label = mUnselectedLabel; - } - else - { - label = mDisabledLabel; - } + label = mUnselectedLabel; } // overlay with keyboard focus border if (hasFocus()) { F32 lerp_amt = gFocusMgr.getFocusFlashAmt(); - drawBorder(gFocusMgr.getFocusColor(), llround(lerp(1.f, 3.f, lerp_amt))); + drawBorder(imagep, gFocusMgr.getFocusColor() % alpha, llround(lerp(1.f, 3.f, lerp_amt))); } if (use_glow_effect) @@ -576,25 +704,27 @@ void LLButton::draw() // Draw button image, if available. // Otherwise draw basic rectangular button. - if (mImagep.notNull()) + if (imagep != NULL) { + // apply automatic 50% alpha fade to disabled image + LLColor4 disabled_color = mFadeWhenDisabled ? mDisabledImageColor.get() % 0.5f : mDisabledImageColor.get(); if ( mScaleImage) { - mImagep->draw(getLocalRect(), getEnabled() ? mImageColor : mDisabledImageColor ); + imagep->draw(getLocalRect(), (enabled ? mImageColor.get() : disabled_color) % alpha ); if (mCurGlowStrength > 0.01f) { gGL.setSceneBlendType(glow_type); - mImagep->drawSolid(0, 0, getRect().getWidth(), getRect().getHeight(), glow_color % mCurGlowStrength); + imagep->drawSolid(0, 0, getRect().getWidth(), getRect().getHeight(), glow_color % (mCurGlowStrength * alpha)); gGL.setSceneBlendType(LLRender::BT_ALPHA); } } else { - mImagep->draw(0, 0, getEnabled() ? mImageColor : mDisabledImageColor ); + imagep->draw(0, 0, (enabled ? mImageColor.get() : disabled_color) % alpha ); if (mCurGlowStrength > 0.01f) { gGL.setSceneBlendType(glow_type); - mImagep->drawSolid(0, 0, glow_color % mCurGlowStrength); + imagep->drawSolid(0, 0, glow_color % (mCurGlowStrength * alpha)); gGL.setSceneBlendType(LLRender::BT_ALPHA); } } @@ -602,9 +732,9 @@ void LLButton::draw() else { // no image - llwarns << "No image for button " << getName() << llendl; + lldebugs << "No image for button " << getName() << llendl; // draw it in pink so we can find it - gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4::pink1, FALSE); + gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4::pink1 % alpha, FALSE); } // let overlay image and text play well together @@ -634,11 +764,12 @@ void LLButton::draw() } // fade out overlay images on disabled buttons - LLColor4 overlay_color = mImageOverlayColor; - if (!getEnabled()) + LLColor4 overlay_color = mImageOverlayColor.get(); + if (!enabled) { overlay_color.mV[VALPHA] = 0.5f; } + overlay_color.mV[VALPHA] *= alpha; switch(mImageOverlayAlignment) { @@ -704,52 +835,51 @@ void LLButton::draw() x++; } - mGLFont->render(label, 0, (F32)x, (F32)(LLBUTTON_V_PAD + y_offset), - label_color, + // *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)(mBottomVPad + y_offset), + label_color % alpha, mHAlign, LLFontGL::BOTTOM, - mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NORMAL, - U32_MAX, text_width, - NULL, FALSE, FALSE); + LLFontGL::NORMAL, + mDropShadowedText ? LLFontGL::DROP_SHADOW_SOFT : LLFontGL::NO_SHADOW, + S32_MAX, text_width, + NULL, mUseEllipses); } - if (sDebugRects - || (LLView::sEditingUI && this == LLView::sEditingUIView)) - { - drawDebugRect(); - } - - // reset hover status for next frame - mNeedsHighlight = FALSE; + LLUICtrl::draw(); } -void LLButton::drawBorder(const LLColor4& color, S32 size) +void LLButton::drawBorder(LLUIImage* imagep, const LLColor4& color, S32 size) { + if (imagep == NULL) return; if (mScaleImage) { - mImagep->drawBorder(getLocalRect(), color, size); + imagep->drawBorder(getLocalRect(), color, size); } else { - mImagep->drawBorder(0, 0, color, size); + imagep->drawBorder(0, 0, color, size); } } -void LLButton::setClickedCallback(void (*cb)(void*), void* userdata) +BOOL LLButton::getToggleState() const { - mClickedCallback = cb; - if (userdata) - { - mCallbackUserData = userdata; - } + return getValue().asBoolean(); } - void LLButton::setToggleState(BOOL b) { - if( b != mToggleState ) + if( b != getToggleState() ) { setControlValue(b); // will fire LLControlVariable callbacks (if any) - mToggleState = b; // may or may not be redundant + setValue(b); // may or may not be redundant + // Unselected label assignments + autoResize(); } } @@ -764,19 +894,11 @@ void LLButton::setFlashing( BOOL b ) BOOL LLButton::toggleState() -{ - setToggleState( !mToggleState ); - return mToggleState; -} - -void LLButton::setValue(const LLSD& value ) { - mToggleState = value.asBoolean(); -} + bool flipped = ! getToggleState(); + setToggleState(flipped); -LLSD LLButton::getValue() const -{ - return mToggleState == TRUE; + return flipped; } void LLButton::setLabel( const LLStringExplicit& label ) @@ -803,26 +925,48 @@ void LLButton::setLabelSelected( const LLStringExplicit& label ) mSelectedLabel = label; } -void LLButton::setDisabledLabel( const LLStringExplicit& label ) +void LLButton::setImageUnselected(LLPointer<LLUIImage> image) { - mDisabledLabel = label; + mImageUnselected = image; + if (mImageUnselected.isNull()) + { + llwarns << "Setting default button image for: " << getName() << " to NULL" << llendl; + } } -void LLButton::setDisabledSelectedLabel( const LLStringExplicit& label ) +void LLButton::autoResize() { - mDisabledSelectedLabel = label; + LLUIString label; + if(getToggleState()) + { + label = mSelectedLabel; + } + else + { + label = mUnselectedLabel; + } + resize(label); } -void LLButton::setImageUnselected(LLPointer<LLUIImage> image) +void LLButton::resize(LLUIString label) { - mImageUnselected = image; + // 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 == TRUE) + { + if (btn_width - (mRightHPad + mLeftHPad) < label_width) + { + setRect(LLRect( getRect().mLeft, getRect().mTop, getRect().mLeft + label_width + mLeftHPad + mRightHPad , getRect().mBottom)); + } + } } - void LLButton::setImages( const std::string &image_name, const std::string &selected_name ) { - setImageUnselected(image_name); - setImageSelected(selected_name); - + setImageUnselected(LLUI::getUIImage(image_name)); + setImageSelected(LLUI::getUIImage(selected_name)); } void LLButton::setImageSelected(LLPointer<LLUIImage> image) @@ -840,26 +984,18 @@ void LLButton::setColor(const LLColor4& color) setImageColor(color); } - void LLButton::setImageDisabled(LLPointer<LLUIImage> image) { mImageDisabled = image; mDisabledImageColor = mImageColor; - mDisabledImageColor.mV[VALPHA] *= 0.5f; + mFadeWhenDisabled = TRUE; } void LLButton::setImageDisabledSelected(LLPointer<LLUIImage> image) { mImageDisabledSelected = image; mDisabledImageColor = mImageColor; - mDisabledImageColor.mV[VALPHA] *= 0.5f; -} - -void LLButton::setDisabledImages( const std::string &image_name, const std::string &selected_name, const LLColor4& c ) -{ - setImageDisabled(image_name); - setImageDisabledSelected(selected_name); - mDisabledImageColor = c; + mFadeWhenDisabled = TRUE; } void LLButton::setImageHoverSelected(LLPointer<LLUIImage> image) @@ -867,24 +1003,11 @@ void LLButton::setImageHoverSelected(LLPointer<LLUIImage> image) mImageHoverSelected = image; } -void LLButton::setDisabledImages( const std::string &image_name, const std::string &selected_name) -{ - LLColor4 clr = mImageColor; - clr.mV[VALPHA] *= .5f; - setDisabledImages( image_name, selected_name, clr ); -} - void LLButton::setImageHoverUnselected(LLPointer<LLUIImage> image) { mImageHoverUnselected = image; } -void LLButton::setHoverImages( const std::string& image_name, const std::string& selected_name ) -{ - setImageHoverUnselected(image_name); - setImageHoverSelected(selected_name); -} - void LLButton::setImageOverlay(const std::string& image_name, LLFontGL::HAlign alignment, const LLColor4& color) { if (image_name.empty()) @@ -899,11 +1022,23 @@ void LLButton::setImageOverlay(const std::string& image_name, LLFontGL::HAlign a } } +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() { - mMouseDownTimer.stop(); - mMouseDownTimer.reset(); + resetMouseDownTimer(); } //------------------------------------------------------------------------- @@ -924,42 +1059,6 @@ S32 round_up(S32 grid, S32 value) } } -void LLButton::setImageUnselected(const std::string &image_name) -{ - setImageUnselected(LLUI::getUIImage(image_name)); - mImageUnselectedName = image_name; -} - -void LLButton::setImageSelected(const std::string &image_name) -{ - setImageSelected(LLUI::getUIImage(image_name)); - mImageSelectedName = image_name; -} - -void LLButton::setImageHoverSelected(const std::string &image_name) -{ - setImageHoverSelected(LLUI::getUIImage(image_name)); - mImageHoverSelectedName = image_name; -} - -void LLButton::setImageHoverUnselected(const std::string &image_name) -{ - setImageHoverUnselected(LLUI::getUIImage(image_name)); - mImageHoverUnselectedName = image_name; -} - -void LLButton::setImageDisabled(const std::string &image_name) -{ - setImageDisabled(LLUI::getUIImage(image_name)); - mImageDisabledName = image_name; -} - -void LLButton::setImageDisabledSelected(const std::string &image_name) -{ - setImageDisabledSelected(LLUI::getUIImage(image_name)); - mImageDisabledSelectedName = image_name; -} - void LLButton::addImageAttributeToXML(LLXMLNodePtr node, const std::string& image_name, const LLUUID& image_id, @@ -975,149 +1074,65 @@ void LLButton::addImageAttributeToXML(LLXMLNodePtr node, } } -// virtual -LLXMLNodePtr LLButton::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - node->createChild("label", TRUE)->setStringValue(getLabelUnselected()); - node->createChild("label_selected", TRUE)->setStringValue(getLabelSelected()); - node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont)); - node->createChild("halign", TRUE)->setStringValue(LLFontGL::nameFromHAlign(mHAlign)); - - addImageAttributeToXML(node,mImageUnselectedName,mImageUnselectedID,std::string("image_unselected")); - addImageAttributeToXML(node,mImageSelectedName,mImageSelectedID,std::string("image_selected")); - addImageAttributeToXML(node,mImageHoverSelectedName,mImageHoverSelectedID,std::string("image_hover_selected")); - addImageAttributeToXML(node,mImageHoverUnselectedName,mImageHoverUnselectedID,std::string("image_hover_unselected")); - addImageAttributeToXML(node,mImageDisabledName,mImageDisabledID,std::string("image_disabled")); - addImageAttributeToXML(node,mImageDisabledSelectedName,mImageDisabledSelectedID,std::string("image_disabled_selected")); - - node->createChild("scale_image", TRUE)->setBoolValue(mScaleImage); - return node; +// static +void LLButton::toggleFloaterAndSetToggleState(LLUICtrl* ctrl, const LLSD& sdname) +{ + bool floater_vis = LLFloaterReg::toggleInstance(sdname.asString()); + LLButton* button = dynamic_cast<LLButton*>(ctrl); + if (button) + button->setToggleState(floater_vis); } -void clicked_help(void* data) +// static +// Gets called once +void LLButton::setFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) { - LLButton* self = (LLButton*)data; - if (!self) return; - - if (!LLUI::sHtmlHelp) - { + LLButton* button = dynamic_cast<LLButton*>(ctrl); + if (!button) return; - } - - LLUI::sHtmlHelp->show(self->getHelpURL()); + // 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(LLUI::sSettingGroups["floater"]->getControl(vis_control_name)); + // Set the clicked callback to toggle the floater + button->setClickedCallback(boost::bind(&LLFloaterReg::toggleFloaterInstance, sdname)); } // static -LLView* LLButton::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +void LLButton::setDockableFloaterToggle(LLUICtrl* ctrl, const LLSD& sdname) { - std::string name("button"); - node->getAttributeString("name", name); - - std::string label = name; - node->getAttributeString("label", label); - - std::string label_selected = label; - node->getAttributeString("label_selected", label_selected); - - LLFontGL* font = selectFont(node); - - std::string image_unselected; - if (node->hasAttribute("image_unselected")) node->getAttributeString("image_unselected",image_unselected); - - std::string image_selected; - if (node->hasAttribute("image_selected")) node->getAttributeString("image_selected",image_selected); - - std::string image_hover_selected; - if (node->hasAttribute("image_hover_selected")) node->getAttributeString("image_hover_selected",image_hover_selected); - - std::string image_hover_unselected; - if (node->hasAttribute("image_hover_unselected")) node->getAttributeString("image_hover_unselected",image_hover_unselected); - - std::string image_disabled_selected; - if (node->hasAttribute("image_disabled_selected")) node->getAttributeString("image_disabled_selected",image_disabled_selected); - - std::string image_disabled; - if (node->hasAttribute("image_disabled")) node->getAttributeString("image_disabled",image_disabled); - - std::string image_overlay; - node->getAttributeString("image_overlay", image_overlay); - - LLFontGL::HAlign image_overlay_alignment = LLFontGL::HCENTER; - std::string image_overlay_alignment_string; - if (node->hasAttribute("image_overlay_alignment")) - { - node->getAttributeString("image_overlay_alignment", image_overlay_alignment_string); - image_overlay_alignment = LLFontGL::hAlignFromName(image_overlay_alignment_string); - } - - - LLButton *button = new LLButton(name, - LLRect(), - image_unselected, - image_selected, - LLStringUtil::null, - NULL, - parent, - font, - label, - label_selected); - - node->getAttributeS32("pad_right", button->mRightHPad); - node->getAttributeS32("pad_left", button->mLeftHPad); - - BOOL is_toggle = button->getIsToggle(); - node->getAttributeBOOL("toggle", is_toggle); - button->setIsToggle(is_toggle); - - if(image_hover_selected != LLStringUtil::null) button->setImageHoverSelected(image_hover_selected); - - if(image_hover_unselected != LLStringUtil::null) button->setImageHoverUnselected(image_hover_unselected); - - if(image_disabled_selected != LLStringUtil::null) button->setImageDisabledSelected(image_disabled_selected ); - - if(image_disabled != LLStringUtil::null) button->setImageDisabled(image_disabled); - - if(image_overlay != LLStringUtil::null) button->setImageOverlay(image_overlay, image_overlay_alignment); - - if (node->hasAttribute("halign")) - { - LLFontGL::HAlign halign = selectFontHAlign(node); - button->setHAlign(halign); - } - - if (node->hasAttribute("scale_image")) - { - BOOL needsScale = FALSE; - node->getAttributeBOOL("scale_image",needsScale); - button->setScaleImage( needsScale ); - } + LLButton* button = dynamic_cast<LLButton*>(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(LLUI::sSettingGroups["floater"]->getControl(vis_control_name)); + // Set the clicked callback to toggle the floater + button->setClickedCallback(boost::bind(&LLDockableFloater::toggleInstance, sdname)); +} - if(label.empty()) - { - button->setLabelUnselected(node->getTextContents()); - } - if (label_selected.empty()) - { - button->setLabelSelected(node->getTextContents()); - } - - if (node->hasAttribute("help_url")) +// 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::sHelpImpl && + ctrl->findHelpTopic(help_topic)) { - std::string help_url; - node->getAttributeString("help_url",help_url); - button->setHelpURLCallback(help_url); + LLUI::sHelpImpl->showTopic(help_topic); + return; // success } - button->initFromXML(node, parent); - - return button; + // 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::setHelpURLCallback(const std::string &help_url) +void LLButton::resetMouseDownTimer() { - mHelpURL = help_url; - setClickedCallback(clicked_help,this); + mMouseDownTimer.stop(); + mMouseDownTimer.reset(); } diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index 724b77541a..cd149e3113 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -39,7 +39,7 @@ #include "v4color.h" #include "llframetimer.h" #include "llfontgl.h" -#include "llimage.h" +#include "lluiimage.h" #include "lluistring.h" // @@ -49,13 +49,9 @@ // PLEASE please use these "constants" when building your own buttons. // They are loaded from settings.xml at run time. extern S32 LLBUTTON_H_PAD; -extern S32 LLBUTTON_V_PAD; extern S32 BTN_HEIGHT_SMALL; extern S32 BTN_HEIGHT; -// All button widths should be rounded up to this size -extern S32 BTN_GRID; - // // Helpful functions // @@ -72,57 +68,116 @@ class LLButton : public LLUICtrl { public: - // simple button with text label - LLButton(const std::string& name, const LLRect &rect, const std::string& control_name = std::string(), - void (*on_click)(void*) = NULL, void *data = NULL); - - LLButton(const std::string& name, const LLRect& rect, - const std::string &unselected_image, - const std::string &selected_image, - const std::string& control_name, - void (*click_callback)(void*), - void *callback_data = NULL, - const LLFontGL* mGLFont = NULL, - const std::string& unselected_label = LLStringUtil::null, - const std::string& selected_label = LLStringUtil::null ); - - virtual ~LLButton(); - void init(void (*click_callback)(void*), void *callback_data, const LLFontGL* font, const std::string& control_name); - + struct Params + : public LLInitParam::Block<Params, LLUICtrl::Params> + { + // text label + Optional<std::string> label_selected; + Optional<bool> label_shadow; + Optional<bool> auto_resize; + Optional<bool> use_ellipses; + + // images + Optional<LLUIImage*> image_unselected, + image_selected, + image_hover_selected, + image_hover_unselected, + image_disabled_selected, + image_disabled, + image_pressed, + image_pressed_selected, + image_overlay; + + Optional<std::string> image_overlay_alignment; + + // colors + Optional<LLUIColor> label_color, + label_color_selected, + label_color_disabled, + label_color_disabled_selected, + highlight_color, + image_color, + image_color_disabled, + image_overlay_color, + flash_color; + + // layout + Optional<S32> pad_right; + Optional<S32> pad_left; + Optional<S32> pad_bottom; // under text label + + // callbacks + Optional<CommitCallbackParam> click_callback, // alias -> commit_callback + mouse_down_callback, + mouse_up_callback, + mouse_held_callback; + + // misc + Optional<bool> is_toggle, + scale_image, + commit_on_return; + + Optional<F32> hover_glow_amount; + Optional<TimeIntervalParam> held_down_delay; + + Params(); + }; +protected: + friend class LLUICtrlFactory; + LLButton(const Params&); + +public: + + ~LLButton(); + // For backward compatability only + typedef boost::function<void(void*)> button_callback_t; + void addImageAttributeToXML(LLXMLNodePtr node, const std::string& imageName, const LLUUID& imageID,const std::string& xmlTagName) const; - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - 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 void draw(); + /*virtual*/ BOOL postBuild(); + virtual void onMouseEnter(S32 x, S32 y, MASK mask); + 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 setClickedCallback( void (*cb)(void *data), void* data = NULL ); // mouse down and up within button - void setMouseDownCallback( void (*cb)(void *data) ) { mMouseDownCallback = cb; } // mouse down within button - void setMouseUpCallback( void (*cb)(void *data) ) { mMouseUpCallback = cb; } // mouse up, EVEN IF NOT IN BUTTON - void setHeldDownCallback( void (*cb)(void *data) ) { mHeldDownCallback = cb; } // Mouse button held down and in button - void setHeldDownDelay( F32 seconds, S32 frames = 0) { mHeldDownDelay = seconds; mHeldDownFrameDelay = frames; } + 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 getIsToggle() const { return mIsToggle; } - void setIsToggle(BOOL is_toggle) { mIsToggle = is_toggle; } BOOL toggleState(); - BOOL getToggleState() const { return mToggleState; } + BOOL getToggleState() const; void setToggleState(BOOL b); + void setHighlight(bool b); void setFlashing( BOOL b ); BOOL getFlashing() const { return mFlashing; } @@ -136,35 +191,31 @@ public: void setImageColor(const std::string& color_control); void setImageColor(const LLColor4& c); - virtual void setColor(const LLColor4& c); + /*virtual*/ void setColor(const LLColor4& c); void setImages(const std::string &image_name, const std::string &selected_name); - void setDisabledImages(const std::string &image_name, const std::string &selected_name); - void setDisabledImages(const std::string &image_name, const std::string &selected_name, const LLColor4& c); - void setHoverImages(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<LLUIImage> getImageOverlay() { return mImageOverlay; } - - - virtual void setValue(const LLSD& value ); - virtual LLSD getValue() const; + void autoResize(); // resize with label of current btn state + void resize(LLUIString label); // resize with label input 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 setDisabledLabel(const LLStringExplicit& disabled_label); - void setDisabledSelectedLabel(const LLStringExplicit& disabled_label); void setDisabledLabelColor( const LLColor4& c ) { mDisabledLabelColor = c; } void setFont(const LLFontGL *font) { mGLFont = ( font ? font : LLFontGL::getFontSansSerif()); } + + S32 getLastDrawCharsCount() const { return mLastDrawCharsCount; } + void setScaleImage(BOOL scale) { mScaleImage = scale; } BOOL getScaleImage() const { return mScaleImage; } @@ -172,19 +223,8 @@ public: void setBorderEnabled(BOOL b) { mBorderEnabled = b; } - static void onHeldDown(void *userdata); // to be called by gIdleCallbacks - void setHoverGlowStrength(F32 strength) { mHoverGlowStrength = strength; } - void setImageUnselected(const std::string &image_name); - const std::string& getImageUnselectedName() const { return mImageUnselectedName; } - void setImageSelected(const std::string &image_name); - const std::string& getImageSelectedName() const { return mImageSelectedName; } - void setImageHoverSelected(const std::string &image_name); - void setImageHoverUnselected(const std::string &image_name); - void setImageDisabled(const std::string &image_name); - void setImageDisabledSelected(const std::string &image_name); - void setImageUnselected(LLPointer<LLUIImage> image); void setImageSelected(LLPointer<LLUIImage> image); void setImageHoverSelected(LLPointer<LLUIImage> image); @@ -195,109 +235,99 @@ public: void setCommitOnReturn(BOOL commit) { mCommitOnReturn = commit; } BOOL getCommitOnReturn() const { return mCommitOnReturn; } - void setHelpURLCallback(const std::string &help_url); - const std::string& getHelpURL() const { return mHelpURL; } + 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; } + protected: - - virtual void drawBorder(const LLColor4& color, S32 size); - - void setImageUnselectedID(const LLUUID &image_id); - const LLUUID& getImageUnselectedID() const { return mImageUnselectedID; } - void setImageSelectedID(const LLUUID &image_id); - const LLUUID& getImageSelectedID() const { return mImageSelectedID; } - void setImageHoverSelectedID(const LLUUID &image_id); - void setImageHoverUnselectedID(const LLUUID &image_id); - void setImageDisabledID(const LLUUID &image_id); - void setImageDisabledSelectedID(const LLUUID &image_id); - const LLPointer<LLUIImage>& getImageUnselected() const { return mImageUnselected; } - const LLPointer<LLUIImage>& getImageSelected() const { return mImageSelected; } + LLPointer<LLUIImage> getImageUnselected() const { return mImageUnselected; } + LLPointer<LLUIImage> getImageSelected() const { return mImageSelected; } LLFrameTimer mMouseDownTimer; private: + void drawBorder(LLUIImage* imagep, const LLColor4& color, S32 size); + void resetMouseDownTimer(); - void (*mClickedCallback)(void* data ); - void (*mMouseDownCallback)(void *data); - void (*mMouseUpCallback)(void *data); - void (*mHeldDownCallback)(void *data); - - const LLFontGL *mGLFont; +private: + commit_signal_t* mMouseDownSignal; + commit_signal_t* mMouseUpSignal; + commit_signal_t* mHeldDownSignal; - S32 mMouseDownFrame; - F32 mHeldDownDelay; // seconds, after which held-down callbacks get called - S32 mHeldDownFrameDelay; // frames, after which held-down callbacks get called - - LLPointer<LLUIImage> mImageOverlay; - LLFontGL::HAlign mImageOverlayAlignment; - LLColor4 mImageOverlayColor; - - LLPointer<LLUIImage> mImageUnselected; - LLUIString mUnselectedLabel; - LLColor4 mUnselectedLabelColor; - - LLPointer<LLUIImage> mImageSelected; - LLUIString mSelectedLabel; - LLColor4 mSelectedLabelColor; + 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<LLUIImage> mImageHoverSelected; + LLPointer<LLUIImage> mImageOverlay; + LLFontGL::HAlign mImageOverlayAlignment; + LLUIColor mImageOverlayColor; - LLPointer<LLUIImage> mImageHoverUnselected; + LLPointer<LLUIImage> mImageUnselected; + LLUIString mUnselectedLabel; + LLUIColor mUnselectedLabelColor; - LLPointer<LLUIImage> mImageDisabled; - LLUIString mDisabledLabel; - LLColor4 mDisabledLabelColor; + LLPointer<LLUIImage> mImageSelected; + LLUIString mSelectedLabel; + LLUIColor mSelectedLabelColor; - LLPointer<LLUIImage> mImageDisabledSelected; - LLUIString mDisabledSelectedLabel; - LLColor4 mDisabledSelectedLabelColor; + LLPointer<LLUIImage> mImageHoverSelected; - LLUUID mImageUnselectedID; - LLUUID mImageSelectedID; - LLUUID mImageHoverSelectedID; - LLUUID mImageHoverUnselectedID; - LLUUID mImageDisabledID; - LLUUID mImageDisabledSelectedID; - std::string mImageUnselectedName; - std::string mImageSelectedName; - std::string mImageHoverSelectedName; - std::string mImageHoverUnselectedName; - std::string mImageDisabledName; - std::string mImageDisabledSelectedName; + LLPointer<LLUIImage> mImageHoverUnselected; - LLColor4 mHighlightColor; - LLColor4 mUnselectedBgColor; - LLColor4 mSelectedBgColor; - LLColor4 mFlashBgColor; + LLPointer<LLUIImage> mImageDisabled; + LLUIColor mDisabledLabelColor; - LLColor4 mImageColor; - LLColor4 mDisabledImageColor; + LLPointer<LLUIImage> mImageDisabledSelected; + LLUIString mDisabledSelectedLabel; + LLUIColor mDisabledSelectedLabelColor; - BOOL mIsToggle; - BOOL mToggleState; - BOOL mScaleImage; + LLPointer<LLUIImage> mImagePressed; + LLPointer<LLUIImage> mImagePressedSelected; - BOOL mDropShadowedText; + LLUIColor mHighlightColor; + LLUIColor mFlashBgColor; - BOOL mBorderEnabled; + LLUIColor mImageColor; + LLUIColor mDisabledImageColor; - BOOL mFlashing; + BOOL mIsToggle; + BOOL mScaleImage; - LLFontGL::HAlign mHAlign; - S32 mLeftHPad; - S32 mRightHPad; + BOOL mDropShadowedText; + BOOL mAutoResize; + BOOL mUseEllipses; + BOOL mBorderEnabled; - F32 mHoverGlowStrength; - F32 mCurGlowStrength; + BOOL mFlashing; - BOOL mNeedsHighlight; - BOOL mCommitOnReturn; + LLFontGL::HAlign mHAlign; + S32 mLeftHPad; + S32 mRightHPad; + S32 mBottomVPad; // under text label - std::string mHelpURL; + F32 mHoverGlowStrength; + F32 mCurGlowStrength; - LLPointer<LLUIImage> mImagep; + BOOL mNeedsHighlight; + BOOL mCommitOnReturn; + BOOL mFadeWhenDisabled; + bool mForcePressedState; - LLFrameTimer mFlashingTimer; + LLFrameTimer mFlashingTimer; }; +// Build time optimization, generate once in .cpp file +#ifndef LLBUTTON_CPP +extern template class LLButton* LLView::getChild<class LLButton>( + const std::string& name, BOOL recurse) const; +#endif + #endif // LL_LLBUTTON_H diff --git a/indra/llui/llcallbackmap.h b/indra/llui/llcallbackmap.h index eadb9c98f3..97b1e2fc50 100644 --- a/indra/llui/llcallbackmap.h +++ b/indra/llui/llcallbackmap.h @@ -30,11 +30,11 @@ * $/LicenseInfo$ */ -#ifndef LL_CALLBACK_MAP_H -#define LL_CALLBACK_MAP_H +#ifndef LLCALLBACKMAP_H +#define LLCALLBACKMAP_H #include <map> -#include "llstring.h" +#include <string> class LLCallbackMap { @@ -45,12 +45,19 @@ public: typedef std::map<std::string, LLCallbackMap> map_t; typedef map_t::iterator map_iter_t; typedef map_t::const_iterator map_const_iter_t; - + + template <class T> + static void* buildPanel(void* data) + { + T* panel = new T(); + return (void*)panel; + } + LLCallbackMap() : mCallback(NULL), mData(NULL) { } - LLCallbackMap(callback_t callback, void* data) : mCallback(callback), mData(data) { } + LLCallbackMap(callback_t callback, void* data = NULL) : mCallback(callback), mData(data) { } callback_t mCallback; void* mData; }; -#endif // LL_CALLBACK_MAP_H +#endif // LLCALLBACKMAP_H diff --git a/indra/llui/llcheckboxctrl.cpp b/indra/llui/llcheckboxctrl.cpp index eda9467d87..3d32157406 100644 --- a/indra/llui/llcheckboxctrl.cpp +++ b/indra/llui/llcheckboxctrl.cpp @@ -31,9 +31,9 @@ */ // The mutants are coming! - #include "linden_common.h" +#define LLCHECKBOXCTRL_CPP #include "llcheckboxctrl.h" #include "llgl.h" @@ -49,101 +49,90 @@ const U32 MAX_STRING_LENGTH = 10; -static LLRegisterWidget<LLCheckBoxCtrl> r("check_box"); - - -LLCheckBoxCtrl::LLCheckBoxCtrl(const std::string& name, const LLRect& rect, - const std::string& label, - const LLFontGL* font, - void (*commit_callback)(LLUICtrl* ctrl, void* userdata), - void* callback_user_data, - BOOL initial_value, - BOOL use_radio_style, - const std::string& control_which) -: LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data, FOLLOWS_LEFT | FOLLOWS_TOP), - mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), - mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), - mRadioStyle( use_radio_style ), - mInitialValue( initial_value ), - mSetValue( initial_value ) +static LLDefaultChildRegistry::Register<LLCheckBoxCtrl> r("check_box"); + +// Compiler optimization, generate extern template +template class LLCheckBoxCtrl* LLView::getChild<class LLCheckBoxCtrl>( + const std::string& name, BOOL recurse) const; + +LLCheckBoxCtrl::Params::Params() +: text_enabled_color("text_enabled_color"), + text_disabled_color("text_disabled_color"), + initial_value("initial_value", false), + label_text("label_text"), + check_button("check_button"), + radio_style("radio_style") +{} + + +LLCheckBoxCtrl::LLCheckBoxCtrl(const LLCheckBoxCtrl::Params& p) +: LLUICtrl(p), + mTextEnabledColor(p.text_enabled_color()), + mTextDisabledColor(p.text_disabled_color()), + mFont(p.font()) { - if (font) - { - mFont = font; - } - else - { - mFont = LLFontGL::getFontSansSerifSmall(); - } + mViewModel->setValue(LLSD(p.initial_value)); + mViewModel->resetDirty(); + static LLUICachedControl<S32> llcheckboxctrl_spacing ("UICheckboxctrlSpacing", 0); + static LLUICachedControl<S32> llcheckboxctrl_hpad ("UICheckboxctrlHPad", 0); + static LLUICachedControl<S32> llcheckboxctrl_vpad ("UICheckboxctrlVPad", 0); + static LLUICachedControl<S32> llcheckboxctrl_btn_size ("UICheckboxctrlBtnSize", 0); // must be big enough to hold all children setUseBoundingRect(TRUE); - mKeyboardFocusOnClick = TRUE; - // Label (add a little space to make sure text actually renders) const S32 FUDGE = 10; - S32 text_width = mFont->getWidth( label ) + FUDGE; + S32 text_width = mFont->getWidth( p.label ) + FUDGE; S32 text_height = llround(mFont->getLineHeight()); LLRect label_rect; label_rect.setOriginAndSize( - LLCHECKBOXCTRL_HPAD + LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING, - LLCHECKBOXCTRL_VPAD + 1, // padding to get better alignment - text_width + LLCHECKBOXCTRL_HPAD, + llcheckboxctrl_hpad + llcheckboxctrl_btn_size + llcheckboxctrl_spacing, + llcheckboxctrl_vpad + 1, // padding to get better alignment + text_width + llcheckboxctrl_hpad, text_height ); // *HACK Get rid of this with SL-55508... // this allows blank check boxes and radio boxes for now - std::string local_label = label; + std::string local_label = p.label; if(local_label.empty()) { local_label = " "; } - mLabel = new LLTextBox( std::string("CheckboxCtrl Label"), label_rect, local_label, mFont ); - mLabel->setFollowsLeft(); - mLabel->setFollowsBottom(); + LLTextBox::Params tbparams = p.label_text; + tbparams.rect(label_rect); + tbparams.initial_value(local_label); + if (p.font.isProvided()) + { + tbparams.font(p.font); + } + tbparams.text_color( p.enabled() ? p.text_enabled_color() : p.text_disabled_color() ); + mLabel = LLUICtrlFactory::create<LLTextBox> (tbparams); addChild(mLabel); // Button // Note: button cover the label by extending all the way to the right. LLRect btn_rect; btn_rect.setOriginAndSize( - LLCHECKBOXCTRL_HPAD, - LLCHECKBOXCTRL_VPAD, - LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING + text_width + LLCHECKBOXCTRL_HPAD, - llmax( text_height, LLCHECKBOXCTRL_BTN_SIZE ) + LLCHECKBOXCTRL_VPAD); + llcheckboxctrl_hpad, + llcheckboxctrl_vpad, + llcheckboxctrl_btn_size + llcheckboxctrl_spacing + text_width + llcheckboxctrl_hpad, + llmax( text_height, llcheckboxctrl_btn_size() ) + llcheckboxctrl_vpad); std::string active_true_id, active_false_id; std::string inactive_true_id, inactive_false_id; - if (mRadioStyle) - { - active_true_id = "UIImgRadioActiveSelectedUUID"; - active_false_id = "UIImgRadioActiveUUID"; - inactive_true_id = "UIImgRadioInactiveSelectedUUID"; - inactive_false_id = "UIImgRadioInactiveUUID"; - mButton = new LLButton(std::string("Radio control button"), btn_rect, - active_false_id, active_true_id, control_which, - &LLCheckBoxCtrl::onButtonPress, this, LLFontGL::getFontSansSerif() ); - mButton->setDisabledImages( inactive_false_id, inactive_true_id ); - mButton->setHoverGlowStrength(0.35f); - } - else - { - active_false_id = "UIImgCheckboxActiveUUID"; - active_true_id = "UIImgCheckboxActiveSelectedUUID"; - inactive_true_id = "UIImgCheckboxInactiveSelectedUUID"; - inactive_false_id = "UIImgCheckboxInactiveUUID"; - mButton = new LLButton(std::string("Checkbox control button"), btn_rect, - active_false_id, active_true_id, control_which, - &LLCheckBoxCtrl::onButtonPress, this, LLFontGL::getFontSansSerif() ); - mButton->setDisabledImages( inactive_false_id, inactive_true_id ); - mButton->setHoverGlowStrength(0.35f); - } - mButton->setIsToggle(TRUE); - mButton->setToggleState( initial_value ); - mButton->setFollowsLeft(); - mButton->setFollowsBottom(); - mButton->setCommitOnReturn(FALSE); + + LLButton::Params params = p.check_button; + params.rect(btn_rect); + //params.control_name(p.control_name); + params.click_callback.function(boost::bind(&LLCheckBoxCtrl::onButtonPress, this, _2)); + 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<LLButton>(params); addChild(mButton); } @@ -154,24 +143,14 @@ LLCheckBoxCtrl::~LLCheckBoxCtrl() // static -void LLCheckBoxCtrl::onButtonPress( void *userdata ) +void LLCheckBoxCtrl::onButtonPress( const LLSD& data ) { - LLCheckBoxCtrl* self = (LLCheckBoxCtrl*) userdata; - - if (self->mRadioStyle) - { - self->setValue(TRUE); - } - - self->setControlValue(self->getValue()); - // HACK: because buttons don't normally commit - self->onCommit(); + //if (mRadioStyle) + //{ + // setValue(TRUE); + //} - if (self->mKeyboardFocusOnClick) - { - self->setFocus( TRUE ); - self->onFocusReceived(); - } + onCommit(); } void LLCheckBoxCtrl::onCommit() @@ -179,6 +158,7 @@ void LLCheckBoxCtrl::onCommit() if( getEnabled() ) { setTentative(FALSE); + setControlValue(getValue()); LLUICtrl::onCommit(); } } @@ -186,7 +166,15 @@ void LLCheckBoxCtrl::onCommit() void LLCheckBoxCtrl::setEnabled(BOOL b) { LLView::setEnabled(b); - mButton->setEnabled(b); + + if (b) + { + mLabel->setColor( mTextEnabledColor.get() ); + } + else + { + mLabel->setColor( mTextDisabledColor.get() ); + } } void LLCheckBoxCtrl::clear() @@ -197,43 +185,33 @@ void LLCheckBoxCtrl::clear() void LLCheckBoxCtrl::reshape(S32 width, S32 height, BOOL called_from_parent) { //stretch or shrink bounding rectangle of label when rebuilding UI at new scale + static LLUICachedControl<S32> llcheckboxctrl_spacing ("UICheckboxctrlSpacing", 0); + static LLUICachedControl<S32> llcheckboxctrl_hpad ("UICheckboxctrlHPad", 0); + static LLUICachedControl<S32> llcheckboxctrl_vpad ("UICheckboxctrlVPad", 0); + static LLUICachedControl<S32> llcheckboxctrl_btn_size ("UICheckboxctrlBtnSize", 0); + const S32 FUDGE = 10; S32 text_width = mFont->getWidth( mLabel->getText() ) + FUDGE; S32 text_height = llround(mFont->getLineHeight()); LLRect label_rect; label_rect.setOriginAndSize( - LLCHECKBOXCTRL_HPAD + LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING, - LLCHECKBOXCTRL_VPAD, + llcheckboxctrl_hpad + llcheckboxctrl_btn_size + llcheckboxctrl_spacing, + llcheckboxctrl_vpad, text_width, text_height ); mLabel->setRect(label_rect); LLRect btn_rect; btn_rect.setOriginAndSize( - LLCHECKBOXCTRL_HPAD, - LLCHECKBOXCTRL_VPAD, - LLCHECKBOXCTRL_BTN_SIZE + LLCHECKBOXCTRL_SPACING + text_width, - llmax( text_height, LLCHECKBOXCTRL_BTN_SIZE ) ); + llcheckboxctrl_hpad, + llcheckboxctrl_vpad, + llcheckboxctrl_btn_size + llcheckboxctrl_spacing + text_width, + llmax( text_height, llcheckboxctrl_btn_size() ) ); mButton->setRect( btn_rect ); LLUICtrl::reshape(width, height, called_from_parent); } -void LLCheckBoxCtrl::draw() -{ - if (getEnabled()) - { - mLabel->setColor( mTextEnabledColor ); - } - else - { - mLabel->setColor( mTextDisabledColor ); - } - - // Draw children - LLUICtrl::draw(); -} - //virtual void LLCheckBoxCtrl::setValue(const LLSD& value ) { @@ -246,6 +224,18 @@ 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 ); @@ -264,12 +254,6 @@ BOOL LLCheckBoxCtrl::setLabelArg( const std::string& key, const LLStringExplicit return res; } -//virtual -std::string LLCheckBoxCtrl::getControlName() const -{ - return mButton->getControlName(); -} - // virtual void LLCheckBoxCtrl::setControlName(const std::string& control_name, LLView* context) { @@ -282,7 +266,7 @@ BOOL LLCheckBoxCtrl::isDirty() const { if ( mButton ) { - return (mSetValue != mButton->getToggleState()); + return mButton->isDirty(); } return FALSE; // Shouldn't get here } @@ -293,78 +277,6 @@ void LLCheckBoxCtrl::resetDirty() { if ( mButton ) { - mSetValue = mButton->getToggleState(); - } -} - - - -// virtual -LLXMLNodePtr LLCheckBoxCtrl::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - node->createChild("label", TRUE)->setStringValue(mLabel->getText()); - - std::string control_name = mButton->getControlName(); - - node->createChild("initial_value", TRUE)->setBoolValue(mInitialValue); - - node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mFont)); - - node->createChild("radio_style", TRUE)->setBoolValue(mRadioStyle); - - return node; -} - -// static -LLView* LLCheckBoxCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("checkbox"); - node->getAttributeString("name", name); - - std::string label(""); - node->getAttributeString("label", label); - - LLFontGL* font = LLView::selectFont(node); - - BOOL radio_style = FALSE; - node->getAttributeBOOL("radio_style", radio_style); - - LLUICtrlCallback callback = NULL; - - if (label.empty()) - { - label.assign(node->getTextContents()); + mButton->resetDirty(); } - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - LLCheckBoxCtrl* checkbox = new LLCheckboxCtrl(name, - rect, - label, - font, - callback, - NULL, - FALSE, - radio_style); // if true, draw radio button style icons - - BOOL initial_value = checkbox->getValue().asBoolean(); - node->getAttributeBOOL("initial_value", initial_value); - - LLColor4 color; - color = LLUI::sColorsGroup->getColor( "LabelTextColor" ); - LLUICtrlFactory::getAttributeColor(node,"text_enabled_color", color); - checkbox->setEnabledColor(color); - - color = LLUI::sColorsGroup->getColor( "LabelDisabledColor" ); - LLUICtrlFactory::getAttributeColor(node,"text_disabled_color", color); - checkbox->setDisabledColor(color); - - checkbox->setValue(initial_value); - - checkbox->initFromXML(node, parent); - - return checkbox; } diff --git a/indra/llui/llcheckboxctrl.h b/indra/llui/llcheckboxctrl.h index ff867f5193..28d50f957d 100644 --- a/indra/llui/llcheckboxctrl.h +++ b/indra/llui/llcheckboxctrl.h @@ -33,24 +33,14 @@ #ifndef LL_LLCHECKBOXCTRL_H #define LL_LLCHECKBOXCTRL_H - -#include "stdtypes.h" #include "lluictrl.h" #include "llbutton.h" +#include "lltextbox.h" #include "v4color.h" -#include "llrect.h" // // Constants // -const S32 LLCHECKBOXCTRL_BTN_SIZE = 13; -const S32 LLCHECKBOXCTRL_VPAD = 2; -const S32 LLCHECKBOXCTRL_HPAD = 2; -const S32 LLCHECKBOXCTRL_SPACING = 5; -const S32 LLCHECKBOXCTRL_HEIGHT = 16; - -// Deprecated, don't use. -#define CHECKBOXCTRL_HEIGHT LLCHECKBOXCTRL_HEIGHT const BOOL RADIO_STYLE = TRUE; const BOOL CHECK_STYLE = FALSE; @@ -59,30 +49,38 @@ const BOOL CHECK_STYLE = FALSE; // Classes // class LLFontGL; -class LLTextBox; class LLViewBorder; class LLCheckBoxCtrl : public LLUICtrl { public: - LLCheckBoxCtrl(const std::string& name, const LLRect& rect, const std::string& label, - const LLFontGL* font = NULL, - void (*commit_callback)(LLUICtrl*, void*) = NULL, - void* callback_userdata = NULL, - BOOL initial_value = FALSE, - BOOL use_radio_style = FALSE, // if true, draw radio button style icons - const std::string& control_which = LLStringUtil::null); + struct Params + : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<LLUIColor> text_enabled_color; + Optional<LLUIColor> text_disabled_color; + Optional<bool> initial_value; // override LLUICtrl initial_value + + Optional<LLTextBox::Params> label_text; + Optional<LLButton::Params> check_button; + + Ignored radio_style; + + Params(); + }; + virtual ~LLCheckBoxCtrl(); - // LLView interface +protected: + LLCheckBoxCtrl(const Params&); + friend class LLUICtrlFactory; - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); +public: + // LLView interface virtual void setEnabled( BOOL b ); - virtual void draw(); virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); // LLUICtrl interface @@ -91,8 +89,8 @@ public: BOOL get() { return (BOOL)getValue().asBoolean(); } void set(BOOL value) { setValue(value); } - virtual void setTentative(BOOL b) { mButton->setTentative(b); } - virtual BOOL getTentative() const { return mButton->getTentative(); } + virtual void setTentative(BOOL b); + virtual BOOL getTentative() const; virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); @@ -108,10 +106,12 @@ public: void setLabel( const LLStringExplicit& label ); std::string getLabel() const; + void setFont( const LLFontGL* font ) { mFont = font; } + const LLFontGL* getFont() { return mFont; } + virtual void setControlName(const std::string& control_name, LLView* context); - virtual std::string getControlName() const; - static void onButtonPress(void *userdata); + void onButtonPress(const LLSD& data); virtual BOOL isDirty() const; // Returns TRUE if the user has modified this control. virtual void resetDirty(); // Clear dirty state @@ -121,19 +121,15 @@ protected: LLButton* mButton; LLTextBox* mLabel; const LLFontGL* mFont; - LLColor4 mTextEnabledColor; - LLColor4 mTextDisabledColor; - BOOL mRadioStyle; - BOOL mInitialValue; // Value set in constructor - BOOL mSetValue; // Value set programmatically - BOOL mKeyboardFocusOnClick; - LLViewBorder* mBorder; -}; - -// HACK: fix old capitalization problem -//typedef LLCheckBoxCtrl LLCheckboxCtrl; -#define LLCheckboxCtrl LLCheckBoxCtrl + LLUIColor mTextEnabledColor; + LLUIColor mTextDisabledColor; +}; +// Build time optimization, generate once in .cpp file +#ifndef LLCHECKBOXCTRL_CPP +extern template class LLCheckBoxCtrl* LLView::getChild<class LLCheckBoxCtrl>( + const std::string& name, BOOL recurse) const; +#endif #endif // LL_LLCHECKBOXCTRL_H diff --git a/indra/llui/llclipboard.cpp b/indra/llui/llclipboard.cpp index 2cb8197a67..cbd4cb380f 100644 --- a/indra/llui/llclipboard.cpp +++ b/indra/llui/llclipboard.cpp @@ -61,6 +61,12 @@ void LLClipboard::copyFromSubstring(const LLWString &src, S32 pos, S32 len, cons LLView::getWindow()->copyTextToClipboard( mString ); } +void LLClipboard::copyFromString(const LLWString &src, const LLUUID& source_id ) +{ + mSourceID = source_id; + mString = src; + LLView::getWindow()->copyTextToClipboard( mString ); +} const LLWString& LLClipboard::getPasteWString( LLUUID* source_id ) { diff --git a/indra/llui/llclipboard.h b/indra/llui/llclipboard.h index 034a7a6aeb..d7ffeb33e0 100644 --- a/indra/llui/llclipboard.h +++ b/indra/llui/llclipboard.h @@ -50,6 +50,7 @@ public: (i.e. X11/Linux). */ void copyFromSubstring(const LLWString ©_from, S32 pos, S32 len, const LLUUID& source_id = LLUUID::null ); + void copyFromString(const LLWString ©_from, const LLUUID& source_id = LLUUID::null ); BOOL canPasteString() const; const LLWString& getPasteWString(LLUUID* source_id = NULL); diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index 28a05c13f5..f29e8785eb 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -48,173 +48,147 @@ #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 LLCOMBOBOX_HEIGHT = 0; S32 LLCOMBOBOX_WIDTH = 0; S32 MAX_COMBO_WIDTH = 500; -static LLRegisterWidget<LLComboBox> r1("combo_box"); +static LLDefaultChildRegistry::Register<LLComboBox> register_combo_box("combo_box"); -LLComboBox::LLComboBox( const std::string& name, const LLRect &rect, const std::string& label, - void (*commit_callback)(LLUICtrl*,void*), - void *callback_userdata - ) -: LLUICtrl(name, rect, TRUE, commit_callback, callback_userdata, - FOLLOWS_LEFT | FOLLOWS_TOP), - mTextEntry(NULL), - mArrowImage(NULL), - mAllowTextEntry(FALSE), - mMaxChars(20), - mTextEntryTentative(TRUE), - mListPosition(BELOW), - mPrearrangeCallback( NULL ), - mTextEntryCallback( NULL ), - mLabel(label) +void LLComboBox::PreferredPositionValues::declareValues() { - // Always use text box - // Text label button - mButton = new LLButton(mLabel, - LLRect(), - LLStringUtil::null, - NULL, this); - mButton->setImageUnselected(std::string("square_btn_32x128.tga")); - mButton->setImageSelected(std::string("square_btn_selected_32x128.tga")); - mButton->setImageDisabled(std::string("square_btn_32x128.tga")); - mButton->setImageDisabledSelected(std::string("square_btn_selected_32x128.tga")); - mButton->setScaleImage(TRUE); - - mButton->setMouseDownCallback(onButtonDown); - mButton->setFont(LLFontGL::getFontSansSerifSmall()); - mButton->setFollows(FOLLOWS_LEFT | FOLLOWS_BOTTOM | FOLLOWS_RIGHT); - mButton->setHAlign( LLFontGL::LEFT ); - mButton->setRightHPad(2); - addChild(mButton); - - // disallow multiple selection - mList = new LLScrollListCtrl(std::string("ComboBox"), LLRect(), - &LLComboBox::onItemSelected, this, FALSE); - mList->setVisible(FALSE); - mList->setBgWriteableColor( LLColor4(1,1,1,1) ); - mList->setCommitOnKeyboardMovement(FALSE); - addChild(mList); - - mArrowImage = LLUI::sImageProvider->getUIImage("combobox_arrow.tga"); - mButton->setImageOverlay("combobox_arrow.tga", LLFontGL::RIGHT); + declare("above", ABOVE); + declare("below", BELOW); +} - updateLayout(); +LLComboBox::ItemParams::ItemParams() +: label("label") +{ } -LLComboBox::~LLComboBox() +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") { - // children automatically deleted, including mMenu, mButton + addSynonym(items, "combo_item"); } -// virtual -LLXMLNodePtr LLComboBox::getXML(bool save_children) const + +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()), + mListPosition(p.list_position), + mLastSelectedIndex(-1) { - LLXMLNodePtr node = LLUICtrl::getXML(); + // Text label button - // Attributes + 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); - node->createChild("allow_text_entry", TRUE)->setBoolValue(mAllowTextEntry); + if(mAllowTextEntry) + { + button_params.pad_right(2); + } - node->createChild("max_chars", TRUE)->setIntValue(mMaxChars); + mArrowImage = button_params.image_unselected; - // Contents + mButton = LLUICtrlFactory::create<LLButton>(button_params); - std::vector<LLScrollListItem*> data_list = mList->getAllData(); - std::vector<LLScrollListItem*>::iterator data_itor; - for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor) + + if(mAllowTextEntry) { - LLScrollListItem* item = *data_itor; - LLScrollListCell* cell = item->getColumn(0); - if (cell) - { - LLXMLNodePtr item_node = node->createChild("combo_item", FALSE); - LLSD value = item->getValue(); - item_node->createChild("value", TRUE)->setStringValue(value.asString()); - item_node->createChild("enabled", TRUE)->setBoolValue(item->getEnabled()); - item_node->setStringValue(cell->getValue().asString()); - } + //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); - return node; -} - -// static -LLView* LLComboBox::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("combo_box"); - node->getAttributeString("name", name); - - std::string label(""); - node->getAttributeString("label", label); + 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); - LLRect rect; - createRect(node, rect, parent, LLRect()); + mList = LLUICtrlFactory::create<LLScrollListCtrl>(params); + addChild(mList); - BOOL allow_text_entry = FALSE; - node->getAttributeBOOL("allow_text_entry", allow_text_entry); + // 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)); - S32 max_chars = 20; - node->getAttributeS32("max_chars", max_chars); + for (LLInitParam::ParamIterator<ItemParams>::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()); + } - LLUICtrlCallback callback = NULL; + mList->addRow(item_params); + } - LLComboBox* combo_box = new LLComboBox(name, - rect, - label, - callback, - NULL); - combo_box->setAllowTextEntry(allow_text_entry, max_chars); + createLineEditor(p); - combo_box->initFromXML(node, parent); + setTopLostCallback(boost::bind(&LLComboBox::hideList, this)); +} - const std::string& contents = node->getValue(); +void LLComboBox::initFromParams(const LLComboBox::Params& p) +{ + LLUICtrl::initFromParams(p); - if (contents.find_first_not_of(" \n\t") != contents.npos) - { - llerrs << "Legacy combo box item format used! Please convert to <combo_item> tags!" << llendl; - } - else + if (!acceptsTextInput() && mLabel.empty()) { - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - if (child->hasName("combo_item")) - { - std::string label = child->getTextContents(); - - std::string value = label; - child->getAttributeString("value", value); - - combo_box->add(label, LLSD(value) ); - } - } + selectFirstItem(); } +} - // if providing user text entry or descriptive label - // don't select an item under the hood - if (!combo_box->acceptsTextInput() && combo_box->mLabel.empty()) +// virtual +BOOL LLComboBox::postBuild() +{ + if (mControlVariable) { - combo_box->selectFirstItem(); + setValue(mControlVariable->getValue()); // selects the appropriate item } - - return combo_box; + return TRUE; } -void LLComboBox::setEnabled(BOOL enabled) + +LLComboBox::~LLComboBox() { - LLView::setEnabled(enabled); - mButton->setEnabled(enabled); + // children automatically deleted, including mMenu, mButton } + void LLComboBox::clear() { if (mTextEntry) @@ -223,9 +197,8 @@ void LLComboBox::clear() } mButton->setLabelSelected(LLStringUtil::null); mButton->setLabelUnselected(LLStringUtil::null); - mButton->setDisabledLabel(LLStringUtil::null); - mButton->setDisabledSelectedLabel(LLStringUtil::null); mList->deselectAllItems(); + mLastSelectedIndex = -1; } void LLComboBox::onCommit() @@ -237,6 +210,7 @@ void LLComboBox::onCommit() mTextEntry->setValue(getSimple()); mTextEntry->setTentative(FALSE); } + setControlValue(getValue()); LLUICtrl::onCommit(); } @@ -330,6 +304,7 @@ BOOL LLComboBox::setSimple(const LLStringExplicit& name) if (found) { setLabel(name); + mLastSelectedIndex = mList->getFirstSelectedIndex(); } return found; @@ -346,6 +321,7 @@ void LLComboBox::setValue(const LLSD& value) { setLabel( mList->getSelectedItemLabel() ); } + mLastSelectedIndex = mList->getFirstSelectedIndex(); } } @@ -393,6 +369,7 @@ void LLComboBox::setLabel(const LLStringExplicit& name) if (mList->selectItemByLabel(name, FALSE)) { mTextEntry->setTentative(FALSE); + mLastSelectedIndex = mList->getFirstSelectedIndex(); } else { @@ -402,10 +379,7 @@ void LLComboBox::setLabel(const LLStringExplicit& name) if (!mAllowTextEntry) { - mButton->setLabelUnselected(name); - mButton->setLabelSelected(name); - mButton->setDisabledLabel(name); - mButton->setDisabledSelectedLabel(name); + mButton->setLabel(name); } } @@ -421,6 +395,7 @@ BOOL LLComboBox::remove(const std::string& name) { mList->deleteSingleItem(mList->getItemIndex(item)); } + mLastSelectedIndex = mList->getFirstSelectedIndex(); } return found; @@ -431,6 +406,7 @@ BOOL LLComboBox::remove(S32 index) if (index < mList->getItemCount()) { mList->deleteSingleItem(index); + setLabel(mList->getSelectedItemLabel()); return TRUE; } return FALSE; @@ -448,41 +424,31 @@ void LLComboBox::onFocusLost() LLUICtrl::onFocusLost(); } -void LLComboBox::onLostTop() -{ - hideList(); -} - - void LLComboBox::setButtonVisible(BOOL visible) { + static LLUICachedControl<S32> drop_shadow_button ("DropShadowButton", 0); + mButton->setVisible(visible); if (mTextEntry) { LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); if (visible) { - text_entry_rect.mRight -= llmax(8,mArrowImage->getWidth()) + 2 * LLUI::sConfigGroup->getS32("DropShadowButton"); + S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; + text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * drop_shadow_button; } //mTextEntry->setRect(text_entry_rect); mTextEntry->reshape(text_entry_rect.getWidth(), text_entry_rect.getHeight(), TRUE); } } -void LLComboBox::draw() -{ - mButton->setEnabled(getEnabled() /*&& !mList->isEmpty()*/); - - // Draw children normally - LLUICtrl::draw(); -} - BOOL LLComboBox::setCurrentByIndex( S32 index ) { BOOL found = mList->selectNthItem( index ); if (found) { setLabel(mList->getSelectedItemLabel()); + mLastSelectedIndex = index; } return found; } @@ -498,54 +464,47 @@ S32 LLComboBox::getCurrentIndex() const } -void LLComboBox::updateLayout() +void LLComboBox::createLineEditor(const LLComboBox::Params& p) { + static LLUICachedControl<S32> drop_shadow_button ("DropShadowButton", 0); LLRect rect = getLocalRect(); if (mAllowTextEntry) { - S32 shadow_size = LLUI::sConfigGroup->getS32("DropShadowButton"); - mButton->setRect(LLRect( getRect().getWidth() - llmax(8,mArrowImage->getWidth()) - 2 * shadow_size, + S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; + S32 shadow_size = drop_shadow_button; + 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); - if (!mTextEntry) - { - LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); - text_entry_rect.mRight -= llmax(8,mArrowImage->getWidth()) + 2 * LLUI::sConfigGroup->getS32("DropShadowButton"); - // clear label on button - std::string cur_label = mButton->getLabelSelected(); - mTextEntry = new LLLineEditor(std::string("combo_text_entry"), - text_entry_rect, - LLStringUtil::null, - LLFontGL::getFontSansSerifSmall(), - mMaxChars, - onTextCommit, - onTextEntry, - NULL, - this); - mTextEntry->setSelectAllonFocusReceived(TRUE); - mTextEntry->setHandleEditKeysDirectly(TRUE); - mTextEntry->setCommitOnFocusLost(FALSE); - mTextEntry->setText(cur_label); - mTextEntry->setIgnoreTab(TRUE); - mTextEntry->setFollowsAll(); - addChild(mTextEntry); - } - else - { - mTextEntry->setVisible(TRUE); - mTextEntry->setMaxTextLength(mMaxChars); - } + LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); + text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * drop_shadow_button; + // 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.handle_edit_keys_directly(true); + params.commit_on_focus_lost(false); + params.follows.flags(FOLLOWS_ALL); + mTextEntry = LLUICtrlFactory::create<LLLineEditor> (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 if (!mAllowTextEntry) + else { mButton->setRect(rect); mButton->setTabStop(TRUE); + mButton->setHAlign(LLFontGL::LEFT); if (mTextEntry) { @@ -664,126 +623,125 @@ void LLComboBox::showList() void LLComboBox::hideList() { - //*HACK: store the original value explicitly somewhere, not just in label - std::string orig_selection = mAllowTextEntry ? mTextEntry->getText() : mButton->getLabelSelected(); - - // assert selection in list - mList->selectItemByLabel(orig_selection, FALSE); + 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 + mList->selectNthItem(mLastSelectedIndex); - mButton->setToggleState(FALSE); - mList->setVisible(FALSE); - mList->highlightNthItem(-1); + mButton->setToggleState(FALSE); + mList->setVisible(FALSE); + mList->mouseOverHighlightNthItem(-1); - setUseBoundingRect(FALSE); - if( gFocusMgr.getTopCtrl() == this ) - { - gFocusMgr.setTopCtrl(NULL); + setUseBoundingRect(FALSE); + if( gFocusMgr.getTopCtrl() == this ) + { + gFocusMgr.setTopCtrl(NULL); + } } } -//------------------------------------------------------------------ -// static functions -//------------------------------------------------------------------ - -// static -void LLComboBox::onButtonDown(void *userdata) +void LLComboBox::onButtonMouseDown() { - LLComboBox *self = (LLComboBox *)userdata; - - if (!self->mList->getVisible()) + if (!mList->getVisible()) { - LLScrollListItem* last_selected_item = self->mList->getLastSelectedItem(); - if (last_selected_item) - { - // highlight the original selection before potentially selecting a new item - self->mList->highlightNthItem(self->mList->getItemIndex(last_selected_item)); - } + // this might change selection, so do it first + prearrangeList(); - if( self->mPrearrangeCallback ) + // 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) { - self->mPrearrangeCallback( self, self->mCallbackUserData ); + mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); } - if (self->mList->getItemCount() != 0) + if (mList->getItemCount() != 0) { - self->showList(); + showList(); } - self->setFocus( TRUE ); + setFocus( TRUE ); // pass mouse capture on to list if button is depressed - if (self->mButton->hasMouseCapture()) + if (mButton->hasMouseCapture()) { - gFocusMgr.setMouseCapture(self->mList); + gFocusMgr.setMouseCapture(mList); + + // But keep the "pressed" look, which buttons normally lose when they + // lose focus + mButton->setForcePressedState(true); } } else { - self->hideList(); + hideList(); } } -// static -void LLComboBox::onItemSelected(LLUICtrl* item, void *userdata) +void LLComboBox::onListMouseUp() { - // Note: item is the LLScrollListCtrl - LLComboBox *self = (LLComboBox *) userdata; + // In some cases this is the termination of a mouse click that started on + // the button, so clear its pressed state + mButton->setForcePressedState(false); +} - const std::string name = self->mList->getSelectedItemLabel(); +//------------------------------------------------------------------ +// static functions +//------------------------------------------------------------------ - S32 cur_id = self->getCurrentIndex(); +void LLComboBox::onItemSelected(const LLSD& data) +{ + const std::string name = mList->getSelectedItemLabel(); + + S32 cur_id = getCurrentIndex(); + mLastSelectedIndex = cur_id; if (cur_id != -1) { - self->setLabel(name); + setLabel(name); - if (self->mAllowTextEntry) + if (mAllowTextEntry) { - gFocusMgr.setKeyboardFocus(self->mTextEntry); - self->mTextEntry->selectAll(); + gFocusMgr.setKeyboardFocus(mTextEntry); + mTextEntry->selectAll(); } } // hiding the list reasserts the old value stored in the text editor/dropdown button - self->hideList(); + hideList(); // commit does the reverse, asserting the value in the list - self->onCommit(); + onCommit(); } -BOOL LLComboBox::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen) +BOOL LLComboBox::handleToolTip(S32 x, S32 y, MASK mask) { std::string tool_tip; - if(LLUICtrl::handleToolTip(x, y, msg, sticky_rect_screen)) + if(LLUICtrl::handleToolTip(x, y, mask)) { return TRUE; } - if (LLUI::sShowXUINames) - { - tool_tip = getShowNamesToolTip(); - } - else + tool_tip = getToolTip(); + if (tool_tip.empty()) { - tool_tip = getToolTip(); - if (tool_tip.empty()) - { - tool_tip = getSelectedItemLabel(); - } + tool_tip = getSelectedItemLabel(); } if( !tool_tip.empty() ) { - msg = tool_tip; - - // Convert rect local to screen coordinates - localPointToScreen( - 0, 0, - &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); - localPointToScreen( - getRect().getWidth(), getRect().getHeight(), - &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) ); + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(tool_tip) + .sticky_rect(calcScreenRect())); } return TRUE; } @@ -804,7 +762,7 @@ BOOL LLComboBox::handleKeyHere(KEY key, MASK mask) if (last_selected_item) { // highlight the original selection before potentially selecting a new item - mList->highlightNthItem(mList->getItemIndex(last_selected_item)); + mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); } result = mList->handleKeyHere(key, mask); @@ -838,7 +796,7 @@ BOOL LLComboBox::handleUnicodeCharHere(llwchar uni_char) if (last_selected_item) { // highlight the original selection before potentially selecting a new item - mList->highlightNthItem(mList->getItemIndex(last_selected_item)); + mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); } result = mList->handleUnicodeCharHere(uni_char); if (mList->getLastSelectedItem() != last_selected_item) @@ -850,46 +808,37 @@ BOOL LLComboBox::handleUnicodeCharHere(llwchar uni_char) return result; } -void LLComboBox::setAllowTextEntry(BOOL allow, S32 max_chars, BOOL set_tentative) -{ - mAllowTextEntry = allow; - mTextEntryTentative = set_tentative; - mMaxChars = max_chars; - - updateLayout(); -} - void LLComboBox::setTextEntry(const LLStringExplicit& text) { if (mTextEntry) { mTextEntry->setText(text); + mHasAutocompletedText = FALSE; updateSelection(); } } -//static -void LLComboBox::onTextEntry(LLLineEditor* line_editor, void* user_data) +void LLComboBox::onTextEntry(LLLineEditor* line_editor) { - LLComboBox* self = (LLComboBox*)user_data; - - if (self->mTextEntryCallback) + if (mTextEntryCallback != NULL) { - (*self->mTextEntryCallback)(line_editor, self->mCallbackUserData); + (mTextEntryCallback)(line_editor, LLSD()); } KEY key = gKeyboard->currentKey(); if (key == KEY_BACKSPACE || key == KEY_DELETE) { - if (self->mList->selectItemByLabel(line_editor->getText(), FALSE)) + if (mList->selectItemByLabel(line_editor->getText(), FALSE)) { line_editor->setTentative(FALSE); + mLastSelectedIndex = mList->getFirstSelectedIndex(); } else { - line_editor->setTentative(self->mTextEntryTentative); - self->mList->deselectAllItems(); + line_editor->setTentative(mTextEntryTentative); + mList->deselectAllItems(); + mLastSelectedIndex = -1; } return; } @@ -902,17 +851,14 @@ void LLComboBox::onTextEntry(LLLineEditor* line_editor, void* user_data) if (key == KEY_DOWN) { - self->setCurrentByIndex(llmin(self->getItemCount() - 1, self->getCurrentIndex() + 1)); - if (!self->mList->getVisible()) + setCurrentByIndex(llmin(getItemCount() - 1, getCurrentIndex() + 1)); + if (!mList->getVisible()) { - if( self->mPrearrangeCallback ) - { - self->mPrearrangeCallback( self, self->mCallbackUserData ); - } + prearrangeList(); - if (self->mList->getItemCount() != 0) + if (mList->getItemCount() != 0) { - self->showList(); + showList(); } } line_editor->selectAll(); @@ -920,17 +866,14 @@ void LLComboBox::onTextEntry(LLLineEditor* line_editor, void* user_data) } else if (key == KEY_UP) { - self->setCurrentByIndex(llmax(0, self->getCurrentIndex() - 1)); - if (!self->mList->getVisible()) + setCurrentByIndex(llmax(0, getCurrentIndex() - 1)); + if (!mList->getVisible()) { - if( self->mPrearrangeCallback ) - { - self->mPrearrangeCallback( self, self->mCallbackUserData ); - } + prearrangeList(); - if (self->mList->getItemCount() != 0) + if (mList->getItemCount() != 0) { - self->showList(); + showList(); } } line_editor->selectAll(); @@ -939,7 +882,7 @@ void LLComboBox::onTextEntry(LLLineEditor* line_editor, void* user_data) else { // RN: presumably text entry - self->updateSelection(); + updateSelection(); } } @@ -948,7 +891,7 @@ 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 = mTextEntry->hasSelection() ? left_wstring : mTextEntry->getWText(); + 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 @@ -956,23 +899,15 @@ void LLComboBox::updateSelection() // callback to populate content if( mTextEntry->getWText().size() == 1 ) { - if (mPrearrangeCallback) - { - mPrearrangeCallback( this, mCallbackUserData ); - } + prearrangeList(mTextEntry->getText()); } if (mList->selectItemByLabel(full_string, FALSE)) { mTextEntry->setTentative(FALSE); + mLastSelectedIndex = mList->getFirstSelectedIndex(); } - else if (!mList->selectItemByPrefix(left_wstring, FALSE)) - { - mList->deselectAllItems(); - mTextEntry->setText(wstring_to_utf8str(user_wstring)); - mTextEntry->setTentative(mTextEntryTentative); - } - else + else if (mList->selectItemByPrefix(left_wstring, FALSE)) { LLWString selected_item = utf8str_to_wstring(mList->getSelectedItemLabel()); LLWString wtext = left_wstring + selected_item.substr(left_wstring.size(), selected_item.size()); @@ -980,17 +915,25 @@ void LLComboBox::updateSelection() 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; } } -//static -void LLComboBox::onTextCommit(LLUICtrl* caller, void* user_data) +void LLComboBox::onTextCommit(const LLSD& data) { - LLComboBox* self = (LLComboBox*)user_data; - std::string text = self->mTextEntry->getText(); - self->setSimple(text); - self->onCommit(); - self->mTextEntry->selectAll(); + std::string text = mTextEntry->getText(); + setSimple(text); + onCommit(); + mTextEntry->selectAll(); } void LLComboBox::setFocus(BOOL b) @@ -1007,6 +950,14 @@ void LLComboBox::setFocus(BOOL b) } } +void LLComboBox::prearrangeList(std::string filter) +{ + if (mPrearrangeCallback) + { + mPrearrangeCallback(this, LLSD(filter)); + } +} + //============================================================================ // LLCtrlListInterface functions @@ -1061,6 +1012,7 @@ BOOL LLComboBox::setCurrentByID(const LLUUID& id) if (found) { setLabel(mList->getSelectedItemLabel()); + mLastSelectedIndex = mList->getFirstSelectedIndex(); } return found; @@ -1114,155 +1066,3 @@ BOOL LLComboBox::selectItemRange( S32 first, S32 last ) { return mList->selectItemRange(first, last); } - - -// -// LLFlyoutButton -// - -static LLRegisterWidget<LLFlyoutButton> r2("flyout_button"); - -const S32 FLYOUT_BUTTON_ARROW_WIDTH = 24; - -LLFlyoutButton::LLFlyoutButton( - const std::string& name, - const LLRect &rect, - const std::string& label, - void (*commit_callback)(LLUICtrl*, void*) , - void *callback_userdata) -: LLComboBox(name, rect, LLStringUtil::null, commit_callback, callback_userdata), - mToggleState(FALSE), - mActionButton(NULL) -{ - // Always use text box - // Text label button - mActionButton = new LLButton(label, - LLRect(), LLStringUtil::null, NULL, this); - mActionButton->setScaleImage(TRUE); - - mActionButton->setClickedCallback(onActionButtonClick); - mActionButton->setFollowsAll(); - mActionButton->setHAlign( LLFontGL::HCENTER ); - mActionButton->setLabel(label); - addChild(mActionButton); - - mActionButtonImage = LLUI::getUIImage("flyout_btn_left.tga"); - mExpanderButtonImage = LLUI::getUIImage("flyout_btn_right.tga"); - mActionButtonImageSelected = LLUI::getUIImage("flyout_btn_left_selected.tga"); - mExpanderButtonImageSelected = LLUI::getUIImage("flyout_btn_right_selected.tga"); - mActionButtonImageDisabled = LLUI::getUIImage("flyout_btn_left_disabled.tga"); - mExpanderButtonImageDisabled = LLUI::getUIImage("flyout_btn_right_disabled.tga"); - - mActionButton->setImageSelected(mActionButtonImageSelected); - mActionButton->setImageUnselected(mActionButtonImage); - mActionButton->setImageDisabled(mActionButtonImageDisabled); - mActionButton->setImageDisabledSelected(LLPointer<LLUIImage>(NULL)); - - mButton->setImageSelected(mExpanderButtonImageSelected); - mButton->setImageUnselected(mExpanderButtonImage); - mButton->setImageDisabled(mExpanderButtonImageDisabled); - mButton->setImageDisabledSelected(LLPointer<LLUIImage>(NULL)); - mButton->setRightHPad(6); - - updateLayout(); -} - -//static -LLView* LLFlyoutButton::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name = "flyout_button"; - node->getAttributeString("name", name); - - std::string label(""); - node->getAttributeString("label", label); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - LLUICtrlCallback callback = NULL; - - LLFlyoutButton* flyout_button = new LLFlyoutButton(name, - rect, - label, - callback, - NULL); - - std::string list_position; - node->getAttributeString("list_position", list_position); - if (list_position == "below") - { - flyout_button->mListPosition = BELOW; - } - else if (list_position == "above") - { - flyout_button->mListPosition = ABOVE; - } - - - flyout_button->initFromXML(node, parent); - - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - if (child->hasName("flyout_button_item")) - { - std::string label = child->getTextContents(); - - std::string value = label; - child->getAttributeString("value", value); - - flyout_button->add(label, LLSD(value) ); - } - } - - flyout_button->updateLayout(); - - return flyout_button; -} - -void LLFlyoutButton::updateLayout() -{ - LLComboBox::updateLayout(); - - mButton->setOrigin(getRect().getWidth() - FLYOUT_BUTTON_ARROW_WIDTH, 0); - mButton->reshape(FLYOUT_BUTTON_ARROW_WIDTH, getRect().getHeight()); - mButton->setFollows(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM); - mButton->setTabStop(FALSE); - mButton->setImageOverlay(mListPosition == BELOW ? "down_arrow.tga" : "up_arrow.tga", LLFontGL::RIGHT); - - mActionButton->setOrigin(0, 0); - mActionButton->reshape(getRect().getWidth() - FLYOUT_BUTTON_ARROW_WIDTH, getRect().getHeight()); -} - -//static -void LLFlyoutButton::onActionButtonClick(void *user_data) -{ - LLFlyoutButton* buttonp = (LLFlyoutButton*)user_data; - // remember last list selection? - buttonp->mList->deselect(); - buttonp->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 - mButton->setLabel(LLStringUtil::null); - LLComboBox::draw(); -} - -void LLFlyoutButton::setEnabled(BOOL enabled) -{ - mActionButton->setEnabled(enabled); - LLComboBox::setEnabled(enabled); -} - - -void LLFlyoutButton::setToggleState(BOOL state) -{ - mToggleState = state; -} - diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h index 33e1baa748..4f27588467 100644 --- a/indra/llui/llcombobox.h +++ b/indra/llui/llcombobox.h @@ -39,16 +39,14 @@ #include "llbutton.h" #include "lluictrl.h" #include "llctrlselectioninterface.h" -#include "llimagegl.h" #include "llrect.h" +#include "llscrolllistctrl.h" +#include "lllineeditor.h" +#include <boost/function.hpp> // Classes class LLFontGL; -class LLButton; -class LLSquareButton; -class LLScrollListCtrl; -class LLLineEditor; class LLViewBorder; extern S32 LLCOMBOBOX_HEIGHT; @@ -57,34 +55,64 @@ extern S32 LLCOMBOBOX_WIDTH; class LLComboBox : public LLUICtrl, public LLCtrlListInterface { -public: +public: typedef enum e_preferred_position { ABOVE, BELOW } EPreferredPosition; - LLComboBox( - const std::string& name, - const LLRect &rect, - const std::string& label, - void (*commit_callback)(LLUICtrl*, void*) = NULL, - void *callback_userdata = NULL - ); - virtual ~LLComboBox(); + struct PreferredPositionValues : public LLInitParam::TypeValuesHelper<EPreferredPosition, PreferredPositionValues> + { + static void declareValues(); + }; - // LLView interface - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + struct ItemParams : public LLInitParam::Block<ItemParams, LLScrollListItem::Params> + { + Optional<std::string> label; + ItemParams(); + }; - virtual void draw(); - virtual void onFocusLost(); - virtual void onLostTop(); + struct Params + : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<bool> allow_text_entry, + show_text_as_tentative, + allow_new_values; + Optional<S32> max_chars; + Optional<commit_callback_t> prearrange_callback, + text_entry_callback; + + Optional<EPreferredPosition, PreferredPositionValues> list_position; + + // components + Optional<LLButton::Params> combo_button; + Optional<LLScrollListCtrl::Params> combo_list; + Optional<LLLineEditor::Params> combo_editor; + + Optional<LLButton::Params> drop_down_button; - virtual void setEnabled(BOOL enabled); + Multiple<ItemParams> items; + + Params(); + }; - virtual BOOL handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect); + + virtual ~LLComboBox(); + /*virtual*/ BOOL postBuild(); + +protected: + friend class LLUICtrlFactory; + LLComboBox(const Params&); + void initFromParams(const Params&); + void prearrangeList(std::string filter = ""); + +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); @@ -105,7 +133,6 @@ public: // items, this is just the label. virtual LLSD getValue() const; - void setAllowTextEntry(BOOL allow, S32 max_chars = 50, BOOL make_tentative = TRUE); void setTextEntry(const LLStringExplicit& text); LLScrollListItem* add(const std::string& name, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE); // add item "name" to menu @@ -134,7 +161,7 @@ public: BOOL setCurrentByIndex( S32 index ); S32 getCurrentIndex() const; - virtual void updateLayout(); + void createLineEditor(const Params&); //======================================================================== LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; }; @@ -170,66 +197,39 @@ public: void* getCurrentUserdata(); - void setPrearrangeCallback( void (*cb)(LLUICtrl*,void*) ) { mPrearrangeCallback = cb; } - void setTextEntryCallback( void (*cb)(LLLineEditor*, void*) ) { mTextEntryCallback = cb; } + void setPrearrangeCallback( commit_callback_t cb ) { mPrearrangeCallback = cb; } + void setTextEntryCallback( commit_callback_t cb ) { mTextEntryCallback = cb; } void setButtonVisible(BOOL visible); - static void onButtonDown(void *userdata); - static void onItemSelected(LLUICtrl* item, void *userdata); - static void onTextEntry(LLLineEditor* line_editor, void* user_data); - static void onTextCommit(LLUICtrl* caller, void* user_data); + 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<LLUIImage> mArrowImage; - std::string mLabel; + LLUIString mLabel; + BOOL mHasAutocompletedText; private: - S32 mButtonPadding; - LLLineEditor* mTextEntry; BOOL mAllowTextEntry; + BOOL mAllowNewValues; S32 mMaxChars; BOOL mTextEntryTentative; - void (*mPrearrangeCallback)(LLUICtrl*,void*); - void (*mTextEntryCallback)(LLLineEditor*, void*); -}; - -class LLFlyoutButton : public LLComboBox -{ -public: - LLFlyoutButton( - const std::string& name, - const LLRect &rect, - const std::string& label, - void (*commit_callback)(LLUICtrl*, void*) = NULL, - void *callback_userdata = NULL); - - virtual void updateLayout(); - virtual void draw(); - virtual void setEnabled(BOOL enabled); - - void setToggleState(BOOL state); - - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - static void onActionButtonClick(void *userdata); - static void onSelectAction(LLUICtrl* ctrl, void *userdata); - -protected: - LLButton* mActionButton; - LLPointer<LLUIImage> mActionButtonImage; - LLPointer<LLUIImage> mExpanderButtonImage; - LLPointer<LLUIImage> mActionButtonImageSelected; - LLPointer<LLUIImage> mExpanderButtonImageSelected; - LLPointer<LLUIImage> mActionButtonImageDisabled; - LLPointer<LLUIImage> mExpanderButtonImageDisabled; - BOOL mToggleState; + commit_callback_t mPrearrangeCallback; + commit_callback_t mTextEntryCallback; + commit_callback_t mSelectionCallback; + S32 mLastSelectedIndex; }; - #endif diff --git a/indra/llui/llconsole.cpp b/indra/llui/llconsole.cpp new file mode 100644 index 0000000000..59499f987b --- /dev/null +++ b/indra/llui/llconsole.cpp @@ -0,0 +1,401 @@ +/** + * @file llconsole.cpp + * @brief a scrolling console output device + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +//#include "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; +const S32 MIN_CONSOLE_WIDTH = 200; + +static LLDefaultChildRegistry::Register<LLConsole> r("console"); + +LLConsole::LLConsole(const LLConsole::Params& p) +: LLUICtrl(p), + LLFixedBuffer(p.max_lines), + mLinePersistTime(p.persist_time), // seconds + mFont(p.font) +{ + if (p.font_size_index.isProvided()) + { + setFontSize(p.font_size_index); + } + mFadeTime = mLinePersistTime - FADE_DURATION; + setMaxLines(LLUI::sSettingGroups["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() +{ + 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<paragraph_num;i++) + { + if (!mParagraphs.empty()) + mParagraphs.pop_front(); + } + break; + } + paragraph_num--; + paragraph_it++; + } + + if (mParagraphs.empty()) + { + return; + } + + // draw remaining lines + F32 y_pos = 0.f; + + LLUIImagePtr imagep = LLUI::getUIImage("Rounded_Square"); + +// F32 console_opacity = llclamp(gSavedSettings.getF32("ConsoleBackgroundOpacity"), 0.f, 1.f); + F32 console_opacity = llclamp(LLUI::sSettingGroups["config"]->getF32("ConsoleBackgroundOpacity"), 0.f, 1.f); +// LLColor4 color = LLUIColorTable::instance().getColor("ConsoleBackground"); + 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 + 8); + S32 target_width = llfloor( (*paragraph_it).mMaxWidth +15); + + 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 += 8; + } +} + +void LLConsole::addLine(const std::string& utf8line) +{ + LLWString wline = utf8str_to_wstring(utf8line); + addLine(wline, 0.f, LLColor4(1.f, 1.f, 1.f, 1.f)); +} + +void LLConsole::addLine(const LLWString& wline) +{ + addLine(wline, 0.f, LLColor4(1.f, 1.f, 1.f, 1.f)); +} + +void LLConsole::addLine(const std::string& utf8line, F32 size, const LLColor4 &color) +{ + LLWString wline = utf8str_to_wstring(utf8line); + addLine(wline, size, color); +} + +//Generate highlight color segments for this paragraph. Pass in default color of paragraph. +void LLConsole::Paragraph::makeParagraphColorSegments (const LLColor4 &color) +{ + LLSD paragraph_color_segments; + LLColor4 lcolor=color; + + 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() ) + { + 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 ); +} + +void LLConsole::addLine(const LLWString& wline, F32 size, const LLColor4 &color) +{ + Paragraph paragraph(wline, color, mTimer.getElapsedTimeF32(), mFont, (F32)getRect().getWidth() ); + + mParagraphs.push_back ( paragraph ); + + // 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 new file mode 100644 index 0000000000..5800a82922 --- /dev/null +++ b/indra/llui/llconsole.h @@ -0,0 +1,162 @@ +/** + * @file llconsole.h + * @brief a simple console-style output device + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLCONSOLE_H +#define LL_LLCONSOLE_H + +#include "llfixedbuffer.h" +#include "lluictrl.h" +#include "v4color.h" +#include <deque> + +class LLSD; + +class LLConsole : public LLFixedBuffer, public LLUICtrl +{ +public: + typedef enum e_font_size + { + MONOSPACE = -1, + SMALL = 0, + BIG = 1 + } EFontSize; + + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<U32> max_lines; + Optional<F32> persist_time; + Optional<S32> font_size_index; + Params() + : max_lines("max_lines", LLUI::sSettingGroups["config"]->getS32("ConsoleMaxLines")), + persist_time("persist_time", 0.f), // forever + font_size_index("font_size_index") + { + mouse_opaque(false); + } + }; +protected: + LLConsole(const Params&); + friend class LLUICtrlFactory; + +public: + //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<LineColorSegment> 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<Line> lines_t; + typedef std::list<ParagraphColorSegment> 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> 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); + + void addLine(const std::string& utf8line, F32 size, const LLColor4 &color); + void addLine(const LLWString& wline, F32 size, const LLColor4 &color); + + // Overrides + /*virtual*/ void draw(); + /*virtual*/ void addLine(const std::string& utf8line); + /*virtual*/ void addLine(const LLWString& line); +private: + F32 mLinePersistTime; // Age at which to stop drawing. + F32 mFadeTime; // Age at which to start fading + const LLFontGL* mFont; + S32 mLastBoxHeight; + S32 mLastBoxWidth; + S32 mConsoleWidth; + S32 mConsoleHeight; + +}; + +extern LLConsole* gConsole; + +#endif diff --git a/indra/llui/llcontainerview.cpp b/indra/llui/llcontainerview.cpp new file mode 100644 index 0000000000..51ef80f4b9 --- /dev/null +++ b/indra/llui/llcontainerview.cpp @@ -0,0 +1,302 @@ +/** + * @file llcontainerview.cpp + * @brief Container for all statistics info + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "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<LLContainerView> r1("container_view"); + +#include "llpanel.h" +#include "llstatview.h" +static ContainerViewRegistry::Register<LLStatView> r2("stat_view"); +static ContainerViewRegistry::Register<LLPanel> r3("panel", &LLPanel::fromXML); + +LLContainerView::LLContainerView(const LLContainerView::Params& p) +: LLView(p), + mShowLabel(p.show_label), + mLabel(p.label), + mDisplayChildren(p.display_children) +{ + mCollapsible = TRUE; + 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::handleMouseDown(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; + if (mDisplayChildren) + { + handled = (LLView::childrenHandleMouseDown(x, y, mask) != NULL); + } + if (!handled) + { + if( mCollapsible && 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 = 4; + 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()) + { + llwarns << "Incorrect visibility!" << llendl; + } + LLRect child_rect = childp->getRequiredRect(); + child_height += child_rect.getHeight(); + child_height += 2; + } + total_height += child_height; + } + + if (total_height < height) + total_height = height; + + if (followsTop()) + { + // HACK: casting away const. Should use setRect or some helper function instead. + const_cast<LLRect&>(getRect()).mBottom = getRect().mTop - total_height; + } + else + { + // HACK: casting away const. Should use setRect or some helper function instead. + const_cast<LLRect&>(getRect()).mTop = getRect().mBottom + total_height; + } + // HACK: casting away const. Should use setRect or some helper function instead. + const_cast<LLRect&>(getRect()).mRight = getRect().mLeft + width; + + 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(const 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 new file mode 100644 index 0000000000..74e38e18bc --- /dev/null +++ b/indra/llui/llcontainerview.h @@ -0,0 +1,100 @@ +/** + * @file llcontainerview.h + * @brief Container for all statistics info. + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#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<ContainerViewRegistry> +{}; + +class LLContainerView : public LLView +{ +public: + struct Params : public LLInitParam::Block<Params, LLView::Params> + { + Optional<std::string> label; + Optional<bool> show_label; + Optional<bool> display_children; + Params() + : label("label"), + show_label("show_label", FALSE), + display_children("display_children", TRUE) + { + 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 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(const 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; +public: + BOOL mCollapsible; + +}; +#endif // LL_CONTAINERVIEW_ diff --git a/indra/llui/lldockablefloater.cpp b/indra/llui/lldockablefloater.cpp new file mode 100644 index 0000000000..74438b184a --- /dev/null +++ b/indra/llui/lldockablefloater.cpp @@ -0,0 +1,240 @@ +/** + * @file lldockablefloater.cpp + * @brief Creates a panel of a specific kind for a toast + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lldockablefloater.h" +#include "llfloaterreg.h" + +//static +LLHandle<LLFloater> 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); +} + +LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, + const LLSD& key, const Params& params) : + LLFloater(key, params), mDockControl(dockControl), mUniqueDocking(true) + , mOverlapsScreenChannel(false) +{ + 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() +{ + mDockTongue = LLUI::getUIImage("windows/Flyout_Pointer.png"); + LLFloater::setDocked(true); + return LLView::postBuild(); +} + +//static +void LLDockableFloater::toggleInstance(const LLSD& sdname) +{ + LLSD key; + std::string name = sdname.asString(); + + LLDockableFloater* instance = + dynamic_cast<LLDockableFloater*> (LLFloaterReg::findInstance(name)); + // if floater closed or docked + if (instance == NULL || instance != NULL && 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) +{ + if(visible && isDocked()) + { + resetInstance(); + } + + if (visible && mDockControl.get() != NULL) + { + mDockControl.get()->repositionDockable(); + } + + if (visible) + { + LLFloater::setFrontmost(TRUE); + } + LLFloater::setVisible(visible); +} + +void LLDockableFloater::setMinimized(BOOL minimize) +{ + if(minimize) + { + setVisible(FALSE); + } +} + +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); + } + } + else + { + docked = false; + } + + 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(mDockControl.get() != NULL && mDockControl.get()->isDockVisible()); +} + +const LLUIImagePtr& LLDockableFloater::getDockTongue() +{ + return mDockTongue; +} + +LLDockControl* LLDockableFloater::getDockControl() +{ + return mDockControl.get(); +} diff --git a/indra/llui/lldockablefloater.h b/indra/llui/lldockablefloater.h new file mode 100644 index 0000000000..2b1ce99ae2 --- /dev/null +++ b/indra/llui/lldockablefloater.h @@ -0,0 +1,148 @@ +/** + * @file lldockablefloater.h + * @brief Creates a panel of a specific kind for a toast. + * + * $LicenseInfo:firstyear=2003&license=viewergpl$ + * + * Copyright (c) 2003-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_DOCKABLEFLOATER_H +#define LL_DOCKABLEFLOATER_H + +#include "llerror.h" +#include "llfloater.h" +#include "lldockcontrol.h" + +/** + * 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<LLFloater> 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; } +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(); + +private: + std::auto_ptr<LLDockControl> mDockControl; + LLUIImagePtr mDockTongue; + static LLHandle<LLFloater> sInstanceHandle; + /** + * Provides possibility to define that dockable floaters can be docked + * non exclusively. + */ + bool mUniqueDocking; + + bool mUseTongue; + + bool mOverlapsScreenChannel; +}; + +#endif /* LL_DOCKABLEFLOATER_H */ diff --git a/indra/llui/lldockcontrol.cpp b/indra/llui/lldockcontrol.cpp new file mode 100644 index 0000000000..0d8e54aa48 --- /dev/null +++ b/indra/llui/lldockcontrol.cpp @@ -0,0 +1,303 @@ +/** + * @file lldockcontrol.cpp + * @brief Creates a panel of a specific kind for a toast + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lldockcontrol.h" +#include "lldockablefloater.h" + +LLDockControl::LLDockControl(LLView* dockWidget, LLFloater* dockableFloater, + const LLUIImagePtr& dockTongue, DocAt dockAt, get_allowed_rect_callback_t get_allowed_rect_callback) : + mDockWidget(dockWidget), mDockableFloater(dockableFloater), mDockTongue(dockTongue) +{ + mDockAt = dockAt; + + if (dockableFloater->isDocked()) + { + on(); + } + else + { + off(); + } + + if (!(get_allowed_rect_callback)) + { + mGetAllowedRectCallback = boost::bind(&LLDockControl::getAllowedRect, this, _1); + } + else + { + mGetAllowedRectCallback = get_allowed_rect_callback; + } + + if (dockWidget != NULL) + { + repositionDockable(); + } + + if (mDockWidget != NULL) + { + mDockWidgetVisible = isDockVisible(); + } + else + { + mDockWidgetVisible = false; + } +} + +LLDockControl::~LLDockControl() +{ +} + +void LLDockControl::setDock(LLView* dockWidget) +{ + mDockWidget = dockWidget; + if (mDockWidget != NULL) + { + repositionDockable(); + mDockWidgetVisible = isDockVisible(); + } + else + { + mDockWidgetVisible = false; + } +} + +void LLDockControl::getAllowedRect(LLRect& rect) +{ + rect = mDockableFloater->getRootView()->getRect(); +} + +void LLDockControl::repositionDockable() +{ + LLRect dockRect = mDockWidget->calcScreenRect(); + LLRect rootRect; + mGetAllowedRectCallback(rootRect); + + // recalculate dockable position if dock position changed, dock visibility changed, + // root view rect changed or recalculation is forced + if (mPrevDockRect != dockRect || mDockWidgetVisible != isDockVisible() + || mRootRect != rootRect || mRecalculateDocablePosition) + { + // undock dockable and off() if dock not visible + if (!isDockVisible()) + { + mDockableFloater->setDocked(false); + // force off() since dockable may not have dockControll at this time + off(); + LLDockableFloater* dockable_floater = + dynamic_cast<LLDockableFloater*> (mDockableFloater); + if(dockable_floater != NULL) + { + dockable_floater->onDockHidden(); + } + } + else + { + if(mEnabled) + { + moveDockable(); + } + LLDockableFloater* dockable_floater = + dynamic_cast<LLDockableFloater*> (mDockableFloater); + if(dockable_floater != NULL) + { + dockable_floater->onDockShown(); + } + } + + mPrevDockRect = dockRect; + mRootRect = rootRect; + mRecalculateDocablePosition = false; + mDockWidgetVisible = isDockVisible(); + } +} + +bool LLDockControl::isDockVisible() +{ + bool res = true; + + if (mDockWidget != NULL) + { + //we should check all hierarchy + res = mDockWidget->isInVisibleChain(); + if (res) + { + LLRect dockRect = mDockWidget->calcScreenRect(); + + switch (mDockAt) + { + case LEFT: // to keep compiler happy + break; + case TOP: + // check is dock inside parent rect + LLRect dockParentRect = + mDockWidget->getParent()->calcScreenRect(); + if (dockRect.mRight <= dockParentRect.mLeft + || dockRect.mLeft >= dockParentRect.mRight) + { + res = false; + } + break; + } + } + } + + return res; +} + +void LLDockControl::moveDockable() +{ + // calculate new dockable position + LLRect dockRect = mDockWidget->calcScreenRect(); + LLRect rootRect; + mGetAllowedRectCallback(rootRect); + + bool use_tongue = false; + LLDockableFloater* dockable_floater = + dynamic_cast<LLDockableFloater*> (mDockableFloater); + if (dockable_floater != NULL) + { + use_tongue = dockable_floater->getUseTongue(); + } + + LLRect dockableRect = mDockableFloater->calcScreenRect(); + S32 x = 0; + S32 y = 0; + LLRect dockParentRect; + switch (mDockAt) + { + case LEFT: + x = dockRect.mLeft; + y = dockRect.mTop + mDockTongue->getHeight() + dockableRect.getHeight(); + // check is dockable inside root view rect + if (x < rootRect.mLeft) + { + x = rootRect.mLeft; + } + if (x + dockableRect.getWidth() > rootRect.mRight) + { + x = rootRect.mRight - dockableRect.getWidth(); + } + + mDockTongueX = x + dockableRect.getWidth()/2 - mDockTongue->getWidth() / 2; + + mDockTongueY = dockRect.mTop; + break; + + case TOP: + x = dockRect.getCenterX() - dockableRect.getWidth() / 2; + y = dockRect.mTop + dockableRect.getHeight(); + // unique docking used with dock tongue, so add tongue height o the Y coordinate + if (use_tongue) + { + y += mDockTongue->getHeight(); + } + + // check is dockable inside root view rect + if (x < rootRect.mLeft) + { + x = rootRect.mLeft; + } + if (x + dockableRect.getWidth() > rootRect.mRight) + { + x = rootRect.mRight - dockableRect.getWidth(); + } + + + // calculate dock tongue position + dockParentRect = mDockWidget->getParent()->calcScreenRect(); + if (dockRect.getCenterX() < dockParentRect.mLeft) + { + mDockTongueX = dockParentRect.mLeft - mDockTongue->getWidth() / 2; + } + else if (dockRect.getCenterX() > dockParentRect.mRight) + { + mDockTongueX = dockParentRect.mRight - mDockTongue->getWidth() / 2;; + } + else + { + mDockTongueX = dockRect.getCenterX() - mDockTongue->getWidth() / 2; + } + mDockTongueY = dockRect.mTop; + + break; + } + + // move dockable + dockableRect.setLeftTopAndSize(x, y, dockableRect.getWidth(), + dockableRect.getHeight()); + LLRect localDocableParentRect; + mDockableFloater->getParent()->screenRectToLocal(dockableRect, + &localDocableParentRect); + mDockableFloater->setRect(localDocableParentRect); + + mDockableFloater->screenPointToLocal(mDockTongueX, mDockTongueY, + &mDockTongueX, &mDockTongueY); + +} + +void LLDockControl::on() +{ + if (isDockVisible()) + { + mEnabled = true; + mRecalculateDocablePosition = true; + } +} + +void LLDockControl::off() +{ + mEnabled = false; +} + +void LLDockControl::forceRecalculatePosition() +{ + mRecalculateDocablePosition = true; +} + +void LLDockControl::drawToungue() +{ + bool use_tongue = false; + LLDockableFloater* dockable_floater = + dynamic_cast<LLDockableFloater*> (mDockableFloater); + if (dockable_floater != NULL) + { + use_tongue = dockable_floater->getUseTongue(); + } + + if (mEnabled && use_tongue) + { + mDockTongue->draw(mDockTongueX, mDockTongueY); + } +} + diff --git a/indra/llui/lldockcontrol.h b/indra/llui/lldockcontrol.h new file mode 100644 index 0000000000..550955c4c5 --- /dev/null +++ b/indra/llui/lldockcontrol.h @@ -0,0 +1,99 @@ +/** + * @file lldockcontrol.h + * @brief Creates a panel of a specific kind for a toast. + * + * $LicenseInfo:firstyear=2003&license=viewergpl$ + * + * Copyright (c) 2003-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_DOCKCONTROL_H +#define LL_DOCKCONTROL_H + +#include "llerror.h" +#include "llview.h" +#include "llfloater.h" +#include "lluiimage.h" + +/** + * Provides services for docking of specified floater. + * This class should be used in case impossibility deriving from LLDockableFloater. + */ +class LLDockControl +{ +public: + enum DocAt + { + TOP + ,LEFT + }; + +public: + // callback for a function getting a rect valid for control's position + typedef boost::function<void (LLRect& )> get_allowed_rect_callback_t; + + LOG_CLASS(LLDockControl); + LLDockControl(LLView* dockWidget, LLFloater* dockableFloater, + const LLUIImagePtr& dockTongue, DocAt dockAt, get_allowed_rect_callback_t get_rect_callback = NULL); + virtual ~LLDockControl(); + +public: + void on(); + void off(); + void forceRecalculatePosition(); + void setDock(LLView* dockWidget); + LLView* getDock() + { + return mDockWidget; + } + void repositionDockable(); + void drawToungue(); + bool isDockVisible(); + + // gets a rect that bounds possible positions for a dockable control (EXT-1111) + void getAllowedRect(LLRect& rect); + + S32 getTongueWidth() { return mDockTongue->getWidth(); } + S32 getTongueHeight() { return mDockTongue->getHeight(); } + +private: + virtual void moveDockable(); +private: + get_allowed_rect_callback_t mGetAllowedRectCallback; + bool mEnabled; + bool mRecalculateDocablePosition; + bool mDockWidgetVisible; + DocAt mDockAt; + LLView* mDockWidget; + LLRect mPrevDockRect; + LLRect mRootRect; + LLFloater* mDockableFloater; + LLUIImagePtr mDockTongue; + S32 mDockTongueX; + S32 mDockTongueY; +}; + +#endif /* LL_DOCKCONTROL_H */ diff --git a/indra/llui/lldraghandle.cpp b/indra/llui/lldraghandle.cpp index 6c92ea1ff7..a93c666648 100644 --- a/indra/llui/lldraghandle.cpp +++ b/indra/llui/lldraghandle.cpp @@ -43,34 +43,46 @@ #include "llmenugl.h" #include "lltextbox.h" #include "llcontrol.h" -#include "llresmgr.h" #include "llfontgl.h" #include "llwindow.h" #include "llfocusmgr.h" +#include "lluictrlfactory.h" const S32 LEADING_PAD = 5; -const S32 TITLE_PAD = 8; +const S32 TITLE_HPAD = 8; const S32 BORDER_PAD = 1; -const S32 LEFT_PAD = BORDER_PAD + TITLE_PAD + LEADING_PAD; +const S32 LEFT_PAD = BORDER_PAD + TITLE_HPAD + LEADING_PAD; const S32 RIGHT_PAD = BORDER_PAD + 32; // HACK: space for close btn and minimize btn S32 LLDragHandle::sSnapMargin = 5; -LLDragHandle::LLDragHandle( const std::string& name, const LLRect& rect, const std::string& title ) -: LLView( name, rect, TRUE ), +LLDragHandle::LLDragHandle(const LLDragHandle::Params& p) +: LLView(p), mDragLastScreenX( 0 ), mDragLastScreenY( 0 ), mLastMouseScreenX( 0 ), mLastMouseScreenY( 0 ), - mDragHighlightColor( LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ) ), - mDragShadowColor( LLUI::sColorsGroup->getColor( "DefaultShadowDark" ) ), mTitleBox( NULL ), mMaxTitleWidth( 0 ), - mForeground( TRUE ) + mForeground( TRUE ), + mDragHighlightColor(p.drag_highlight_color()), + mDragShadowColor(p.drag_shadow_color()) + +{ + static LLUICachedControl<S32> snap_margin ("SnapMargin", 0); + sSnapMargin = snap_margin; +} + +LLDragHandle::~LLDragHandle() { - sSnapMargin = LLUI::sConfigGroup->getS32("SnapMargin"); + removeChild(mTitleBox); + delete mTitleBox; +} - setSaveToXML(false); +void LLDragHandle::initFromParams(const LLDragHandle::Params& p) +{ + LLView::initFromParams(p); + setTitle( p.label ); } void LLDragHandle::setTitleVisible(BOOL visible) @@ -81,63 +93,53 @@ void LLDragHandle::setTitleVisible(BOOL visible) } } -void LLDragHandle::setTitleBox(LLTextBox* titlebox) -{ +void LLDragHandleTop::setTitle(const std::string& title) +{ + std::string trimmed_title = title; + LLStringUtil::trim(trimmed_title); + if( mTitleBox ) { - removeChild(mTitleBox); - delete mTitleBox; + mTitleBox->setText(trimmed_title); } - mTitleBox = titlebox; - if(mTitleBox) + 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; + mTitleBox = LLUICtrlFactory::create<LLTextBox> (params); addChild( mTitleBox ); } -} - -LLDragHandleTop::LLDragHandleTop(const std::string& name, const LLRect &rect, const std::string& title) -: LLDragHandle(name, rect, title) -{ - setFollowsAll(); - setTitle( title ); -} - -LLDragHandleLeft::LLDragHandleLeft(const std::string& name, const LLRect &rect, const std::string& title) -: LLDragHandle(name, rect, title) -{ - setFollowsAll(); - setTitle( title ); -} - -void LLDragHandleTop::setTitle(const std::string& title) -{ - std::string trimmed_title = title; - LLStringUtil::trim(trimmed_title); - - const LLFontGL* font = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF ); - LLTextBox* titlebox = new LLTextBox( std::string("Drag Handle Title"), getRect(), trimmed_title, font ); - titlebox->setFollows(FOLLOWS_TOP | FOLLOWS_LEFT | FOLLOWS_RIGHT); - titlebox->setFontStyle(LLFontGL::DROP_SHADOW_SOFT); - setTitleBox(titlebox); reshapeTitleBox(); } -const std::string& LLDragHandleTop::getTitle() const +std::string LLDragHandleTop::getTitle() const { - return getTitleBox() == NULL ? LLStringUtil::null : getTitleBox()->getText(); + return mTitleBox == NULL ? LLStringUtil::null : mTitleBox->getText(); } void LLDragHandleLeft::setTitle(const std::string& ) { - setTitleBox(NULL); + if( mTitleBox ) + { + removeChild(mTitleBox); + delete mTitleBox; + mTitleBox = NULL; + } /* no title on left edge */ } -const std::string& LLDragHandleLeft::getTitle() const +std::string LLDragHandleLeft::getTitle() const { return LLStringUtil::null; } @@ -184,9 +186,9 @@ void LLDragHandleTop::draw() */ // Colorize the text to match the frontmost state - if (getTitleBox()) + if (mTitleBox) { - getTitleBox()->setEnabled(getForeground()); + mTitleBox->setEnabled(getForeground()); } LLView::draw(); @@ -229,9 +231,9 @@ void LLDragHandleLeft::draw() */ // Colorize the text to match the frontmost state - if (getTitleBox()) + if (mTitleBox) { - getTitleBox()->setEnabled(getForeground()); + mTitleBox->setEnabled(getForeground()); } LLView::draw(); @@ -239,23 +241,25 @@ void LLDragHandleLeft::draw() void LLDragHandleTop::reshapeTitleBox() { - if( ! getTitleBox()) + static LLUICachedControl<S32> title_vpad("UIFloaterTitleVPad", 0); + if( ! mTitleBox) { return; } - const LLFontGL* font = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF ); - S32 title_width = font->getWidth( getTitleBox()->getText() ) + TITLE_PAD; + const LLFontGL* font = LLFontGL::getFontSansSerif(); + S32 title_width = font->getWidth( mTitleBox->getText() ) + TITLE_HPAD; if (getMaxTitleWidth() > 0) title_width = llmin(title_width, getMaxTitleWidth()); S32 title_height = llround(font->getLineHeight()); LLRect title_rect; title_rect.setLeftTopAndSize( LEFT_PAD, - getRect().getHeight() - BORDER_PAD, + getRect().getHeight() - title_vpad, getRect().getWidth() - LEFT_PAD - RIGHT_PAD, title_height); - getTitleBox()->setRect( title_rect ); + // calls reshape on mTitleBox + mTitleBox->setShape( title_rect ); } void LLDragHandleTop::reshape(S32 width, S32 height, BOOL called_from_parent) @@ -316,6 +320,23 @@ BOOL LLDragHandle::handleHover(S32 x, S32 y, MASK mask) S32 delta_x = screen_x - mDragLastScreenX; S32 delta_y = screen_y - mDragLastScreenY; + // if dragging a docked floater we want to undock + if (((LLFloater*)getParent())->isDocked()) + { + const S32 SLOP = 12; + + if (delta_y <= -SLOP || + delta_y >= SLOP) + { + ((LLFloater*)getParent())->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); @@ -337,14 +358,14 @@ BOOL LLDragHandle::handleHover(S32 x, S32 y, MASK mask) LLView* snap_view = getParent()->findSnapRect(new_rect, mouse_dir, SNAP_PARENT_AND_SIBLINGS, sSnapMargin); - getParent()->snappedTo(snap_view); + 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()->userSetShape(translated_rect); + getParent()->setShape(translated_rect, true); mDragLastScreenX += delta_x; mDragLastScreenY += delta_y; diff --git a/indra/llui/lldraghandle.h b/indra/llui/lldraghandle.h index 9eb3e55a6c..dc5410787b 100644 --- a/indra/llui/lldraghandle.h +++ b/indra/llui/lldraghandle.h @@ -45,8 +45,25 @@ class LLTextBox; class LLDragHandle : public LLView { public: - LLDragHandle(const std::string& name, const LLRect& rect, const std::string& title ); - virtual ~LLDragHandle() { setTitleBox(NULL); } + struct Params + : public LLInitParam::Block<Params, LLView::Params> + { + Optional<std::string> label; + Optional<LLUIColor> drag_highlight_color; + Optional<LLUIColor> 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")) + { + mouse_opaque(true); + follows.flags(FOLLOWS_ALL); + } + }; + void initFromParams(const Params&); + + virtual ~LLDragHandle(); virtual void setValue(const LLSD& value); @@ -57,25 +74,27 @@ public: void setTitleVisible(BOOL visible); virtual void setTitle( const std::string& title ) = 0; - virtual const std::string& getTitle() const = 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: - LLTextBox* getTitleBox() const { return mTitleBox; } - void setTitleBox(LLTextBox*); - + LLDragHandle(const Params&); + friend class LLUICtrlFactory; + +protected: + LLTextBox* mTitleBox; + private: S32 mDragLastScreenX; S32 mDragLastScreenY; S32 mLastMouseScreenX; S32 mLastMouseScreenY; LLCoordGL mLastMouseDir; - LLColor4 mDragHighlightColor; - LLColor4 mDragShadowColor; - LLTextBox* mTitleBox; + LLUIColor mDragHighlightColor; + LLUIColor mDragShadowColor; S32 mMaxTitleWidth; BOOL mForeground; @@ -88,11 +107,12 @@ private: class LLDragHandleTop : public LLDragHandle { +protected: + LLDragHandleTop(const Params& p) : LLDragHandle(p) {} + friend class LLUICtrlFactory; public: - LLDragHandleTop(const std::string& name, const LLRect& rect, const std::string& title ); - virtual void setTitle( const std::string& title ); - virtual const std::string& getTitle() const; + virtual std::string getTitle() const; virtual void draw(); virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); @@ -105,11 +125,12 @@ private: class LLDragHandleLeft : public LLDragHandle { +protected: + LLDragHandleLeft(const Params& p) : LLDragHandle(p) {} + friend class LLUICtrlFactory; public: - LLDragHandleLeft(const std::string& name, const LLRect& rect, const std::string& title ); - virtual void setTitle( const std::string& title ); - virtual const std::string& getTitle() const; + virtual std::string getTitle() const; virtual void draw(); virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); diff --git a/indra/llui/llf32uictrl.cpp b/indra/llui/llf32uictrl.cpp new file mode 100644 index 0000000000..0978005b78 --- /dev/null +++ b/indra/llui/llf32uictrl.cpp @@ -0,0 +1,57 @@ +/** + * @file llf32uictrl.cpp + * @author Nat Goodspeed + * @date 2008-09-08 + * @brief Implementation for llf32uictrl. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llf32uictrl.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +LLF32UICtrl::LLF32UICtrl(const Params& p) +: LLUICtrl(p), + mInitialValue(p.initial_value().asReal()), + mMinValue(p.min_value), + mMaxValue(p.max_value), + mIncrement(p.increment) +{ + mViewModel->setValue(p.initial_value); +} + +F32 LLF32UICtrl::getValueF32() const +{ + return mViewModel->getValue().asReal(); +} diff --git a/indra/llui/llf32uictrl.h b/indra/llui/llf32uictrl.h new file mode 100644 index 0000000000..0a54fe761b --- /dev/null +++ b/indra/llui/llf32uictrl.h @@ -0,0 +1,83 @@ +/** + * @file llf32uictrl.h + * @author Nat Goodspeed + * @date 2008-09-08 + * @brief Base class for float-valued LLUICtrl widgets + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLF32UICTRL_H) +#define LL_LLF32UICTRL_H + +#include "lluictrl.h" + +class LLF32UICtrl: public LLUICtrl +{ +public: + struct Params: public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<F32> min_value, + max_value, + increment; + + Params() + : min_value("min_val", 0.f), + max_value("max_val", 1.f), + increment("increment", 0.1f) + {} + }; + +protected: + LLF32UICtrl(const Params& p); + +public: + virtual F32 getValueF32() const; + + virtual void setValue(const LLSD& value ) { mViewModel->setValue(value); } + virtual LLSD getValue() const { return LLSD(getValueF32()); } + + 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 F32 getInitialValue() const { return mInitialValue; } + virtual F32 getMinValue() const { return mMinValue; } + virtual F32 getMaxValue() const { return mMaxValue; } + virtual F32 getIncrement() const { return mIncrement; } + virtual void setMinValue(F32 min_value) { mMinValue = min_value; } + virtual void setMaxValue(F32 max_value) { mMaxValue = max_value; } + virtual void setIncrement(F32 increment) { mIncrement = increment;} + +protected: + F32 mInitialValue; + F32 mMinValue; + F32 mMaxValue; + F32 mIncrement; +}; + +#endif /* ! defined(LL_LLF32UICTRL_H) */ diff --git a/indra/llui/llfiltereditor.cpp b/indra/llui/llfiltereditor.cpp new file mode 100644 index 0000000000..390504234d --- /dev/null +++ b/indra/llui/llfiltereditor.cpp @@ -0,0 +1,51 @@ +/** + * @file llfiltereditor.cpp + * @brief LLFilterEditor implementation + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// 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) +{ +} + + +void LLFilterEditor::handleKeystroke() +{ + this->LLSearchEditor::handleKeystroke(); + + // Commit on every keystroke. + onCommit(); +} diff --git a/indra/llui/llfiltereditor.h b/indra/llui/llfiltereditor.h new file mode 100644 index 0000000000..c43a76b130 --- /dev/null +++ b/indra/llui/llfiltereditor.h @@ -0,0 +1,65 @@ +/** + * @file llfiltereditor.h + * @brief Text editor widget that represents a filter 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=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_FILTEREDITOR_H +#define LL_FILTEREDITOR_H + +#include "llsearcheditor.h" + +class LLFilterEditor : public LLSearchEditor +{ +public: + struct Params : public LLInitParam::Block<Params, LLSearchEditor::Params> + { + Params() + { + name = "filter_editor"; + } + }; + +protected: + LLFilterEditor(const Params&); + friend class LLUICtrlFactory; + + /*virtual*/ void handleKeystroke(); +}; + +#endif // LL_FILTEREDITOR_H diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp new file mode 100644 index 0000000000..3694ecd4f4 --- /dev/null +++ b/indra/llui/llflatlistview.cpp @@ -0,0 +1,1062 @@ +/** + * @file llflatlistview.cpp + * @brief LLFlatListView base class + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpanel.h" +#include "lltextbox.h" + +#include "llflatlistview.h" + +static const LLDefaultChildRegistry::Register<LLFlatListView> flat_list_view("flat_list_view"); + +const LLSD SELECTED_EVENT = LLSD().with("selected", true); +const LLSD UNSELECTED_EVENT = LLSD().with("selected", false); + +static const std::string COMMENT_TEXTBOX = "comment_text"; + +//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") +{}; + +void LLFlatListView::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */) +{ + LLScrollContainer::reshape(width, height, called_from_parent); + setItemsNoScrollWidth(width); + rearrangeItems(); +} + +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::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) +{ + 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); +} + +bool LLFlatListView::removeItemByValue(const LLSD& value) +{ + if (value.isUndefined()) return false; + + item_pair_t* item_pair = getItemPair(value); + if (!item_pair) return false; + + return removeItemPair(item_pair); +} + +bool LLFlatListView::removeItemByUUID(const LLUUID& uuid) +{ + return removeItemByValue(LLSD(uuid)); +} + +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<LLSD>& 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(std::vector<LLUUID>& 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<LLPanel*>& 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 items rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1)); +} + +void LLFlatListView::setNoItemsCommentText(const std::string& comment_text) +{ + if (NULL == mNoItemsCommentTextbox) + { + LLRect comment_rect = getRect(); + comment_rect.setOriginAndSize(0, 0, comment_rect.getWidth(), comment_rect.getHeight()); + comment_rect.stretch(-getBorderWidth()); + LLTextBox::Params text_p; + text_p.name(COMMENT_TEXTBOX); + text_p.border_visible(false); + text_p.rect(comment_rect); + text_p.follows.flags(FOLLOWS_ALL); + mNoItemsCommentTextbox = LLUICtrlFactory::create<LLTextBox>(text_p, this); + } + + mNoItemsCommentTextbox->setValue(comment_text); +} + +void LLFlatListView::clear() +{ + // 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(); + mSelectedItemPairs.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) + { + llwarns << "No comparator specified for sorting FlatListView items." << llendl; + 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) +{ + 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<LLPanel> (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(getSelectedItemsRect()); + params.visible(false); + params.bevel_style(LLViewBorder::BEVEL_IN); + mSelectedItemsBorder = LLUICtrlFactory::create<LLViewBorder> (params); + mItemsPanel->addChild( mSelectedItemsBorder ); +}; + +// 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<S32> scrollbar_size ("UIScrollbarSize", 0); + + setNoItemsCommentVisible(mItemPairs.empty()); + + 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 items rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1)); +} + +void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) +{ + if (!item_pair) 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_CONTROL) || !mMultipleSelection) resetSelection(); + selectItemPair(item_pair, select_item); +} + +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 'A': + { + if(MASK_CONTROL & mask) + { + selectAll(); + handled = TRUE; + } + 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 items rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1)); + + return true; +} + +LLRect LLFlatListView::getLastSelectedItemRect() +{ + if (!mSelectedItemPairs.size()) + { + return LLRect::null; + } + + return mSelectedItemPairs.back()->first->getRect(); +} + +LLRect LLFlatListView::getSelectedItemsRect() +{ + if (!mSelectedItemPairs.size()) + { + return LLRect::null; + } + LLRect rc = getLastSelectedItemRect(); + for ( pairs_const_iterator_t + it = mSelectedItemPairs.begin(), + it_end = mSelectedItemPairs.end(); + it != it_end; ++it ) + { + rc.unionWith((*it)->first->getRect()); + } + return rc; +} + +void LLFlatListView::selectFirstItem () +{ + selectItemPair(mItemPairs.front(), true); + ensureSelectedVisible(); +} + +void LLFlatListView::selectLastItem () +{ + selectItemPair(mItemPairs.back(), true); + ensureSelectedVisible(); +} + +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 ( !mItemPairs.size() ) + return false; + + + item_pair_t* to_sel_pair = NULL; + item_pair_t* cur_sel_pair = NULL; + if ( mSelectedItemPairs.size() ) + { + // 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; + } + } + } + } + else + { + // If there weren't selected items then choose the first one bases on given direction + cur_sel_pair = (is_up_direction) ? mItemPairs.back() : mItemPairs.front(); + // Force selection to first item + to_sel_pair = cur_sel_pair; + } + + + 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); + + return true; + } + return false; +} + +bool LLFlatListView::selectAll() +{ + if (!mAllowSelection) + return false; + + 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 items rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1)); + + return true; +} + +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) +{ + llassert(item_pair); + + bool deleted = 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); + break; + } + } + + mItemsPanel->removeChild(item_pair->first); + item_pair->first->die(); + delete item_pair; + + rearrangeItems(); + notifyParentItemsRectChanged(); + + return true; +} + +void LLFlatListView::notifyParentItemsRectChanged() +{ + S32 comment_height = 0; + + // take into account comment text height if exists + if (mNoItemsCommentTextbox && mNoItemsCommentTextbox->getVisible()) + { + comment_height = mNoItemsCommentTextbox->getTextPixelHeight(); + } + + 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) + { + if (visible) + { + // We have to update child rect here because of issues with rect after reshaping while creating LLTextbox + // It is possible to have invalid LLRect if Flat List is in LLAccordionTab + LLRect comment_rect = getLocalRect(); + + // To see comment correctly (EXT - 3244) in mNoItemsCommentTextbox we must get border width + // of LLFlatListView (@see getBorderWidth()) and stretch mNoItemsCommentTextbox to this width + // But getBorderWidth() returns 0 if LLFlatListView not visible. So we have to get border width + // from 'scroll_border' + LLViewBorder* scroll_border = getChild<LLViewBorder>("scroll border"); + comment_rect.stretch(-scroll_border->getBorderWidth()); + mNoItemsCommentTextbox->setRect(comment_rect); + } + mNoItemsCommentTextbox->setVisible(visible); + } +} + +void LLFlatListView::getItems(std::vector<LLPanel*>& 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<LLSD>& 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() +{ + mSelectedItemsBorder->setVisible(TRUE); +} +// virtual +void LLFlatListView::onFocusLost() +{ + mSelectedItemsBorder->setVisible(FALSE); +} + +//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<LLPanel*>& 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. + for (pairs_iterator_t + iter = mItemPairs.begin(), + iter_end = mItemPairs.end(); + iter != iter_end; ++iter) + { + LLPanel* pItem = (*iter)->first; + if (1 == pItem->notify(action)) + { + selectItemPair((*iter), false); + mItemsPanel->removeChild(pItem); + detached_items.push_back(pItem); + } + } + 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 + for (pairs_iterator_t + iter = mItemPairs.begin(), + iter_end = mItemPairs.end(); + iter != iter_end; ++iter) + { + (*iter)->first = NULL; + delete *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 + { + for (std::vector<LLPanel*>::const_iterator + detached_iter = detached_items.begin(), + detached_iter_end = detached_items.end(); + detached_iter != detached_iter_end; ++detached_iter) + { + LLPanel* pDetachedItem = *detached_iter; + for (pairs_iterator_t + iter = mItemPairs.begin(), + iter_end = mItemPairs.end(); + iter != iter_end; ++iter) + { + item_pair_t* item_pair = *iter; + if (item_pair->first == pDetachedItem) + { + mItemPairs.erase(iter); + item_pair->first = NULL; + delete item_pair; + break; + } + } + } + rearrangeItems(); + } + notifyParentItemsRectChanged(); + } +} + +//EOF diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h new file mode 100644 index 0000000000..5999e79f61 --- /dev/null +++ b/indra/llui/llflatlistview.h @@ -0,0 +1,430 @@ +/** + * @file llflatlistview.h + * @brief LLFlatListView base class + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLATLISTVIEW_H +#define LL_LLFLATLISTVIEW_H + +#include "llpanel.h" +#include "llscrollcontainer.h" + +class LLTextBox; + +/** + * 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: + + /** + * 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<Params, LLScrollContainer::Params> + { + /** turning on/off selection support */ + Optional<bool> allow_select; + + /** turning on/off multiple selection (works while clicking and holding CTRL)*/ + Optional<bool> multi_select; + + /** don't allow to deselect all selected items (for mouse events on items only) */ + Optional<bool> keep_one_selected; + + /** padding between items */ + Optional<U32> item_pad; + + Params(); + }; + + virtual ~LLFlatListView() { clear(); }; + + /** + * 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); + + /** + * Remove an item specified by value + * @return true if the item was removed, false otherwise + */ + virtual bool removeItemByValue(const LLSD& value); + + /** + * Remove an item specified by uuid + * @return true if the item was removed, false otherwise + */ + virtual bool removeItemByUUID(const LLUUID& uuid); + + /** + * Get an item by value + * @return the item as LLPanel if associated with value, NULL otherwise + */ + virtual LLPanel* getItemByValue(const LLSD& value) const; + + template<class T> + T* getTypedItemByValue(const LLSD& value) const + { + return dynamic_cast<T*>(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<LLPanel*>& items) const; + + /** + * Get all items values. + */ + virtual void getValues(std::vector<LLSD>& 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<LLSD>& 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(std::vector<LLUUID>& 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<LLPanel*>& 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 */ + void setCommitOnSelectionChange(bool b) { mCommitOnSelectionChange = b; } + + /** Get number of selected items in the list */ + U32 numSelected() const {return mSelectedItemPairs.size(); } + + /** Get number of items in the list */ + U32 size() const { return mItemPairs.size(); } + + + /** 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<LLPanel*>& 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 selectFirstItem (); + void selectLastItem (); + + virtual S32 notify(const LLSD& info) ; + +protected: + + /** Pairs LLpanel representing a single item LLPanel and LLSD associated with it */ + typedef std::pair<LLPanel*, LLSD> item_pair_t; + + typedef std::list<item_pair_t*> 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 selectAll(); + + virtual bool isSelected(item_pair_t* item_pair) const; + + virtual bool removeItemPair(item_pair_t* item_pair); + + /** + * 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(); + + LLRect getSelectedItemsRect(); + + void ensureSelectedVisible(); + +private: + + void setItemsNoScrollWidth(S32 new_width) {mItemsNoScrollWidth = new_width - 2 * mBorderThickness;} + + void setNoItemsCommentVisible(bool visible) const; + +private: + + /** Comparator to use when sorting the list. */ + const ItemComparator* mItemComparator; + + 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; + + /** 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; +}; + +#endif diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 21f8f6e5f7..79d8f90fec 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -43,11 +43,13 @@ #include "llbutton.h" #include "llcheckboxctrl.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" @@ -56,59 +58,39 @@ #include "llcontrol.h" #include "lltabcontainer.h" #include "v2math.h" +#include "lltrans.h" +#include "llhelp.h" +#include "llmultifloater.h" +#include "llsdutil.h" -const S32 MINIMIZED_WIDTH = 160; -const S32 CLOSE_BOX_FROM_TOP = 1; // use this to control "jumping" behavior when Ctrl-Tabbing const S32 TABBED_FLOATER_OFFSET = 0; -std::string LLFloater::sButtonActiveImageNames[BUTTON_COUNT] = -{ - "UIImgBtnCloseActiveUUID", //BUTTON_CLOSE - "UIImgBtnRestoreActiveUUID", //BUTTON_RESTORE - "UIImgBtnMinimizeActiveUUID", //BUTTON_MINIMIZE - "UIImgBtnTearOffActiveUUID", //BUTTON_TEAR_OFF - "UIImgBtnCloseActiveUUID", //BUTTON_EDIT -}; - -std::string LLFloater::sButtonInactiveImageNames[BUTTON_COUNT] = -{ - "UIImgBtnCloseInactiveUUID", //BUTTON_CLOSE - "UIImgBtnRestoreInactiveUUID", //BUTTON_RESTORE - "UIImgBtnMinimizeInactiveUUID", //BUTTON_MINIMIZE - "UIImgBtnTearOffInactiveUUID", //BUTTON_TEAR_OFF - "UIImgBtnCloseInactiveUUID", //BUTTON_EDIT -}; - -std::string LLFloater::sButtonPressedImageNames[BUTTON_COUNT] = -{ - "UIImgBtnClosePressedUUID", //BUTTON_CLOSE - "UIImgBtnRestorePressedUUID", //BUTTON_RESTORE - "UIImgBtnMinimizePressedUUID", //BUTTON_MINIMIZE - "UIImgBtnTearOffPressedUUID", //BUTTON_TEAR_OFF - "UIImgBtnClosePressedUUID", //BUTTON_EDIT -}; std::string LLFloater::sButtonNames[BUTTON_COUNT] = { - "llfloater_close_btn", //BUTTON_CLOSE + "llfloater_close_btn", //BUTTON_CLOSE "llfloater_restore_btn", //BUTTON_RESTORE "llfloater_minimize_btn", //BUTTON_MINIMIZE "llfloater_tear_off_btn", //BUTTON_TEAR_OFF - "llfloater_edit_btn", //BUTTON_EDIT + "llfloater_dock_btn", //BUTTON_DOCK + "llfloater_help_btn" //BUTTON_HELP }; -std::string LLFloater::sButtonToolTips[BUTTON_COUNT] = +std::string LLFloater::sButtonToolTips[BUTTON_COUNT]; + +std::string LLFloater::sButtonToolTipsIndex[BUTTON_COUNT]= { #ifdef LL_DARWIN - "Close (Cmd-W)", //BUTTON_CLOSE + "BUTTON_CLOSE_DARWIN", //"Close (Cmd-W)", //BUTTON_CLOSE #else - "Close (Ctrl-W)", //BUTTON_CLOSE + "BUTTON_CLOSE_WIN", //"Close (Ctrl-W)", //BUTTON_CLOSE #endif - "Restore", //BUTTON_RESTORE - "Minimize", //BUTTON_MINIMIZE - "Tear Off", //BUTTON_TEAR_OFF - "Edit", //BUTTON_EDIT + "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] = @@ -117,330 +99,364 @@ LLFloater::click_callback LLFloater::sButtonCallbacks[BUTTON_COUNT] = LLFloater::onClickMinimize, //BUTTON_RESTORE LLFloater::onClickMinimize, //BUTTON_MINIMIZE LLFloater::onClickTearOff, //BUTTON_TEAR_OFF - LLFloater::onClickEdit, //BUTTON_EDIT + LLFloater::onClickDock, //BUTTON_DOCK + LLFloater::onClickHelp //BUTTON_HELP }; LLMultiFloater* LLFloater::sHostp = NULL; -BOOL LLFloater::sEditModeEnabled; +BOOL LLFloater::sQuitting = FALSE; // Flag to prevent storing visibility controls while quitting LLFloater::handle_map_t LLFloater::sFloaterMap; LLFloaterView* gFloaterView = NULL; -LLFloater::LLFloater() : - //FIXME: we should initialize *all* member variables here - LLPanel(), mAutoFocus(TRUE), - mResizable(FALSE), - mDragOnLeft(FALSE), - mMinWidth(0), - mMinHeight(0) +/*==========================================================================*| +// 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()) + { + //llerrs << "Mismatched LLSD types: (" << a << ") mismatches (" << b << ")" << llendl; + 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) { - // automatically take focus when opened - mAutoFocus = TRUE; - for (S32 i = 0; i < BUTTON_COUNT; i++) - { - mButtonsEnabled[i] = FALSE; - mButtons[i] = NULL; - } - for (S32 i = 0; i < 4; i++) - { - mResizeBar[i] = NULL; - mResizeHandle[i] = NULL; - } - mDragHandle = NULL; - mHandle.bind(this); - mNotificationContext = new LLFloaterNotificationContext(getHandle()); + return llsd_equals(a, b); } -LLFloater::LLFloater(const std::string& name) -: LLPanel(name), mAutoFocus(TRUE) // automatically take focus when opened +//************************************ + +LLFloater::Params::Params() +: title("title"), + short_title("short_title"), + single_instance("single_instance", false), + auto_tile("auto_tile", 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), + 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") { - for (S32 i = 0; i < BUTTON_COUNT; i++) - { - mButtonsEnabled[i] = FALSE; - mButtons[i] = NULL; - } - for (S32 i = 0; i < 4; i++) - { - mResizeBar[i] = NULL; - mResizeHandle[i] = NULL; - } - std::string title; // null string - initFloater(title, FALSE, DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT, FALSE, TRUE, TRUE); // defaults + visible = false; } -LLFloater::LLFloater(const std::string& name, const LLRect& rect, const std::string& title, - BOOL resizable, - S32 min_width, - S32 min_height, - BOOL drag_on_left, - BOOL minimizable, - BOOL close_btn, - BOOL bordered) -: LLPanel(name, rect, bordered), mAutoFocus(TRUE) // automatically take focus when opened +//static +const LLFloater::Params& LLFloater::getDefaultParams() { - for (S32 i = 0; i < BUTTON_COUNT; i++) - { - mButtonsEnabled[i] = FALSE; - mButtons[i] = NULL; - } - for (S32 i = 0; i < 4; i++) - { - mResizeBar[i] = NULL; - mResizeHandle[i] = NULL; - } - initFloater( title, resizable, min_width, min_height, drag_on_left, minimizable, close_btn); + return LLUICtrlFactory::getDefaultParams<LLFloater>(); } -LLFloater::LLFloater(const std::string& name, const std::string& rect_control, const std::string& title, - BOOL resizable, - S32 min_width, - S32 min_height, - BOOL drag_on_left, - BOOL minimizable, - BOOL close_btn, - BOOL bordered) -: LLPanel(name, rect_control, bordered), mAutoFocus(TRUE) // automatically take focus when opened +//static +void LLFloater::initClass() { + // translate tooltips for floater buttons for (S32 i = 0; i < BUTTON_COUNT; i++) { - mButtonsEnabled[i] = FALSE; - mButtons[i] = NULL; - } - for (S32 i = 0; i < 4; i++) - { - mResizeBar[i] = NULL; - mResizeHandle[i] = NULL; - } - initFloater( title, resizable, min_width, min_height, drag_on_left, minimizable, close_btn); -} + sButtonToolTips[i] = LLTrans::getString( sButtonToolTipsIndex[i] ); + } +} + +// 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), + mKey(key), + mAutoTile(p.auto_tile), + mCanTearOff(p.can_tear_off), + mCanMinimize(p.can_minimize), + mCanClose(p.can_close), + mDragOnLeft(p.can_drag_on_left), + mResizable(p.can_resize), + mMinWidth(p.min_width), + mMinHeight(p.min_height), + mHeaderHeight(p.header_height), + mLegacyHeaderHeight(p.legacy_header_height), + 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) +// mNotificationContext(NULL) +{ + mHandle.bind(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); + + // chrome floaters don't take focus at all + setFocusRoot(!getIsChrome()); + + initFloater(p); +} // Note: Floaters constructed from XML call init() twice! -void LLFloater::initFloater(const std::string& title, - BOOL resizable, S32 min_width, S32 min_height, - BOOL drag_on_left, BOOL minimizable, BOOL close_btn) +void LLFloater::initFloater(const Params& p) { - mHandle.bind(this); - mNotificationContext = new LLFloaterNotificationContext(getHandle()); - - // Init function can be called more than once, so clear out old data. - for (S32 i = 0; i < BUTTON_COUNT; i++) + // Close button. + if (mCanClose) { - mButtonsEnabled[i] = FALSE; - if (mButtons[i] != NULL) - { - removeChild(mButtons[i]); - delete mButtons[i]; - mButtons[i] = NULL; - } + mButtonsEnabled[BUTTON_CLOSE] = TRUE; } - mButtonScale = 1.f; - //sjb: Thia is a bit of a hack: - BOOL need_border = hasBorder(); - // remove the border since deleteAllChildren() will also delete the border (but not clear mBorder) - removeBorder(); - // this will delete mBorder too - deleteAllChildren(); - // add the border back if we want it - if (need_border) + // Help button: '?' + if ( !mHelpTopic.empty() ) { - addBorder(); + mButtonsEnabled[BUTTON_HELP] = TRUE; } - // chrome floaters don't take focus at all - setFocusRoot(!getIsChrome()); + // Minimize button only for top draggers + if ( !mDragOnLeft && mCanMinimize ) + { + mButtonsEnabled[BUTTON_MINIMIZE] = TRUE; + } - // Reset cached pointers - mDragHandle = NULL; - for (S32 i = 0; i < 4; i++) + if(mCanDock) { - mResizeBar[i] = NULL; - mResizeHandle[i] = NULL; + mButtonsEnabled[BUTTON_DOCK] = TRUE; } - mCanTearOff = TRUE; - mEditing = FALSE; - // Clicks stop here. - setMouseOpaque(TRUE); + buildButtons(p); - mFirstLook = TRUE; - mForeground = FALSE; - mDragOnLeft = drag_on_left == TRUE; + // Floaters are created in the invisible state + setVisible(FALSE); - // Floaters always draw their background, unlike every other panel. - setBackgroundVisible(TRUE); + // add self to handle->floater map + sFloaterMap[mHandle] = this; - // Floaters start not minimized. When minimized, they save their - // prior rectangle to be used on restore. - mMinimized = FALSE; - mExpandedRect.set(0,0,0,0); - - S32 close_pad; // space to the right of close box - S32 close_box_size; // For layout purposes, how big is the close box? - if (close_btn) - { - close_box_size = LLFLOATER_CLOSE_BOX_SIZE; - close_pad = 0; - } - else + if (!getParent()) { - close_box_size = 0; - close_pad = 0; + gFloaterView->addChild(this); } +} - S32 minimize_box_size; - S32 minimize_pad; - if (minimizable && !drag_on_left) - { - minimize_box_size = LLFLOATER_CLOSE_BOX_SIZE; - minimize_pad = 0; - } - else +void LLFloater::addDragHandle() +{ + if (!mDragHandle) { - minimize_box_size = 0; - minimize_pad = 0; + if (mDragOnLeft) + { + LLDragHandleLeft::Params p; + p.name("drag"); + p.follows.flags(FOLLOWS_ALL); + p.label(mTitle); + mDragHandle = LLUICtrlFactory::create<LLDragHandleLeft>(p); + } + else // drag on top + { + LLDragHandleTop::Params p; + p.name("Drag Handle"); + p.follows.flags(FOLLOWS_ALL); + p.label(mTitle); + mDragHandle = LLUICtrlFactory::create<LLDragHandleTop>(p); + } + addChild(mDragHandle); } + layoutDragHandle(); +} - // Drag Handle - // Add first so it's in the background. -// const S32 drag_pad = 2; - if (drag_on_left) +void LLFloater::layoutDragHandle() +{ + static LLUICachedControl<S32> floater_close_box_size ("UIFloaterCloseBoxSize", 0); + S32 close_box_size = mCanClose ? floater_close_box_size : 0; + + LLRect rect; + if (mDragOnLeft) { - LLRect drag_handle_rect; - drag_handle_rect.setOriginAndSize( - 0, 0, - DRAG_HANDLE_WIDTH, - getRect().getHeight() - LLPANEL_BORDER_WIDTH - close_box_size); - mDragHandle = new LLDragHandleLeft(std::string("drag"), drag_handle_rect, title ); + rect.setLeftTopAndSize(0, 0, DRAG_HANDLE_WIDTH, getRect().getHeight() - LLPANEL_BORDER_WIDTH - close_box_size); } else // drag on top { - LLRect drag_handle_rect( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - mDragHandle = new LLDragHandleTop( std::string("Drag Handle"), drag_handle_rect, title ); + rect = getLocalRect(); } - addChild(mDragHandle); + mDragHandle->setRect(rect); + updateButtons(); + applyTitle(); +} - // Resize Handle - mResizable = resizable; - mMinWidth = min_width; - mMinHeight = min_height; +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<LLResizeBar>(p); + addChild( mResizeBar[LLResizeBar::LEFT] ); + + p.name("resizebar_top"); + p.min_size(mMinHeight); + p.side(LLResizeBar::TOP); + + mResizeBar[LLResizeBar::TOP] = LLUICtrlFactory::create<LLResizeBar>(p); + addChild( mResizeBar[LLResizeBar::TOP] ); + + p.name("resizebar_right"); + p.min_size(mMinWidth); + p.side(LLResizeBar::RIGHT); + mResizeBar[LLResizeBar::RIGHT] = LLUICtrlFactory::create<LLResizeBar>(p); + addChild( mResizeBar[LLResizeBar::RIGHT] ); + + p.name("resizebar_bottom"); + p.min_size(mMinHeight); + p.side(LLResizeBar::BOTTOM); + mResizeBar[LLResizeBar::BOTTOM] = LLUICtrlFactory::create<LLResizeBar>(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<LLResizeHandle>(handle_p); + addChild(mResizeHandle[0]); + + handle_p.corner(LLResizeHandle::RIGHT_TOP); + mResizeHandle[1] = LLUICtrlFactory::create<LLResizeHandle>(handle_p); + addChild(mResizeHandle[1]); + + handle_p.corner(LLResizeHandle::LEFT_BOTTOM); + mResizeHandle[2] = LLUICtrlFactory::create<LLResizeHandle>(handle_p); + addChild(mResizeHandle[2]); - if( mResizable ) - { - // Resize bars (sides) - const S32 RESIZE_BAR_THICKNESS = 3; - mResizeBar[LLResizeBar::LEFT] = new LLResizeBar( - std::string("resizebar_left"), - this, - LLRect( 0, getRect().getHeight(), RESIZE_BAR_THICKNESS, 0), - min_width, S32_MAX, LLResizeBar::LEFT ); - addChild( mResizeBar[0] ); - - mResizeBar[LLResizeBar::TOP] = new LLResizeBar( - std::string("resizebar_top"), - this, - LLRect( 0, getRect().getHeight(), getRect().getWidth(), getRect().getHeight() - RESIZE_BAR_THICKNESS), - min_height, S32_MAX, LLResizeBar::TOP ); - addChild( mResizeBar[1] ); - - mResizeBar[LLResizeBar::RIGHT] = new LLResizeBar( - std::string("resizebar_right"), - this, - LLRect( getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0), - min_width, S32_MAX, LLResizeBar::RIGHT ); - addChild( mResizeBar[2] ); - - mResizeBar[LLResizeBar::BOTTOM] = new LLResizeBar( - std::string("resizebar_bottom"), - this, - LLRect( 0, RESIZE_BAR_THICKNESS, getRect().getWidth(), 0), - min_height, S32_MAX, LLResizeBar::BOTTOM ); - addChild( mResizeBar[3] ); - - - // Resize handles (corners) - mResizeHandle[0] = new LLResizeHandle( - std::string("Resize Handle"), - LLRect( getRect().getWidth() - RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_HEIGHT, getRect().getWidth(), 0), - min_width, - min_height, - LLResizeHandle::RIGHT_BOTTOM); - addChild(mResizeHandle[0]); - - mResizeHandle[1] = new LLResizeHandle( - std::string("resize"), - LLRect( getRect().getWidth() - RESIZE_HANDLE_WIDTH, getRect().getHeight(), getRect().getWidth(), getRect().getHeight() - RESIZE_HANDLE_HEIGHT), - min_width, - min_height, - LLResizeHandle::RIGHT_TOP ); - addChild(mResizeHandle[1]); - - mResizeHandle[2] = new LLResizeHandle( std::string("resize"), - LLRect( 0, RESIZE_HANDLE_HEIGHT, RESIZE_HANDLE_WIDTH, 0 ), - min_width, - min_height, - LLResizeHandle::LEFT_BOTTOM ); - addChild(mResizeHandle[2]); + handle_p.corner(LLResizeHandle::LEFT_TOP); + mResizeHandle[3] = LLUICtrlFactory::create<LLResizeHandle>(handle_p); + addChild(mResizeHandle[3]); - mResizeHandle[3] = new LLResizeHandle( std::string("resize"), - LLRect( 0, getRect().getHeight(), RESIZE_HANDLE_WIDTH, getRect().getHeight() - RESIZE_HANDLE_HEIGHT ), - min_width, - min_height, - LLResizeHandle::LEFT_TOP ); - addChild(mResizeHandle[3]); - } + layoutResizeCtrls(); +} - // Close button. - if (close_btn) - { - mButtonsEnabled[BUTTON_CLOSE] = TRUE; - } +void LLFloater::layoutResizeCtrls() +{ + LLRect rect; - // Minimize button only for top draggers - if ( !drag_on_left && minimizable ) - { - mButtonsEnabled[BUTTON_MINIMIZE] = TRUE; - } + // Resize bars (sides) + const S32 RESIZE_BAR_THICKNESS = 3; + rect = LLRect( 0, getRect().getHeight(), RESIZE_BAR_THICKNESS, 0); + mResizeBar[LLResizeBar::LEFT]->setRect(rect); - // Keep track of whether this window has ever been dragged while it - // was minimized. If it has, we'll remember its position for the - // next time it's minimized. - mHasBeenDraggedWhileMinimized = FALSE; - mPreviousMinimizedLeft = 0; - mPreviousMinimizedBottom = 0; + rect = LLRect( 0, getRect().getHeight(), getRect().getWidth(), getRect().getHeight() - RESIZE_BAR_THICKNESS); + mResizeBar[LLResizeBar::TOP]->setRect(rect); - buildButtons(); + rect = LLRect(getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0); + mResizeBar[LLResizeBar::RIGHT]->setRect(rect); - // JC - Don't do this here, because many floaters first construct themselves, - // then show themselves. Put it in setVisibleAndFrontmost. - // make_ui_sound("UISndWindowOpen"); + rect = LLRect(0, RESIZE_BAR_THICKNESS, getRect().getWidth(), 0); + mResizeBar[LLResizeBar::BOTTOM]->setRect(rect); - // RN: floaters are created in the invisible state - setVisible(FALSE); + // Resize handles (corners) + rect = LLRect( getRect().getWidth() - RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_HEIGHT, getRect().getWidth(), 0); + mResizeHandle[0]->setRect(rect); - // add self to handle->floater map - sFloaterMap[mHandle] = this; + 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); - if (!getParent()) + rect = LLRect( 0, getRect().getHeight(), RESIZE_HANDLE_WIDTH, getRect().getHeight() - RESIZE_HANDLE_HEIGHT ); + mResizeHandle[3]->setRect(rect); +} + +void LLFloater::enableResizeCtrls(bool enable) +{ + for (S32 i = 0; i < 4; ++i) { - gFloaterView->addChild(this); + mResizeBar[i]->setVisible(enable); + mResizeBar[i]->setEnabled(enable); + + mResizeHandle[i]->setVisible(enable); + mResizeHandle[i]->setEnabled(enable); } } // virtual LLFloater::~LLFloater() { - delete mNotificationContext; - mNotificationContext = NULL; - - control_map_t::iterator itor; - for (itor = mFloaterControls.begin(); itor != mFloaterControls.end(); ++itor) - { - delete itor->second; - } - mFloaterControls.clear(); + LLFloaterReg::removeInstance(mInstanceName, mKey); + +// delete mNotificationContext; +// mNotificationContext = NULL; //// am I not hosted by another floater? //if (mHostHandle.isDead()) @@ -469,12 +485,41 @@ LLFloater::~LLFloater() delete mResizeBar[i]; delete mResizeHandle[i]; } + + storeRectControl(); + setVisible(false); // We're not visible if we're destroyed + storeVisibilityControl(); + storeDockStateControl(); +} + +void LLFloater::storeRectControl() +{ + if( mRectControl.size() > 1 ) + { + LLUI::sSettingGroups["floater"]->setRect( mRectControl, getRect() ); + } +} + +void LLFloater::storeVisibilityControl() +{ + if( !sQuitting && mVisibilityControl.size() > 1 ) + { + LLUI::sSettingGroups["floater"]->setBOOL( mVisibilityControl, getVisible() ); + } +} + +void LLFloater::storeDockStateControl() +{ + if( !sQuitting && mDocStateControl.size() > 1 ) + { + LLUI::sSettingGroups["floater"]->setBOOL( mDocStateControl, isDocked() ); + } } void LLFloater::setVisible( BOOL visible ) { - LLPanel::setVisible(visible); + LLPanel::setVisible(visible); // calls handleVisibilityChange() if( visible && mFirstLook ) { mFirstLook = FALSE; @@ -504,10 +549,25 @@ void LLFloater::setVisible( BOOL visible ) } ++dependent_it; } + + storeVisibilityControl(); +} + +// virtual +void LLFloater::handleVisibilityChange ( BOOL new_visibility ) +{ + if (new_visibility) + { + if (getHost()) + getHost()->setFloaterFlashing(this, FALSE); + } + LLPanel::handleVisibilityChange ( new_visibility ); } -void LLFloater::open() /* Flawfinder: ignore */ +void LLFloater::openFloater(const LLSD& key) { + mKey = key; // in case we need to open ourselves again + if (getSoundFlags() != SILENT // don't play open sound for hosted (tabbed) windows && !getHost() @@ -525,9 +585,11 @@ void LLFloater::open() /* Flawfinder: ignore */ // only select tabs if window they are hosted in is visible getFloaterHost()->addFloater(this, getFloaterHost()->getVisible()); } - else if (getHost() != NULL) + + if (getHost() != NULL) { - // already hosted + getHost()->setMinimized(FALSE); + getHost()->setVisibleAndFrontmost(mAutoFocus); getHost()->showFloater(this); } else @@ -536,11 +598,19 @@ void LLFloater::open() /* Flawfinder: ignore */ setVisibleAndFrontmost(mAutoFocus); } - onOpen(); + mOpenSignal(this, key); + onOpen(key); + + dirtyRect(); } -void LLFloater::close(bool app_quitting) +void LLFloater::closeFloater(bool app_quitting) { + 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); @@ -570,7 +640,7 @@ void LLFloater::close(bool app_quitting) if (floaterp) { ++dependent_it; - floaterp->close(); + floaterp->closeFloater(app_quitting); } else { @@ -598,8 +668,30 @@ void LLFloater::close(bool app_quitting) } } - // Let floater do cleanup. + 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); + } + } + else + { + setVisible(FALSE); // hide before destroying (so handleVisibilityChange() gets called) + destroy(); + } } } @@ -607,6 +699,7 @@ void LLFloater::close(bool app_quitting) void LLFloater::reshape(S32 width, S32 height, BOOL called_from_parent) { LLPanel::reshape(width, height, called_from_parent); + storeRectControl(); } void LLFloater::releaseFocus() @@ -616,10 +709,7 @@ void LLFloater::releaseFocus() gFocusMgr.setTopCtrl(NULL); } - if( gFocusMgr.childHasKeyboardFocus( this ) ) - { - gFocusMgr.setKeyboardFocus(NULL); - } + setFocus(FALSE); if( gFocusMgr.childHasMouseCapture( this ) ) { @@ -664,26 +754,45 @@ void LLFloater::center() centerWithin(gFloaterView->getRect()); } +LLMultiFloater* LLFloater::getHost() +{ + return (LLMultiFloater*)mHostHandle.get(); +} + +void LLFloater::applySavedVariables() +{ + applyRectControl(); + applyDockState(); +} + void LLFloater::applyRectControl() { - if (!getRectControl().empty()) + if (mRectControl.size() > 1) { - const LLRect& rect = LLUI::sConfigGroup->getRect(getRectControl()); - translate( rect.mLeft - getRect().mLeft, rect.mBottom - getRect().mBottom); - if (mResizable) + const LLRect& rect = LLUI::sSettingGroups["floater"]->getRect(mRectControl); + if (rect.getWidth() > 0 && rect.getHeight() > 0) { - reshape(llmax(mMinWidth, rect.getWidth()), llmax(mMinHeight, rect.getHeight())); + translate( rect.mLeft - getRect().mLeft, rect.mBottom - getRect().mBottom); + if (mResizable) + { + reshape(llmax(mMinWidth, rect.getWidth()), llmax(mMinHeight, rect.getHeight())); + } } } } -void LLFloater::applyTitle() +void LLFloater::applyDockState() { - if (gNoRender) + if (mDocStateControl.size() > 1) { - return; + bool dockState = LLUI::sSettingGroups["floater"]->getBOOL(mDocStateControl); + setDocked(dockState); } +} + +void LLFloater::applyTitle() +{ if (!mDragHandle) { return; @@ -699,7 +808,7 @@ void LLFloater::applyTitle() } } -const std::string& LLFloater::getCurrentTitle() const +std::string LLFloater::getCurrentTitle() const { return mDragHandle ? mDragHandle->getTitle() : LLStringUtil::null; } @@ -710,7 +819,7 @@ void LLFloater::setTitle( const std::string& title ) applyTitle(); } -std::string LLFloater::getTitle() +std::string LLFloater::getTitle() const { if (mTitle.empty()) { @@ -728,7 +837,7 @@ void LLFloater::setShortTitle( const std::string& short_title ) applyTitle(); } -std::string LLFloater::getShortTitle() +std::string LLFloater::getShortTitle() const { if (mShortTitle.empty()) { @@ -740,8 +849,6 @@ std::string LLFloater::getShortTitle() } } - - BOOL LLFloater::canSnapTo(const LLView* other_view) { if (NULL == other_view) @@ -763,7 +870,7 @@ BOOL LLFloater::canSnapTo(const LLView* other_view) return LLPanel::canSnapTo(other_view); } -void LLFloater::snappedTo(const LLView* snap_view) +void LLFloater::setSnappedTo(const LLView* snap_view) { if (!snap_view || snap_view == getParent()) { @@ -772,16 +879,18 @@ void LLFloater::snappedTo(const LLView* snap_view) else { //RN: assume it's a floater as it must be a sibling to our parent floater - LLFloater* floaterp = (LLFloater*)snap_view; - - setSnapTarget(floaterp->getHandle()); + const LLFloater* floaterp = dynamic_cast<const LLFloater*>(snap_view); + if (floaterp) + { + setSnapTarget(floaterp->getHandle()); + } } } -void LLFloater::userSetShape(const LLRect& new_rect) +void LLFloater::handleReshape(const LLRect& new_rect, bool by_user) { const LLRect old_rect = getRect(); - LLView::userSetShape(new_rect); + LLView::handleReshape(new_rect, by_user); // if not minimized, adjust all snapped dependents to new shape if (!isMinimized()) @@ -816,7 +925,7 @@ void LLFloater::userSetShape(const LLRect& new_rect) delta_y += new_rect.mBottom - old_rect.mBottom; dependent_rect.translate(delta_x, delta_y); - floaterp->userSetShape(dependent_rect); + floaterp->setShape(dependent_rect, by_user); } } } @@ -834,10 +943,17 @@ void LLFloater::userSetShape(const LLRect& new_rect) void LLFloater::setMinimized(BOOL minimize) { + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + static LLUICachedControl<S32> minimized_width ("UIMinimizedWidth", 0); + if (minimize == mMinimized) return; 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 @@ -899,10 +1015,8 @@ void LLFloater::setMinimized(BOOL minimize) } } - mMinimized = TRUE; - // Reshape *after* setting mMinimized - reshape( MINIMIZED_WIDTH, LLFLOATER_HEADER_SIZE, TRUE); + reshape( minimized_width, floater_header_size, TRUE); } else { @@ -968,7 +1082,7 @@ void LLFloater::setFocus( BOOL b ) } LLUICtrl* last_focus = gFocusMgr.getLastFocusForGroup(this); // a descendent already has focus - BOOL child_had_focus = gFocusMgr.childHasKeyboardFocus(this); + BOOL child_had_focus = hasFocus(); // give focus to first valid descendent LLPanel::setFocus(b); @@ -996,6 +1110,14 @@ void LLFloater::setFocus( BOOL b ) } // 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 @@ -1143,7 +1265,7 @@ void LLFloater::removeDependentFloater(LLFloater* floaterp) floaterp->mDependeeHandle = LLHandle<LLFloater>(); } -BOOL LLFloater::offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButtons index) +BOOL LLFloater::offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index) { if( mButtonsEnabled[index] ) { @@ -1162,6 +1284,12 @@ BOOL LLFloater::offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButtons inde return FALSE; } +BOOL LLFloater::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + LLPanel::handleScrollWheel(x,y,clicks); + return TRUE;//always +} + // virtual BOOL LLFloater::handleMouseDown(S32 x, S32 y, MASK mask) { @@ -1232,6 +1360,7 @@ void LLFloater::bringToFront( S32 x, S32 y ) // virtual void LLFloater::setVisibleAndFrontmost(BOOL take_focus) { + gFocusMgr.setTopCtrl(NULL); setVisible(TRUE); setFrontmost(take_focus); } @@ -1253,42 +1382,49 @@ void LLFloater::setFrontmost(BOOL take_focus) } } -//static -void LLFloater::setEditModeEnabled(BOOL enable) +void LLFloater::setCanDock(bool b) { - if (enable != sEditModeEnabled) + if(b != mCanDock) { - S32 count = 0; - for(handle_map_iter_t iter = sFloaterMap.begin(); iter != sFloaterMap.end(); ++iter) + mCanDock = b; + if(mCanDock) { - LLFloater* floater = iter->second; - if (!floater->isDead()) - { - iter->second->mButtonsEnabled[BUTTON_EDIT] = enable; - iter->second->updateButtons(); - } - count++; + mButtonsEnabled[BUTTON_DOCK] = !mDocked; + } + else + { + mButtonsEnabled[BUTTON_DOCK] = FALSE; } } - - sEditModeEnabled = enable; + updateButtons(); } +void LLFloater::setDocked(bool docked, bool pop_on_undock) +{ + if(docked != mDocked && mCanDock) + { + mDocked = docked; + mButtonsEnabled[BUTTON_DOCK] = !mDocked; + updateButtons(); + + storeDockStateControl(); + } + +} // static -void LLFloater::onClickMinimize(void *userdata) +void LLFloater::onClickMinimize(LLFloater* self) { - LLFloater* self = (LLFloater*) userdata; - if (!self) return; - + if (!self) + return; self->setMinimized( !self->isMinimized() ); } -void LLFloater::onClickTearOff(void *userdata) +void LLFloater::onClickTearOff(LLFloater* self) { - LLFloater* self = (LLFloater*) userdata; - if (!self) return; - + if (!self) + return; + S32 floater_header_size = self->mHeaderHeight; LLMultiFloater* host_floater = self->getHost(); if (host_floater) //Tear off { @@ -1297,17 +1433,18 @@ void LLFloater::onClickTearOff(void *userdata) // reparent to floater view gFloaterView->addChild(self); - self->open(); /* Flawfinder: ignore */ + self->openFloater(self->getKey()); // only force position for floaters that don't have that data saved - if (self->getRectControl().empty()) + if (self->mRectControl.size() <= 1) { - new_rect.setLeftTopAndSize(host_floater->getRect().mLeft + 5, host_floater->getRect().mTop - LLFLOATER_HEADER_SIZE - 5, self->getRect().getWidth(), self->getRect().getHeight()); + 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. { @@ -1317,18 +1454,34 @@ void LLFloater::onClickTearOff(void *userdata) self->setMinimized(FALSE); // to reenable minimize button if it was minimized new_host->showFloater(self); // make sure host is visible - new_host->open(); + new_host->openFloater(new_host->getKey()); } + self->setTornOff(false); } + self->updateButtons(); } // static -void LLFloater::onClickEdit(void *userdata) +void LLFloater::onClickDock(LLFloater* self) { - LLFloater* self = (LLFloater*) userdata; - if (!self) return; + if(self && self->mCanDock) + { + self->setDocked(!self->mDocked, true); + } +} - self->mEditing = self->mEditing ? FALSE : TRUE; +// static +void LLFloater::onClickHelp( LLFloater* self ) +{ + if (self && LLUI::sHelpImpl) + { + // find the current help context for this floater + std::string help_topic; + if (self->findHelpTopic(help_topic)) + { + LLUI::sHelpImpl->showTopic(help_topic); + } + } } // static @@ -1354,7 +1507,8 @@ LLFloater* LLFloater::getClosableFloaterFromFocus() // The focused floater may not be closable, // Find and close a parental floater that is closeable, if any. - for(LLFloater* floater_to_close = focused_floater; + LLFloater* prev_floater = NULL; + for(LLFloater* floater_to_close = focused_floater; NULL != floater_to_close; floater_to_close = gFloaterView->getParentFloater(floater_to_close)) { @@ -1362,6 +1516,14 @@ LLFloater* LLFloater::getClosableFloaterFromFocus() { return floater_to_close; } + + // If floater has as parent root view + // gFloaterView->getParentFloater(floater_to_close) returns + // the same floater_to_close, so we need to check this. + if (prev_floater == floater_to_close) { + break; + } + prev_floater = floater_to_close; } return NULL; @@ -1373,7 +1535,7 @@ void LLFloater::closeFocusedFloater() LLFloater* floater_to_close = LLFloater::getClosableFloaterFromFocus(); if(floater_to_close) { - floater_to_close->close(); + floater_to_close->closeFloater(); } // if nothing took focus after closing focused floater @@ -1388,18 +1550,18 @@ void LLFloater::closeFocusedFloater() // static -void LLFloater::onClickClose( void* userdata ) +void LLFloater::onClickClose( LLFloater* self ) { - LLFloater* self = (LLFloater*) userdata; - if (!self) return; - - self->close(); + if (!self) + return; + self->closeFloater(false); } // virtual void LLFloater::draw() { + F32 alpha = getDrawContext().mAlpha; // draw background if( isBackgroundVisible() ) { @@ -1408,34 +1570,56 @@ void LLFloater::draw() S32 right = getRect().getWidth() - LLPANEL_BORDER_WIDTH; S32 bottom = LLPANEL_BORDER_WIDTH; - LLColor4 shadow_color = LLUI::sColorsGroup->getColor("ColorDropShadow"); - F32 shadow_offset = (F32)LLUI::sConfigGroup->getS32("DropShadowFloater"); + static LLUICachedControl<S32> shadow_offset_S32 ("DropShadowFloater", 0); + static LLUIColor shadow_color_cached = LLUIColorTable::instance().getColor("ColorDropShadow"); + LLColor4 shadow_color = shadow_color_cached; + F32 shadow_offset = (F32)shadow_offset_S32; + if (!isBackgroundOpaque()) { shadow_offset *= 0.2f; shadow_color.mV[VALPHA] *= 0.5f; } gl_drop_shadow(left, top, right, bottom, - shadow_color, + shadow_color % alpha, llround(shadow_offset)); - // No transparent windows in simple UI + LLUIImage* image = NULL; + LLColor4 color; if (isBackgroundOpaque()) { - gl_rect_2d( left, top, right, bottom, getBackgroundColor() ); + // NOTE: image may not be set + image = getBackgroundImage(); + color = getBackgroundColor(); } else { - gl_rect_2d( left, top, right, bottom, getTransparentColor() ); + image = getTransparentImage(); + color = getTransparentColor(); } - if(gFocusMgr.childHasKeyboardFocus(this) && !getIsChrome() && !getCurrentTitle().empty()) + if (image) + { + // We're using images for this floater's backgrounds + image->draw(getLocalRect(), UI_VERTEX_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 - const LLFontGL* font = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF ); - LLRect r = getRect(); - gl_rect_2d_offset_local(0, r.getHeight(), r.getWidth(), r.getHeight() - (S32)font->getLineHeight() - 1, - LLUI::sColorsGroup->getColor("TitleBarFocusColor"), 0, TRUE); + 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() - (S32)font->getLineHeight() - 1, + titlebar_focus_color % alpha, 0, TRUE); + } } } @@ -1445,9 +1629,9 @@ void LLFloater::draw() { if (hasFocus() && getDefaultButton()->getEnabled()) { - LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); + 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<LLButton*>(focus_ctrl) != NULL && focus_ctrl->getParent() == this; + BOOL focus_is_child_button = dynamic_cast<LLButton*>(focus_ctrl) != NULL && dynamic_cast<LLButton*>(focus_ctrl)->getParent() == this; // only enable default button when current focus is not a button getDefaultButton()->setBorderEnabled(!focus_is_child_button); } @@ -1466,8 +1650,9 @@ void LLFloater::draw() } else { + //FIXME: get rid of this hack // draw children - LLView* focused_child = gFocusMgr.getKeyboardFocus(); + LLView* focused_child = dynamic_cast<LLView*>(gFocusMgr.getKeyboardFocus()); BOOL focused_child_visible = FALSE; if (focused_child && focused_child->getParent() == this) { @@ -1485,16 +1670,6 @@ void LLFloater::draw() drawChild(focused_child); } - if( isBackgroundVisible() ) - { - // add in a border to improve spacialized visual aclarity ;) - // use lines instead of gl_rect_2d so we can round the edges as per james' recommendation - LLUI::setLineWidth(1.5f); - LLColor4 outlineColor = gFocusMgr.childHasKeyboardFocus(this) ? LLUI::sColorsGroup->getColor("FloaterFocusBorderColor") : LLUI::sColorsGroup->getColor("FloaterUnfocusBorderColor"); - gl_rect_2d_offset_local(0, getRect().getHeight() + 1, getRect().getWidth() + 1, 0, outlineColor, -LLPANEL_BORDER_WIDTH, FALSE); - LLUI::setLineWidth(1.f); - } - // update tearoff button for torn off floaters // when last host goes away if (mCanTearOff && !getHost()) @@ -1511,6 +1686,7 @@ 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); @@ -1524,6 +1700,7 @@ void LLFloater::setCanMinimize(BOOL can_minimize) void LLFloater::setCanClose(BOOL can_close) { + mCanClose = can_close; mButtonsEnabled[BUTTON_CLOSE] = can_close; updateButtons(); @@ -1538,83 +1715,10 @@ void LLFloater::setCanTearOff(BOOL can_tear_off) } -void LLFloater::setCanResize(BOOL can_resize) +void LLFloater::setCanResize(BOOL can_resize) { - if (mResizable && !can_resize) - { - for (S32 i = 0; i < 4; i++) - { - removeChild(mResizeBar[i], TRUE); - mResizeBar[i] = NULL; - - removeChild(mResizeHandle[i], TRUE); - mResizeHandle[i] = NULL; - } - } - else if (!mResizable && can_resize) - { - // Resize bars (sides) - const S32 RESIZE_BAR_THICKNESS = 3; - mResizeBar[0] = new LLResizeBar( - std::string("resizebar_left"), - this, - LLRect( 0, getRect().getHeight(), RESIZE_BAR_THICKNESS, 0), - mMinWidth, S32_MAX, LLResizeBar::LEFT ); - addChild( mResizeBar[0] ); - - mResizeBar[1] = new LLResizeBar( - std::string("resizebar_top"), - this, - LLRect( 0, getRect().getHeight(), getRect().getWidth(), getRect().getHeight() - RESIZE_BAR_THICKNESS), - mMinHeight, S32_MAX, LLResizeBar::TOP ); - addChild( mResizeBar[1] ); - - mResizeBar[2] = new LLResizeBar( - std::string("resizebar_right"), - this, - LLRect( getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0), - mMinWidth, S32_MAX, LLResizeBar::RIGHT ); - addChild( mResizeBar[2] ); - - mResizeBar[3] = new LLResizeBar( - std::string("resizebar_bottom"), - this, - LLRect( 0, RESIZE_BAR_THICKNESS, getRect().getWidth(), 0), - mMinHeight, S32_MAX, LLResizeBar::BOTTOM ); - addChild( mResizeBar[3] ); - - - // Resize handles (corners) - mResizeHandle[0] = new LLResizeHandle( - std::string("Resize Handle"), - LLRect( getRect().getWidth() - RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_HEIGHT, getRect().getWidth(), 0), - mMinWidth, - mMinHeight, - LLResizeHandle::RIGHT_BOTTOM); - addChild(mResizeHandle[0]); - - mResizeHandle[1] = new LLResizeHandle( std::string("resize"), - LLRect( getRect().getWidth() - RESIZE_HANDLE_WIDTH, getRect().getHeight(), getRect().getWidth(), getRect().getHeight() - RESIZE_HANDLE_HEIGHT), - mMinWidth, - mMinHeight, - LLResizeHandle::RIGHT_TOP ); - addChild(mResizeHandle[1]); - - mResizeHandle[2] = new LLResizeHandle( std::string("resize"), - LLRect( 0, RESIZE_HANDLE_HEIGHT, RESIZE_HANDLE_WIDTH, 0 ), - mMinWidth, - mMinHeight, - LLResizeHandle::LEFT_BOTTOM ); - addChild(mResizeHandle[2]); - - mResizeHandle[3] = new LLResizeHandle( std::string("resize"), - LLRect( 0, getRect().getHeight(), RESIZE_HANDLE_WIDTH, getRect().getHeight() - RESIZE_HANDLE_HEIGHT ), - mMinWidth, - mMinHeight, - LLResizeHandle::LEFT_TOP ); - addChild(mResizeHandle[3]); - } mResizable = can_resize; + enableResizeCtrls(can_resize); } void LLFloater::setCanDrag(BOOL can_drag) @@ -1633,17 +1737,37 @@ void LLFloater::setCanDrag(BOOL can_drag) void LLFloater::updateButtons() { + static LLUICachedControl<S32> floater_close_box_size ("UIFloaterCloseBoxSize", 0); + static LLUICachedControl<S32> close_box_from_top ("UICloseBoxFromTop", 0); S32 button_count = 0; for (S32 i = 0; i < BUTTON_COUNT; i++) { - if(!mButtons[i]) continue; - mButtons[i]->setEnabled(mButtonsEnabled[i]); + if (!mButtons[i]) + { + continue; + } - if (mButtonsEnabled[i] - //*HACK: always render close button for hosted floaters - // so that users don't accidentally hit the button when closing multiple windows - // in the chatterbox - || (i == BUTTON_CLOSE && mButtonScale != 1.f)) + 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++; @@ -1652,17 +1776,17 @@ void LLFloater::updateButtons() { btn_rect.setLeftTopAndSize( LLPANEL_BORDER_WIDTH, - getRect().getHeight() - CLOSE_BOX_FROM_TOP - (LLFLOATER_CLOSE_BOX_SIZE + 1) * button_count, - llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), - llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + getRect().getHeight() - close_box_from_top - (floater_close_box_size + 1) * button_count, + llround((F32)floater_close_box_size * mButtonScale), + llround((F32)floater_close_box_size * mButtonScale)); } else { btn_rect.setLeftTopAndSize( - getRect().getWidth() - LLPANEL_BORDER_WIDTH - (LLFLOATER_CLOSE_BOX_SIZE + 1) * button_count, - getRect().getHeight() - CLOSE_BOX_FROM_TOP, - llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), - llround((F32)LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + getRect().getWidth() - LLPANEL_BORDER_WIDTH - (floater_close_box_size + 1) * button_count, + getRect().getHeight() - close_box_from_top, + llround((F32)floater_close_box_size * mButtonScale), + llround((F32)floater_close_box_size * mButtonScale)); } mButtons[i]->setRect(btn_rect); @@ -1670,56 +1794,65 @@ void LLFloater::updateButtons() // 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 if (mButtons[i]) + else { mButtons[i]->setVisible(FALSE); } } if (mDragHandle) - mDragHandle->setMaxTitleWidth(getRect().getWidth() - (button_count * (LLFLOATER_CLOSE_BOX_SIZE + 1))); + mDragHandle->setMaxTitleWidth(getRect().getWidth() - (button_count * (floater_close_box_size + 1))); } -void LLFloater::buildButtons() +void LLFloater::buildButtons(const Params& floater_params) { + static LLUICachedControl<S32> floater_close_box_size ("UIFloaterCloseBoxSize", 0); + static LLUICachedControl<S32> 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 - (LLFLOATER_CLOSE_BOX_SIZE + 1) * (i + 1), - llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), - llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); + getRect().getHeight() - close_box_from_top - (floater_close_box_size + 1) * (i + 1), + llround(floater_close_box_size * mButtonScale), + llround(floater_close_box_size * mButtonScale)); } else { btn_rect.setLeftTopAndSize( - getRect().getWidth() - LLPANEL_BORDER_WIDTH - (LLFLOATER_CLOSE_BOX_SIZE + 1) * (i + 1), - getRect().getHeight() - CLOSE_BOX_FROM_TOP, - llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale), - llround(LLFLOATER_CLOSE_BOX_SIZE * mButtonScale)); - } - - LLButton* buttonp = new LLButton( - sButtonNames[i], - btn_rect, - sButtonActiveImageNames[i], - sButtonPressedImageNames[i], - LLStringUtil::null, - sButtonCallbacks[i], - this, - LLFontGL::getFontSansSerif()); - - buttonp->setTabStop(FALSE); - buttonp->setFollowsTop(); - buttonp->setFollowsRight(); - buttonp->setToolTip( sButtonToolTips[i] ); - buttonp->setImageColor(LLUI::sColorsGroup->getColor("FloaterButtonImageColor")); - buttonp->setHoverImages(sButtonPressedImageNames[i], - sButtonPressedImageNames[i]); - buttonp->setScaleImage(TRUE); - buttonp->setSaveToXML(false); + getRect().getWidth() - LLPANEL_BORDER_WIDTH - (floater_close_box_size + 1) * (i + 1), + getRect().getHeight() - close_box_from_top, + llround(floater_close_box_size * mButtonScale), + llround(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); + p.scale_image(true); + p.chrome(true); + + LLButton* buttonp = LLUICtrlFactory::create<LLButton>(p); addChild(buttonp); mButtons[i] = buttonp; } @@ -1727,16 +1860,66 @@ void LLFloater::buildButtons() updateButtons(); } +// 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) +{ + // TODO: per-floater localizable tooltips set in XML + return sButtonToolTips[e]; +} + ///////////////////////////////////////////////////// // LLFloaterView -LLFloaterView::LLFloaterView( const std::string& name, const LLRect& rect ) -: LLUICtrl( name, rect, FALSE, NULL, NULL, FOLLOWS_ALL ), +static LLDefaultChildRegistry::Register<LLFloaterView> r("floater_view"); + +LLFloaterView::LLFloaterView (const Params& p) +: LLUICtrl (p), mFocusCycleMode(FALSE), mSnapOffsetBottom(0) + ,mSnapOffsetRight(0) { - setTabStop(FALSE); - resetStartingFloaterPosition(); } // By default, adjust vertical. @@ -1760,41 +1943,50 @@ void LLFloaterView::reshapeFloater(S32 width, S32 height, BOOL called_from_paren // dependents use same follow flags as their "dependee" continue; } - LLRect r = floaterp->getRect(); - - // Compute absolute distance from each edge of screen - S32 left_offset = llabs(r.mLeft - 0); - S32 right_offset = llabs(old_width - r.mRight); - - S32 top_offset = llabs(old_height - r.mTop); - S32 bottom_offset = llabs(r.mBottom - 0); // Make if follow the edge it is closest to U32 follow_flags = 0x0; - if (left_offset < right_offset) + if (floaterp->isMinimized()) { - follow_flags |= FOLLOWS_LEFT; + follow_flags |= (FOLLOWS_LEFT | FOLLOWS_TOP); } else { - follow_flags |= FOLLOWS_RIGHT; - } + LLRect r = floaterp->getRect(); - // "No vertical adjustment" usually means that the bottom of the view - // has been pushed up or down. Hence we want the floaters to follow - // the top. - if (!adjust_vertical) - { - follow_flags |= FOLLOWS_TOP; - } - else if (top_offset < bottom_offset) - { - follow_flags |= FOLLOWS_TOP; - } - else - { - follow_flags |= FOLLOWS_BOTTOM; + // Compute absolute distance from each edge of screen + S32 left_offset = llabs(r.mLeft - 0); + S32 right_offset = llabs(old_width - r.mRight); + + S32 top_offset = llabs(old_height - r.mTop); + S32 bottom_offset = llabs(r.mBottom - 0); + + + if (left_offset < right_offset) + { + follow_flags |= FOLLOWS_LEFT; + } + else + { + follow_flags |= FOLLOWS_RIGHT; + } + + // "No vertical adjustment" usually means that the bottom of the view + // has been pushed up or down. Hence we want the floaters to follow + // the top. + if (!adjust_vertical) + { + follow_flags |= FOLLOWS_TOP; + } + else if (top_offset < bottom_offset) + { + follow_flags |= FOLLOWS_TOP; + } + else + { + follow_flags |= FOLLOWS_BOTTOM; + } } floaterp->setFollows(follow_flags); @@ -1830,74 +2022,11 @@ void LLFloaterView::restoreAll() } -void LLFloaterView::getNewFloaterPosition(S32* left,S32* top) -{ - // Workaround: mRect may change between when this object is created and the first time it is used. - static BOOL first = TRUE; - if( first ) - { - resetStartingFloaterPosition(); - first = FALSE; - } - - const S32 FLOATER_PAD = 16; - LLCoordWindow window_size; - getWindow()->getSize(&window_size); - LLRect full_window(0, window_size.mY, window_size.mX, 0); - LLRect floater_creation_rect( - 160, - full_window.getHeight() - 2 * MENU_BAR_HEIGHT, - full_window.getWidth() * 2 / 3, - 130 ); - floater_creation_rect.stretch( -FLOATER_PAD ); - - *left = mNextLeft; - *top = mNextTop; - - const S32 STEP = 25; - S32 bottom = floater_creation_rect.mBottom + 2 * STEP; - S32 right = floater_creation_rect.mRight - 4 * STEP; - - mNextTop -= STEP; - mNextLeft += STEP; - - if( (mNextTop < bottom ) || (mNextLeft > right) ) - { - mColumn++; - mNextTop = floater_creation_rect.mTop; - mNextLeft = STEP * mColumn; - - if( (mNextTop < bottom) || (mNextLeft > right) ) - { - // Advancing the column didn't work, so start back at the beginning - resetStartingFloaterPosition(); - } - } -} - -void LLFloaterView::resetStartingFloaterPosition() -{ - const S32 FLOATER_PAD = 16; - LLCoordWindow window_size; - getWindow()->getSize(&window_size); - LLRect full_window(0, window_size.mY, window_size.mX, 0); - LLRect floater_creation_rect( - 160, - full_window.getHeight() - 2 * MENU_BAR_HEIGHT, - full_window.getWidth() * 2 / 3, - 130 ); - floater_creation_rect.stretch( -FLOATER_PAD ); - - mNextLeft = floater_creation_rect.mLeft; - mNextTop = floater_creation_rect.mTop; - mColumn = 0; -} - LLRect LLFloaterView::findNeighboringPosition( LLFloater* reference_floater, LLFloater* neighbor ) { LLRect base_rect = reference_floater->getRect(); - S32 width = neighbor->getRect().getWidth(); - S32 height = neighbor->getRect().getHeight(); + LLRect::tCoordType width = neighbor->getRect().getWidth(); + LLRect::tCoordType height = neighbor->getRect().getHeight(); LLRect new_rect = neighbor->getRect(); LLRect expanded_base_rect = base_rect; @@ -1910,16 +2039,16 @@ LLRect LLFloaterView::findNeighboringPosition( LLFloater* reference_floater, LLF if (sibling && sibling != neighbor && sibling->getVisible() && - expanded_base_rect.rectInRect(&sibling->getRect())) + expanded_base_rect.overlaps(sibling->getRect())) { base_rect.unionWith(sibling->getRect()); } } - S32 left_margin = llmax(0, base_rect.mLeft); - S32 right_margin = llmax(0, getRect().getWidth() - base_rect.mRight); - S32 top_margin = llmax(0, getRect().getHeight() - base_rect.mTop); - S32 bottom_margin = llmax(0, base_rect.mBottom); + 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 @@ -2038,6 +2167,11 @@ void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus) 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); + } } } @@ -2104,16 +2238,19 @@ void LLFloaterView::focusFrontFloater() void LLFloaterView::getMinimizePosition(S32 *left, S32 *bottom) { - S32 col = 0; + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + static LLUICachedControl<S32> minimized_width ("UIMinimizedWidth", 0); LLRect snap_rect_local = getLocalSnapRect(); - for(S32 row = snap_rect_local.mBottom; - row < snap_rect_local.getHeight() - LLFLOATER_HEADER_SIZE; - row += LLFLOATER_HEADER_SIZE ) //loop rows - { - for(col = snap_rect_local.mLeft; - col < snap_rect_local.getWidth() - MINIMIZED_WIDTH; - col += MINIMIZED_WIDTH) + 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(); @@ -2124,10 +2261,10 @@ void LLFloaterView::getMinimizePosition(S32 *left, S32 *bottom) if(floater->isMinimized()) { LLRect r = floater->getRect(); - if((r.mBottom < (row + LLFLOATER_HEADER_SIZE)) - && (r.mBottom > (row - LLFLOATER_HEADER_SIZE)) - && (r.mLeft < (col + MINIMIZED_WIDTH)) - && (r.mLeft > (col - MINIMIZED_WIDTH))) + 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 @@ -2179,7 +2316,7 @@ void LLFloaterView::closeAllChildren(bool app_quitting) // dialogs to appear. if (floaterp->canClose() && !floaterp->isDead()) { - floaterp->close(app_quitting); + floaterp->closeFloater(app_quitting); } } } @@ -2202,14 +2339,13 @@ BOOL LLFloaterView::allChildrenClosed() return true; } - void LLFloaterView::refresh() { // 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 = (LLFloater*)*child_it; - if( floaterp->getVisible() ) + LLFloater* floaterp = dynamic_cast<LLFloater*>(*child_it); + if (floaterp && floaterp->getVisible() ) { // minimized floaters are kept fully onscreen adjustToFitScreen(floaterp, !floaterp->isMinimized()); @@ -2224,12 +2360,12 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out // floater is hosted elsewhere, so ignore return; } - S32 screen_width = getSnapRect().getWidth(); - S32 screen_height = getSnapRect().getHeight(); - // convert to local coordinate frame - LLRect snap_rect_local = getLocalSnapRect(); + LLRect::tCoordType screen_width = getSnapRect().getWidth(); + LLRect::tCoordType screen_height = getSnapRect().getHeight(); - if( floater->isResizable() ) + + // only automatically resize non-minimized, resizable floaters + if( floater->isResizable() && !floater->isMinimized() ) { LLRect view_rect = floater->getRect(); S32 old_width = view_rect.getWidth(); @@ -2252,21 +2388,18 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out 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->reshape( new_width, new_height, TRUE ); - if (floater->followsRight()) - { - floater->translate(old_width - new_width, 0); - } + floater->setRect(new_rect); - if (floater->followsTop()) - { - floater->translate(0, old_height - new_height); - } + floater->translateIntoRect( getLocalRect(), false ); } } // move window fully onscreen - if (floater->translateIntoRect( snap_rect_local, allow_partial_outside )) + if (floater->translateIntoRect( getLocalRect(), allow_partial_outside )) { floater->clearSnapTarget(); } @@ -2302,11 +2435,12 @@ LLRect LLFloaterView::getSnapRect() const { LLRect snap_rect = getRect(); snap_rect.mBottom += mSnapOffsetBottom; + snap_rect.mRight -= mSnapOffsetRight; return snap_rect; } -LLFloater *LLFloaterView::getFocusedFloater() +LLFloater *LLFloaterView::getFocusedFloater() const { for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) { @@ -2319,7 +2453,7 @@ LLFloater *LLFloaterView::getFocusedFloater() return NULL; } -LLFloater *LLFloaterView::getFrontmost() +LLFloater *LLFloaterView::getFrontmost() const { for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) { @@ -2332,7 +2466,7 @@ LLFloater *LLFloaterView::getFrontmost() return NULL; } -LLFloater *LLFloaterView::getBackmost() +LLFloater *LLFloaterView::getBackmost() const { LLFloater* back_most = NULL; for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) @@ -2348,18 +2482,51 @@ LLFloater *LLFloaterView::getBackmost() void LLFloaterView::syncFloaterTabOrder() { - // bring focused floater to front - for ( child_list_const_reverse_iter_t child_it = getChildList()->rbegin(); child_it != getChildList()->rend(); ++child_it) + // look for a visible modal dialog, starting from first (should be only one) + LLModalDialog* modal_dialog = NULL; + for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) { - LLFloater* floaterp = (LLFloater*)*child_it; - if (gFocusMgr.childHasKeyboardFocus(floaterp)) + LLModalDialog* dialog = dynamic_cast<LLModalDialog*>(*child_it); + if (dialog && dialog->isModal() && dialog->getVisible()) { - bringToFront(floaterp, FALSE); + modal_dialog = dialog; break; } } - // then sync draw order to tab order + if (modal_dialog) + { + // If we have a visible modal dialog, make sure that it has focus + if( gFocusMgr.getTopCtrl() != modal_dialog ) + { + gFocusMgr.setTopCtrl( 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 = (LLFloater*)*child_it; + if (gFocusMgr.childHasKeyboardFocus(floaterp)) + { + bringToFront(floaterp, FALSE); + break; + } + } + } + + // sync draw order to tab order for ( child_list_const_reverse_iter_t child_it = getChildList()->rbegin(); child_it != getChildList()->rend(); ++child_it) { LLFloater* floaterp = (LLFloater*)*child_it; @@ -2367,7 +2534,7 @@ void LLFloaterView::syncFloaterTabOrder() } } -LLFloater* LLFloaterView::getParentFloater(LLView* viewp) +LLFloater* LLFloaterView::getParentFloater(LLView* viewp) const { LLView* parentp = viewp->getParent(); @@ -2415,8 +2582,12 @@ void LLFloaterView::pushVisibleAll(BOOL visible, const skip_list_t& skip_list) void LLFloaterView::popVisibleAll(const skip_list_t& skip_list) { - for (child_list_const_iter_t child_iter = getChildList()->begin(); - child_iter != getChildList()->end(); ++child_iter) + // 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()) @@ -2426,641 +2597,211 @@ void LLFloaterView::popVisibleAll(const skip_list_t& skip_list) } } -// -// LLMultiFloater -// - -LLMultiFloater::LLMultiFloater() : - mTabContainer(NULL), - mTabPos(LLTabContainer::TOP), - mAutoResize(TRUE), - mOrigMinWidth(0), - mOrigMinHeight(0) -{ - -} - -LLMultiFloater::LLMultiFloater(LLTabContainer::TabPosition tab_pos) : - mTabContainer(NULL), - mTabPos(tab_pos), - mAutoResize(TRUE), - mOrigMinWidth(0), - mOrigMinHeight(0) -{ - -} - -LLMultiFloater::LLMultiFloater(const std::string &name) : - LLFloater(name), - mTabContainer(NULL), - mTabPos(LLTabContainer::TOP), - mAutoResize(FALSE), - mOrigMinWidth(0), - mOrigMinHeight(0) -{ -} - -LLMultiFloater::LLMultiFloater( - const std::string& name, - const LLRect& rect, - LLTabContainer::TabPosition tab_pos, - BOOL auto_resize) : - LLFloater(name, rect, name), - mTabContainer(NULL), - mTabPos(LLTabContainer::TOP), - mAutoResize(auto_resize), - mOrigMinWidth(0), - mOrigMinHeight(0) -{ - mTabContainer = new LLTabContainer(std::string("Preview Tabs"), - LLRect(LLPANEL_BORDER_WIDTH, getRect().getHeight() - LLFLOATER_HEADER_SIZE, getRect().getWidth() - LLPANEL_BORDER_WIDTH, 0), - mTabPos, - FALSE, - FALSE); - mTabContainer->setFollowsAll(); - if (isResizable()) - { - mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); - } - - addChild(mTabContainer); -} - -LLMultiFloater::LLMultiFloater( - const std::string& name, - const std::string& rect_control, - LLTabContainer::TabPosition tab_pos, - BOOL auto_resize) : - LLFloater(name, rect_control, name), - mTabContainer(NULL), - mTabPos(tab_pos), - mAutoResize(auto_resize), - mOrigMinWidth(0), - mOrigMinHeight(0) -{ - mTabContainer = new LLTabContainer(std::string("Preview Tabs"), - LLRect(LLPANEL_BORDER_WIDTH, getRect().getHeight() - LLFLOATER_HEADER_SIZE, getRect().getWidth() - LLPANEL_BORDER_WIDTH, 0), - mTabPos, - FALSE, - FALSE); - mTabContainer->setFollowsAll(); - if (isResizable() && mTabPos == LLTabContainer::BOTTOM) - { - mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); - } - - addChild(mTabContainer); - -} - - -void LLMultiFloater::open() /* Flawfinder: ignore */ -{ - if (mTabContainer->getTabCount() > 0) - { - LLFloater::open(); /* Flawfinder: ignore */ - } - else - { - // for now, don't allow multifloaters - // without any child floaters - close(); - } -} - -void LLMultiFloater::onClose(bool app_quitting) -{ - if(closeAllFloaters() == TRUE) - { - LLFloater::onClose(app_quitting); - }//else not all tabs could be closed... -} - -void LLMultiFloater::draw() +void LLFloater::setInstanceName(const std::string& name) { - if (mTabContainer->getTabCount() == 0) - { - //RN: could this potentially crash in draw hierarchy? - close(); - } - else - { - for (S32 i = 0; i < mTabContainer->getTabCount(); i++) - { - LLFloater* floaterp = (LLFloater*)mTabContainer->getPanelByIndex(i); - if (floaterp->getShortTitle() != mTabContainer->getPanelTitle(i)) - { - mTabContainer->setPanelTitle(i, floaterp->getShortTitle()); - } - } - 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->close(); - 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) -{ - S32 new_width = llmax(getRect().getWidth(), content_width + LLPANEL_BORDER_WIDTH * 2); - S32 new_height = llmax(getRect().getHeight(), content_height + LLFLOATER_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) - { - llerrs << "Tab Container used without having been initialized." << llendl; + if (name == mInstanceName) 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()) + llassert_always(mInstanceName.empty()); + mInstanceName = name; + if (!mInstanceName.empty()) { - // floaterp is hosted by somebody else and - // this is adding it, so remove it from it's 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(); - - // remove minimize and close buttons - floaterp->setCanMinimize(FALSE); - floaterp->setCanResize(FALSE); - floaterp->setCanDrag(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(floaterp, floaterp->getShortTitle(), FALSE, onTabSelected, this, 0, FALSE, 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); - } -} - -/** - 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) -{ - // 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); - } -} - -void LLMultiFloater::removeFloater(LLFloater* floaterp) -{ - if ( 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); - if (!floater_data.mCanResize) + // save_rect and save_visibility only apply to registered floaters + if (!mRectControl.empty()) { - // restore original size - floaterp->reshape(floater_data.mWidth, floater_data.mHeight); + mRectControl = LLFloaterReg::declareRectControl(mInstanceName); } - 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 - close(); - } -} - -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) + if (!mVisibilityControl.empty()) { - cur_floaterp->setVisible(visible); + mVisibilityControl = LLFloaterReg::declareVisibilityControl(mInstanceName); } - - // if no tab selected, and we're being shown, - // select last tab to be added - if (visible && !cur_floaterp) + if(!mDocStateControl.empty()) { - mTabContainer->selectLastTab(); + mDocStateControl = LLFloaterReg::declareDockStateControl(mInstanceName); } - } -} -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->close(); - } - return TRUE; } - - return LLFloater::handleKeyHere(key, mask); } -LLFloater* LLMultiFloater::getActiveFloater() +void LLFloater::setKey(const LLSD& newkey) { - return (LLFloater*)mTabContainer->getCurrentPanel(); + // Note: We don't have to do anything special with registration when we change keys + mKey = newkey; } -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) +//static +void LLFloater::setupParamsForExport(Params& p, LLView* parent) { - if ( floaterp && floaterp->getHost() == this ) - return mTabContainer->getTabPanelFlashing(floaterp); + // Do rectangle munging to topleft layout first + LLPanel::setupParamsForExport(p, parent); - return FALSE; -} + // Copy the rectangle out to apply layout constraints + LLRect rect = p.rect; -/** - BOOL setFloaterFlashing(LLFloater* floaterp, BOOL flashing) + // Null out other settings + p.rect.left.setProvided(false); + p.rect.top.setProvided(false); + p.rect.right.setProvided(false); + p.rect.bottom.setProvided(false); - 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. + // Explicitly set width/height + p.rect.width.set( rect.getWidth(), true ); + p.rect.height.set( rect.getHeight(), true ); - Requires: floaterp != NULL -**/ -void LLMultiFloater::setFloaterFlashing(LLFloater* floaterp, BOOL flashing) -{ - if ( floaterp && floaterp->getHost() == this ) - mTabContainer->setTabPanelFlashing(floaterp, flashing); + // 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); + } } -//static -void LLMultiFloater::onTabSelected(void* userdata, bool from_click) +void LLFloater::initFromParams(const LLFloater::Params& p) { - LLMultiFloater* floaterp = (LLMultiFloater*)userdata; + // *NOTE: We have too many classes derived from LLFloater to retrofit them + // all to pass in params via constructors. So we use this method. - floaterp->tabOpen((LLFloater*)floaterp->mTabContainer->getCurrentPanel(), from_click); -} + // control_name, tab_stop, focus_lost_callback, initial_value, rect, enabled, visible + LLPanel::initFromParams(p); -void LLMultiFloater::setCanResize(BOOL can_resize) -{ - LLFloater::setCanResize(can_resize); - if (isResizable() && mTabContainer->getTabPosition() == LLTabContainer::BOTTOM) - { - mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); - } - else - { - mTabContainer->setRightTabBtnOffset(0); - } -} + mTitle = p.title; + mShortTitle = p.short_title; + applyTitle(); -BOOL LLMultiFloater::postBuild() -{ - // remember any original xml minimum size - getResizeLimits(&mOrigMinWidth, &mOrigMinHeight); + 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; + mAutoTile = p.auto_tile; - if (mTabContainer) + if (p.save_rect) { - return TRUE; + mRectControl = "t"; // flag to build mRectControl name once mInstanceName is set } - - requires<LLTabContainer>("Preview Tabs"); - if (checkRequirements()) + if (p.save_visibility) { - mTabContainer = getChild<LLTabContainer>("Preview Tabs"); - return TRUE; + mVisibilityControl = "t"; // flag to build mVisibilityControl name once mInstanceName is set } - return FALSE; -} - -void LLMultiFloater::updateResizeLimits() -{ - // initialize minimum size constraint to the original xml values. - S32 new_min_width = mOrigMinWidth; - S32 new_min_height = mOrigMinHeight; - // possibly increase minimum size constraint due to children's minimums. - for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) + if(p.save_dock_state) { - 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() + LLFLOATER_HEADER_SIZE + TABCNTR_HEADER_HEIGHT); - } + mDocStateControl = "t"; // flag to build mDocStateControl name once mInstanceName is set } - 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()) + + // open callback + if (p.open_callback.isProvided()) { - const LLRect& expanded = getExpandedRect(); - LLRect newrect; - newrect.setLeftTopAndSize(expanded.mLeft, expanded.mTop, llmax(expanded.getWidth(), new_width), llmax(expanded.getHeight(), new_height)); - setExpandedRect(newrect); + mOpenSignal.connect(initCommitCallback(p.open_callback)); } - else + // close callback + if (p.close_callback.isProvided()) { - 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); + mCloseSignal.connect(initCommitCallback(p.close_callback)); } } -// virtual -LLXMLNodePtr LLFloater::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLPanel::getXML(); - - node->createChild("title", TRUE)->setStringValue(getCurrentTitle()); - - node->createChild("can_resize", TRUE)->setBoolValue(isResizable()); - - node->createChild("can_minimize", TRUE)->setBoolValue(isMinimizeable()); - - node->createChild("can_close", TRUE)->setBoolValue(isCloseable()); - - node->createChild("can_drag_on_left", TRUE)->setBoolValue(isDragOnLeft()); - - node->createChild("min_width", TRUE)->setIntValue(getMinWidth()); - - node->createChild("min_height", TRUE)->setIntValue(getMinHeight()); +LLFastTimer::DeclareTimer POST_BUILD("Floater Post Build"); - node->createChild("can_tear_off", TRUE)->setBoolValue(mCanTearOff); - - return node; -} - -// static -LLView* LLFloater::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +bool LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node) { - std::string name("floater"); - node->getAttributeString("name", name); - - LLFloater *floaterp = new LLFloater(name); + Params params(LLUICtrlFactory::getDefaultParams<LLFloater>()); + LLXUIParser::instance().readXUI(node, params); // *TODO: Error checking - std::string filename; - node->getAttributeString("filename", filename); - - if (filename.empty()) + if (output_node) { - // Load from node - floaterp->initFloaterXML(node, parent, factory); + Params output_params(params); + setupParamsForExport(output_params, parent); + Params default_params(LLUICtrlFactory::getDefaultParams<LLFloater>()); + output_node->setName(node->getName()->mString); + LLXUIParser::instance().writeXUI( + output_node, output_params, &default_params); } - else + + // 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()) { - // Load from file - factory->buildFloater(floaterp, filename); + params.rect.top.set(0); } - - return floaterp; -} - -void LLFloater::initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory, BOOL open) /* Flawfinder: ignore */ -{ - std::string name(getName()); - std::string title(getCurrentTitle()); - std::string short_title(getShortTitle()); - std::string rect_control(""); - BOOL resizable = isResizable(); - S32 min_width = getMinWidth(); - S32 min_height = getMinHeight(); - BOOL drag_on_left = isDragOnLeft(); - BOOL minimizable = isMinimizeable(); - BOOL close_btn = isCloseable(); - LLRect rect; - - node->getAttributeString("name", name); - node->getAttributeString("title", title); - node->getAttributeString("short_title", short_title); - node->getAttributeString("rect_control", rect_control); - node->getAttributeBOOL("can_resize", resizable); - node->getAttributeBOOL("can_minimize", minimizable); - node->getAttributeBOOL("can_close", close_btn); - node->getAttributeBOOL("can_drag_on_left", drag_on_left); - node->getAttributeS32("min_width", min_width); - node->getAttributeS32("min_height", min_height); - - if (! rect_control.empty()) + if (!params.rect.left.isProvided() && !params.rect.right.isProvided()) { - setRectControl(rect_control); + params.rect.left.set(0); } - createRect(node, rect, parent, LLRect()); + params.from_xui = true; + applyXUILayout(params, parent); + initFromParams(params); - setRect(rect); - setName(name); - - initFloater(title, - resizable, - min_width, - min_height, - drag_on_left, - minimizable, - close_btn); - - setTitle(title); - applyTitle (); - - setShortTitle(short_title); - - BOOL can_tear_off; - if (node->getAttributeBOOL("can_tear_off", can_tear_off)) - { - setCanTearOff(can_tear_off); - } + initFloater(params); - initFromXML(node, parent); - LLMultiFloater* last_host = LLFloater::getFloaterHost(); if (node->hasName("multi_floater")) { LLFloater::setFloaterHost((LLMultiFloater*) this); } - initChildrenXML(node, factory); + 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; - BOOL result = postBuild(); + // This will also update drag handle, title bar, close box, etc. + setRect(rect); + } - if (!result) + BOOL result; { - llerrs << "Failed to construct floater " << name << llendl; + LLFastTimer ft(POST_BUILD); + + result = postBuild(); } - applyRectControl(); - if (open) /* Flawfinder: ignore */ + if (!result) { - this->open(); /* Flawfinder: ignore */ + llerrs << "Failed to construct floater " << getName() << llendl; } + 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(); +} + +/* static */ +bool LLFloater::isShown(const LLFloater* floater) +{ + return floater && floater->isShown(); } diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 0e3d148b09..f70495c0f0 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -39,8 +39,7 @@ #include "llpanel.h" #include "lluuid.h" -#include "lltabcontainer.h" -#include "llnotifications.h" +//#include "llnotificationsutil.h" #include <set> class LLDragHandle; @@ -50,17 +49,10 @@ class LLButton; class LLMultiFloater; class LLFloater; -const S32 LLFLOATER_VPAD = 6; -const S32 LLFLOATER_HPAD = 6; -const S32 LLFLOATER_CLOSE_BOX_SIZE = 16; -const S32 LLFLOATER_HEADER_SIZE = 18; const BOOL RESIZE_YES = TRUE; const BOOL RESIZE_NO = FALSE; -const S32 DEFAULT_MIN_WIDTH = 100; -const S32 DEFAULT_MIN_HEIGHT = 100; - const BOOL DRAG_ON_TOP = FALSE; const BOOL DRAG_ON_LEFT = TRUE; @@ -73,78 +65,104 @@ const BOOL CLOSE_NO = FALSE; const BOOL ADJUST_VERTICAL_YES = TRUE; const BOOL ADJUST_VERTICAL_NO = FALSE; -// associates a given notification instance with a particular floater -class LLFloaterNotificationContext : - public LLNotificationContext -{ -public: - LLFloaterNotificationContext(LLHandle<LLFloater> floater_handle) : - mFloaterHandle(floater_handle) - {} - - LLFloater* getFloater() { return mFloaterHandle.get(); } -private: - LLHandle<LLFloater> mFloaterHandle; -}; - - class LLFloater : public LLPanel { friend class LLFloaterView; +friend class LLFloaterReg; +friend class LLMultiFloater; public: - enum EFloaterButtons + struct KeyCompare { - BUTTON_CLOSE, +// 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_EDIT, + BUTTON_DOCK, + BUTTON_HELP, BUTTON_COUNT }; - LLFloater(); - LLFloater(const std::string& name); //simple constructor for data-driven initialization - LLFloater( const std::string& name, const LLRect& rect, const std::string& title, - BOOL resizable = FALSE, - S32 min_width = DEFAULT_MIN_WIDTH, - S32 min_height = DEFAULT_MIN_HEIGHT, - BOOL drag_on_left = FALSE, - BOOL minimizable = TRUE, - BOOL close_btn = TRUE, - BOOL bordered = BORDER_NO); - - LLFloater( const std::string& name, const std::string& rect_control, const std::string& title, - BOOL resizable = FALSE, - S32 min_width = DEFAULT_MIN_WIDTH, - S32 min_height = DEFAULT_MIN_HEIGHT, - BOOL drag_on_left = FALSE, - BOOL minimizable = TRUE, - BOOL close_btn = TRUE, - BOOL bordered = BORDER_NO); + struct Params + : public LLInitParam::Block<Params, LLPanel::Params> + { + Optional<std::string> title, + short_title; + + Optional<bool> single_instance, + auto_tile, + can_resize, + can_minimize, + can_close, + can_drag_on_left, + can_tear_off, + save_rect, + save_visibility, + save_dock_state, + can_dock; + Optional<S32> header_height, + legacy_header_height; // HACK see initFromXML() + + // Images for top-right controls + Optional<LLUIImage*> close_image, + restore_image, + minimize_image, + tear_off_image, + dock_image, + help_image; + Optional<LLUIImage*> close_pressed_image, + restore_pressed_image, + minimize_pressed_image, + tear_off_pressed_image, + dock_pressed_image, + help_pressed_image; + + Optional<CommitCallbackParam> open_callback, + close_callback; + + 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(); - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - void initFloaterXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory, BOOL open = TRUE); + // Don't export top/left for rect, only height/width + static void setupParamsForExport(Params& p, LLView* parent); - /*virtual*/ void userSetShape(const LLRect& new_rect); + void initFromParams(const LLFloater::Params& p); + bool initFloaterXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); + + /*virtual*/ void handleReshape(const LLRect& new_rect, bool by_user = false); /*virtual*/ BOOL canSnapTo(const LLView* other_view); - /*virtual*/ void snappedTo(const LLView* snap_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); - // Can be called multiple times to reset floater parameters. - // Deletes all children of the floater. - virtual void initFloater(const std::string& title, BOOL resizable, - S32 min_width, S32 min_height, BOOL drag_on_left, - BOOL minimizable, BOOL close_btn); + void initFloater(const Params& p); - virtual void open(); /* Flawfinder: ignore */ + void openFloater(const LLSD& key = LLSD()); // If allowed, close the floater cleanly, releasing focus. - // app_quitting is passed to onClose() below. - virtual void close(bool app_quitting = false); + void closeFloater(bool app_quitting = false); /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); @@ -153,18 +171,15 @@ public: // moves to center of gFloaterView void center(); - // applies rectangle stored in mRectControl, if any - void applyRectControl(); - - LLMultiFloater* getHost() { return (LLMultiFloater*)mHostHandle.get(); } + LLMultiFloater* getHost(); void applyTitle(); - const std::string& getCurrentTitle() const; + std::string getCurrentTitle() const; void setTitle( const std::string& title); - std::string getTitle(); + std::string getTitle() const; void setShortTitle( const std::string& short_title ); - std::string getShortTitle(); + std::string getShortTitle() const; void setTitleVisible(bool visible); virtual void setMinimized(BOOL b); void moveResizeHandlesToFront(); @@ -172,7 +187,14 @@ public: void addDependentFloater(LLHandle<LLFloater> dependent_handle, BOOL reposition = TRUE); LLFloater* getDependee() { return (LLFloater*)mDependeeHandle.get(); } void removeDependentFloater(LLFloater* dependent); - BOOL isMinimized() { return mMinimized; } + 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); + BOOL isFirstLook() { return mFirstLook; } // EXT-2653: This function is necessary to prevent overlapping for secondary showed toasts BOOL isFrontmost(); BOOL isDependent() { return !mDependeeHandle.isDead(); } void setCanMinimize(BOOL can_minimize); @@ -185,33 +207,34 @@ public: void setResizeLimits( S32 min_width, S32 min_height ); void getResizeLimits( S32* min_width, S32* min_height ) { *min_width = mMinWidth; *min_height = mMinHeight; } - bool isMinimizeable() const{ return mButtonsEnabled[BUTTON_MINIMIZE]; } - // Does this window have a close button, NOT can we close it right now. - bool isCloseable() const{ return (mButtonsEnabled[BUTTON_CLOSE]); } + 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 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 onOpen() {} - - // Call destroy() to free memory, or setVisible(FALSE) to keep it - // If app_quitting, you might not want to save your visibility. - // Defaults to destroy(). - virtual void onClose(bool app_quitting) { destroy(); } + + 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); + /*virtual*/ void setVisible(BOOL visible); // do not override + /*virtual*/ void handleVisibilityChange ( BOOL new_visibility ); // do not override + void setFrontmost(BOOL take_focus = TRUE); - + // Defaults to false. virtual BOOL canSaveAs() const { return FALSE; } @@ -222,6 +245,18 @@ public: LLHandle<LLFloater> getSnapTarget() const { return mSnappedTo; } LLHandle<LLFloater> getHandle() const { return mHandle; } + const LLSD& getKey() { return mKey; } + 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; } // Return a closeable floater, if any, given the current focus. static LLFloater* getClosableFloaterFromFocus(); @@ -230,69 +265,118 @@ public: // handle refocusing. static void closeFocusedFloater(); - LLNotification::Params contextualNotification(const std::string& name) - { - return LLNotification::Params(name).context(mNotificationContext); - } +// LLNotification::Params contextualNotification(const std::string& name) +// { +// return LLNotification::Params(name).context(mNotificationContext); +// } - static void onClickClose(void *userdata); - static void onClickMinimize(void *userdata); - static void onClickTearOff(void *userdata); - static void onClickEdit(void *userdata); + 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 void setEditModeEnabled(BOOL enable); - static BOOL getEditModeEnabled() { return sEditModeEnabled; } - static LLMultiFloater* getFloaterHost() {return sHostp; } - + static LLMultiFloater* getFloaterHost() {return sHostp; } + protected: + void setRectControl(const std::string& rectname) { mRectControl = rectname; }; + + virtual void applySavedVariables(); + + void applyRectControl(); + void applyDockState(); + 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 setVisibleAndFrontmost(BOOL take_focus=TRUE); - void setExpandedRect(const LLRect& rect) { mExpandedRect = rect; } // size when not minimized + 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 LLDragHandle* getDragHandle() const { return mDragHandle; } - void destroy() { die(); } // Don't call this directly. You probably want to call close(). JC + void destroy() { die(); } // Don't call this directly. You probably want to call closeFloater() private: - void setForeground(BOOL b); // called only by floaterview void cleanupHandles(); // remove handles to dead floaters void createMinimizeButton(); void updateButtons(); - void buildButtons(); - BOOL offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButtons index); + 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); + static std::string getButtonTooltip(const Params& p, EFloaterButton e); + + BOOL offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index); + void addResizeCtrls(); + void layoutResizeCtrls(); + void enableResizeCtrls(bool enable); + void addDragHandle(); + void layoutDragHandle(); // repair layout + +public: + // 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; + +protected: + std::string mRectControl; + std::string mVisibilityControl; + std::string mDocStateControl; + LLSD mKey; // Key used for retrieving instances; set (for now) by LLFLoaterReg - LLRect mExpandedRect; LLDragHandle* mDragHandle; LLResizeBar* mResizeBar[4]; LLResizeHandle* mResizeHandle[4]; - LLButton *mMinimizeButton; + +private: + LLRect mExpandedRect; + + LLUIString mTitle; + LLUIString mShortTitle; + + BOOL mSingleInstance; // TRUE if there is only ever one instance of the floater + std::string mInstanceName; // Store the instance name so we can remove ourselves from the list + BOOL mAutoTile; // TRUE if placement of new instances tiles + BOOL mCanTearOff; + BOOL mCanMinimize; + BOOL mCanClose; + BOOL mDragOnLeft; + BOOL mResizable; + + 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<LLFloater> mDependeeHandle; - std::string mTitle; - std::string mShortTitle; + BOOL mFirstLook; // TRUE if the _next_ time this floater is visible will be the first time in the session that it is visible. - - BOOL mResizable; - S32 mMinWidth; - S32 mMinHeight; - - BOOL mEditing; typedef std::set<LLHandle<LLFloater> > handle_set_t; typedef std::set<LLHandle<LLFloater> >::iterator handle_set_iter_t; handle_set_t mDependents; - bool mDragOnLeft; - BOOL mButtonsEnabled[BUTTON_COUNT]; + bool mButtonsEnabled[BUTTON_COUNT]; LLButton* mButtons[BUTTON_COUNT]; F32 mButtonScale; BOOL mAutoFocus; @@ -301,14 +385,17 @@ private: LLHandle<LLFloater> mHostHandle; LLHandle<LLFloater> mLastHostHandle; + bool mCanDock; + bool mDocked; + bool mTornOff; + static LLMultiFloater* sHostp; - static BOOL sEditModeEnabled; - static std::string sButtonActiveImageNames[BUTTON_COUNT]; - static std::string sButtonInactiveImageNames[BUTTON_COUNT]; - static std::string sButtonPressedImageNames[BUTTON_COUNT]; + static BOOL sQuitting; static std::string sButtonNames[BUTTON_COUNT]; static std::string sButtonToolTips[BUTTON_COUNT]; - typedef void (*click_callback)(void *); + static std::string sButtonToolTipsIndex[BUTTON_COUNT]; + + typedef void(*click_callback)(LLFloater*); static click_callback sButtonCallbacks[BUTTON_COUNT]; typedef std::map<LLHandle<LLFloater>, LLFloater*> handle_map_t; @@ -320,29 +407,30 @@ private: BOOL mHasBeenDraggedWhileMinimized; S32 mPreviousMinimizedBottom; S32 mPreviousMinimizedLeft; - - LLFloaterNotificationContext* mNotificationContext; + +// LLFloaterNotificationContext* mNotificationContext; LLRootHandle<LLFloater> mHandle; }; + ///////////////////////////////////////////////////////////// // LLFloaterView // Parent of all floating panels class LLFloaterView : public LLUICtrl { -public: - LLFloaterView( const std::string& name, const LLRect& rect ); +protected: + LLFloaterView (const Params& p); + friend class LLUICtrlFactory; +public: /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); void reshapeFloater(S32 width, S32 height, BOOL called_from_parent, BOOL adjust_vertical); /*virtual*/ void draw(); /*virtual*/ LLRect getSnapRect() const; - void refresh(); + /*virtual*/ void refresh(); - void getNewFloaterPosition( S32* left, S32* top ); - void resetStartingFloaterPosition(); LLRect findNeighboringPosition( LLFloater* reference_floater, LLFloater* neighbor ); // Given a child of gFloaterView, make sure this view can fit entirely onscreen. @@ -365,10 +453,10 @@ public: void closeAllChildren(bool app_quitting); BOOL allChildrenClosed(); - LLFloater* getFrontmost(); - LLFloater* getBackmost(); - LLFloater* getParentFloater(LLView* viewp); - LLFloater* getFocusedFloater(); + 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 @@ -377,6 +465,7 @@ public: S32 getZOrder(LLFloater* child); void setSnapOffsetBottom(S32 offset) { mSnapOffsetBottom = offset; } + void setSnapOffsetRight(S32 offset) { mSnapOffsetRight = offset; } private: S32 mColumn; @@ -384,110 +473,12 @@ private: S32 mNextTop; BOOL mFocusCycleMode; S32 mSnapOffsetBottom; + S32 mSnapOffsetRight; }; -// https://wiki.lindenlab.com/mediawiki/index.php?title=LLMultiFloater&oldid=81376 -class LLMultiFloater : public LLFloater -{ -public: - LLMultiFloater(); - LLMultiFloater(LLTabContainer::TabPosition tab_pos); - LLMultiFloater(const std::string& name); - LLMultiFloater(const std::string& name, const LLRect& rect, LLTabContainer::TabPosition tab_pos = LLTabContainer::TOP, BOOL auto_resize = TRUE); - LLMultiFloater(const std::string& name, const std::string& rect_control, LLTabContainer::TabPosition tab_pos = LLTabContainer::TOP, BOOL auto_resize = TRUE); - virtual ~LLMultiFloater() {}; - - virtual BOOL postBuild(); - /*virtual*/ void open(); /* Flawfinder: ignore */ - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void draw(); - /*virtual*/ void setVisible(BOOL visible); - /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask); - - 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); - 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; } - static void onTabSelected(void* userdata, bool); - - virtual void updateResizeLimits(); - -protected: - struct LLFloaterData - { - S32 mWidth; - S32 mHeight; - BOOL mCanMinimize; - BOOL mCanResize; - }; - - LLTabContainer* mTabContainer; - - typedef std::map<LLHandle<LLFloater>, LLFloaterData> floater_data_map_t; - floater_data_map_t mFloaterDataMap; - - LLTabContainer::TabPosition mTabPos; - BOOL mAutoResize; - S32 mOrigMinWidth, mOrigMinHeight; // logically const but initialized late -}; - -// visibility policy specialized for floaters -template<> -class VisibilityPolicy<LLFloater> -{ -public: - // visibility methods - static bool visible(LLFloater* instance, const LLSD& key) - { - if (instance) - { - return !instance->isMinimized() && instance->isInVisibleChain(); - } - return FALSE; - } - - static void show(LLFloater* instance, const LLSD& key) - { - if (instance) - { - instance->open(); - if (instance->getHost()) - { - instance->getHost()->open(); - } - } - } - - static void hide(LLFloater* instance, const LLSD& key) - { - if (instance) instance->close(); - } -}; - - -// singleton implementation for floaters (provides visibility policy) -// https://wiki.lindenlab.com/mediawiki/index.php?title=LLFloaterSingleton&oldid=164990 - -template <class T> class LLFloaterSingleton : public LLUISingleton<T, VisibilityPolicy<LLFloater> > -{ -}; +// +// Globals +// extern LLFloaterView* gFloaterView; diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp new file mode 100644 index 0000000000..eb67e3a561 --- /dev/null +++ b/indra/llui/llfloaterreg.cpp @@ -0,0 +1,461 @@ +/** + * @file llfloaterreg.cpp + * @brief LLFloaterReg Floater Registration Class + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llfloaterreg.h" + +#include "llfloater.h" +#include "llmultifloater.h" +#include "llfloaterreglistener.h" + +//******************************************************* + +//static +LLFloaterReg::instance_list_t LLFloaterReg::sNullInstanceList; +LLFloaterReg::instance_map_t LLFloaterReg::sInstanceMap; +LLFloaterReg::build_map_t LLFloaterReg::sBuildMap; +std::map<std::string,std::string> LLFloaterReg::sGroupMap; + +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 +LLRect LLFloaterReg::getFloaterRect(const std::string& name) +{ + LLRect rect; + const std::string& groupname = sGroupMap[name]; + if (!groupname.empty()) + { + instance_list_t& list = sInstanceMap[groupname]; + if (!list.empty()) + { + static LLUICachedControl<S32> floater_offset ("UIFloaterOffset", 16); + LLFloater* last_floater = list.back(); + if (last_floater->getHost()) + { + rect = last_floater->getHost()->getRect(); + } + else + { + rect = last_floater->getRect(); + } + rect.translate(floater_offset, -floater_offset); + } + } + return rect; +} + +//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]; + int index = list.size(); + + res = build_func(key); + + bool success = LLUICtrlFactory::getInstance()->buildFloater(res, xui_file, NULL); + if (!success) + { + llwarns << "Failed to build floater type: '" << name << "'." << llendl; + return NULL; + } + + // Note: key should eventually be a non optional LLFloater arg; for now, set mKey to be safe + res->mKey = key; + res->setInstanceName(name); + res->applySavedVariables(); // Can't apply rect and dock state until setting instance name + if (res->mAutoTile && !res->getHost() && index > 0) + { + const LLRect& cur_rect = res->getRect(); + LLRect next_rect = getFloaterRect(groupname); + next_rect.setLeftTopAndSize(next_rect.mLeft, next_rect.mTop, cur_rect.getWidth(), cur_rect.getHeight()); + res->setRect(next_rect); + res->setRectControl(LLStringUtil::null); // don't save rect of tiled floaters + gFloaterView->adjustToFitScreen(res, true); + } + else + { + gFloaterView->adjustToFitScreen(res, false); + } + list.push_back(res); + } + } + if (!res) + { + llwarns << "Floater type: '" << name << "' not registered." << llendl; + } + } + 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) +{ + 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) + { + // When toggling *visibility*, close the host instead of the floater when hosted + if (instance->getHost()) + instance->getHost()->closeFloater(); + else + instance->closeFloater(); + return true; + } + else + { + return false; + } +} + +//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 (LLFloater::isShown(instance)) + { + // When toggling *visibility*, close the host instead of the floater when hosted + if (instance->getHost()) + instance->getHost()->closeFloater(); + else + instance->closeFloater(); + return false; + } + else + { + return showInstance(name, key, TRUE) ? true : false; + } +} + +//static +// returns true if the instance exists and is visible +bool LLFloaterReg::instanceVisible(const std::string& name, const LLSD& key) +{ + LLFloater* instance = findInstance(name, key); + return LLFloater::isShown(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 (LLUI::sSettingGroups["floater"]->controlExists(controlname)) + { + BOOL isvis = LLUI::sSettingGroups["floater"]->getBOOL(controlname); + if (isvis) + { + showInstance(name, LLSD()); // keyed floaters shouldn't set save_vis to true + } + } + } +} + +//static +void LLFloaterReg::hideVisibleInstances(const std::set<std::string>& 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) +{ + std::string res = std::string("floater_rect_") + name; + LLStringUtil::replaceChar( res, ' ', '_' ); + return res; +} + +//static +std::string LLFloaterReg::declareRectControl(const std::string& name) +{ + std::string controlname = getRectControlName(name); + LLUI::sSettingGroups["floater"]->declareRect(controlname, LLRect(), + llformat("Window Position and Size for %s", name.c_str()), + TRUE); + return controlname; +} + +//static +std::string LLFloaterReg::getVisibilityControlName(const std::string& name) +{ + std::string res = std::string("floater_vis_") + name; + LLStringUtil::replaceChar( res, ' ', '_' ); + return res; +} + +//static +std::string LLFloaterReg::declareVisibilityControl(const std::string& name) +{ + std::string controlname = getVisibilityControlName(name); + LLUI::sSettingGroups["floater"]->declareBOOL(controlname, FALSE, + llformat("Window Visibility for %s", name.c_str()), + TRUE); + return controlname; +} + +//static +std::string LLFloaterReg::declareDockStateControl(const std::string& name) +{ + std::string controlname = getDockStateControlName(name); + LLUI::sSettingGroups["floater"]->declareBOOL(controlname, TRUE, + llformat("Window Docking state for %s", name.c_str()), + TRUE); + 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 (LLUI::sSettingGroups["floater"]->controlExists(getRectControlName(name))) + { + declareRectControl(name); + } + if (LLUI::sSettingGroups["floater"]->controlExists(getVisibilityControlName(name))) + { + declareVisibilityControl(name); + } + } +} + +// Callbacks + +// static +// Call once (i.e use for init callbacks) +void LLFloaterReg::initUICtrlToFloaterVisibilityControl(LLUICtrl* ctrl, const LLSD& sdname) +{ + // Get the visibility control name for the floater + std::string vis_control_name = LLFloaterReg::declareVisibilityControl(sdname.asString()); + // Set the control value to the floater visibility control (Sets the value as well) + ctrl->setControlVariable(LLUI::sSettingGroups["floater"]->getControl(vis_control_name)); +} + +// callback args may use "floatername.key" format +static void parse_name_key(std::string& name, LLSD& key) +{ + std::string instname = name; + std::size_t dotpos = instname.find("."); + if (dotpos != std::string::npos) + { + name = instname.substr(0, dotpos); + key = LLSD(instname.substr(dotpos+1, std::string::npos)); + } +} + +//static +void LLFloaterReg::showFloaterInstance(const LLSD& sdname) +{ + LLSD key; + std::string name = sdname.asString(); + parse_name_key(name, key); + showInstance(name, key, TRUE); +} +//static +void LLFloaterReg::hideFloaterInstance(const LLSD& sdname) +{ + LLSD key; + std::string name = sdname.asString(); + parse_name_key(name, key); + hideInstance(name, key); +} +//static +void LLFloaterReg::toggleFloaterInstance(const LLSD& sdname) +{ + LLSD key; + std::string name = sdname.asString(); + parse_name_key(name, key); + toggleInstance(name, key); +} + +//static +bool LLFloaterReg::floaterInstanceVisible(const LLSD& sdname) +{ + LLSD key; + std::string name = sdname.asString(); + parse_name_key(name, key); + return instanceVisible(name, key); +} + diff --git a/indra/llui/llfloaterreg.h b/indra/llui/llfloaterreg.h new file mode 100644 index 0000000000..634a235926 --- /dev/null +++ b/indra/llui/llfloaterreg.h @@ -0,0 +1,158 @@ +/** + * @file llfloaterreg.h + * @brief LLFloaterReg Floater Registration Class + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#ifndef LLFLOATERREG_H +#define LLFLOATERREG_H + +/// llcommon +#include "llboost.h" +#include "llrect.h" +#include "llstl.h" +#include "llsd.h" + +/// llui +#include "lluictrl.h" + +#include <boost/function.hpp> + +//******************************************************* +// +// Floater Class Registry +// + +class LLFloater; + +typedef boost::function<LLFloater* (const LLSD& key)> 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<LLFloater*> instance_list_t; + typedef const instance_list_t const_instance_list_t; + typedef std::map<std::string, instance_list_t> instance_map_t; + + struct BuildData + { + LLFloaterBuildFunc mFunc; + std::string mFile; + }; + typedef std::map<std::string, BuildData> 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<std::string,std::string> sGroupMap; + +public: + // Registration + + // usage: LLFloaterClassRegistry::add("foo", (LLFloaterBuildFunc)&LLFloaterClassRegistry::build<LLFloaterFoo>); + template <class T> + 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); + + // Helpers + static LLRect getFloaterRect(const std::string& name); + + // 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<std::string>& exceptions = std::set<std::string>()); + static void restoreVisibleInstances(); + + // Control Variables + static std::string getRectControlName(const std::string& name); + static std::string declareRectControl(const std::string& name); + static std::string getVisibilityControlName(const std::string& name); + static std::string declareVisibilityControl(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 initUICtrlToFloaterVisibilityControl(LLUICtrl* ctrl, const LLSD& sdname); + static void showFloaterInstance(const LLSD& sdname); + static void hideFloaterInstance(const LLSD& sdname); + static void toggleFloaterInstance(const LLSD& sdname); + static bool floaterInstanceVisible(const LLSD& sdname); + + // Typed find / get / show + template <class T> + static T* findTypedInstance(const std::string& name, const LLSD& key = LLSD()) + { + return dynamic_cast<T*>(findInstance(name, key)); + } + + template <class T> + static T* getTypedInstance(const std::string& name, const LLSD& key = LLSD()) + { + return dynamic_cast<T*>(getInstance(name, key)); + } + + template <class T> + static T* showTypedInstance(const std::string& name, const LLSD& key = LLSD(), BOOL focus = FALSE) + { + return dynamic_cast<T*>(showInstance(name, key, focus)); + } + +}; + +#endif diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp new file mode 100644 index 0000000000..082d7c1573 --- /dev/null +++ b/indra/llui/llfloaterreglistener.cpp @@ -0,0 +1,130 @@ +/** + * @file llfloaterreglistener.cpp + * @author Nat Goodspeed + * @date 2009-08-12 + * @brief Implementation for llfloaterreglistener. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llfloaterreglistener.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llfloaterreg.h" +#include "llfloater.h" +#include "llbutton.h" + +LLFloaterRegListener::LLFloaterRegListener(): + LLEventAPI("LLFloaterReg", + "LLFloaterReg listener to (e.g.) show/hide LLFloater instances") +{ + add("getBuildMap", + "Return on [\"reply\"] data about all registered LLFloaterReg floater names", + &LLFloaterRegListener::getBuildMap, + LLSD().with("reply", LLSD())); + LLSD requiredName; + requiredName["name"] = LLSD(); + add("showInstance", + "Ask to display the floater specified in [\"name\"]", + &LLFloaterRegListener::showInstance, + requiredName); + add("hideInstance", + "Ask to hide the floater specified in [\"name\"]", + &LLFloaterRegListener::hideInstance, + requiredName); + add("toggleInstance", + "Ask to toggle the state of the floater specified in [\"name\"]", + &LLFloaterRegListener::toggleInstance, + requiredName); + LLSD requiredNameButton; + requiredNameButton["name"] = LLSD(); + requiredNameButton["button"] = LLSD(); + add("clickButton", + "Simulate clicking the named [\"button\"] in the visible floater named in [\"name\"]", + &LLFloaterRegListener::clickButton, + requiredNameButton); +} + +void LLFloaterRegListener::getBuildMap(const LLSD& event) const +{ + // Honor the "reqid" convention by echoing event["reqid"] in our reply packet. + LLReqID reqID(event); + LLSD reply(reqID.makeResponse()); + // Build an LLSD map that mirrors sBuildMap. Since we have no good way to + // represent a C++ callable in LLSD, the only part of BuildData we can + // store is the filename. For each LLSD map entry, it would be more + // extensible to store a nested LLSD map containing a single key "file" -- + // but we don't bother, simply storing the string filename instead. + for (LLFloaterReg::build_map_t::const_iterator mi(LLFloaterReg::sBuildMap.begin()), + mend(LLFloaterReg::sBuildMap.end()); + mi != mend; ++mi) + { + reply[mi->first] = mi->second.mFile; + } + // Send the reply to the LLEventPump named in event["reply"]. + LLEventPumps::instance().obtain(event["reply"]).post(reply); +} + +void LLFloaterRegListener::showInstance(const LLSD& event) const +{ + LLFloaterReg::showInstance(event["name"], event["key"], event["focus"]); +} + +void LLFloaterRegListener::hideInstance(const LLSD& event) const +{ + LLFloaterReg::hideInstance(event["name"], event["key"]); +} + +void LLFloaterRegListener::toggleInstance(const LLSD& event) const +{ + LLFloaterReg::toggleInstance(event["name"], event["key"]); +} + +void LLFloaterRegListener::clickButton(const LLSD& event) const +{ + // If the caller requests a reply, build the reply. + LLReqID reqID(event); + LLSD reply(reqID.makeResponse()); + + LLFloater* floater = LLFloaterReg::findInstance(event["name"], event["key"]); + if (! LLFloater::isShown(floater)) + { + reply["type"] = "LLFloater"; + reply["name"] = event["name"]; + reply["key"] = event["key"]; + reply["error"] = floater? "!isShown()" : "NULL"; + } + else + { + // Here 'floater' points to an LLFloater instance with the specified + // name and key which isShown(). + LLButton* button = floater->findChild<LLButton>(event["button"]); + if (! LLButton::isAvailable(button)) + { + reply["type"] = "LLButton"; + reply["name"] = event["button"]; + reply["error"] = button? "!isAvailable()" : "NULL"; + } + else + { + // Here 'button' points to an isAvailable() LLButton child of + // 'floater' with the specified button name. Pretend to click it. + button->onCommit(); + // Leave reply["error"] isUndefined(): no error, i.e. success. + } + } + + // Send a reply only if caller asked for a reply. + LLSD replyPump(event["reply"]); + if (replyPump.isString()) // isUndefined() if absent + { + LLEventPumps::instance().obtain(replyPump).post(reply); + } +} diff --git a/indra/llui/llfloaterreglistener.h b/indra/llui/llfloaterreglistener.h new file mode 100644 index 0000000000..a38117f6b0 --- /dev/null +++ b/indra/llui/llfloaterreglistener.h @@ -0,0 +1,36 @@ +/** + * @file llfloaterreglistener.h + * @author Nat Goodspeed + * @date 2009-08-12 + * @brief Wrap (subset of) LLFloaterReg API with an event API + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLFLOATERREGLISTENER_H) +#define LL_LLFLOATERREGLISTENER_H + +#include "lleventapi.h" +#include <string> + +class LLSD; + +/// Event API wrapper for LLFloaterReg +class LLFloaterRegListener: public LLEventAPI +{ +public: + /// As all public LLFloaterReg methods are static, there's no point in + /// binding an LLFloaterReg instance. + LLFloaterRegListener(); + +private: + void getBuildMap(const LLSD& event) const; + void showInstance(const LLSD& event) const; + void hideInstance(const LLSD& event) const; + void toggleInstance(const LLSD& event) const; + void clickButton(const LLSD& event) const; +}; + +#endif /* ! defined(LL_LLFLOATERREGLISTENER_H) */ diff --git a/indra/llui/llflyoutbutton.cpp b/indra/llui/llflyoutbutton.cpp new file mode 100644 index 0000000000..abb0b869eb --- /dev/null +++ b/indra/llui/llflyoutbutton.cpp @@ -0,0 +1,83 @@ +/** + * @file llflyoutbutton.cpp + * @brief LLFlyoutButton base class + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +// file includes +#include "llflyoutbutton.h" + +//static LLDefaultChildRegistry::Register<LLFlyoutButton> 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<LLButton>(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 new file mode 100644 index 0000000000..d8c0f1a50d --- /dev/null +++ b/indra/llui/llflyoutbutton.h @@ -0,0 +1,74 @@ +/** + * @file llflyoutbutton.h + * @brief LLFlyoutButton base class + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// 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<Params, LLComboBox::Params> + { + Optional<LLButton::Params> action_button; + Deprecated allow_text_entry; + + Params() + : action_button("action_button"), + allow_text_entry("allow_text_entry") + { + 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 661ffdd467..35fbc7b0a8 100644 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -38,6 +38,89 @@ const F32 FOCUS_FADE_TIME = 0.3f; +// NOTE: the LLFocusableElement implementation has been moved here from lluictrl.cpp. + +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::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent) +{ + 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); +} + + + LLFocusMgr gFocusMgr; LLFocusMgr::LLFocusMgr() @@ -49,7 +132,6 @@ LLFocusMgr::LLFocusMgr() mDefaultKeyboardFocus( NULL ), mKeystrokesOnly(FALSE), mTopCtrl( NULL ), - mFocusWeight(0.f), mAppHasFocus(TRUE) // Macs don't seem to notify us that we've gotten focus, so default to true #ifdef _DEBUG , mMouseCaptorName("none") @@ -87,19 +169,25 @@ void LLFocusMgr::releaseFocusIfNeeded( const LLView* view ) } -void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystrokes_only) +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 && !new_focus->hasAncestor(mLockedView)))) + (new_focus != mLockedView + && dynamic_cast<LLView*>(new_focus) + && !dynamic_cast<LLView*>(new_focus)->hasAncestor(mLockedView)))) { // don't allow focus to go to anything that is not the locked focus // or one of its descendants return; } - //llinfos << "Keyboard focus handled by " << (new_focus ? new_focus->getName() : "nothing") << llendl; - mKeystrokesOnly = keystrokes_only; if( new_focus != mKeyboardFocus ) @@ -107,21 +195,62 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystroke mLastKeyboardFocus = mKeyboardFocus; mKeyboardFocus = new_focus; - if( mLastKeyboardFocus ) + // list of the focus and it's ancestors + view_handle_list_t old_focus_list = 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<LLView*>(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()) { - mLastKeyboardFocus->onFocusLost(); + 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) + { + mCachedKeyboardFocusList.pop_front(); + old_focus_view->onFocusLost(); + } } - // clear out any existing flash - if (new_focus) + // 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) + { + 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) { - mFocusWeight = 0.f; - new_focus->onFocusReceived(); + return; } - mFocusTimer.reset(); #ifdef _DEBUG - mKeyboardFocusName = new_focus ? new_focus->getName() : std::string("none"); + LLUICtrl* focus_ctrl = dynamic_cast<LLUICtrl*>(new_focus); + mKeyboardFocusName = focus_ctrl ? focus_ctrl->getName() : std::string("none"); #endif // If we've got a default keyboard focus, and the caller is @@ -131,8 +260,8 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystroke mDefaultKeyboardFocus->setFocus(TRUE); } - LLView* focus_subtree = mKeyboardFocus; - LLView* viewp = mKeyboardFocus; + LLView* focus_subtree = dynamic_cast<LLView*>(mKeyboardFocus); + LLView* viewp = dynamic_cast<LLView*>(mKeyboardFocus); // find root-most focus root while(viewp) { @@ -146,7 +275,8 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystroke if (focus_subtree) { - mFocusHistory[focus_subtree->getHandle()] = mKeyboardFocus ? mKeyboardFocus->getHandle() : LLHandle<LLView>(); + LLView* focused_view = dynamic_cast<LLView*>(mKeyboardFocus); + mFocusHistory[focus_subtree->getHandle()] = focused_view ? focused_view->getHandle() : LLHandle<LLView>(); } } @@ -154,13 +284,15 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystroke { 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 = mKeyboardFocus; + LLView* focus_view = dynamic_cast<LLView*>(mKeyboardFocus); while( focus_view ) { if( focus_view == parent ) @@ -175,7 +307,7 @@ BOOL LLFocusMgr::childHasKeyboardFocus(const LLView* parent ) const // Returns TRUE is parent or any descedent of parent is the mouse captor. BOOL LLFocusMgr::childHasMouseCapture( const LLView* parent ) const { - if( mMouseCaptor && mMouseCaptor->isView() ) + if( mMouseCaptor && dynamic_cast<LLView*>(mMouseCaptor) != NULL ) { LLView* captor_view = (LLView*)mMouseCaptor; while( captor_view ) @@ -190,7 +322,7 @@ BOOL LLFocusMgr::childHasMouseCapture( const LLView* parent ) const return FALSE; } -void LLFocusMgr::removeKeyboardFocusWithoutCallback( const LLView* focus ) +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 @@ -220,24 +352,19 @@ void LLFocusMgr::setMouseCapture( LLMouseHandler* new_captor ) { LLMouseHandler* old_captor = mMouseCaptor; mMouseCaptor = new_captor; - /* - if (new_captor) + + if (LLView::sDebugMouseHandling) { - if ( new_captor->getName() == "Stickto") + if (new_captor) { llinfos << "New mouse captor: " << new_captor->getName() << llendl; } else { - llinfos << "New mouse captor: " << new_captor->getName() << llendl; + llinfos << "New mouse captor: NULL" << llendl; } } - else - { - llinfos << "New mouse captor: NULL" << llendl; - } - */ - + if( old_captor ) { old_captor->onMouseCaptureLost(); @@ -295,7 +422,7 @@ void LLFocusMgr::setTopCtrl( LLUICtrl* new_top ) if (old_top) { - old_top->onLostTop(); + old_top->onTopLost(); } } } @@ -313,7 +440,7 @@ void LLFocusMgr::removeTopCtrlWithoutCallback( const LLUICtrl* top_view ) void LLFocusMgr::lockFocus() { - mLockedView = mKeyboardFocus; + mLockedView = dynamic_cast<LLUICtrl*>(mKeyboardFocus); } void LLFocusMgr::unlockFocus() @@ -323,12 +450,13 @@ void LLFocusMgr::unlockFocus() F32 LLFocusMgr::getFocusFlashAmt() const { - return clamp_rescale(getFocusTime(), 0.f, FOCUS_FADE_TIME, mFocusWeight, 0.f); + return clamp_rescale(mFocusFlashTimer.getElapsedTimeF32(), 0.f, FOCUS_FADE_TIME, 1.f, 0.f); } LLColor4 LLFocusMgr::getFocusColor() const { - LLColor4 focus_color = lerp(LLUI::sColorsGroup->getColor( "FocusColor" ), LLColor4::white, getFocusFlashAmt()); + 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) { @@ -339,8 +467,7 @@ LLColor4 LLFocusMgr::getFocusColor() const void LLFocusMgr::triggerFocusFlash() { - mFocusTimer.reset(); - mFocusWeight = 1.f; + mFocusFlashTimer.reset(); } void LLFocusMgr::setAppHasFocus(BOOL focus) diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h index aaeb25a870..83ecd1d301 100644 --- a/indra/llui/llfocusmgr.h +++ b/indra/llui/llfocusmgr.h @@ -37,10 +37,44 @@ #include "llstring.h" #include "llframetimer.h" -#include "llview.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<void(LLFocusableElement*)> 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 handleUnicodeChar(llwchar uni_char, BOOL called_from_parent); + +protected: + virtual void onFocusReceived(); + virtual void onFocusLost(); + virtual void onTopLost(); // called when registered as top ctrl and user clicks elsewhere + focus_signal_t* mFocusLostCallback; + focus_signal_t* mFocusReceivedCallback; + focus_signal_t* mFocusChangedCallback; + focus_signal_t* mTopLostCallback; +}; + class LLFocusMgr { @@ -55,15 +89,14 @@ public: BOOL childHasMouseCapture( const LLView* parent ) const; // Keyboard Focus - void setKeyboardFocus(LLUICtrl* new_focus, BOOL lock = FALSE, BOOL keystrokes_only = FALSE); // new_focus = NULL to release the focus. - LLUICtrl* getKeyboardFocus() const { return mKeyboardFocus; } - LLUICtrl* getLastKeyboardFocus() const { return mLastKeyboardFocus; } + 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 LLView* focus ); + void removeKeyboardFocusWithoutCallback( const LLFocusableElement* focus ); BOOL getKeystrokesOnly() { return mKeystrokesOnly; } void setKeystrokesOnly(BOOL keystrokes_only) { mKeystrokesOnly = keystrokes_only; } - F32 getFocusTime() const { return mFocusTimer.getElapsedTimeF32(); } F32 getFocusFlashAmt() const; S32 getFocusFlashWidth() const { return llround(lerp(1.f, 3.f, getFocusFlashAmt())); } LLColor4 getFocusColor() const; @@ -75,8 +108,8 @@ public: // If setKeyboardFocus(NULL) is called, and there is a non-NULL default // keyboard focus view, focus goes there. JC - void setDefaultKeyboardFocus(LLUICtrl* default_focus) { mDefaultKeyboardFocus = default_focus; } - LLUICtrl* getDefaultKeyboardFocus() const { return mDefaultKeyboardFocus; } + void setDefaultKeyboardFocus(LLFocusableElement* default_focus) { mDefaultKeyboardFocus = default_focus; } + LLFocusableElement* getDefaultKeyboardFocus() const { return mDefaultKeyboardFocus; } // Top View @@ -98,16 +131,19 @@ private: LLMouseHandler* mMouseCaptor; // Mouse events are premptively routed to this object // Keyboard Focus - LLUICtrl* mKeyboardFocus; // Keyboard events are preemptively routed to this object - LLUICtrl* mLastKeyboardFocus; // who last had focus - LLUICtrl* mDefaultKeyboardFocus; + LLFocusableElement* mKeyboardFocus; // Keyboard events are preemptively routed to this object + LLFocusableElement* mLastKeyboardFocus; // who last had focus + LLFocusableElement* mDefaultKeyboardFocus; BOOL mKeystrokesOnly; + + // caching list of keyboard focus ancestors for calling onFocusReceived and onFocusLost + typedef std::list<LLHandle<LLView> > view_handle_list_t; + view_handle_list_t mCachedKeyboardFocusList; // Top View LLUICtrl* mTopCtrl; - LLFrameTimer mFocusTimer; - F32 mFocusWeight; + LLFrameTimer mFocusFlashTimer; BOOL mAppHasFocus; diff --git a/indra/llui/llfunctorregistry.cpp b/indra/llui/llfunctorregistry.cpp index 0c5b1655b1..5f9644f258 100644 --- a/indra/llui/llfunctorregistry.cpp +++ b/indra/llui/llfunctorregistry.cpp @@ -31,6 +31,7 @@ * $/LicenseInfo$ **/ +#include "linden_common.h" #include "llfunctorregistry.h" // This is a default functor always resident in the system. diff --git a/indra/llui/llfunctorregistry.h b/indra/llui/llfunctorregistry.h index 8864f7af15..2c0bcc6012 100644 --- a/indra/llui/llfunctorregistry.h +++ b/indra/llui/llfunctorregistry.h @@ -40,7 +40,7 @@ #include <boost/function.hpp> #include "llsd.h" -#include "llmemory.h" +#include "llsingleton.h" /** * @class LLFunctorRegistry diff --git a/indra/llui/llhandle.h b/indra/llui/llhandle.h new file mode 100644 index 0000000000..899f6b9326 --- /dev/null +++ b/indra/llui/llhandle.h @@ -0,0 +1,171 @@ +/** +* @file llhandle.h +* @brief "Handle" to an object (usually a floater) whose lifetime you don't +* control. +* +* $LicenseInfo:firstyear=2001&license=viewergpl$ +* +* Copyright (c) 2001-2009, Linden Research, Inc. +* +* Second Life Viewer Source Code +* The source code in this file ("Source Code") is provided by Linden Lab +* to you under the terms of the GNU General Public License, version 2.0 +* ("GPL"), unless you have obtained a separate licensing agreement +* ("Other License"), formally executed by you and Linden Lab. Terms of +* the GPL can be found in doc/GPL-license.txt in this distribution, or +* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 +* +* There are special exceptions to the terms and conditions of the GPL as +* it is applied to this Source Code. View the full text of the exception +* in the file doc/FLOSS-exception.txt in this software distribution, or +* online at +* http://secondlifegrid.net/programs/open_source/licensing/flossexception +* +* By copying, modifying or distributing this software, you acknowledge +* that you have read and understood your obligations described above, +* and agree to abide by those obligations. +* +* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO +* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, +* COMPLETENESS OR PERFORMANCE. +* $/LicenseInfo$ +*/ +#ifndef LLHANDLE_H +#define LLHANDLE_H + +#include "llpointer.h" + +template <typename T> +class LLTombStone : public LLRefCount +{ +public: + LLTombStone(T* target = NULL) : mTarget(target) {} + + void setTarget(T* target) { mTarget = target; } + T* getTarget() const { return mTarget; } +private: + T* mTarget; +}; + +// LLHandles are used to refer to objects whose lifetime you do not control or influence. +// Calling get() on a handle will return a pointer to the referenced object or NULL, +// if the object no longer exists. Note that during the lifetime of the returned pointer, +// you are assuming that the object will not be deleted by any action you perform, +// or any other thread, as normal when using pointers, so avoid using that pointer outside of +// the local code block. +// +// https://wiki.lindenlab.com/mediawiki/index.php?title=LLHandle&oldid=79669 + +template <typename T> +class LLHandle +{ +public: + LLHandle() : mTombStone(getDefaultTombStone()) {} + const LLHandle<T>& operator =(const LLHandle<T>& other) + { + mTombStone = other.mTombStone; + return *this; + } + + bool isDead() const + { + return mTombStone->getTarget() == NULL; + } + + void markDead() + { + mTombStone = getDefaultTombStone(); + } + + T* get() const + { + return mTombStone->getTarget(); + } + + friend bool operator== (const LLHandle<T>& lhs, const LLHandle<T>& rhs) + { + return lhs.mTombStone == rhs.mTombStone; + } + friend bool operator!= (const LLHandle<T>& lhs, const LLHandle<T>& rhs) + { + return !(lhs == rhs); + } + friend bool operator< (const LLHandle<T>& lhs, const LLHandle<T>& rhs) + { + return lhs.mTombStone < rhs.mTombStone; + } + friend bool operator> (const LLHandle<T>& lhs, const LLHandle<T>& rhs) + { + return lhs.mTombStone > rhs.mTombStone; + } +protected: + +protected: + LLPointer<LLTombStone<T> > mTombStone; + +private: + static LLPointer<LLTombStone<T> >& getDefaultTombStone() + { + static LLPointer<LLTombStone<T> > sDefaultTombStone = new LLTombStone<T>; + return sDefaultTombStone; + } +}; + +template <typename T> +class LLRootHandle : public LLHandle<T> +{ +public: + LLRootHandle(T* object) { bind(object); } + LLRootHandle() {}; + ~LLRootHandle() { unbind(); } + + // this is redundant, since a LLRootHandle *is* an LLHandle + LLHandle<T> getHandle() { return LLHandle<T>(*this); } + + void bind(T* object) + { + // unbind existing tombstone + if (LLHandle<T>::mTombStone.notNull()) + { + if (LLHandle<T>::mTombStone->getTarget() == object) return; + LLHandle<T>::mTombStone->setTarget(NULL); + } + // tombstone reference counted, so no paired delete + LLHandle<T>::mTombStone = new LLTombStone<T>(object); + } + + void unbind() + { + LLHandle<T>::mTombStone->setTarget(NULL); + } + + //don't allow copying of root handles, since there should only be one +private: + LLRootHandle(const LLRootHandle& other) {}; +}; + +// Use this as a mixin for simple classes that need handles and when you don't +// want handles at multiple points of the inheritance hierarchy +template <typename T> +class LLHandleProvider +{ +protected: + typedef LLHandle<T> handle_type_t; + LLHandleProvider() + { + // provided here to enforce T deriving from LLHandleProvider<T> + } + + LLHandle<T> getHandle() + { + // perform lazy binding to avoid small tombstone allocations for handle + // providers whose handles are never referenced + mHandle.bind(static_cast<T*>(this)); + return mHandle; + } + +private: + LLRootHandle<T> mHandle; +}; + +#endif diff --git a/indra/llui/llhelp.h b/indra/llui/llhelp.h new file mode 100644 index 0000000000..938419d374 --- /dev/null +++ b/indra/llui/llhelp.h @@ -0,0 +1,49 @@ +/** + * @file llhelp.h + * @brief Abstract interface to the Help system + * @author Tofu Linden + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLHELP_H +#define LL_LLHELP_H + +class LLHelp +{ + public: + virtual void showTopic(const std::string &topic) = 0; + // return default (fallback) topic name suitable for showTopic() + virtual std::string defaultTopic() = 0; + // return topic to use before the user logs in + virtual std::string preLoginTopic() = 0; + // return topic to use for the top-level help, invoked by F1 + virtual std::string f1HelpTopic() = 0; +}; + +#endif // headerguard diff --git a/indra/llui/lliconctrl.cpp b/indra/llui/lliconctrl.cpp index cb3b2a3a62..b1bd2b89a9 100644 --- a/indra/llui/lliconctrl.cpp +++ b/indra/llui/lliconctrl.cpp @@ -40,65 +40,36 @@ #include "llcontrol.h" #include "llui.h" #include "lluictrlfactory.h" +#include "lluiimage.h" -const F32 RESOLUTION_BUMP = 1.f; +static LLDefaultChildRegistry::Register<LLIconCtrl> r("icon"); -static LLRegisterWidget<LLIconCtrl> r("icon"); - -LLIconCtrl::LLIconCtrl(const std::string& name, const LLRect &rect, const LLUUID &image_id) -: LLUICtrl(name, - rect, - FALSE, // mouse opaque - NULL, NULL, - FOLLOWS_LEFT | FOLLOWS_TOP), - mColor( LLColor4::white ) +LLIconCtrl::Params::Params() +: image("image_name"), + color("color"), + scale_image("scale_image") { - setImage( image_id ); - setTabStop(FALSE); + tab_stop = false; + mouse_opaque = false; } -LLIconCtrl::LLIconCtrl(const std::string& name, const LLRect &rect, const std::string &image_name) -: LLUICtrl(name, - rect, - FALSE, // mouse opaque - NULL, NULL, - FOLLOWS_LEFT | FOLLOWS_TOP), - mColor( LLColor4::white ), - mImageName(image_name) -{ - setImage( image_name ); - setTabStop(FALSE); -} - - -LLIconCtrl::~LLIconCtrl() +LLIconCtrl::LLIconCtrl(const LLIconCtrl::Params& p) +: LLUICtrl(p), + mColor(p.color()), + mImagep(p.image), + mPriority(0), + mDrawWidth(0), + mDrawHeight(0) { - mImagep = NULL; -} - - -void LLIconCtrl::setImage(const std::string& image_name) -{ - //RN: support UUIDs masquerading as strings - if (LLUUID::validate(image_name)) - { - mImageID = LLUUID(image_name); - - setImage(mImageID); - } - else + if (mImagep.notNull()) { - mImageName = image_name; - mImagep = LLUI::sImageProvider->getUIImage(image_name); - mImageID.setNull(); + LLUICtrl::setValue(mImagep->getName()); } } -void LLIconCtrl::setImage(const LLUUID& image_id) +LLIconCtrl::~LLIconCtrl() { - mImageName.clear(); - mImagep = LLUI::sImageProvider->getUIImageByID(image_id); - mImageID = image_id; + mImagep = NULL; } @@ -106,69 +77,51 @@ void LLIconCtrl::draw() { if( mImagep.notNull() ) { - mImagep->draw(getLocalRect(), mColor ); + mImagep->draw(getLocalRect(), mColor.get() % getDrawContext().mAlpha ); } LLUICtrl::draw(); } // virtual +// value might be a string or a UUID void LLIconCtrl::setValue(const LLSD& value ) { - if (value.isUUID()) + LLSD tvalue(value); + if (value.isString() && LLUUID::validate(value.asString())) + { + //RN: support UUIDs masquerading as strings + tvalue = LLSD(LLUUID(value.asString())); + } + LLUICtrl::setValue(tvalue); + if (tvalue.isUUID()) { - setImage(value.asUUID()); + mImagep = LLUI::getUIImageByID(tvalue.asUUID(), mPriority); } else { - setImage(value.asString()); + mImagep = LLUI::getUIImage(tvalue.asString(), mPriority); } -} -// virtual -LLSD LLIconCtrl::getValue() const -{ - LLSD ret = getImage(); - return ret; + setIconImageDrawSize(); } -// virtual -LLXMLNodePtr LLIconCtrl::getXML(bool save_children) const +std::string LLIconCtrl::getImageName() const { - LLXMLNodePtr node = LLUICtrl::getXML(); - - if (mImageName != "") - { - node->createChild("image_name", TRUE)->setStringValue(mImageName); - } - - node->createChild("color", TRUE)->setFloatValue(4, mColor.mV); - - return node; + if (getValue().isString()) + return getValue().asString(); + else + return std::string(); } -LLView* LLIconCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +void LLIconCtrl::setIconImageDrawSize() { - std::string name("icon"); - node->getAttributeString("name", name); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - std::string image_name; - if (node->hasAttribute("image_name")) + if(mImagep.notNull() && mDrawWidth && mDrawHeight) { - node->getAttributeString("image_name", image_name); + if(mImagep->getImage().notNull()) + { + mImagep->getImage()->setKnownDrawSize(mDrawWidth, mDrawHeight) ; + } } - - LLColor4 color(LLColor4::white); - LLUICtrlFactory::getAttributeColor(node,"color", color); - - LLIconCtrl* icon = new LLIconCtrl(name, rect, image_name); - - icon->setColor(color); - - icon->initFromXML(node, parent); - - return icon; } + diff --git a/indra/llui/lliconctrl.h b/indra/llui/lliconctrl.h index 50778cf226..66368f979b 100644 --- a/indra/llui/lliconctrl.h +++ b/indra/llui/lliconctrl.h @@ -37,7 +37,6 @@ #include "v4color.h" #include "lluictrl.h" #include "stdenums.h" -#include "llimagegl.h" class LLTextBox; class LLUICtrlFactory; @@ -45,36 +44,49 @@ class LLUICtrlFactory; // // Classes // + +// class LLIconCtrl : public LLUICtrl { public: - LLIconCtrl(const std::string& name, const LLRect &rect, const LLUUID &image_id); - LLIconCtrl(const std::string& name, const LLRect &rect, const std::string &image_name); + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<LLUIImage*> image; + Optional<LLUIColor> color; + Ignored scale_image; + Params(); + }; +protected: + LLIconCtrl(const Params&); + friend class LLUICtrlFactory; + +public: virtual ~LLIconCtrl(); // llview overrides virtual void draw(); - void setImage(const std::string& image_name); - void setImage(const LLUUID& image_name); - const LLUUID &getImage() const { return mImageID; } - std::string getImageName() const { return mImageName; } - - // Takes a UUID, wraps get/setImage + // lluictrl overrides virtual void setValue(const LLSD& value ); - virtual LLSD getValue() const; + + std::string getImageName() const; void setColor(const LLColor4& color) { mColor = color; } + +private: + void setIconImageDrawSize() ; + +protected: + S32 mPriority; - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + //the output size of the icon image if set. + S32 mDrawWidth ; + S32 mDrawHeight ; private: - LLColor4 mColor; - std::string mImageName; - LLUUID mImageID; - LLPointer<LLUIImage> mImagep; + LLUIColor mColor; + LLPointer<LLUIImage> mImagep; }; #endif diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp index 51ef3dbacf..ede32084d0 100644 --- a/indra/llui/llkeywords.cpp +++ b/indra/llui/llkeywords.cpp @@ -223,19 +223,21 @@ LLColor3 LLKeywords::readColor( const std::string& s ) { F32 r, g, b; r = g = b = 0.0f; - S32 read = sscanf(s.c_str(), "%f, %f, %f]", &r, &g, &b ); - if( read != 3 ) /* Flawfinder: ignore */ + S32 values_read = sscanf(s.c_str(), "%f, %f, %f]", &r, &g, &b ); + if( values_read != 3 ) { llinfos << " poorly formed color in keyword file" << llendl; } return LLColor3( r, g, b ); } +LLFastTimer::DeclareTimer 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<LLTextSegment *>* seg_list, const LLWString& wtext, const LLColor4 &defaultColor) +void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLWString& wtext, const LLColor4 &defaultColor, LLTextEditor& editor) { - std::for_each(seg_list->begin(), seg_list->end(), DeletePointer()); + LLFastTimer ft(FTM_SYNTAX_COLORING); seg_list->clear(); if( wtext.empty() ) @@ -243,9 +245,9 @@ void LLKeywords::findSegments(std::vector<LLTextSegment *>* seg_list, const LLWS return; } - S32 text_len = wtext.size(); + S32 text_len = wtext.size() + 1; - seg_list->push_back( new LLTextSegment( LLColor3(defaultColor), 0, text_len ) ); + seg_list->push_back( new LLNormalTextSegment( defaultColor, 0, text_len, editor ) ); const llwchar* base = wtext.c_str(); const llwchar* cur = base; @@ -296,9 +298,9 @@ void LLKeywords::findSegments(std::vector<LLTextSegment *>* seg_list, const LLWS } S32 seg_end = cur - base; - LLTextSegment* text_segment = new LLTextSegment( cur_token->getColor(), seg_start, seg_end ); + LLTextSegmentPtr text_segment = new LLNormalTextSegment( cur_token->getColor(), seg_start, seg_end, editor ); text_segment->setToken( cur_token ); - insertSegment( seg_list, text_segment, text_len, defaultColor); + insertSegment( seg_list, text_segment, text_len, defaultColor, editor); line_done = TRUE; // to break out of second loop. break; } @@ -405,9 +407,9 @@ void LLKeywords::findSegments(std::vector<LLTextSegment *>* seg_list, const LLWS } - LLTextSegment* text_segment = new LLTextSegment( cur_delimiter->getColor(), seg_start, seg_end ); + 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); + 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. @@ -438,9 +440,9 @@ void LLKeywords::findSegments(std::vector<LLTextSegment *>* seg_list, const LLWS // llinfos << "Seg: [" << word.c_str() << "]" << llendl; - LLTextSegment* text_segment = new LLTextSegment( cur_token->getColor(), seg_start, seg_end ); + LLTextSegmentPtr text_segment = new LLNormalTextSegment( cur_token->getColor(), seg_start, seg_end, editor ); text_segment->setToken( cur_token ); - insertSegment( seg_list, text_segment, text_len, defaultColor); + insertSegment( seg_list, text_segment, text_len, defaultColor, editor); } cur += seg_len; continue; @@ -455,25 +457,24 @@ void LLKeywords::findSegments(std::vector<LLTextSegment *>* seg_list, const LLWS } } -void LLKeywords::insertSegment(std::vector<LLTextSegment*>* seg_list, LLTextSegment* new_segment, S32 text_len, const LLColor4 &defaultColor ) +void LLKeywords::insertSegment(std::vector<LLTextSegmentPtr>* seg_list, LLTextSegmentPtr new_segment, S32 text_len, const LLColor4 &defaultColor, LLTextEditor& editor ) { - LLTextSegment* last = seg_list->back(); + LLTextSegmentPtr last = seg_list->back(); S32 new_seg_end = new_segment->getEnd(); if( new_segment->getStart() == last->getStart() ) { - *last = *new_segment; - delete new_segment; + seg_list->pop_back(); } else { last->setEnd( new_segment->getStart() ); - seg_list->push_back( new_segment ); } + seg_list->push_back( new_segment ); if( new_seg_end < text_len ) { - seg_list->push_back( new LLTextSegment( defaultColor, new_seg_end, text_len ) ); + seg_list->push_back( new LLNormalTextSegment( defaultColor, new_seg_end, text_len, editor ) ); } } diff --git a/indra/llui/llkeywords.h b/indra/llui/llkeywords.h index 38f5e993c2..53377869ca 100644 --- a/indra/llui/llkeywords.h +++ b/indra/llui/llkeywords.h @@ -39,9 +39,10 @@ #include <map> #include <list> #include <deque> +#include "llpointer.h" class LLTextSegment; - +typedef LLPointer<LLTextSegment> LLTextSegmentPtr; class LLKeywordToken { @@ -84,7 +85,7 @@ public: BOOL loadFromFile(const std::string& filename); BOOL isLoaded() const { return mLoaded; } - void findSegments(std::vector<LLTextSegment *> *seg_list, const LLWString& text, const LLColor4 &defaultColor ); + void findSegments(std::vector<LLTextSegmentPtr> *seg_list, const LLWString& text, const LLColor4 &defaultColor, class LLTextEditor& editor ); // Add the token as described void addToken(LLKeywordToken::TOKEN_TYPE type, @@ -103,7 +104,7 @@ public: private: LLColor3 readColor(const std::string& s); - void insertSegment(std::vector<LLTextSegment *> *seg_list, LLTextSegment* new_segment, S32 text_len, const LLColor4 &defaultColor); + void insertSegment(std::vector<LLTextSegmentPtr> *seg_list, LLTextSegmentPtr new_segment, S32 text_len, const LLColor4 &defaultColor, class LLTextEditor& editor); BOOL mLoaded; word_token_map_t mWordTokenMap; diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp new file mode 100644 index 0000000000..1aaba88c49 --- /dev/null +++ b/indra/llui/lllayoutstack.cpp @@ -0,0 +1,823 @@ +/** + * @file lllayoutstack.cpp + * @brief LLLayout class - dynamic stacking of UI elements + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// 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 "llresizebar.h" +#include "llcriticaldamp.h" + +static LLDefaultChildRegistry::Register<LLLayoutStack> register_layout_stack("layout_stack", &LLLayoutStack::fromXML); + + +// +// LLLayoutStack +// +struct LLLayoutStack::LayoutPanel +{ + LayoutPanel(LLPanel* panelp, ELayoutOrientation orientation, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize) : mPanel(panelp), + mMinWidth(min_width), + mMinHeight(min_height), + mAutoResize(auto_resize), + mUserResize(user_resize), + mOrientation(orientation), + mCollapsed(FALSE), + mCollapseAmt(0.f), + mVisibleAmt(1.f), // default to fully visible + mResizeBar(NULL) + { + LLResizeBar::Side side = (orientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM; + LLRect resize_bar_rect = panelp->getRect(); + + S32 min_dim; + if (orientation == HORIZONTAL) + { + min_dim = mMinHeight; + } + else + { + min_dim = mMinWidth; + } + LLResizeBar::Params p; + p.name("resize"); + p.resizing_view(mPanel); + p.min_size(min_dim); + p.side(side); + p.snapping_enabled(false); + mResizeBar = LLUICtrlFactory::create<LLResizeBar>(p); + // panels initialized as hidden should not start out partially visible + if (!mPanel->getVisible()) + { + mVisibleAmt = 0.f; + } + } + + ~LayoutPanel() + { + // probably not necessary, but... + delete mResizeBar; + mResizeBar = NULL; + } + + F32 getCollapseFactor() + { + if (mOrientation == HORIZONTAL) + { + F32 collapse_amt = + clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinWidth / (F32)llmax(1, mPanel->getRect().getWidth())); + return mVisibleAmt * collapse_amt; + } + else + { + F32 collapse_amt = + clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, llmin(1.f, (F32)mMinHeight / (F32)llmax(1, mPanel->getRect().getHeight()))); + return mVisibleAmt * collapse_amt; + } + } + + LLPanel* mPanel; + S32 mMinWidth; + S32 mMinHeight; + BOOL mAutoResize; + BOOL mUserResize; + BOOL mCollapsed; + LLResizeBar* mResizeBar; + ELayoutOrientation mOrientation; + F32 mVisibleAmt; + F32 mCollapseAmt; +}; + +LLLayoutStack::Params::Params() +: orientation("orientation", std::string("vertical")), + animate("animate", true), + clip("clip", true), + border_size("border_size", LLCachedControl<S32>(*LLUI::sSettingGroups["config"], "UIResizeBarHeight", 0)) +{ + name="stack"; +} + +LLLayoutStack::LLLayoutStack(const LLLayoutStack::Params& p) +: LLView(p), + mMinWidth(0), + mMinHeight(0), + mPanelSpacing(p.border_size), + mOrientation((p.orientation() == "vertical") ? VERTICAL : HORIZONTAL), + mAnimate(p.animate), + mAnimatedThisFrame(false), + mClip(p.clip) +{} + +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(); + + e_panel_list_t::iterator panel_it; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + // clip to layout rectangle, not bounding rectangle + LLRect clip_rect = (*panel_it)->mPanel->getRect(); + // scale clipping rectangle by visible amount + if (mOrientation == HORIZONTAL) + { + clip_rect.mRight = clip_rect.mLeft + llround((F32)clip_rect.getWidth() * (*panel_it)->getCollapseFactor()); + } + else + { + clip_rect.mBottom = clip_rect.mTop - llround((F32)clip_rect.getHeight() * (*panel_it)->getCollapseFactor()); + } + + LLPanel* panelp = (*panel_it)->mPanel; + + LLLocalClipRect clip(clip_rect, mClip); + // only force drawing invisible children if visible amount is non-zero + drawChild(panelp, 0, 0, !clip_rect.isEmpty()); + } + mAnimatedThisFrame = false; +} + +void LLLayoutStack::removeChild(LLView* view) +{ + LayoutPanel* embedded_panelp = findEmbeddedPanel(dynamic_cast<LLPanel*>(view)); + + if (embedded_panelp) + { + mPanels.erase(std::find(mPanels.begin(), mPanels.end(), embedded_panelp)); + delete embedded_panelp; + } + + // need to update resizebars + + calcMinExtents(); + + LLView::removeChild(view); +} + +BOOL LLLayoutStack::postBuild() +{ + updateLayout(); + return TRUE; +} + +static void get_attribute_s32_and_write(LLXMLNodePtr node, + const char* name, + S32 *value, + S32 default_value, + LLXMLNodePtr output_child) +{ + BOOL has_attr = node->getAttributeS32(name, *value); + if (has_attr && *value != default_value && output_child) + { + // create an attribute child node + LLXMLNodePtr child_attr = output_child->createChild(name, TRUE); + child_attr->setIntValue(*value); + } +} + +static void get_attribute_bool_and_write(LLXMLNodePtr node, + const char* name, + BOOL *value, + BOOL default_value, + LLXMLNodePtr output_child) +{ + BOOL has_attr = node->getAttributeBOOL(name, *value); + if (has_attr && *value != default_value && output_child) + { + LLXMLNodePtr child_attr = output_child->createChild(name, TRUE); + child_attr->setBoolValue(*value); + } +} +//static +LLView* LLLayoutStack::fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node) +{ + LLLayoutStack::Params p(LLUICtrlFactory::getDefaultParams<LLLayoutStack>()); + LLXUIParser::instance().readXUI(node, p, LLUICtrlFactory::getInstance()->getCurFileName()); + + // Export must happen before setupParams() mungles rectangles and before + // this item gets added to parent (otherwise screws up last_child_rect + // logic). JC + if (output_node) + { + Params output_params(p); + setupParamsForExport(output_params, parent); + LLLayoutStack::Params default_params(LLUICtrlFactory::getDefaultParams<LLLayoutStack>()); + output_node->setName(node->getName()->mString); + LLXUIParser::instance().writeXUI( + output_node, output_params, &default_params); + } + + p.from_xui = true; + applyXUILayout(p, parent); + LLLayoutStack* layout_stackp = LLUICtrlFactory::create<LLLayoutStack>(p); + + if (parent && layout_stackp) + { + S32 tab_group = p.tab_group.isProvided() ? p.tab_group() : parent->getLastTabGroup(); + + parent->addChild(layout_stackp, tab_group); + } + + for (LLXMLNodePtr child_node = node->getFirstChild(); child_node.notNull(); child_node = child_node->getNextSibling()) + { + const S32 DEFAULT_MIN_WIDTH = 0; + const S32 DEFAULT_MIN_HEIGHT = 0; + const BOOL DEFAULT_AUTO_RESIZE = TRUE; + + S32 min_width = DEFAULT_MIN_WIDTH; + S32 min_height = DEFAULT_MIN_HEIGHT; + BOOL auto_resize = DEFAULT_AUTO_RESIZE; + + LLXMLNodePtr output_child; + if (output_node) + { + output_child = output_node->createChild("", FALSE); + } + + // Layout stack allows child nodes to acquire additional attributes, + // such as "min_width" in: <button label="Foo" min_width="100"/> + // If these attributes exist and have non-default values, write them + // to the output node. + get_attribute_s32_and_write(child_node, "min_width", &min_width, + DEFAULT_MIN_WIDTH, output_child); + get_attribute_s32_and_write(child_node, "min_height", &min_height, + DEFAULT_MIN_HEIGHT, output_child); + get_attribute_bool_and_write(child_node, "auto_resize", &auto_resize, + DEFAULT_AUTO_RESIZE, output_child); + + if (child_node->hasName("layout_panel")) + { + BOOL user_resize = TRUE; + get_attribute_bool_and_write(child_node, "user_resize", &user_resize, + TRUE, output_child); + LLPanel* panelp = (LLPanel*)LLPanel::fromXML(child_node, layout_stackp, output_child); + if (panelp) + { + panelp->setFollowsNone(); + layout_stackp->addPanel(panelp, min_width, min_height, auto_resize, user_resize); + } + } + else + { + BOOL user_resize = FALSE; + get_attribute_bool_and_write(child_node, "user_resize", &user_resize, + FALSE, output_child); + + LLPanel::Params p; + p.mouse_opaque(false); + LLPanel* panelp = LLUICtrlFactory::create<LLPanel>(p); + LLView* new_child = LLUICtrlFactory::getInstance()->createFromXML(child_node, panelp, LLStringUtil::null, LLPanel::child_registry_t::instance(), output_child); + if (new_child) + { + // put child in new embedded panel + layout_stackp->addPanel(panelp, min_width, min_height, auto_resize, user_resize); + // resize panel to contain widget and move widget to be contained in panel + panelp->setRect(new_child->getRect()); + new_child->setOrigin(0, 0); + } + else + { + panelp->die(); + } + } + + if (output_child && !output_child->mChildren && output_child->mAttributes.empty() && output_child->getValue().empty()) + { + output_node->deleteChild(output_child); + } + } + + if (!layout_stackp->postBuild()) + { + delete layout_stackp; + return NULL; + } + + return layout_stackp; +} + +S32 LLLayoutStack::getDefaultHeight(S32 cur_height) +{ + // if we are spanning our children (crude upward propagation of size) + // then don't enforce our size on our children + if (mOrientation == HORIZONTAL) + { + cur_height = llmax(mMinHeight, getRect().getHeight()); + } + + return cur_height; +} + +S32 LLLayoutStack::getDefaultWidth(S32 cur_width) +{ + // if we are spanning our children (crude upward propagation of size) + // then don't enforce our size on our children + if (mOrientation == VERTICAL) + { + cur_width = llmax(mMinWidth, getRect().getWidth()); + } + + return cur_width; +} + +void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, EAnimate animate, S32 index) +{ + // panel starts off invisible (collapsed) + if (animate == ANIMATE) + { + panel->setVisible(FALSE); + } + LayoutPanel* embedded_panel = new LayoutPanel(panel, mOrientation, min_width, min_height, auto_resize, user_resize); + + mPanels.insert(mPanels.begin() + llclamp(index, 0, (S32)mPanels.size()), embedded_panel); + + if (panel->getParent() != this) + { + addChild(panel); + } + addChild(embedded_panel->mResizeBar); + + // 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); + } + + // start expanding panel animation + if (animate == ANIMATE) + { + panel->setVisible(TRUE); + } +} + +void LLLayoutStack::removePanel(LLPanel* panel) +{ + removeChild(panel); +} + +void LLLayoutStack::collapsePanel(LLPanel* panel, BOOL collapsed) +{ + LayoutPanel* panel_container = findEmbeddedPanel(panel); + if (!panel_container) return; + + panel_container->mCollapsed = collapsed; +} + +void LLLayoutStack::updatePanelAutoResize(const std::string& panel_name, BOOL auto_resize) +{ + LayoutPanel* panel = findEmbeddedPanelByName(panel_name); + + if (panel) + { + panel->mAutoResize = auto_resize; + } +} + +void LLLayoutStack::setPanelUserResize(const std::string& panel_name, BOOL user_resize) +{ + LayoutPanel* panel = findEmbeddedPanelByName(panel_name); + + if (panel) + { + panel->mUserResize = user_resize; + } +} + +bool LLLayoutStack::getPanelMinSize(const std::string& panel_name, S32* min_widthp, S32* min_heightp) +{ + LayoutPanel* panel = findEmbeddedPanelByName(panel_name); + + if (panel) + { + if (min_widthp) *min_widthp = panel->mMinWidth; + if (min_heightp) *min_heightp = panel->mMinHeight; + } + + return NULL != panel; +} + +static LLFastTimer::DeclareTimer FTM_UPDATE_LAYOUT("Update LayoutStacks"); +void LLLayoutStack::updateLayout(BOOL force_resize) +{ + LLFastTimer ft(FTM_UPDATE_LAYOUT); + static LLUICachedControl<S32> resize_bar_overlap ("UIResizeBarOverlap", 0); + calcMinExtents(); + + // calculate current extents + S32 total_width = 0; + S32 total_height = 0; + + const F32 ANIM_OPEN_TIME = 0.02f; + const F32 ANIM_CLOSE_TIME = 0.03f; + + e_panel_list_t::iterator panel_it; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + LLPanel* panelp = (*panel_it)->mPanel; + if (panelp->getVisible()) + { + if (mAnimate) + { + if (!mAnimatedThisFrame) + { + (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_OPEN_TIME)); + if ((*panel_it)->mVisibleAmt > 0.99f) + { + (*panel_it)->mVisibleAmt = 1.f; + } + } + } + else + { + (*panel_it)->mVisibleAmt = 1.f; + } + } + else // not visible + { + if (mAnimate) + { + if (!mAnimatedThisFrame) + { + (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); + if ((*panel_it)->mVisibleAmt < 0.001f) + { + (*panel_it)->mVisibleAmt = 0.f; + } + } + } + else + { + (*panel_it)->mVisibleAmt = 0.f; + } + } + + if ((*panel_it)->mCollapsed) + { + (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); + } + else + { + (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); + } + + if (mOrientation == HORIZONTAL) + { + // enforce minimize size constraint by default + if (panelp->getRect().getWidth() < (*panel_it)->mMinWidth) + { + panelp->reshape((*panel_it)->mMinWidth, panelp->getRect().getHeight()); + } + total_width += llround(panelp->getRect().getWidth() * (*panel_it)->getCollapseFactor()); + // want n-1 panel gaps for n panels + if (panel_it != mPanels.begin()) + { + total_width += mPanelSpacing; + } + } + else //VERTICAL + { + // enforce minimize size constraint by default + if (panelp->getRect().getHeight() < (*panel_it)->mMinHeight) + { + panelp->reshape(panelp->getRect().getWidth(), (*panel_it)->mMinHeight); + } + total_height += llround(panelp->getRect().getHeight() * (*panel_it)->getCollapseFactor()); + if (panel_it != mPanels.begin()) + { + total_height += mPanelSpacing; + } + } + } + + S32 num_resizable_panels = 0; + S32 shrink_headroom_available = 0; + S32 shrink_headroom_total = 0; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + // panels that are not fully visible do not count towards shrink headroom + if ((*panel_it)->getCollapseFactor() < 1.f) + { + continue; + } + + // if currently resizing a panel or the panel is flagged as not automatically resizing + // only track total available headroom, but don't use it for automatic resize logic + if ((*panel_it)->mResizeBar->hasMouseCapture() + || (!(*panel_it)->mAutoResize + && !force_resize)) + { + if (mOrientation == HORIZONTAL) + { + shrink_headroom_total += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; + } + else //VERTICAL + { + shrink_headroom_total += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; + } + } + else + { + num_resizable_panels++; + if (mOrientation == HORIZONTAL) + { + shrink_headroom_available += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; + shrink_headroom_total += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; + } + else //VERTICAL + { + shrink_headroom_available += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; + shrink_headroom_total += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; + } + } + } + + // calculate how many pixels need to be distributed among layout panels + // positive means panels need to grow, negative means shrink + S32 pixels_to_distribute; + if (mOrientation == HORIZONTAL) + { + pixels_to_distribute = getRect().getWidth() - total_width; + } + else //VERTICAL + { + pixels_to_distribute = getRect().getHeight() - total_height; + } + + // now we distribute the pixels... + S32 cur_x = 0; + S32 cur_y = getRect().getHeight(); + + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + LLPanel* panelp = (*panel_it)->mPanel; + + S32 cur_width = panelp->getRect().getWidth(); + S32 cur_height = panelp->getRect().getHeight(); + S32 new_width = llmax((*panel_it)->mMinWidth, cur_width); + S32 new_height = llmax((*panel_it)->mMinHeight, cur_height); + + S32 delta_size = 0; + + // if panel can automatically resize (not animating, and resize flag set)... + if ((*panel_it)->getCollapseFactor() == 1.f + && (force_resize || (*panel_it)->mAutoResize) + && !(*panel_it)->mResizeBar->hasMouseCapture()) + { + if (mOrientation == HORIZONTAL) + { + // if we're shrinking + if (pixels_to_distribute < 0) + { + // shrink proportionally to amount over minimum + // so we can do this in one pass + delta_size = (shrink_headroom_available > 0) ? llround((F32)pixels_to_distribute * ((F32)(cur_width - (*panel_it)->mMinWidth) / (F32)shrink_headroom_available)) : 0; + shrink_headroom_available -= (cur_width - (*panel_it)->mMinWidth); + } + else + { + // grow all elements equally + delta_size = llround((F32)pixels_to_distribute / (F32)num_resizable_panels); + num_resizable_panels--; + } + pixels_to_distribute -= delta_size; + new_width = llmax((*panel_it)->mMinWidth, cur_width + delta_size); + } + else + { + new_width = getDefaultWidth(new_width); + } + + if (mOrientation == VERTICAL) + { + if (pixels_to_distribute < 0) + { + // shrink proportionally to amount over minimum + // so we can do this in one pass + delta_size = (shrink_headroom_available > 0) ? llround((F32)pixels_to_distribute * ((F32)(cur_height - (*panel_it)->mMinHeight) / (F32)shrink_headroom_available)) : 0; + shrink_headroom_available -= (cur_height - (*panel_it)->mMinHeight); + } + else + { + delta_size = llround((F32)pixels_to_distribute / (F32)num_resizable_panels); + num_resizable_panels--; + } + pixels_to_distribute -= delta_size; + new_height = llmax((*panel_it)->mMinHeight, cur_height + delta_size); + } + else + { + new_height = getDefaultHeight(new_height); + } + } + else + { + if (mOrientation == HORIZONTAL) + { + new_height = getDefaultHeight(new_height); + } + else // VERTICAL + { + new_width = getDefaultWidth(new_width); + } + } + + // adjust running headroom count based on new sizes + shrink_headroom_total += delta_size; + + LLRect panel_rect; + panel_rect.setLeftTopAndSize(cur_x, cur_y, new_width, new_height); + panelp->setShape(panel_rect); + + LLRect resize_bar_rect = panel_rect; + if (mOrientation == HORIZONTAL) + { + resize_bar_rect.mLeft = panel_rect.mRight - resize_bar_overlap; + resize_bar_rect.mRight = panel_rect.mRight + mPanelSpacing + resize_bar_overlap; + } + else + { + resize_bar_rect.mTop = panel_rect.mBottom + resize_bar_overlap; + resize_bar_rect.mBottom = panel_rect.mBottom - mPanelSpacing - resize_bar_overlap; + } + (*panel_it)->mResizeBar->setRect(resize_bar_rect); + + if (mOrientation == HORIZONTAL) + { + cur_x += llround(new_width * (*panel_it)->getCollapseFactor()) + mPanelSpacing; + } + else //VERTICAL + { + cur_y -= llround(new_height * (*panel_it)->getCollapseFactor()) + mPanelSpacing; + } + } + + // update resize bars with new limits + LLResizeBar* last_resize_bar = NULL; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + LLPanel* panelp = (*panel_it)->mPanel; + + if (mOrientation == HORIZONTAL) + { + (*panel_it)->mResizeBar->setResizeLimits( + (*panel_it)->mMinWidth, + (*panel_it)->mMinWidth + shrink_headroom_total); + } + else //VERTICAL + { + (*panel_it)->mResizeBar->setResizeLimits( + (*panel_it)->mMinHeight, + (*panel_it)->mMinHeight + shrink_headroom_total); + } + + // toggle resize bars based on panel visibility, resizability, etc + BOOL resize_bar_enabled = panelp->getVisible() && (*panel_it)->mUserResize; + (*panel_it)->mResizeBar->setVisible(resize_bar_enabled); + + if (resize_bar_enabled) + { + last_resize_bar = (*panel_it)->mResizeBar; + } + } + + // hide last resize bar as there is nothing past it + // resize bars need to be in between two resizable panels + if (last_resize_bar) + { + last_resize_bar->setVisible(FALSE); + } + + // not enough room to fit existing contents + if (force_resize == FALSE + // layout did not complete by reaching target position + && ((mOrientation == VERTICAL && cur_y != -mPanelSpacing) + || (mOrientation == HORIZONTAL && cur_x != getRect().getWidth() + mPanelSpacing))) + { + // do another layout pass with all stacked elements contributing + // even those that don't usually resize + llassert_always(force_resize == FALSE); + updateLayout(TRUE); + } + + mAnimatedThisFrame = true; +} // end LLLayoutStack::updateLayout + + +LLLayoutStack::LayoutPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) const +{ + if (!panelp) return NULL; + + e_panel_list_t::const_iterator panel_it; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + if ((*panel_it)->mPanel == panelp) + { + return *panel_it; + } + } + return NULL; +} + +LLLayoutStack::LayoutPanel* LLLayoutStack::findEmbeddedPanelByName(const std::string& name) const +{ + LayoutPanel* result = NULL; + + for (e_panel_list_t::const_iterator panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + LayoutPanel* p = *panel_it; + + if (p->mPanel->getName() == name) + { + result = p; + break; + } + } + + return result; +} + +// Compute sum of min_width or min_height of children +void LLLayoutStack::calcMinExtents() +{ + mMinWidth = 0; + mMinHeight = 0; + + e_panel_list_t::iterator panel_it; + for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + { + if (mOrientation == HORIZONTAL) + { + mMinHeight = llmax( mMinHeight, + (*panel_it)->mMinHeight); + mMinWidth += (*panel_it)->mMinWidth; + if (panel_it != mPanels.begin()) + { + mMinWidth += mPanelSpacing; + } + } + else //VERTICAL + { + mMinWidth = llmax( mMinWidth, + (*panel_it)->mMinWidth); + mMinHeight += (*panel_it)->mMinHeight; + if (panel_it != mPanels.begin()) + { + mMinHeight += mPanelSpacing; + } + } + } +} + +// 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 (LLLayoutStack::instance_iter it = beginInstances(); it != endInstances(); ++it) + { + it->updateLayout(); + } +} diff --git a/indra/llui/lllayoutstack.h b/indra/llui/lllayoutstack.h new file mode 100644 index 0000000000..c4f10038f8 --- /dev/null +++ b/indra/llui/lllayoutstack.h @@ -0,0 +1,130 @@ +/** + * @file lllayoutstack.h + * @author Richard Nelson + * @brief LLLayout class - dynamic stacking of UI elements + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLLAYOUTSTACK_H +#define LL_LLLAYOUTSTACK_H + +#include "llview.h" + +class LLPanel; + +class LLLayoutStack : public LLView, public LLInstanceTracker<LLLayoutStack> +{ +public: + struct Params : public LLInitParam::Block<Params, LLView::Params> + { + Optional<std::string> orientation; + Optional<S32> border_size; + Optional<bool> animate, + clip; + + Params(); + }; + + typedef enum e_layout_orientation + { + HORIZONTAL, + VERTICAL + } ELayoutOrientation; + + virtual ~LLLayoutStack(); + + /*virtual*/ void draw(); + /*virtual*/ void removeChild(LLView*); + /*virtual*/ BOOL postBuild(); + + static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); + + S32 getMinWidth() const { return mMinWidth; } + S32 getMinHeight() const { return mMinHeight; } + + typedef enum e_animate + { + NO_ANIMATE, + ANIMATE + } EAnimate; + + void addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, EAnimate animate = NO_ANIMATE, S32 index = S32_MAX); + void removePanel(LLPanel* panel); + void collapsePanel(LLPanel* panel, BOOL collapsed = TRUE); + S32 getNumPanels() { return mPanels.size(); } + + void updatePanelAutoResize(const std::string& panel_name, BOOL auto_resize); + void setPanelUserResize(const std::string& panel_name, BOOL user_resize); + + /** + * Gets minimal width and/or height of the specified by name panel. + * + * If it is necessary to get only the one dimension pass NULL for another one. + * @returns true if specified by panel_name internal panel exists, false otherwise. + */ + bool getPanelMinSize(const std::string& panel_name, S32* min_widthp, S32* min_heightp); + + void updateLayout(BOOL force_resize = FALSE); + + S32 getPanelSpacing() const { return mPanelSpacing; } + BOOL getAnimate () const { return mAnimate; } + void setAnimate (BOOL animate) { mAnimate = animate; } + + static void updateClass(); + +protected: + LLLayoutStack(const Params&); + friend class LLUICtrlFactory; + +private: + struct LayoutPanel; + + void calcMinExtents(); + S32 getDefaultHeight(S32 cur_height); + S32 getDefaultWidth(S32 cur_width); + + const ELayoutOrientation mOrientation; + + typedef std::vector<LayoutPanel*> e_panel_list_t; + e_panel_list_t mPanels; + + LayoutPanel* findEmbeddedPanel(LLPanel* panelp) const; + LayoutPanel* findEmbeddedPanelByName(const std::string& name) const; + + S32 mMinWidth; // calculated by calcMinExtents + S32 mMinHeight; // calculated by calcMinExtents + S32 mPanelSpacing; + + // true if we already applied animation this frame + bool mAnimatedThisFrame; + bool mAnimate; + bool mClip; +}; // end class LLLayoutStack + +#endif diff --git a/indra/llui/lllazyvalue.h b/indra/llui/lllazyvalue.h new file mode 100644 index 0000000000..cf45214628 --- /dev/null +++ b/indra/llui/lllazyvalue.h @@ -0,0 +1,88 @@ +/** + * @file lllazyvalue.h + * @brief generic functor/value abstraction for lazy evaluation of a value + * parsing construction parameters from xml and LLSD + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LAZY_VALUE_H +#define LL_LAZY_VALUE_H + +#include <boost/function.hpp> + +// Holds on to a value of type T *or* calls a functor to generate a value of type T +template<typename T> +class LLLazyValue +{ +public: + typedef typename boost::add_reference<typename boost::add_const<T>::type>::type T_const_ref; + typedef typename boost::function<T_const_ref (void)> function_type; + +public: + LLLazyValue(const function_type& value) + : mValueGetter(value) + {} + LLLazyValue(T_const_ref value) + : mValue(value) + {} + LLLazyValue() + : mValue() + {} + + void set(const LLLazyValue& val) + { + mValueGetter = val.mValueGetter; + } + + void set(T_const_ref val) + { + mValue = val; + mValueGetter = NULL; + } + + T_const_ref get() const + { + if (!mValueGetter.empty()) + { + return mValueGetter(); + } + return mValue; + } + + bool isUsingFunction() const + { + return mValueGetter != NULL; + } + +private: + function_type mValueGetter; + T mValue; +}; + +#endif // LL_LAZY_VALUE_H diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 964254d93f..73e4d126f3 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -34,10 +34,10 @@ #include "linden_common.h" +#define LLLINEEDITOR_CPP #include "lllineeditor.h" #include "lltexteditor.h" -#include "audioengine.h" #include "llmath.h" #include "llfontgl.h" #include "llgl.h" @@ -64,122 +64,133 @@ // Constants // -const S32 UI_LINEEDITOR_CURSOR_THICKNESS = 2; -const S32 UI_LINEEDITOR_H_PAD = 2; -const S32 UI_LINEEDITOR_V_PAD = 1; 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 PREEDIT_MARKER_BRIGHTNESS = 0.4f; -const S32 PREEDIT_MARKER_GAP = 1; -const S32 PREEDIT_MARKER_POSITION = 2; -const S32 PREEDIT_MARKER_THICKNESS = 1; -const F32 PREEDIT_STANDOUT_BRIGHTNESS = 0.6f; -const S32 PREEDIT_STANDOUT_GAP = 1; -const S32 PREEDIT_STANDOUT_POSITION = 2; -const S32 PREEDIT_STANDOUT_THICKNESS = 2; +static LLDefaultChildRegistry::Register<LLLineEditor> r1("line_editor"); -static LLRegisterWidget<LLLineEditor> r1("line_editor"); - -/* static */ LLPointer<LLUIImage> LLLineEditor::sImage; +// Compiler optimization, generate extern template +template class LLLineEditor* LLView::getChild<class LLLineEditor>( + const std::string& name, BOOL recurse) const; // // Member functions // - -LLLineEditor::LLLineEditor(const std::string& name, const LLRect& rect, - const std::string& default_text, const LLFontGL* font, - S32 max_length_bytes, - void (*commit_callback)(LLUICtrl* caller, void* user_data ), - void (*keystroke_callback)(LLLineEditor* caller, void* user_data ), - void (*focus_lost_callback)(LLFocusableElement* caller, void* user_data ), - void* userdata, - LLLinePrevalidateFunc prevalidate_func, - LLViewBorder::EBevel border_bevel, - LLViewBorder::EStyle border_style, - S32 border_thickness) - : - LLUICtrl( name, rect, TRUE, commit_callback, userdata, FOLLOWS_TOP | FOLLOWS_LEFT ), - mMaxLengthBytes(max_length_bytes), - mCursorPos( 0 ), - mScrollHPos( 0 ), - mTextPadLeft(0), - mTextPadRight(0), - mCommitOnFocusLost( TRUE ), - mRevertOnEsc( TRUE ), - mKeystrokeCallback( keystroke_callback ), - mIsSelecting( FALSE ), - mSelectionStart( 0 ), - mSelectionEnd( 0 ), - mLastSelectionX(-1), - mLastSelectionY(-1), - mLastSelectionStart(-1), - mLastSelectionEnd(-1), - mPrevalidateFunc( prevalidate_func ), - mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ), - mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ), - mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ), - mTentativeFgColor( LLUI::sColorsGroup->getColor( "TextFgTentativeColor" ) ), - mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ), - mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ), - mFocusBgColor( LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ), - mBorderThickness( border_thickness ), - mIgnoreArrowKeys( FALSE ), - mIgnoreTab( TRUE ), - mDrawAsterixes( FALSE ), - mHandleEditKeysDirectly( FALSE ), - mSelectAllonFocusReceived( FALSE ), - mPassDelete(FALSE), - mReadOnly(FALSE), - mImage( sImage ), - mReplaceNewlinesWithSpaces( TRUE ) -{ - llassert( max_length_bytes > 0 ); - - // line history support: - // - initialize line history list - mLineHistory.insert( mLineHistory.end(), "" ); - // - disable line history by default - mHaveHistory = FALSE; - // - reset current history line pointer - mCurrentHistoryLine = 0; - - if (font) - { - mGLFont = font; - } - else - { - mGLFont = LLFontGL::getFontSansSerifSmall(); - } - - setFocusLostCallback(focus_lost_callback); - setTextPadding(0, 0); +void LLLineEditor::PrevalidateNamedFuncs::declareValues() +{ + declare("ascii", LLLineEditor::prevalidateASCII); + declare("float", LLLineEditor::prevalidateFloat); + declare("int", LLLineEditor::prevalidateInt); + declare("positive_s32", LLLineEditor::prevalidatePositiveS32); + declare("non_negative_s32", LLLineEditor::prevalidateNonNegativeS32); + declare("alpha_num", LLLineEditor::prevalidateAlphaNum); + declare("alpha_num_space", LLLineEditor::prevalidateAlphaNumSpace); + declare("ascii_printable_no_pipe", LLLineEditor::prevalidateASCIIPrintableNoPipe); + declare("ascii_printable_no_space", LLLineEditor::prevalidateASCIIPrintableNoSpace); +} + +LLLineEditor::Params::Params() +: max_length_bytes("max_length", 254), + keystroke_callback("keystroke_callback"), + prevalidate_callback("prevalidate_callback"), + background_image("background_image"), + background_image_disabled("background_image_disabled"), + background_image_focused("background_image_focused"), + select_on_focus("select_on_focus", false), + handle_edit_keys_directly("handle_edit_keys_directly", false), + revert_on_esc("revert_on_esc", true), + commit_on_focus_lost("commit_on_focus_lost", true), + ignore_tab("ignore_tab", true), + cursor_color("cursor_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") +{ + mouse_opaque = true; + addSynonym(select_on_focus, "select_all_on_focus_received"); + addSynonym(border, "border"); + addSynonym(label, "watermark_text"); +} + +LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) +: LLUICtrl(p), + mMaxLengthBytes(p.max_length_bytes), + 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 ), + 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( FALSE ), + mHandleEditKeysDirectly(p.handle_edit_keys_directly), + mSelectAllonFocusReceived( p.select_on_focus ), + mPassDelete(FALSE), + mReadOnly(FALSE), + mBgImage( p.background_image ), + mBgImageDisabled( p.background_image_disabled ), + mBgImageFocused( p.background_image_focused ), + mHaveHistory(FALSE), + mReplaceNewlinesWithSpaces( TRUE ), + mLabel(p.label), + mCursorColor(p.cursor_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) +{ + llassert( mMaxLengthBytes > 0 ); mScrollTimer.reset(); + mTripleClickTimer.reset(); + setText(p.default_text()); + + // 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<LLViewBorder>(border_p); + addChild( mBorder ); - setText(default_text); - + // clamp text padding to current editor size + updateTextPadding(); setCursor(mText.length()); - // Scalable UI somehow made these rectangles off-by-one. - // I don't know why. JC - LLRect border_rect(0, getRect().getHeight()-1, getRect().getWidth()-1, 0); - mBorder = new LLViewBorder( std::string("line ed border"), border_rect, border_bevel, border_style, mBorderThickness ); - addChild( mBorder ); - mBorder->setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP|FOLLOWS_BOTTOM); - - if( ! sImage) - { - sImage = LLUI::getUIImage("sm_rounded_corners_simple.tga"); - } - mImage = sImage; + setPrevalidate(p.prevalidate_callback()); } - - + LLLineEditor::~LLLineEditor() { mCommitOnFocusLost = FALSE; @@ -222,15 +233,44 @@ void LLLineEditor::onFocusLost() LLUICtrl::onFocusLost(); } +// virtual void LLLineEditor::onCommit() { // put current line into the line history updateHistory(); + setControlValue(getValue()); LLUICtrl::onCommit(); 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() @@ -239,23 +279,38 @@ void LLLineEditor::updateHistory() // 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 && mText.length() && ( mLineHistory.empty() || getText() != mLineHistory.back() ) ) + if( mHaveHistory && getLength() ) { - // discard possible empty line at the end of the history - // inserted by setText() - if( !mLineHistory.back().length() ) + 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.pop_back(); + mLineHistory.push_back( getText() ); } - mLineHistory.insert( mLineHistory.end(), getText() ); - mCurrentHistoryLine = mLineHistory.size() - 1; + + // 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); - setTextPadding(mTextPadLeft, mTextPadRight); // For clamping side-effect. + updateTextPadding(); // For clamping side-effect. setCursor(mCursorPos); // For clamping side-effect. } @@ -273,12 +328,23 @@ void LLLineEditor::setMaxTextLength(S32 max_text_length) mMaxLengthBytes = max_len; } +void LLLineEditor::getTextPadding(S32 *left, S32 *right) +{ + *left = mTextPadLeft; + *right = mTextPadRight; +} + void LLLineEditor::setTextPadding(S32 left, S32 right) { - mTextPadLeft = llclamp(left, 0, getRect().getWidth()); - mTextPadRight = llclamp(right, 0, getRect().getWidth()); - mMinHPixels = UI_LINEEDITOR_H_PAD + mTextPadLeft; - mMaxHPixels = getRect().getWidth() - mMinHPixels - mTextPadRight; + mTextPadLeft = left; + mTextPadRight = right; + updateTextPadding(); +} + +void LLLineEditor::updateTextPadding() +{ + mTextLeftEdge = llclamp(mTextPadLeft, 0, getRect().getWidth()); + mTextRightEdge = getRect().getWidth() - llclamp(mTextPadRight, 0, getRect().getWidth()); } @@ -319,11 +385,8 @@ void LLLineEditor::setText(const LLStringExplicit &new_text) } setCursor(llmin((S32)mText.length(), getCursor())); - // Newly set text goes always in the last line of history. - // Possible empty strings (as with chat line) will be deleted later. - mLineHistory.insert( mLineHistory.end(), new_text ); // Set current history line to end of history. - mCurrentHistoryLine = mLineHistory.size() - 1; + mCurrentHistoryLine = mLineHistory.end() - 1; mPrevText = mText; } @@ -347,8 +410,8 @@ void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) mScrollHPos + mGLFont->charFromPixelOffset( wtext, mScrollHPos, - (F32)(local_mouse_x - mMinHPixels), - (F32)(mMaxHPixels - mMinHPixels + 1)); // min-max range is inclusive + (F32)(local_mouse_x - mTextLeftEdge), + (F32)(mTextRightEdge - mTextLeftEdge + 1)); // min-max range is inclusive setCursor(cursor_pos); } @@ -358,11 +421,11 @@ void LLLineEditor::setCursor( S32 pos ) mCursorPos = llclamp( pos, 0, mText.length()); S32 pixels_after_scroll = findPixelNearestPos(); - if( pixels_after_scroll > mMaxHPixels ) + 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)(mMaxHPixels - mMinHPixels + width_chars_to_left))); - S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mMaxHPixels - mMinHPixels - UI_LINEEDITOR_CURSOR_THICKNESS - UI_LINEEDITOR_H_PAD), mText.length(), getCursor()); + S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mTextRightEdge - mTextLeftEdge + width_chars_to_left))); + S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), getCursor()); if (old_cursor_pos == last_visible_char) { mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD)); @@ -439,6 +502,7 @@ void LLLineEditor::selectAll() BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask) { setFocus( TRUE ); + mTripleClickTimer.setTimerExpirySec(TRIPLE_CLICK_INTERVAL); if (mSelectionEnd == 0 && mSelectionStart == mText.length()) { @@ -452,19 +516,19 @@ BOOL LLLineEditor::handleDoubleClick(S32 x, S32 y, MASK mask) BOOL doSelectAll = TRUE; // Select the word we're on - if( LLTextEditor::isPartOfWord( wtext[mCursorPos] ) ) + if( LLWStringUtil::isPartOfWord( wtext[mCursorPos] ) ) { S32 old_selection_start = mLastSelectionStart; S32 old_selection_end = mLastSelectionEnd; // Select word the cursor is over - while ((mCursorPos > 0) && LLTextEditor::isPartOfWord( wtext[mCursorPos-1] )) + while ((mCursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[mCursorPos-1] )) { // Find the start of the word mCursorPos--; } startSelection(); - while ((mCursorPos < (S32)wtext.length()) && LLTextEditor::isPartOfWord( wtext[mCursorPos] ) ) + while ((mCursorPos < (S32)wtext.length()) && LLWStringUtil::isPartOfWord( wtext[mCursorPos] ) ) { // Find the end of the word mCursorPos++; } @@ -502,18 +566,13 @@ BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask) { return TRUE; } - if (mSelectAllonFocusReceived - && gFocusMgr.getKeyboardFocus() != this) - { - setFocus( TRUE ); - } - else + + if (!mSelectAllonFocusReceived + || gFocusMgr.getKeyboardFocus() == this) { mLastSelectionStart = -1; mLastSelectionStart = -1; - setFocus( TRUE ); - if (mask & MASK_SHIFT) { // Handle selection extension @@ -555,21 +614,37 @@ BOOL LLLineEditor::handleMouseDown(S32 x, S32 y, MASK mask) } else { - // Save selection for word/line selecting on double-click - mLastSelectionStart = mSelectionStart; - mLastSelectionEnd = mSelectionEnd; + 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(); + // 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; } @@ -611,17 +686,17 @@ BOOL LLLineEditor::handleHover(S32 x, S32 y, MASK mask) S32 increment = llround(mScrollTimer.getElapsedTimeF32() / AUTO_SCROLL_TIME); mScrollTimer.reset(); mScrollTimer.setTimerExpirySec(AUTO_SCROLL_TIME); - if( (x < mMinHPixels) && (mScrollHPos > 0 ) ) + if( (x < mTextLeftEdge) && (mScrollHPos > 0 ) ) { // Scroll to the left mScrollHPos = llclamp(mScrollHPos - increment, 0, mText.length()); } else - if( (x > mMaxHPixels) && (mCursorPos < (S32)mText.length()) ) + 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 >= mMaxHPixels ) + if( pixels_after_scrolling_one_char >= mTextRightEdge ) { // ...scroll to the right mScrollHPos = llclamp(mScrollHPos + increment, 0, mText.length()); @@ -683,7 +758,10 @@ BOOL LLLineEditor::handleMouseUp(S32 x, S32 y, MASK mask) // 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; } @@ -783,7 +861,7 @@ S32 LLLineEditor::prevWordPos(S32 cursorPos) const { cursorPos--; } - while( (cursorPos > 0) && LLTextEditor::isPartOfWord( wtext[cursorPos-1] ) ) + while( (cursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[cursorPos-1] ) ) { cursorPos--; } @@ -793,7 +871,7 @@ S32 LLLineEditor::prevWordPos(S32 cursorPos) const S32 LLLineEditor::nextWordPos(S32 cursorPos) const { const LLWString& wtext = mText.getWString(); - while( (cursorPos < getLength()) && LLTextEditor::isPartOfWord( wtext[cursorPos] ) ) + while( (cursorPos < getLength()) && LLWStringUtil::isPartOfWord( wtext[cursorPos] ) ) { cursorPos++; } @@ -936,7 +1014,7 @@ void LLLineEditor::cut() else if( mKeystrokeCallback ) { - mKeystrokeCallback( this, mCallbackUserData ); + mKeystrokeCallback( this ); } } } @@ -1057,7 +1135,7 @@ void LLLineEditor::pasteHelper(bool is_primary) else if( mKeystrokeCallback ) { - mKeystrokeCallback( this, mCallbackUserData ); + mKeystrokeCallback( this ); } } } @@ -1203,9 +1281,9 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) case KEY_UP: if( mHaveHistory && ( MASK_CONTROL == mask ) ) { - if( mCurrentHistoryLine > 0 ) + if( mCurrentHistoryLine > mLineHistory.begin() ) { - mText.assign( mLineHistory[ --mCurrentHistoryLine ] ); + mText.assign( *(--mCurrentHistoryLine) ); setCursor(llmin((S32)mText.length(), getCursor())); } else @@ -1220,9 +1298,9 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) case KEY_DOWN: if( mHaveHistory && ( MASK_CONTROL == mask ) ) { - if( !mLineHistory.empty() && mCurrentHistoryLine < mLineHistory.size() - 1 ) + if( !mLineHistory.empty() && mCurrentHistoryLine < mLineHistory.end() - 1 ) { - mText.assign( mLineHistory[ ++mCurrentHistoryLine ] ); + mText.assign( *(++mCurrentHistoryLine) ); setCursor(llmin((S32)mText.length(), getCursor())); } else @@ -1370,7 +1448,7 @@ BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask ) { if (mKeystrokeCallback) { - mKeystrokeCallback(this, mCallbackUserData); + mKeystrokeCallback(this); } } } @@ -1420,7 +1498,7 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char) { // 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 - mKeystrokeCallback( this, mCallbackUserData ); + mKeystrokeCallback( this ); } } } @@ -1461,16 +1539,58 @@ void LLLineEditor::doDelete() { if( mKeystrokeCallback ) { - mKeystrokeCallback( this, mCallbackUserData ); + mKeystrokeCallback( this ); } } } } +void LLLineEditor::drawBackground() +{ + bool has_focus = hasFocus(); + LLUIImage* image; + if ( mReadOnly ) + { + image = mBgImageDisabled; + } + else if ( has_focus ) + { + image = mBgImageFocused; + } + else + { + image = mBgImage; + } + + F32 alpha = getDrawContext().mAlpha; + // 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); +} + void LLLineEditor::draw() { + F32 alpha = getDrawContext().mAlpha; S32 text_len = mText.length(); + static LLUICachedControl<S32> lineeditor_cursor_thickness ("UILineEditorCursorThickness", 0); + static LLUICachedControl<F32> preedit_marker_brightness ("UIPreeditMarkerBrightness", 0); + static LLUICachedControl<S32> preedit_marker_gap ("UIPreeditMarkerGap", 0); + static LLUICachedControl<S32> preedit_marker_position ("UIPreeditMarkerPosition", 0); + static LLUICachedControl<S32> preedit_marker_thickness ("UIPreeditMarkerThickness", 0); + static LLUICachedControl<F32> preedit_standout_brightness ("UIPreeditStandoutBrightness", 0); + static LLUICachedControl<S32> preedit_standout_gap ("UIPreeditStandoutGap", 0); + static LLUICachedControl<S32> preedit_standout_position ("UIPreeditStandoutPosition", 0); + static LLUICachedControl<S32> preedit_standout_thickness ("UIPreeditStandoutThickness", 0); std::string saved_text; if (mDrawAsterixes) @@ -1488,37 +1608,14 @@ void LLLineEditor::draw() LLRect background( 0, getRect().getHeight(), getRect().getWidth(), 0 ); background.stretch( -mBorderThickness ); - LLColor4 bg_color = mReadOnlyBgColor; - -#if 0 // for when we're ready for image art. - if( hasFocus()) - { - mImage->drawBorder(0, 0, getRect().getWidth(), getRect().getHeight(), gFocusMgr.getFocusColor(), gFocusMgr.getFocusFlashWidth()); - } - mImage->draw(getLocalRect(), mReadOnly ? mReadOnlyBgColor : mWriteableBgColor ); -#else // the old programmer art. - // drawing solids requires texturing be disabled - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - // draw background for text - if( !mReadOnly ) - { - if( gFocusMgr.getKeyboardFocus() == this ) - { - bg_color = mFocusBgColor; - } - else - { - bg_color = mWriteableBgColor; - } - } - gl_rect_2d(background, bg_color); - } -#endif + S32 lineeditor_v_pad = llround((background.getHeight() - mGLFont->getLineHeight())/2); + drawBackground(); + // draw text - S32 cursor_bottom = background.mBottom + 1; + // 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; @@ -1526,19 +1623,21 @@ void LLLineEditor::draw() { if (!getTentative()) { - text_color = mFgColor; + text_color = mFgColor.get(); } else { - text_color = mTentativeFgColor; + text_color = mTentativeFgColor.get(); } } else { - text_color = mReadOnlyFgColor; + text_color = mReadOnlyFgColor.get(); } - LLColor4 label_color = mTentativeFgColor; - + 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. @@ -1556,27 +1655,29 @@ void LLLineEditor::draw() } 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 + bg_color * (1 - PREEDIT_STANDOUT_BRIGHTNESS)).setAlpha(1.0f)); + 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 + bg_color * (1 - PREEDIT_MARKER_BRIGHTNESS)).setAlpha(1.0f)); + 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)mMinHPixels; - F32 text_bottom = (F32)background.mBottom + (F32)UI_LINEEDITOR_V_PAD; + F32 rendered_pixels_right = (F32)mTextLeftEdge; + F32 text_bottom = (F32)background.mBottom + (F32)lineeditor_v_pad; if( (gFocusMgr.getKeyboardFocus() == this) && hasSelection() ) { @@ -1601,32 +1702,36 @@ void LLLineEditor::draw() rendered_pixels_right, text_bottom, text_color, LLFontGL::LEFT, LLFontGL::BOTTOM, - LLFontGL::NORMAL, + 0, + LLFontGL::NO_SHADOW, select_left - mScrollHPos, - mMaxHPixels - llround(rendered_pixels_right), + mTextRightEdge - llround(rendered_pixels_right), &rendered_pixels_right); } - if( (rendered_pixels_right < (F32)mMaxHPixels) && (rendered_text < text_len) ) + if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) ) { - LLColor4 color(1.f - bg_color.mV[0], 1.f - bg_color.mV[1], 1.f - bg_color.mV[2], 1.f); + 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, mMaxHPixels - llround(rendered_pixels_right)); + width = llmin(width, mTextRightEdge - llround(rendered_pixels_right)); gl_rect_2d(llround(rendered_pixels_right), cursor_top, llround(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, - LLColor4( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], 1 ), + tmp_color, LLFontGL::LEFT, LLFontGL::BOTTOM, - LLFontGL::NORMAL, + 0, + LLFontGL::NO_SHADOW, select_right - mScrollHPos - rendered_text, - mMaxHPixels - llround(rendered_pixels_right), + mTextRightEdge - llround(rendered_pixels_right), &rendered_pixels_right); } - if( (rendered_pixels_right < (F32)mMaxHPixels) && (rendered_text < text_len) ) + if( (rendered_pixels_right < (F32)mTextRightEdge) && (rendered_text < text_len) ) { // unselected, right side mGLFont->render( @@ -1634,9 +1739,10 @@ void LLLineEditor::draw() rendered_pixels_right, text_bottom, text_color, LLFontGL::LEFT, LLFontGL::BOTTOM, - LLFontGL::NORMAL, + 0, + LLFontGL::NO_SHADOW, S32_MAX, - mMaxHPixels - llround(rendered_pixels_right), + mTextRightEdge - llround(rendered_pixels_right), &rendered_pixels_right); } } @@ -1647,28 +1753,29 @@ void LLLineEditor::draw() rendered_pixels_right, text_bottom, text_color, LLFontGL::LEFT, LLFontGL::BOTTOM, - LLFontGL::NORMAL, + 0, + LLFontGL::NO_SHADOW, S32_MAX, - mMaxHPixels - llround(rendered_pixels_right), + mTextRightEdge - llround(rendered_pixels_right), &rendered_pixels_right); } -#if 0 // for when we're ready for image art. +#if 1 // for when we're ready for image art. mBorder->setVisible(FALSE); // no more programmatic art. #endif // If we're editing... - if( gFocusMgr.getKeyboardFocus() == this) + if( hasFocus()) { //mBorder->setVisible(TRUE); // ok, programmer art just this once. // (Flash the cursor every half second) - if (gShowTextEditCursor && !mReadOnly) + if (!mReadOnly && gFocusMgr.getAppHasFocus()) { F32 elapsed = mKeystrokeTimer.getElapsedTimeF32(); if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) { S32 cursor_left = findPixelNearestPos(); - cursor_left -= UI_LINEEDITOR_CURSOR_THICKNESS / 2; - S32 cursor_right = cursor_left + UI_LINEEDITOR_CURSOR_THICKNESS; + 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(" "))); @@ -1681,17 +1788,19 @@ void LLLineEditor::draw() cursor_right, cursor_bottom, text_color); if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) { - mGLFont->render(mText, getCursor(), (F32)(cursor_left + UI_LINEEDITOR_CURSOR_THICKNESS / 2), text_bottom, - LLColor4( 1.f - text_color.mV[0], 1.f - text_color.mV[1], 1.f - text_color.mV[2], 1 ), + 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, - LLFontGL::NORMAL, + 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 = getScreenRect(); - LLCoordGL ime_pos( screen_pos.mLeft + pixels_after_scroll, screen_pos.mTop - UI_LINEEDITOR_V_PAD ); + 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::sGLScaleFactor.mV[VX]); ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]); @@ -1705,13 +1814,14 @@ void LLLineEditor::draw() if (0 == mText.length() && mReadOnly) { mGLFont->render(mLabel.getWString(), 0, - mMinHPixels, (F32)text_bottom, + mTextLeftEdge, (F32)text_bottom, label_color, LLFontGL::LEFT, LLFontGL::BOTTOM, - LLFontGL::NORMAL, + 0, + LLFontGL::NO_SHADOW, S32_MAX, - mMaxHPixels - llround(rendered_pixels_right), + mTextRightEdge - llround(rendered_pixels_right), &rendered_pixels_right, FALSE); } @@ -1729,13 +1839,14 @@ void LLLineEditor::draw() if (0 == mText.length()) { mGLFont->render(mLabel.getWString(), 0, - mMinHPixels, (F32)text_bottom, + mTextLeftEdge, (F32)text_bottom, label_color, LLFontGL::LEFT, LLFontGL::BOTTOM, - LLFontGL::NORMAL, + 0, + LLFontGL::NO_SHADOW, S32_MAX, - mMaxHPixels - llround(rendered_pixels_right), + mTextRightEdge - llround(rendered_pixels_right), &rendered_pixels_right, FALSE); } // Draw children (border) @@ -1753,7 +1864,7 @@ void LLLineEditor::draw() S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) const { S32 dpos = getCursor() - mScrollHPos + cursor_offset; - S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mMinHPixels; + S32 result = mGLFont->getWidth(mText.getWString().c_str(), mScrollHPos, dpos) + mTextLeftEdge; return result; } @@ -1849,7 +1960,7 @@ void LLLineEditor::setRect(const LLRect& rect) } } -void LLLineEditor::setPrevalidate(BOOL (*func)(const LLWString &)) +void LLLineEditor::setPrevalidate(LLLinePrevalidateFunc func) { mPrevalidateFunc = func; updateAllowingLanguageInput(); @@ -2095,20 +2206,28 @@ BOOL LLLineEditor::prevalidateAlphaNumSpace(const LLWString &str) return rv; } +// Used for most names of things stored on the server, due to old file-formats +// that used the pipe (|) for multiline text storage. Examples include +// inventory item names, parcel names, object names, etc. // static -BOOL LLLineEditor::prevalidatePrintableNotPipe(const LLWString &str) +BOOL LLLineEditor::prevalidateASCIIPrintableNoPipe(const LLWString &str) { BOOL rv = TRUE; S32 len = str.length(); if(len == 0) return rv; while(len--) { - if('|' == str[len]) + llwchar wc = str[len]; + if (wc < 0x20 + || wc > 0x7f + || wc == '|') { rv = FALSE; break; } - if(!((' ' == str[len]) || LLStringOps::isAlnum((char)str[len]) || LLStringOps::isPunct((char)str[len]))) + if(!(wc == ' ' + || LLStringOps::isAlnum((char)wc) + || LLStringOps::isPunct((char)wc) ) ) { rv = FALSE; break; @@ -2118,15 +2237,19 @@ BOOL LLLineEditor::prevalidatePrintableNotPipe(const LLWString &str) } +// Used for avatar names // static -BOOL LLLineEditor::prevalidatePrintableNoSpace(const LLWString &str) +BOOL LLLineEditor::prevalidateASCIIPrintableNoSpace(const LLWString &str) { BOOL rv = TRUE; S32 len = str.length(); if(len == 0) return rv; while(len--) { - if(LLStringOps::isSpace(str[len])) + llwchar wc = str[len]; + if (wc < 0x20 + || wc > 0x7f + || LLStringOps::isSpace(wc)) { rv = FALSE; break; @@ -2141,6 +2264,7 @@ BOOL LLLineEditor::prevalidatePrintableNoSpace(const LLWString &str) return rv; } + // static BOOL LLLineEditor::prevalidateASCII(const LLWString &str) { @@ -2169,244 +2293,11 @@ void LLLineEditor::setSelectAllonFocusReceived(BOOL b) } -void LLLineEditor::setKeystrokeCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data)) -{ - mKeystrokeCallback = keystroke_callback; -} - -// virtual -LLXMLNodePtr LLLineEditor::getXML(bool save_children) const +void LLLineEditor::setKeystrokeCallback(callback_t callback, void* user_data) { - LLXMLNodePtr node = LLUICtrl::getXML(); - - node->createChild("max_length", TRUE)->setIntValue(mMaxLengthBytes); - - node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont)); - - if (mBorder) - { - std::string bevel; - switch(mBorder->getBevel()) - { - default: - case LLViewBorder::BEVEL_NONE: bevel = "none"; break; - case LLViewBorder::BEVEL_IN: bevel = "in"; break; - case LLViewBorder::BEVEL_OUT: bevel = "out"; break; - case LLViewBorder::BEVEL_BRIGHT:bevel = "bright"; break; - } - node->createChild("bevel_style", TRUE)->setStringValue(bevel); - - std::string style; - switch(mBorder->getStyle()) - { - default: - case LLViewBorder::STYLE_LINE: style = "line"; break; - case LLViewBorder::STYLE_TEXTURE: style = "texture"; break; - } - node->createChild("border_style", TRUE)->setStringValue(style); - - node->createChild("border_thickness", TRUE)->setIntValue(mBorder->getBorderWidth()); - } - - if (!mLabel.empty()) - { - node->createChild("label", TRUE)->setStringValue(mLabel.getString()); - } - - node->createChild("select_all_on_focus_received", TRUE)->setBoolValue(mSelectAllonFocusReceived); - - node->createChild("handle_edit_keys_directly", TRUE)->setBoolValue(mHandleEditKeysDirectly ); - - addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor"); - addColorXML(node, mFgColor, "text_color", "TextFgColor"); - addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor"); - addColorXML(node, mTentativeFgColor, "text_tentative_color", "TextFgTentativeColor"); - addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor"); - addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor"); - addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor"); - - node->createChild("select_on_focus", TRUE)->setBoolValue(mSelectAllonFocusReceived ); - - return node; + mKeystrokeCallback = boost::bind(callback, _1, user_data); } -// static -LLView* LLLineEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("line_editor"); - node->getAttributeString("name", name); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - S32 max_text_length = 128; - node->getAttributeS32("max_length", max_text_length); - - LLFontGL* font = LLView::selectFont(node); - - std::string text = node->getTextContents().substr(0, max_text_length - 1); - - LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_IN; - LLViewBorder::getBevelFromAttribute(node, bevel_style); - - LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE; - std::string border_string; - node->getAttributeString("border_style", border_string); - LLStringUtil::toLower(border_string); - - if (border_string == "texture") - { - border_style = LLViewBorder::STYLE_TEXTURE; - } - - S32 border_thickness = 1; - node->getAttributeS32("border_thickness", border_thickness); - - LLUICtrlCallback commit_callback = NULL; - - LLLineEditor* line_editor = new LLLineEditor(name, - rect, - text, - font, - max_text_length, - commit_callback, - NULL, - NULL, - NULL, - NULL, - bevel_style, - border_style, - border_thickness); - - std::string label; - if(node->getAttributeString("label", label)) - { - line_editor->setLabel(label); - } - BOOL select_all_on_focus_received = FALSE; - if (node->getAttributeBOOL("select_all_on_focus_received", select_all_on_focus_received)) - { - line_editor->setSelectAllonFocusReceived(select_all_on_focus_received); - } - BOOL handle_edit_keys_directly = FALSE; - if (node->getAttributeBOOL("handle_edit_keys_directly", handle_edit_keys_directly)) - { - line_editor->setHandleEditKeysDirectly(handle_edit_keys_directly); - } - BOOL commit_on_focus_lost = TRUE; - if (node->getAttributeBOOL("commit_on_focus_lost", commit_on_focus_lost)) - { - line_editor->setCommitOnFocusLost(commit_on_focus_lost); - } - - line_editor->setColorParameters(node); - - if(node->hasAttribute("select_on_focus")) - { - BOOL selectall = FALSE; - node->getAttributeBOOL("select_on_focus", selectall); - line_editor->setSelectAllonFocusReceived(selectall); - } - - std::string prevalidate; - if(node->getAttributeString("prevalidate", prevalidate)) - { - LLStringUtil::toLower(prevalidate); - - if ("ascii" == prevalidate) - { - line_editor->setPrevalidate( LLLineEditor::prevalidateASCII ); - } - else if ("float" == prevalidate) - { - line_editor->setPrevalidate( LLLineEditor::prevalidateFloat ); - } - else if ("int" == prevalidate) - { - line_editor->setPrevalidate( LLLineEditor::prevalidateInt ); - } - else if ("positive_s32" == prevalidate) - { - line_editor->setPrevalidate( LLLineEditor::prevalidatePositiveS32 ); - } - else if ("non_negative_s32" == prevalidate) - { - line_editor->setPrevalidate( LLLineEditor::prevalidateNonNegativeS32 ); - } - else if ("alpha_num" == prevalidate) - { - line_editor->setPrevalidate( LLLineEditor::prevalidateAlphaNum ); - } - else if ("alpha_num_space" == prevalidate) - { - line_editor->setPrevalidate( LLLineEditor::prevalidateAlphaNumSpace ); - } - else if ("printable_not_pipe" == prevalidate) - { - line_editor->setPrevalidate( LLLineEditor::prevalidatePrintableNotPipe ); - } - else if ("printable_no_space" == prevalidate) - { - line_editor->setPrevalidate( LLLineEditor::prevalidatePrintableNoSpace ); - } - } - - line_editor->initFromXML(node, parent); - - return line_editor; -} - -//static -void LLLineEditor::cleanupLineEditor() -{ - sImage = NULL; -} - -/* static */ -LLPointer<LLUIImage> LLLineEditor::parseImage(std::string name, LLXMLNodePtr from, LLPointer<LLUIImage> def) -{ - std::string xml_name; - if (from->hasAttribute(name.c_str())) from->getAttributeString(name.c_str(), xml_name); - if (xml_name == LLStringUtil::null) return def; - LLPointer<LLUIImage> image = LLUI::getUIImage(xml_name); - return image.isNull() ? def : image; -} - -void LLLineEditor::setColorParameters(LLXMLNodePtr node) -{ - // overrides default image if supplied. - mImage = parseImage(std::string("image"), node, mImage); - - LLColor4 color; - if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color)) - { - setCursorColor(color); - } - if(node->hasAttribute("text_color")) - { - LLUICtrlFactory::getAttributeColor(node,"text_color", color); - setFgColor(color); - } - if(node->hasAttribute("text_readonly_color")) - { - LLUICtrlFactory::getAttributeColor(node,"text_readonly_color", color); - setReadOnlyFgColor(color); - } - if (LLUICtrlFactory::getAttributeColor(node,"text_tentative_color", color)) - { - setTentativeFgColor(color); - } - if(node->hasAttribute("bg_readonly_color")) - { - LLUICtrlFactory::getAttributeColor(node,"bg_readonly_color", color); - setReadOnlyBgColor(color); - } - if(node->hasAttribute("bg_writeable_color")) - { - LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color); - setWriteableBgColor(color); - } -} BOOL LLLineEditor::setTextArg( const std::string& key, const LLStringExplicit& text ) { @@ -2429,13 +2320,19 @@ void LLLineEditor::updateAllowingLanguageInput() // 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 && mPrevalidateFunc == NULL) { - getWindow()->allowLanguageTextInput(this, TRUE); + window->allowLanguageTextInput(this, TRUE); } else { - getWindow()->allowLanguageTextInput(this, FALSE); + window->allowLanguageTextInput(this, FALSE); } } @@ -2446,14 +2343,20 @@ BOOL LLLineEditor::hasPreeditString() const void LLLineEditor::resetPreedit() { - if (hasPreeditString()) + if (hasSelection()) { - if (hasSelection()) + if (hasPreeditString()) { llwarns << "Preedit and selection!" << llendl; deselect(); } - + else + { + deleteSelection(); + } + } + if (hasPreeditString()) + { const S32 preedit_pos = mPreeditPositions.front(); mText.erase(preedit_pos, mPreeditPositions.back() - preedit_pos); mText.insert(preedit_pos, mPreeditOverwrittenWString); @@ -2513,7 +2416,7 @@ void LLLineEditor::updatePreedit(const LLWString &preedit_string, mKeystrokeTimer.reset(); if( mKeystrokeCallback ) { - mKeystrokeCallback( this, mCallbackUserData ); + mKeystrokeCallback( this ); } } @@ -2655,147 +2558,3 @@ LLWString LLLineEditor::getConvertedText() const } return text; } - -static LLRegisterWidget<LLSearchEditor> r2("search_editor"); - - -LLSearchEditor::LLSearchEditor(const std::string& name, - const LLRect& rect, - S32 max_length_bytes, - void (*search_callback)(const std::string& search_string, void* user_data), - void* userdata) - : - LLUICtrl(name, rect, TRUE, NULL, userdata), - mSearchCallback(search_callback) -{ - LLRect search_edit_rect(0, getRect().getHeight(), getRect().getWidth(), 0); - mSearchEdit = new LLLineEditor(std::string("search edit"), - search_edit_rect, - LLStringUtil::null, - NULL, - max_length_bytes, - NULL, - onSearchEdit, - NULL, - this); - - mSearchEdit->setFollowsAll(); - mSearchEdit->setSelectAllonFocusReceived(TRUE); - - addChild(mSearchEdit); - - S32 btn_width = rect.getHeight(); // button is square, and as tall as search editor - LLRect clear_btn_rect(rect.getWidth() - btn_width, rect.getHeight(), rect.getWidth(), 0); - mClearSearchButton = new LLButton(std::string("clear search"), - clear_btn_rect, - std::string("icn_clear_lineeditor.tga"), - std::string("UIImgBtnCloseInactiveUUID"), - LLStringUtil::null, - onClearSearch, - this, - NULL, - LLStringUtil::null); - mClearSearchButton->setFollowsRight(); - mClearSearchButton->setFollowsTop(); - mClearSearchButton->setImageColor(LLUI::sColorsGroup->getColor("TextFgTentativeColor")); - mClearSearchButton->setTabStop(FALSE); - mSearchEdit->addChild(mClearSearchButton); - - mSearchEdit->setTextPadding(0, btn_width); -} - - -//virtual -void LLSearchEditor::setValue(const LLSD& value ) -{ - mSearchEdit->setValue(value); -} - -//virtual -LLSD LLSearchEditor::getValue() const -{ - return mSearchEdit->getValue(); -} - -//virtual -BOOL LLSearchEditor::setTextArg( const std::string& key, const LLStringExplicit& text ) -{ - return mSearchEdit->setTextArg(key, text); -} - -//virtual -BOOL LLSearchEditor::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - return mSearchEdit->setLabelArg(key, text); -} - -//virtual -void LLSearchEditor::clear() -{ - if (mSearchEdit) - { - mSearchEdit->clear(); - } -} - -void LLSearchEditor::draw() -{ - mClearSearchButton->setVisible(!mSearchEdit->getWText().empty()); - - LLUICtrl::draw(); -} - - -//static -void LLSearchEditor::onSearchEdit(LLLineEditor* caller, void* user_data ) -{ - LLSearchEditor* search_editor = (LLSearchEditor*)user_data; - if (search_editor->mSearchCallback) - { - search_editor->mSearchCallback(caller->getText(), search_editor->mCallbackUserData); - } -} - -//static -void LLSearchEditor::onClearSearch(void* user_data) -{ - LLSearchEditor* search_editor = (LLSearchEditor*)user_data; - - search_editor->setText(LLStringUtil::null); - if (search_editor->mSearchCallback) - { - search_editor->mSearchCallback(LLStringUtil::null, search_editor->mCallbackUserData); - } -} - -// static -LLView* LLSearchEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("search_editor"); - node->getAttributeString("name", name); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - S32 max_text_length = 128; - node->getAttributeS32("max_length", max_text_length); - - std::string text = node->getValue().substr(0, max_text_length - 1); - - LLSearchEditor* search_editor = new LLSearchEditor(name, - rect, - max_text_length, - NULL, NULL); - - std::string label; - if(node->getAttributeString("label", label)) - { - search_editor->mSearchEdit->setLabel(label); - } - - search_editor->setText(text); - - search_editor->initFromXML(node, parent); - - return search_editor; -} diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index fc5fcc5b90..49e9539b16 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -51,39 +51,70 @@ #include "llviewborder.h" #include "llpreeditor.h" +#include <boost/function.hpp> class LLFontGL; class LLLineEditorRollback; class LLButton; -typedef BOOL (*LLLinePrevalidateFunc)(const LLWString &wstr); - +typedef boost::function<BOOL (const LLWString &wstr)> LLLinePrevalidateFunc; class LLLineEditor : public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor { - public: - LLLineEditor(const std::string& name, - const LLRect& rect, - const std::string& default_text = LLStringUtil::null, - const LLFontGL* glfont = NULL, - S32 max_length_bytes = 254, - void (*commit_callback)(LLUICtrl* caller, void* user_data) = NULL, - void (*keystroke_callback)(LLLineEditor* caller, void* user_data) = NULL, - void (*focus_lost_callback)(LLFocusableElement* caller, void* user_data) = NULL, - void* userdata = NULL, - LLLinePrevalidateFunc prevalidate_func = NULL, - LLViewBorder::EBevel border_bevel = LLViewBorder::BEVEL_IN, - LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE, - S32 border_thickness = 1); - virtual ~LLLineEditor(); + struct PrevalidateNamedFuncs + : public LLInitParam::TypeValuesHelper<LLLinePrevalidateFunc, PrevalidateNamedFuncs> - virtual LLXMLNodePtr getXML(bool save_children = true) const; - void setColorParameters(LLXMLNodePtr node); - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - static void cleanupLineEditor(); + { + static void declareValues(); + }; + + typedef boost::function<void (LLLineEditor* caller)> keystroke_callback_t; + + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<std::string> default_text; + Optional<S32> max_length_bytes; + + Optional<keystroke_callback_t> keystroke_callback; + + Optional<LLLinePrevalidateFunc, PrevalidateNamedFuncs> prevalidate_callback; + + Optional<LLViewBorder::Params> border; + + Optional<LLUIImage*> background_image, + background_image_disabled, + background_image_focused; + + Optional<bool> select_on_focus, + handle_edit_keys_directly, + revert_on_esc, + commit_on_focus_lost, + ignore_tab; + + // colors + Optional<LLUIColor> cursor_color, + text_color, + text_readonly_color, + text_tentative_color, + highlight_color, + preedit_bg_color; + + Optional<S32> text_pad_left, + text_pad_right; + + Ignored bg_visible; + + Params(); + }; +protected: + LLLineEditor(const Params&); + friend class LLUICtrlFactory; + friend class LLFloaterEditUI; +public: + virtual ~LLLineEditor(); // mousehandler overrides /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); @@ -131,20 +162,22 @@ public: virtual void setRect(const LLRect& rect); virtual BOOL acceptsTextInput() const; virtual void onCommit(); - virtual BOOL isDirty() const { return mText.getString() != mPrevText; } // Returns TRUE if user changed value at all - virtual void resetDirty() { mPrevText = mText.getString(); } // Clear dirty state + virtual BOOL isDirty() const; // Returns TRUE if user changed value at all + virtual void resetDirty(); // Clear dirty state // assumes UTF8 text - virtual void setValue(const LLSD& value ) { setText(value.asString()); } - virtual LLSD getValue() const { return LLSD(getText()); } + 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 ); 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 { return mText.getString(); } - const LLWString& getWText() const { return mText.getWString(); } + LLWString getWText() const { return mText.getWString(); } LLWString getConvertedText() const; // trimmed text with paragraphs converted to newlines S32 getLength() const { return mText.length(); } @@ -155,26 +188,21 @@ public: // Selects characters 'start' to 'end'. void setSelection(S32 start, S32 end); + virtual void getSelectionRange(S32 *position, S32 *length) const; void setCommitOnFocusLost( BOOL b ) { mCommitOnFocusLost = b; } void setRevertOnEsc( BOOL b ) { mRevertOnEsc = b; } void setCursorColor(const LLColor4& c) { mCursorColor = c; } - const LLColor4& getCursorColor() const { return mCursorColor; } + 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; } - void setWriteableBgColor( const LLColor4& c ) { mWriteableBgColor = c; } - void setReadOnlyBgColor( const LLColor4& c ) { mReadOnlyBgColor = c; } - void setFocusBgColor(const LLColor4& c) { mFocusBgColor = c; } - const LLColor4& getFgColor() const { return mFgColor; } - const LLColor4& getReadOnlyFgColor() const { return mReadOnlyFgColor; } - const LLColor4& getTentativeFgColor() const { return mTentativeFgColor; } - const LLColor4& getWriteableBgColor() const { return mWriteableBgColor; } - const LLColor4& getReadOnlyBgColor() const { return mReadOnlyBgColor; } - const LLColor4& getFocusBgColor() const { return mFocusBgColor; } + const LLColor4& getFgColor() const { return mFgColor.get(); } + const LLColor4& getReadOnlyFgColor() const { return mReadOnlyFgColor.get(); } + const LLColor4& getTentativeFgColor() const { return mTentativeFgColor.get(); } void setIgnoreArrowKeys(BOOL b) { mIgnoreArrowKeys = b; } void setIgnoreTab(BOOL b) { mIgnoreTab = b; } @@ -193,22 +221,25 @@ public: void setHandleEditKeysDirectly( BOOL b ) { mHandleEditKeysDirectly = b; } void setSelectAllonFocusReceived(BOOL b); - - void setKeystrokeCallback(void (*keystroke_callback)(LLLineEditor* caller, void* user_data)); + + typedef boost::function<void (LLLineEditor* caller, void* user_data)> callback_t; + void setKeystrokeCallback(callback_t callback, void* user_data); void setMaxTextLength(S32 max_text_length); - void setTextPadding(S32 left, S32 right); // Used to specify room for children before or after text. + // 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( BOOL (*func)(const LLWString &) ); + void setPrevalidate( LLLinePrevalidateFunc func ); static BOOL prevalidateFloat(const LLWString &str ); static BOOL prevalidateInt(const LLWString &str ); static BOOL prevalidatePositiveS32(const LLWString &str); static BOOL prevalidateNonNegativeS32(const LLWString &str); static BOOL prevalidateAlphaNum(const LLWString &str ); static BOOL prevalidateAlphaNumSpace(const LLWString &str ); - static BOOL prevalidatePrintableNotPipe(const LLWString &str); - static BOOL prevalidatePrintableNoSpace(const LLWString &str); + static BOOL prevalidateASCIIPrintableNoPipe(const LLWString &str); + static BOOL prevalidateASCIIPrintableNoSpace(const LLWString &str); static BOOL prevalidateASCII(const LLWString &str); static BOOL postvalidateFloat(const std::string &str); @@ -233,6 +264,10 @@ private: 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 @@ -245,9 +280,9 @@ private: 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_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const; virtual S32 getPreeditFontSize() const; + virtual LLWString getPreeditString() const { return getWText(); } protected: LLUIString mText; // The string being edited. @@ -256,8 +291,9 @@ protected: // line history support: BOOL mHaveHistory; // flag for enabled line history - std::vector<std::string> mLineHistory; // line history storage - U32 mCurrentHistoryLine; // currently browsed history line + typedef std::vector<std::string> line_history_t; + line_history_t mLineHistory; // line history storage + line_history_t::iterator mCurrentHistoryLine; // currently browsed history line LLViewBorder* mBorder; const LLFontGL* mGLFont; @@ -267,13 +303,13 @@ protected: 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 mMinHPixels; - S32 mMaxHPixels; + 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; - void (*mKeystrokeCallback)( LLLineEditor* caller, void* userdata ); + keystroke_callback_t mKeystrokeCallback; BOOL mIsSelecting; // Selection for clipboard operations S32 mSelectionStart; @@ -283,18 +319,17 @@ protected: S32 mLastSelectionStart; S32 mLastSelectionEnd; - S32 (*mPrevalidateFunc)(const LLWString &str); + LLLinePrevalidateFunc mPrevalidateFunc; LLFrameTimer mKeystrokeTimer; + LLTimer mTripleClickTimer; - LLColor4 mCursorColor; - - LLColor4 mFgColor; - LLColor4 mReadOnlyFgColor; - LLColor4 mTentativeFgColor; - LLColor4 mWriteableBgColor; - LLColor4 mReadOnlyBgColor; - LLColor4 mFocusBgColor; + LLUIColor mCursorColor; + LLUIColor mFgColor; + LLUIColor mReadOnlyFgColor; + LLUIColor mTentativeFgColor; + LLUIColor mHighlightColor; // background for selected text + LLUIColor mPreeditBgColor; // preedit marker background color S32 mBorderThickness; @@ -314,13 +349,10 @@ protected: LLPreeditor::standouts_t mPreeditStandouts; private: - // Utility on top of LLUI::getUIImage, looks up a named image in a given XML node and returns it if possible - // or returns a given default image if anything in the process fails. - static LLPointer<LLUIImage> parseImage(std::string name, LLXMLNodePtr from, LLPointer<LLUIImage> def); - // Global instance used as default for member instance below. - static LLPointer<LLUIImage> sImage; // Instances that by default point to the statics but can be overidden in XML. - LLPointer<LLUIImage> mImage; + LLPointer<LLUIImage> mBgImage; + LLPointer<LLUIImage> mBgImageDisabled; + LLPointer<LLUIImage> mBgImageFocused; BOOL mReplaceNewlinesWithSpaces; // if false, will replace pasted newlines with paragraph symbol. @@ -363,45 +395,10 @@ private: }; // end class LLLineEditor - - -/* - * @brief A line editor with a button to clear it and a callback to call on every edit event. - */ -class LLSearchEditor : public LLUICtrl -{ -public: - LLSearchEditor(const std::string& name, - const LLRect& rect, - S32 max_length_bytes, - void (*search_callback)(const std::string& search_string, void* user_data), - void* userdata); - - virtual ~LLSearchEditor() {} - - /*virtual*/ void draw(); - - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - - void setText(const LLStringExplicit &new_text) { mSearchEdit->setText(new_text); } - - void setSearchCallback(void (*search_callback)(const std::string& search_string, void* user_data), void* data) { mSearchCallback = search_callback; mCallbackUserData = data; } - - // 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 clear(); - -private: - static void onSearchEdit(LLLineEditor* caller, void* user_data ); - static void onClearSearch(void* user_data); - - LLLineEditor* mSearchEdit; - class LLButton* mClearSearchButton; - void (*mSearchCallback)(const std::string& search_string, void* user_data); - -}; +// Build time optimization, generate once in .cpp file +#ifndef LLLINEEDITOR_CPP +extern template class LLLineEditor* LLView::getChild<class LLLineEditor>( + const std::string& name, BOOL recurse) const; +#endif #endif // LL_LINEEDITOR_ diff --git a/indra/llui/lllocalcliprect.cpp b/indra/llui/lllocalcliprect.cpp new file mode 100644 index 0000000000..43c21e250c --- /dev/null +++ b/indra/llui/lllocalcliprect.cpp @@ -0,0 +1,146 @@ +/** +* @file lllocalcliprect.cpp +* +* $LicenseInfo:firstyear=2009&license=viewergpl$ +* +* Copyright (c) 2009, Linden Research, Inc. +* +* Second Life Viewer Source Code +* The source code in this file ("Source Code") is provided by Linden Lab +* to you under the terms of the GNU General Public License, version 2.0 +* ("GPL"), unless you have obtained a separate licensing agreement +* ("Other License"), formally executed by you and Linden Lab. Terms of +* the GPL can be found in doc/GPL-license.txt in this distribution, or +* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 +* +* There are special exceptions to the terms and conditions of the GPL as +* it is applied to this Source Code. View the full text of the exception +* in the file doc/FLOSS-exception.txt in this software distribution, or +* online at +* http://secondlifegrid.net/programs/open_source/licensing/flossexception +* +* By copying, modifying or distributing this software, you acknowledge +* that you have read and understood your obligations described above, +* and agree to abide by those obligations. +* +* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO +* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, +* COMPLETENESS OR PERFORMANCE. +* $/LicenseInfo$ +*/ +#include "linden_common.h" + +#include "lllocalcliprect.h" + +#include "llfontgl.h" +#include "llgl.h" +#include "llui.h" + +#include <stack> + +//--------------------------------------------------------------------------- +// LLScreenClipRect +// implementation class in screen space +//--------------------------------------------------------------------------- +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<LLRect> sClipRectStack; +}; + +/*static*/ std::stack<LLRect> 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::sGLScaleFactor.mV[VX]); + y = llfloor(rect.mBottom * LLUI::sGLScaleFactor.mV[VY]); + w = llmax(0, llceil(rect.getWidth() * LLUI::sGLScaleFactor.mV[VX])) + 1; + h = llmax(0, llceil(rect.getHeight() * LLUI::sGLScaleFactor.mV[VY])) + 1; + glScissor( x,y,w,h ); + stop_glerror(); +} + +//--------------------------------------------------------------------------- +// LLLocalClipRect +//--------------------------------------------------------------------------- +LLLocalClipRect::LLLocalClipRect(const LLRect& rect, BOOL enabled /* = TRUE */) +{ + LLRect screen(rect.mLeft + LLFontGL::sCurOrigin.mX, + rect.mTop + LLFontGL::sCurOrigin.mY, + rect.mRight + LLFontGL::sCurOrigin.mX, + rect.mBottom + LLFontGL::sCurOrigin.mY); + mScreenClipRect = new LLScreenClipRect(screen, enabled); +} + +LLLocalClipRect::~LLLocalClipRect() +{ + delete mScreenClipRect; + mScreenClipRect = NULL; +} diff --git a/indra/llui/lllocalcliprect.h b/indra/llui/lllocalcliprect.h new file mode 100644 index 0000000000..cd0c55ca72 --- /dev/null +++ b/indra/llui/lllocalcliprect.h @@ -0,0 +1,53 @@ +/** +* @file lllocalcliprect.h +* +* $LicenseInfo:firstyear=2009&license=viewergpl$ +* +* Copyright (c) 2009, Linden Research, Inc. +* +* Second Life Viewer Source Code +* The source code in this file ("Source Code") is provided by Linden Lab +* to you under the terms of the GNU General Public License, version 2.0 +* ("GPL"), unless you have obtained a separate licensing agreement +* ("Other License"), formally executed by you and Linden Lab. Terms of +* the GPL can be found in doc/GPL-license.txt in this distribution, or +* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 +* +* There are special exceptions to the terms and conditions of the GPL as +* it is applied to this Source Code. View the full text of the exception +* in the file doc/FLOSS-exception.txt in this software distribution, or +* online at +* http://secondlifegrid.net/programs/open_source/licensing/flossexception +* +* By copying, modifying or distributing this software, you acknowledge +* that you have read and understood your obligations described above, +* and agree to abide by those obligations. +* +* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO +* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, +* COMPLETENESS OR PERFORMANCE. +* $/LicenseInfo$ +*/ +#ifndef LLLOCALCLIPRECT_H +#define LLLOCALCLIPRECT_H + +#include "llrect.h" // can't forward declare, it's templated + +// Clip rendering to a specific rectangle using GL scissor +// Just create one of these on the stack: +// { +// LLLocalClipRect(rect); +// draw(); +// } +class LLLocalClipRect +{ +public: + LLLocalClipRect(const LLRect& rect, BOOL enabled = TRUE); + ~LLLocalClipRect(); + +private: + // implementation class + class LLScreenClipRect* mScreenClipRect; +}; + +#endif diff --git a/indra/llui/llmenubutton.cpp b/indra/llui/llmenubutton.cpp new file mode 100644 index 0000000000..cdbd17e4dc --- /dev/null +++ b/indra/llui/llmenubutton.cpp @@ -0,0 +1,143 @@ +/** + * @file llbutton.cpp + * @brief LLButton base class + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llmenubutton.h" + +// Linden library includes +#include "llmenugl.h" +#include "llstring.h" +#include "v4color.h" + +static LLDefaultChildRegistry::Register<LLMenuButton> r("menu_button"); + + +LLMenuButton::Params::Params() +: menu_filename("menu_filename") +{ +} + + +LLMenuButton::LLMenuButton(const LLMenuButton::Params& p) +: LLButton(p), + mMenu(NULL), + mMenuVisibleLastFrame(false) +{ + std::string menu_filename = p.menu_filename; + + if (!menu_filename.empty()) + { + mMenu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>(menu_filename, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); + if (!mMenu) + { + llwarns << "Error loading menu_button menu" << llendl; + } + } +} + +void LLMenuButton::toggleMenu() +{ + if(!mMenu) + return; + + if (mMenu->getVisible() || mMenuVisibleLastFrame) + { + mMenu->setVisible(FALSE); + } + else + { + LLRect rect = getRect(); + //mMenu->needsArrange(); //so it recalculates the visible elements + LLMenuGL::showPopup(getParent(), mMenu, rect.mLeft, rect.mBottom); + } +} + + +void LLMenuButton::hideMenu() +{ + if(!mMenu) + return; + mMenu->setVisible(FALSE); +} + + +BOOL LLMenuButton::handleKeyHere(KEY key, MASK mask ) +{ + if( KEY_RETURN == key && mask == MASK_NONE && !gKeyboard->getKeyRepeated(key)) + { + toggleMenu(); + return TRUE; + } + + if (mMenu && mMenu->getVisible() && key == KEY_ESCAPE && mask == MASK_NONE) + { + mMenu->setVisible(FALSE); + return TRUE; + } + + return FALSE; +} + +BOOL LLMenuButton::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (hasTabStop() && !getIsChrome()) + { + setFocus(TRUE); + } + + toggleMenu(); + + if (getSoundFlags() & MOUSE_DOWN) + { + make_ui_sound("UISndClick"); + } + + return TRUE; +} + +void LLMenuButton::draw() +{ + //we save this off so next frame when we try to close it by + //button click, and it hides menus before we get to it, we know + mMenuVisibleLastFrame = mMenu && mMenu->getVisible(); + + if (mMenuVisibleLastFrame) + { + setForcePressedState(true); + } + + LLButton::draw(); + + setForcePressedState(false); +} + diff --git a/indra/llui/llmenubutton.h b/indra/llui/llmenubutton.h new file mode 100644 index 0000000000..d0e99d9f40 --- /dev/null +++ b/indra/llui/llmenubutton.h @@ -0,0 +1,70 @@ +/** + * @file llbutton.h + * @brief Header for buttons + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLMENUBUTTON_H +#define LL_LLMENUBUTTON_H + +#include "llbutton.h" + +class LLMenuGL; + +class LLMenuButton +: public LLButton +{ +public: + struct Params + : public LLInitParam::Block<Params, LLButton::Params> + { + // filename for it's toggleable menu + Optional<std::string> menu_filename; + + Params(); + }; + + void toggleMenu(); + /*virtual*/ void draw(); + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask ); + void hideMenu(); + LLMenuGL* getMenu() { return mMenu; } + +protected: + friend class LLUICtrlFactory; + LLMenuButton(const Params&); + +private: + LLMenuGL* mMenu; + bool mMenuVisibleLastFrame; +}; + + +#endif // LL_LLMENUBUTTON_H diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 0762d7c12d..c172a2b714 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -23,7 +23,7 @@ * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. - * + * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. @@ -46,21 +46,20 @@ #include "llmenugl.h" +#include "llgl.h" #include "llmath.h" #include "llrender.h" #include "llfocusmgr.h" -#include "llfont.h" #include "llcoord.h" #include "llwindow.h" #include "llcriticaldamp.h" #include "lluictrlfactory.h" +#include "llbutton.h" #include "llfontgl.h" #include "llresmgr.h" #include "llui.h" -#include "lluitrans.h" - #include "llstl.h" #include "v2math.h" @@ -78,7 +77,6 @@ S32 MENU_BAR_WIDTH = 0; ///============================================================================ const std::string SEPARATOR_NAME("separator"); -const std::string TEAROFF_SEPARATOR_LABEL( "~~~~~~~~~~~" ); const std::string SEPARATOR_LABEL( "-----------" ); const std::string VERTICAL_SEPARATOR_LABEL( "|" ); @@ -92,7 +90,6 @@ const U32 RIGHT_PAD_PIXELS = 2; const U32 RIGHT_WIDTH_PIXELS = 15; const U32 RIGHT_PLAIN_PIXELS = RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS; -const U32 ACCEL_PAD_PIXELS = 10; const U32 PLAIN_PAD_PIXELS = LEFT_PAD_PIXELS + LEFT_WIDTH_PIXELS + RIGHT_PAD_PIXELS + RIGHT_WIDTH_PIXELS; const U32 BRIEF_PAD_PIXELS = 2; @@ -101,8 +98,8 @@ const U32 SEPARATOR_HEIGHT_PIXELS = 8; const S32 TEAROFF_SEPARATOR_HEIGHT_PIXELS = 10; const S32 MENU_ITEM_PADDING = 4; -const std::string BOOLEAN_TRUE_PREFIX( "X" ); -const std::string BRANCH_SUFFIX( ">" ); +const std::string BOOLEAN_TRUE_PREFIX( "\xE2\x9C\x94" ); // U+2714 HEAVY CHECK MARK +const std::string BRANCH_SUFFIX( "\xE2\x96\xB6" ); // U+25B6 BLACK RIGHT-POINTING TRIANGLE const std::string ARROW_UP ("^^^^^^^"); const std::string ARROW_DOWN("vvvvvvv"); @@ -110,12 +107,6 @@ const F32 MAX_MOUSE_SLOPE_SUB_MENU = 0.9f; const S32 PIE_GESTURE_ACTIVATE_DISTANCE = 10; -LLColor4 LLMenuItemGL::sEnabledColor( 0.0f, 0.0f, 0.0f, 1.0f ); -LLColor4 LLMenuItemGL::sDisabledColor( 0.5f, 0.5f, 0.5f, 1.0f ); -LLColor4 LLMenuItemGL::sHighlightBackground( 0.0f, 0.0f, 0.7f, 1.0f ); -LLColor4 LLMenuItemGL::sHighlightForeground( 1.0f, 1.0f, 1.0f, 1.0f ); - -LLColor4 LLMenuGL::sDefaultBackgroundColor( 0.25f, 0.25f, 0.25f, 0.75f ); BOOL LLMenuGL::sKeyboardMode = FALSE; LLHandle<LLView> LLMenuHolderGL::sItemLastSelectedHandle; @@ -128,72 +119,109 @@ const F32 PIE_SHRINK_TIME = 0.2f; // time of transition between unbounded and bo const F32 ACTIVATE_HIGHLIGHT_TIME = 0.3f; +static MenuRegistry::Register<LLMenuItemGL> register_menu_item("menu_item"); +static MenuRegistry::Register<LLMenuItemSeparatorGL> register_separator("menu_item_separator"); +static MenuRegistry::Register<LLMenuItemCallGL> register_menu_item_call("menu_item_call"); +static MenuRegistry::Register<LLMenuItemCheckGL> register_menu_item_check("menu_item_check"); +// Created programmatically but we need to specify custom colors in xml +static MenuRegistry::Register<LLMenuItemTearOffGL> register_menu_item_tear_off("menu_item_tear_off"); +static MenuRegistry::Register<LLMenuGL> register_menu("menu"); + +static LLDefaultChildRegistry::Register<LLMenuGL> register_menu_default("menu"); + + + ///============================================================================ /// Class LLMenuItemGL ///============================================================================ +LLMenuItemGL::Params::Params() +: shortcut("shortcut"), + jump_key("jump_key", KEY_NONE), + use_mac_ctrl("use_mac_ctrl", 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") +{ + mouse_opaque = true; +} + // Default constructor -LLMenuItemGL::LLMenuItemGL( const std::string& name, const std::string& label, KEY key, MASK mask ) : - LLView( name, TRUE ), - mJumpKey(KEY_NONE), - mAcceleratorKey( key ), - mAcceleratorMask( mask ), +LLMenuItemGL::LLMenuItemGL(const LLMenuItemGL::Params& p) +: LLUICtrl(p), + mJumpKey(p.jump_key), mAllowKeyRepeat(FALSE), mHighlight( FALSE ), mGotHover( FALSE ), mBriefItem( FALSE ), - mFont( LLFontGL::getFontSansSerif() ), - mStyle(LLFontGL::NORMAL), - mDrawTextDisabled( 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()) { - setLabel( label ); -} - -// virtual -LLXMLNodePtr LLMenuItemGL::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLView::getXML(); - - node->createChild("type", TRUE)->setStringValue(getType()); - - node->createChild("label", TRUE)->setStringValue(mLabel); - - if (mAcceleratorKey != KEY_NONE) +#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) { - std::stringstream out; - if (mAcceleratorMask & MASK_CONTROL) - { - out << "control|"; - } - if (mAcceleratorMask & MASK_ALT) - { - out << "alt|"; - } - if (mAcceleratorMask & MASK_SHIFT) - { - out << "shift|"; - } - out << LLKeyboard::stringFromKey(mAcceleratorKey); - - node->createChild("shortcut", TRUE)->setStringValue(out.str()); - #ifdef LL_DARWIN - // Write in special tag if this key is really a ctrl combination on the Mac - if (mAcceleratorMask & MASK_MAC_CONTROL) + if ( useMacCtrl ) { - node->createChild("useMacCtrl", TRUE)->setBoolValue( TRUE ); + 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; +} - return node; +//virtual +void LLMenuItemGL::setValue(const LLSD& value) +{ + setLabel(value.asString()); } +//virtual BOOL LLMenuItemGL::handleAcceleratorKey(KEY key, MASK mask) { if( getEnabled() && (!gKeyboard->getKeyRepeated(key) || mAllowKeyRepeat) && (key == mAcceleratorKey) && (mask == (mAcceleratorMask & MASK_NORMALKEYS)) ) { - doIt(); + onCommit(); return TRUE; } return FALSE; @@ -206,6 +234,26 @@ BOOL LLMenuItemGL::handleHover(S32 x, S32 y, MASK mask) return TRUE; } +//virtual +BOOL LLMenuItemGL::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + return LLUICtrl::handleRightMouseDown(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 <LLKeyBinding*> *listp) @@ -255,47 +303,8 @@ BOOL LLMenuItemGL::addToAcceleratorList(std::list <LLKeyBinding*> *listp) // the current accelerator key and mask to the provided string. void LLMenuItemGL::appendAcceleratorString( std::string& st ) const { - // break early if this is a silly thing to do. - if( KEY_NONE == mAcceleratorKey ) - { - return; - } - - // Append any masks -#ifdef LL_DARWIN - // Standard Mac names for modifier keys in menu equivalents - // We could use the symbol characters, but they only exist in certain fonts. - if( mAcceleratorMask & MASK_CONTROL ) - { - if ( mAcceleratorMask & MASK_MAC_CONTROL ) - { - st.append( LLUITrans::getString("accel-mac-control") ); - } - else - { - st.append( LLUITrans::getString("accel-mac-command") ); // Symbol would be "\xE2\x8C\x98" - } - } - if( mAcceleratorMask & MASK_ALT ) - st.append( LLUITrans::getString("accel-mac-option") ); // Symbol would be "\xE2\x8C\xA5" - if( mAcceleratorMask & MASK_SHIFT ) - st.append( LLUITrans::getString("accel-mac-shift") ); // Symbol would be "\xE2\x8C\xA7" -#else - if( mAcceleratorMask & MASK_CONTROL ) - st.append( LLUITrans::getString("accel-win-control") ); - if( mAcceleratorMask & MASK_ALT ) - st.append( LLUITrans::getString("accel-win-alt") ); - if( mAcceleratorMask & MASK_SHIFT ) - st.append( LLUITrans::getString("accel-win-shift") ); -#endif - - std::string keystr = LLKeyboard::stringFromKey( mAcceleratorKey ); - if ((mAcceleratorMask & MASK_NORMALKEYS) && - (keystr[0] == '-' || keystr[0] == '=')) - { - st.append( " " ); - } - st.append( keystr ); + st = LLKeyboard::stringFromAccelerator( mAcceleratorMask, mAcceleratorKey ); + LL_DEBUGS("HotKeys") << "appendAcceleratorString: " << st << LL_ENDL; } void LLMenuItemGL::setJumpKey(KEY key) @@ -310,9 +319,20 @@ U32 LLMenuItemGL::getNominalHeight( void ) const return llround(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() +LLMenuGL* LLMenuItemGL::getMenu() const { return (LLMenuGL*) getParent(); } @@ -336,7 +356,7 @@ U32 LLMenuItemGL::getNominalWidth( void ) const if( KEY_NONE != mAcceleratorKey ) { - width += ACCEL_PAD_PIXELS; + width += getMenu()->getShortcutPad(); std::string temp; appendAcceleratorString( temp ); width += mFont->getWidth( temp ); @@ -354,15 +374,19 @@ void LLMenuItemGL::buildDrawLabel( void ) mDrawAccelLabel = st; } -void LLMenuItemGL::doIt( void ) +void LLMenuItemGL::onCommit( void ) { - // close all open menus by default - // if parent menu is actually visible (and we are not triggering menu item via accelerator) + // 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) @@ -372,6 +396,12 @@ void LLMenuItemGL::doIt( void ) { getMenu()->clearHoverItem(); } + + if (mHighlight != highlight) + { + dirtyRect(); + } + mHighlight = highlight; } @@ -402,7 +432,7 @@ BOOL LLMenuItemGL::handleKeyHere( KEY key, MASK mask ) // switch to keyboard navigation mode LLMenuGL::setKeyboardMode(TRUE); - doIt(); + onCommit(); return TRUE; } } @@ -410,25 +440,30 @@ BOOL LLMenuItemGL::handleKeyHere( KEY key, MASK mask ) return FALSE; } -BOOL LLMenuItemGL::handleMouseUp( S32 x, S32 y, MASK ) +BOOL LLMenuItemGL::handleMouseUp( S32 x, S32 y, MASK mask) { // switch to mouse navigation mode LLMenuGL::setKeyboardMode(FALSE); - doIt(); + onCommit(); make_ui_sound("UISndClickRelease"); - return TRUE; + return LLView::handleMouseUp(x, y, mask); } -BOOL LLMenuItemGL::handleMouseDown( S32 x, S32 y, MASK ) +BOOL LLMenuItemGL::handleMouseDown( S32 x, S32 y, MASK mask) { // switch to mouse navigation mode LLMenuGL::setKeyboardMode(FALSE); setHighlight(TRUE); - return 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 ) { @@ -439,55 +474,53 @@ void LLMenuItemGL::draw( void ) // let disabled items be highlighted, just don't draw them as such if( getEnabled() && getHighlight() && !mBriefItem) { - gGL.color4fv( sHighlightBackground.mV ); + int debug_count = 0; + if (dynamic_cast<LLMenuItemCallGL*>(this)) + debug_count++; + gGL.color4fv( mHighlightBackground.get().mV ); + gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); } LLColor4 color; - U8 font_style = mStyle; - if (getEnabled() && !mDrawTextDisabled ) - { - font_style |= LLFontGL::DROP_SHADOW_SOFT; - } - if ( getEnabled() && getHighlight() ) { - color = sHighlightForeground; + color = mHighlightForeground.get(); } else if( getEnabled() && !mDrawTextDisabled ) { - color = sEnabledColor; + color = mEnabledColor.get(); } else { - color = sDisabledColor; + color = mDisabledColor.get(); } // Draw the text on top. if (mBriefItem) { mFont->render( mLabel, 0, BRIEF_PAD_PIXELS / 2, 0, color, - LLFontGL::LEFT, LLFontGL::BOTTOM, font_style ); + 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) + 1.f, color, - LLFontGL::LEFT, LLFontGL::BOTTOM, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + 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) + 1.f, color, - LLFontGL::LEFT, LLFontGL::BOTTOM, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + 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) + 1.f, color, - LLFontGL::RIGHT, LLFontGL::BOTTOM, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + 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) + 1.f, color, - LLFontGL::RIGHT, LLFontGL::BOTTOM, font_style, S32_MAX, S32_MAX, NULL, FALSE ); + LLFontGL::RIGHT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE ); } } @@ -515,38 +548,40 @@ BOOL LLMenuItemGL::setLabelArg( const std::string& key, const LLStringExplicit& return TRUE; } +void LLMenuItemGL::handleVisibilityChange(BOOL new_visibility) +{ + if (getMenu()) + { + getMenu()->needsArrange(); + } + LLView::handleVisibilityChange(new_visibility); +} + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLMenuItemSeparatorGL // // This class represents a separator. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuItemSeparatorGL : public LLMenuItemGL +LLMenuItemSeparatorGL::Params::Params() { -public: - LLMenuItemSeparatorGL( const std::string &name = SEPARATOR_NAME ); - - virtual std::string getType() const { return "separator"; } - - // doIt() - do the primary funcationality of the menu item. - virtual void doIt( void ) {} - - 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); + name = "separator"; + label = SEPARATOR_LABEL; +} - virtual U32 getNominalHeight( void ) const { return SEPARATOR_HEIGHT_PIXELS; } -}; +LLMenuItemSeparatorGL::LLMenuItemSeparatorGL(const LLMenuItemSeparatorGL::Params& p) : + LLMenuItemGL( p ) +{ +} -LLMenuItemSeparatorGL::LLMenuItemSeparatorGL( const std::string &name ) : - LLMenuItemGL( name, SEPARATOR_LABEL ) +//virtual +U32 LLMenuItemSeparatorGL::getNominalHeight( void ) const { + return SEPARATOR_HEIGHT_PIXELS; } void LLMenuItemSeparatorGL::draw( void ) { - gGL.color4fv( getDisabledColor().mV ); + gGL.color4fv( mDisabledColor.get().mV ); const S32 y = getRect().getHeight() / 2; const S32 PAD = 6; gl_line_2d( PAD, y, getRect().getWidth() - PAD, y ); @@ -557,11 +592,14 @@ BOOL LLMenuItemSeparatorGL::handleMouseDown(S32 x, S32 y, MASK mask) LLMenuGL* parent_menu = getMenu(); if (y > getRect().getHeight() / 2) { - return parent_menu->handleMouseDown(x + getRect().mLeft, getRect().mTop + 1, mask); + // 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->handleMouseDown(x, prev_menu_item->getRect().getHeight(), mask) : FALSE; } else { - return parent_menu->handleMouseDown(x + getRect().mLeft, getRect().mBottom - 1, mask); + LLView* next_menu_item = parent_menu->findPrevSibling(this); + return next_menu_item ? next_menu_item->handleMouseDown(x, 0, mask) : FALSE; } } @@ -570,11 +608,13 @@ BOOL LLMenuItemSeparatorGL::handleMouseUp(S32 x, S32 y, MASK mask) LLMenuGL* parent_menu = getMenu(); if (y > getRect().getHeight() / 2) { - return parent_menu->handleMouseUp(x + getRect().mLeft, getRect().mTop + 1, mask); + LLView* prev_menu_item = parent_menu->findNextSibling(this); + return prev_menu_item ? prev_menu_item->handleMouseUp(x, prev_menu_item->getRect().getHeight(), mask) : FALSE; } else { - return parent_menu->handleMouseUp(x + getRect().mLeft, getRect().mBottom - 1, mask); + LLView* next_menu_item = parent_menu->findPrevSibling(this); + return next_menu_item ? next_menu_item->handleMouseUp(x, 0, mask) : FALSE; } } @@ -593,7 +633,6 @@ BOOL LLMenuItemSeparatorGL::handleHover(S32 x, S32 y, MASK mask) } } - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLMenuItemVerticalSeparatorGL // @@ -617,19 +656,19 @@ LLMenuItemVerticalSeparatorGL::LLMenuItemVerticalSeparatorGL( void ) //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLMenuItemTearOffGL //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -LLMenuItemTearOffGL::LLMenuItemTearOffGL(LLHandle<LLFloater> parent_floater_handle) : - LLMenuItemGL(std::string("tear off"), TEAROFF_SEPARATOR_LABEL), - mParentHandle(parent_floater_handle) +LLMenuItemTearOffGL::LLMenuItemTearOffGL(const LLMenuItemTearOffGL::Params& p) +: LLMenuItemGL(p), + mParentHandle(p.parent_floater_handle) { } -void LLMenuItemTearOffGL::doIt() +void LLMenuItemTearOffGL::onCommit() { if (getMenu()->getTornOff()) { LLTearOffMenu* torn_off_menu = (LLTearOffMenu*)(getMenu()->getParent()); - torn_off_menu->close(); + torn_off_menu->closeFloater(); } else { @@ -639,7 +678,7 @@ void LLMenuItemTearOffGL::doIt() getMenu()->highlightNextItem(this); } - getMenu()->arrange(); + getMenu()->needsArrange(); LLFloater* parent_floater = mParentHandle.get(); LLFloater* tear_off_menu = LLTearOffMenu::create(getMenu()); @@ -656,7 +695,7 @@ void LLMenuItemTearOffGL::doIt() tear_off_menu->setFocus(TRUE); } } - LLMenuItemGL::doIt(); + LLMenuItemGL::onCommit(); } void LLMenuItemTearOffGL::draw() @@ -664,17 +703,17 @@ void LLMenuItemTearOffGL::draw() // disabled items can be highlighted, but shouldn't render as such if( getEnabled() && getHighlight() && !isBriefItem()) { - gGL.color4fv( getHighlightBGColor().mV ); + gGL.color4fv( mHighlightBackground.get().mV ); gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); } if (getEnabled()) { - gGL.color4fv( getEnabledColor().mV ); + gGL.color4fv( mEnabledColor.get().mV ); } else { - gGL.color4fv( getDisabledColor().mV ); + gGL.color4fv( mDisabledColor.get().mV ); } const S32 y = getRect().getHeight() / 3; const S32 PAD = 6; @@ -697,11 +736,16 @@ U32 LLMenuItemTearOffGL::getNominalHeight( void ) const class LLMenuItemBlankGL : public LLMenuItemGL { public: - LLMenuItemBlankGL( void ) : LLMenuItemGL( LLStringUtil::null, LLStringUtil::null ) + struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params> { - setEnabled(FALSE); - } - virtual void doIt( void ) {} + Params() + { + name=""; + enabled = false; + } + }; + LLMenuItemBlankGL( const Params& p ) : LLMenuItemGL( p ) + {} virtual void draw( void ) {} }; @@ -710,238 +754,137 @@ public: /// Class LLMenuItemCallGL ///============================================================================ -LLMenuItemCallGL::LLMenuItemCallGL( const std::string& name, - const std::string& label, - menu_callback clicked_cb, - enabled_callback enabled_cb, - void* user_data, - KEY key, MASK mask, - BOOL enabled, - on_disabled_callback on_disabled_cb) : - LLMenuItemGL( name, label, key, mask ), - mCallback( clicked_cb ), - mEnabledCallback( enabled_cb ), - mLabelCallback(NULL), - mUserData( user_data ), - mOnDisabledCallback(on_disabled_cb) -{ - if(!enabled) setEnabled(FALSE); -} - -LLMenuItemCallGL::LLMenuItemCallGL( const std::string& name, - menu_callback clicked_cb, - enabled_callback enabled_cb, - void* user_data, - KEY key, MASK mask, - BOOL enabled, - on_disabled_callback on_disabled_cb) : - LLMenuItemGL( name, name, key, mask ), - mCallback( clicked_cb ), - mEnabledCallback( enabled_cb ), - mLabelCallback(NULL), - mUserData( user_data ), - mOnDisabledCallback(on_disabled_cb) -{ - if(!enabled) setEnabled(FALSE); -} - -LLMenuItemCallGL::LLMenuItemCallGL(const std::string& name, - const std::string& label, - menu_callback clicked_cb, - enabled_callback enabled_cb, - label_callback label_cb, - void* user_data, - KEY key, MASK mask, - BOOL enabled, - on_disabled_callback on_disabled_cb) : - LLMenuItemGL(name, label, key, mask), - mCallback(clicked_cb), - mEnabledCallback(enabled_cb), - mLabelCallback(label_cb), - mUserData(user_data), - mOnDisabledCallback(on_disabled_cb) -{ - if(!enabled) setEnabled(FALSE); -} - -LLMenuItemCallGL::LLMenuItemCallGL(const std::string& name, - menu_callback clicked_cb, - enabled_callback enabled_cb, - label_callback label_cb, - void* user_data, - KEY key, MASK mask, - BOOL enabled, - on_disabled_callback on_disabled_cb) : - LLMenuItemGL(name, name, key, mask), - mCallback(clicked_cb), - mEnabledCallback(enabled_cb), - mLabelCallback(label_cb), - mUserData(user_data), - mOnDisabledCallback(on_disabled_cb) -{ - if(!enabled) setEnabled(FALSE); -} - -void LLMenuItemCallGL::setEnabledControl(std::string enabled_control, LLView *context) -{ - // Register new listener - if (!enabled_control.empty()) - { - LLControlVariable *control = context->findControl(enabled_control); - if (!control) - { - context->addBoolControl(enabled_control, getEnabled()); - control = context->findControl(enabled_control); - llassert_always(control); - } - control->getSignal()->connect(boost::bind(&LLView::controlListener, _1, getHandle(), std::string("enabled"))); - setEnabled(control->getValue()); - } +LLMenuItemCallGL::LLMenuItemCallGL(const LLMenuItemCallGL::Params& p) +: LLMenuItemGL(p) +{ } -void LLMenuItemCallGL::setVisibleControl(std::string visible_control, LLView *context) +void LLMenuItemCallGL::initFromParams(const Params& p) { - // Register new listener - if (!visible_control.empty()) + if (p.on_visible.isProvided()) { - LLControlVariable *control = context->findControl(visible_control); - if (!control) + 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()) { - context->addBoolControl(visible_control, getVisible()); - control = context->findControl(visible_control); - llassert_always(control); + LLControlVariable* control = findControl(p.on_enable.control_name()); + if (control) + { + setEnabledControlVariable(control); + } } - control->getSignal()->connect(boost::bind(&LLView::controlListener, _1, getHandle(), std::string("visible"))); - setVisible(control->getValue()); } + if (p.on_click.isProvided()) + { + setCommitCallback(initCommitCallback(p.on_click)); + } + + LLUICtrl::initFromParams(p); } -// virtual -LLXMLNodePtr LLMenuItemCallGL::getXML(bool save_children) const +void LLMenuItemCallGL::onCommit( void ) { - LLXMLNodePtr node = LLMenuItemGL::getXML(); - - // Contents + // RN: menu item can be deleted in callback, so beware + getMenu()->setItemLastSelected( this ); + + LLMenuItemGL::onCommit(); +} - std::vector<LLListenerEntry> listeners = mDispatcher->getListeners(); - std::vector<LLListenerEntry>::iterator itor; - for (itor = listeners.begin(); itor != listeners.end(); ++itor) +void LLMenuItemCallGL::updateEnabled( void ) +{ + if (mEnableSignal.num_slots() > 0) { - std::string listener_name = findEventListener((LLSimpleListener*)itor->listener); - if (!listener_name.empty()) + bool enabled = mEnableSignal(this, LLSD()); + if (mEnabledControlVariable) { - LLXMLNodePtr child_node = node->createChild("on_click", FALSE); - child_node->createChild("function", TRUE)->setStringValue(listener_name); - child_node->createChild("filter", TRUE)->setStringValue(itor->filter.asString()); - child_node->createChild("userdata", TRUE)->setStringValue(itor->userdata.asString()); + if (!enabled) + { + // callback overrides control variable; this will call setEnabled() + mEnabledControlVariable->set(false); + } + } + else + { + setEnabled(enabled); } } - - return node; } -// doIt() - Call the callback provided -void LLMenuItemCallGL::doIt( void ) +void LLMenuItemCallGL::updateVisible( void ) { - // RN: menu item can be deleted in callback, so beware - getMenu()->setItemLastSelected( this ); - - if( mCallback ) + if (mVisibleSignal.num_slots() > 0) { - mCallback( mUserData ); + bool visible = mVisibleSignal(this, LLSD()); + setVisible(visible); } - LLPointer<LLEvent> fired_event = new LLEvent(this); - fireEvent(fired_event, "on_click"); - LLMenuItemGL::doIt(); } void LLMenuItemCallGL::buildDrawLabel( void ) { - LLPointer<LLEvent> fired_event = new LLEvent(this); - fireEvent(fired_event, "on_build"); - if( mEnabledCallback ) - { - setEnabled( mEnabledCallback( mUserData ) ); - } - if(mLabelCallback) - { - std::string label; - mLabelCallback(label, mUserData); - mLabel = label; - } + 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)) ) + if( (!gKeyboard->getKeyRepeated(key) || getAllowKeyRepeat()) && (key == mAcceleratorKey) && (mask == (mAcceleratorMask & MASK_NORMALKEYS)) ) { - LLPointer<LLEvent> fired_event = new LLEvent(this); - fireEvent(fired_event, "on_build"); - if( mEnabledCallback ) + updateEnabled(); + if (getEnabled()) { - setEnabled( mEnabledCallback( mUserData ) ); - } - if( !getEnabled() ) - { - if( mOnDisabledCallback ) - { - mOnDisabledCallback( mUserData ); - } + onCommit(); + return TRUE; } } - return LLMenuItemGL::handleAcceleratorKey(key, mask); + return FALSE; } +// handleRightMouseUp moved into base class LLMenuItemGL so clicks are +// handled for all menu item types + ///============================================================================ /// Class LLMenuItemCheckGL ///============================================================================ - -LLMenuItemCheckGL::LLMenuItemCheckGL ( const std::string& name, - const std::string& label, - menu_callback clicked_cb, - enabled_callback enabled_cb, - check_callback check_cb, - void* user_data, - KEY key, MASK mask ) : - LLMenuItemCallGL( name, label, clicked_cb, enabled_cb, user_data, key, mask ), - mCheckCallback( check_cb ), - mChecked(FALSE) +LLMenuItemCheckGL::LLMenuItemCheckGL (const LLMenuItemCheckGL::Params& p) +: LLMenuItemCallGL(p) { } -LLMenuItemCheckGL::LLMenuItemCheckGL ( const std::string& name, - menu_callback clicked_cb, - enabled_callback enabled_cb, - check_callback check_cb, - void* user_data, - KEY key, MASK mask ) : - LLMenuItemCallGL( name, name, clicked_cb, enabled_cb, user_data, key, mask ), - mCheckCallback( check_cb ), - mChecked(FALSE) +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); } -LLMenuItemCheckGL::LLMenuItemCheckGL ( const std::string& name, - const std::string& label, - menu_callback clicked_cb, - enabled_callback enabled_cb, - std::string control_name, - LLView *context, - void* user_data, - KEY key, MASK mask ) : - LLMenuItemCallGL( name, label, clicked_cb, enabled_cb, user_data, key, mask ), - mCheckCallback( NULL ), - mChecked(FALSE) +void LLMenuItemCheckGL::onCommit( void ) { - setControlName(control_name, context); + LLMenuItemCallGL::onCommit(); } //virtual void LLMenuItemCheckGL::setValue(const LLSD& value) { - mChecked = value.asBoolean(); - if(mChecked) + LLUICtrl::setValue(value); + if(value.asBoolean()) { mDrawBoolLabel = BOOLEAN_TRUE_PREFIX; } @@ -951,34 +894,21 @@ void LLMenuItemCheckGL::setValue(const LLSD& value) } } -void LLMenuItemCheckGL::setCheckedControl(std::string checked_control, LLView *context) -{ - // Register new listener - if (!checked_control.empty()) - { - LLControlVariable *control = context->findControl(checked_control); - if (!control) - { - context->addBoolControl(checked_control, mChecked); - control = context->findControl(checked_control); - llassert_always(control); - } - control->getSignal()->connect(boost::bind(&LLView::controlListener, _1, getHandle(), std::string("value"))); - mChecked = control->getValue(); - } -} - -// virtual -LLXMLNodePtr LLMenuItemCheckGL::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLMenuItemCallGL::getXML(); - return node; -} - // called to rebuild the draw label void LLMenuItemCheckGL::buildDrawLabel( void ) { - if(mChecked || (mCheckCallback && mCheckCallback( getUserData() ) ) ) + // 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 = BOOLEAN_TRUE_PREFIX; } @@ -989,95 +919,59 @@ void LLMenuItemCheckGL::buildDrawLabel( void ) LLMenuItemCallGL::buildDrawLabel(); } - ///============================================================================ -/// Class LLMenuItemToggleGL +/// Class LLMenuItemBranchGL ///============================================================================ - -LLMenuItemToggleGL::LLMenuItemToggleGL( const std::string& name, const std::string& label, BOOL* toggle, - KEY key, MASK mask ) : - LLMenuItemGL( name, label, key, mask ), - mToggle( toggle ) +LLMenuItemBranchGL::LLMenuItemBranchGL(const LLMenuItemBranchGL::Params& p) + : LLMenuItemGL(p) { -} - -LLMenuItemToggleGL::LLMenuItemToggleGL( const std::string& name, BOOL* toggle, - KEY key, MASK mask ) : - LLMenuItemGL( name, name, key, mask ), - mToggle( toggle ) -{ -} - - -// called to rebuild the draw label -void LLMenuItemToggleGL::buildDrawLabel( void ) -{ - if( *mToggle ) - { - mDrawBoolLabel = BOOLEAN_TRUE_PREFIX; - } - else + LLMenuGL* branch = p.branch; + if (branch) { - mDrawBoolLabel.clear(); + mBranchHandle = branch->getHandle(); + branch->setVisible(FALSE); + branch->setParentMenuItem(this); } - mDrawAccelLabel.clear(); - std::string st = mDrawAccelLabel; - appendAcceleratorString( st ); - mDrawAccelLabel = st; } -// doIt() - do the primary funcationality of the menu item. -void LLMenuItemToggleGL::doIt( void ) +LLMenuItemBranchGL::~LLMenuItemBranchGL() { - getMenu()->setItemLastSelected( this ); - //llinfos << "LLMenuItemToggleGL::doIt " << mLabel.c_str() << llendl; - *mToggle = !(*mToggle); - buildDrawLabel(); - LLMenuItemGL::doIt(); + LLView::deleteViewByHandle(mBranchHandle); } - -LLMenuItemBranchGL::LLMenuItemBranchGL( const std::string& name, const std::string& label, LLHandle<LLView> branch, - KEY key, MASK mask ) : - LLMenuItemGL( name, label, key, mask ), - mBranch( branch ) +// virtual +LLView* LLMenuItemBranchGL::getChildView(const std::string& name, BOOL recurse) const { - if(!dynamic_cast<LLMenuGL*>(branch.get())) + LLMenuGL* branch = getBranch(); + if (branch) { - llerrs << "Non-menu handle passed as branch reference." << llendl; - } + if (branch->getName() == name) + { + return branch; + } - if(getBranch()) - { - getBranch()->setVisible( FALSE ); - getBranch()->setParentMenuItem(this); + // Always recurse on branches + return branch->getChildView(name, recurse); } -} -LLMenuItemBranchGL::~LLMenuItemBranchGL() -{ - LLView::deleteViewByHandle(mBranch); + return LLView::getChildView(name, recurse); } -// virtual -LLView* LLMenuItemBranchGL::getChildView(const std::string& name, BOOL recurse, BOOL create_if_missing) const +LLView* LLMenuItemBranchGL::findChildView(const std::string& name, BOOL recurse) const { - // richard: this is redundant with parent, remove - if (getBranch()) + LLMenuGL* branch = getBranch(); + if (branch) { - if(getBranch()->getName() == name) + if (branch->getName() == name) { - return getBranch(); + return branch; } // Always recurse on branches - LLView* child = getBranch()->getChildView(name, recurse, FALSE); - if(child) - { - return child; - } + return branch->findChildView(name, recurse); } - return LLView::getChildView(name, recurse, create_if_missing);; + + return LLView::findChildView(name, recurse); } // virtual @@ -1086,49 +980,35 @@ BOOL LLMenuItemBranchGL::handleMouseUp(S32 x, S32 y, MASK mask) // switch to mouse navigation mode LLMenuGL::setKeyboardMode(FALSE); - doIt(); + onCommit(); make_ui_sound("UISndClickRelease"); return TRUE; } BOOL LLMenuItemBranchGL::handleAcceleratorKey(KEY key, MASK mask) { - if(getBranch()) - { - return getBranch()->handleAcceleratorKey(key, mask); - } - return FALSE; -} - -// virtual -LLXMLNodePtr LLMenuItemBranchGL::getXML(bool save_children) const -{ - if (getBranch()) - { - return getBranch()->getXML(); - } - - return LLMenuItemGL::getXML(); + 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<LLKeyBinding*> *listp) { - if(getBranch()) - { - U32 item_count = getBranch()->getItemCount(); - LLMenuItemGL *item; + LLMenuGL* branch = getBranch(); + if (!branch) + return FALSE; - while (item_count--) + U32 item_count = branch->getItemCount(); + LLMenuItemGL *item; + + while (item_count--) + { + if ((item = branch->getItem(item_count))) { - if ((item = getBranch()->getItem(item_count))) - { - return item->addToAcceleratorList(listp); - } + return item->addToAcceleratorList(listp); } } + return FALSE; } @@ -1143,23 +1023,24 @@ void LLMenuItemBranchGL::buildDrawLabel( void ) mDrawBranchLabel = BRANCH_SUFFIX; } -// doIt() - do the primary functionality of the menu item. -void LLMenuItemBranchGL::doIt( void ) +void LLMenuItemBranchGL::onCommit( void ) { openMenu(); // keyboard navigation automatically propagates highlight to sub-menu // to facilitate fast menu control via jump keys - if (getBranch() && LLMenuGL::getKeyboardMode() && !getBranch()->getHighlightedItem()) + 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 (called_from_parent && getBranch()) + if (getBranch() && called_from_parent) { handled = getBranch()->handleKey(key, mask, called_from_parent); } @@ -1175,7 +1056,7 @@ BOOL LLMenuItemBranchGL::handleKey(KEY key, MASK mask, BOOL called_from_parent) BOOL LLMenuItemBranchGL::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent) { BOOL handled = FALSE; - if (called_from_parent && getBranch()) + if (getBranch() && called_from_parent) { handled = getBranch()->handleUnicodeChar(uni_char, TRUE); } @@ -1191,21 +1072,21 @@ BOOL LLMenuItemBranchGL::handleUnicodeChar(llwchar uni_char, BOOL called_from_pa void LLMenuItemBranchGL::setHighlight( BOOL highlight ) { - if (highlight == getHighlight()) return; + if (highlight == getHighlight()) + return; - if(!getBranch()) - { + LLMenuGL* branch = getBranch(); + if (!branch) return; - } - BOOL auto_open = getEnabled() && (!getBranch()->getVisible() || getBranch()->getTornOff()); + BOOL auto_open = getEnabled() && (!branch->getVisible() || branch->getTornOff()); // torn off menus don't open sub menus on hover unless they have focus if (getMenu()->getTornOff() && !((LLFloater*)getMenu()->getParent())->hasFocus()) { auto_open = FALSE; } // don't auto open torn off sub-menus (need to explicitly active menu item to give them focus) - if (getBranch()->getTornOff()) + if (branch->getTornOff()) { auto_open = FALSE; } @@ -1219,14 +1100,14 @@ void LLMenuItemBranchGL::setHighlight( BOOL highlight ) } else { - if (getBranch()->getTornOff()) + if (branch->getTornOff()) { - ((LLFloater*)getBranch()->getParent())->setFocus(FALSE); - getBranch()->clearHoverItem(); + ((LLFloater*)branch->getParent())->setFocus(FALSE); + branch->clearHoverItem(); } else { - getBranch()->setVisible( FALSE ); + branch->setVisible( FALSE ); } } } @@ -1249,102 +1130,129 @@ void LLMenuItemBranchGL::updateBranchParent(LLView* parentp) } } -void LLMenuItemBranchGL::onVisibilityChange( BOOL new_visibility ) +void LLMenuItemBranchGL::handleVisibilityChange( BOOL new_visibility ) { if (new_visibility == FALSE && getBranch() && !getBranch()->getTornOff()) { getBranch()->setVisible(FALSE); } - LLMenuItemGL::onVisibilityChange(new_visibility); + LLMenuItemGL::handleVisibilityChange(new_visibility); } BOOL LLMenuItemBranchGL::handleKeyHere( KEY key, MASK mask ) { - if (getMenu()->getVisible() && getBranch() && getBranch()->getVisible() && key == KEY_LEFT) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(TRUE); + LLMenuGL* branch = getBranch(); + if (!branch) + return LLMenuItemGL::handleKeyHere(key, mask); - BOOL handled = getBranch()->clearHoverItem(); - if (getBranch()->getTornOff()) - { - ((LLFloater*)getBranch()->getParent())->setFocus(FALSE); - } - if (handled && getMenu()->getTornOff()) + // 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) { - ((LLFloater*)getMenu()->getParent())->setFocus(TRUE); - } - return handled; - } + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(TRUE); - if (getHighlight() && - getMenu()->isOpen() && - key == KEY_RIGHT && getBranch() && !getBranch()->getHighlightedItem()) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(TRUE); + BOOL handled = branch->clearHoverItem(); + if (branch->getTornOff()) + { + ((LLFloater*)branch->getParent())->setFocus(FALSE); + } + if (handled && getMenu()->getTornOff()) + { + ((LLFloater*)getMenu()->getParent())->setFocus(TRUE); + } + return handled; + } - LLMenuItemGL* itemp = getBranch()->highlightNextItem(NULL); - if (itemp) + if (key == KEY_RIGHT && !branch->getHighlightedItem()) { - return TRUE; + // 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() { - if(!getBranch()) return; + LLMenuGL* branch = getBranch(); + if (!branch) + return; - if (getBranch()->getTornOff()) + if (branch->getTornOff()) { - gFloaterView->bringToFront((LLFloater*)getBranch()->getParent()); + gFloaterView->bringToFront((LLFloater*)branch->getParent()); // this might not be necessary, as torn off branches don't get focus and hence no highligth - getBranch()->highlightNextItem(NULL); + branch->highlightNextItem(NULL); } - else if( !getBranch()->getVisible() ) + else if( !branch->getVisible() ) { // get valid rectangle for menus const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect(); - getBranch()->arrange(); + branch->arrange(); - LLRect rect = getBranch()->getRect(); + 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, getBranch()->getParent()); + localPointToOtherView(left, top, &left, &top, branch->getParent()); - rect.setLeftTopAndSize( left, top, - rect.getWidth(), rect.getHeight() ); + branch_rect.setLeftTopAndSize( left, top, + branch_rect.getWidth(), branch_rect.getHeight() ); - if (getBranch()->getCanTearOff()) + if (branch->getCanTearOff()) { - rect.translate(0, TEAROFF_SEPARATOR_HEIGHT_PIXELS); + branch_rect.translate(0, TEAROFF_SEPARATOR_HEIGHT_PIXELS); } - getBranch()->setRect( rect ); - S32 x = 0; - S32 y = 0; - getBranch()->localPointToOtherView( 0, 0, &x, &y, getBranch()->getParent() ); + 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 ) { - delta_y = menu_region_rect.mBottom - y; + // open upwards if menu extends past bottom + // adjust by the height of the menu item branch since it is a submenu + delta_y = branch_rect.getHeight() - getRect().getHeight(); } - S32 menu_region_width = menu_region_rect.getWidth(); - if( x - menu_region_rect.mLeft > menu_region_width - rect.getWidth() ) + if( x + branch_rect.getWidth() > menu_region_rect.mRight ) { // move sub-menu over to left side - delta_x = llmax(-x, (-1 * (rect.getWidth() + getRect().getWidth()))); + delta_x = llmax(-x, ( -(branch_rect.getWidth() + getRect().getWidth()))); } - getBranch()->translate( delta_x, delta_y ); - getBranch()->setVisible( TRUE ); - getBranch()->getParent()->sendChildToFront(getBranch()); + branch->translate( delta_x, delta_y ); + + branch->setVisible( TRUE ); + branch->getParent()->sendChildToFront(branch); + + dirtyRect(); } } @@ -1361,10 +1269,7 @@ class LLMenuItemBranchDownGL : public LLMenuItemBranchGL protected: public: - LLMenuItemBranchDownGL( const std::string& name, const std::string& label, LLHandle<LLView> branch, - KEY key = KEY_NONE, MASK mask = MASK_NONE ); - - virtual std::string getType() const { return "menu"; } + 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 @@ -1392,11 +1297,8 @@ public: virtual BOOL handleAcceleratorKey(KEY key, MASK mask); }; -LLMenuItemBranchDownGL::LLMenuItemBranchDownGL( const std::string& name, - const std::string& label, - LLHandle<LLView> branch, - KEY key, MASK mask ) : - LLMenuItemBranchGL( name, label, branch, key, mask ) +LLMenuItemBranchDownGL::LLMenuItemBranchDownGL( const Params& p) : + LLMenuItemBranchGL(p) { } @@ -1471,20 +1373,26 @@ void LLMenuItemBranchDownGL::openMenu( void ) // set the hover status (called by it's menu) void LLMenuItemBranchDownGL::setHighlight( BOOL highlight ) { - if (highlight == getHighlight()) return; + 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 (getBranch()->getTornOff()) + if (branch->getTornOff()) { - ((LLFloater*)getBranch()->getParent())->setFocus(FALSE); - getBranch()->clearHoverItem(); + ((LLFloater*)branch->getParent())->setFocus(FALSE); + branch->clearHoverItem(); } else { - getBranch()->setVisible( FALSE ); + branch->setVisible( FALSE ); } } } @@ -1501,7 +1409,7 @@ BOOL LLMenuItemBranchDownGL::handleMouseDown( S32 x, S32 y, MASK mask ) { // switch to mouse control mode LLMenuGL::setKeyboardMode(FALSE); - doIt(); + onCommit(); make_ui_sound("UISndClick"); return TRUE; } @@ -1529,7 +1437,7 @@ 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()->getVisible() && (isActive() || LLMenuGL::getKeyboardMode())) + if (getHighlight() && getMenu()->isOpen() && (isActive() || LLMenuGL::getKeyboardMode())) { if (key == KEY_LEFT) { @@ -1540,7 +1448,7 @@ BOOL LLMenuItemBranchDownGL::handleKeyHere(KEY key, MASK mask) // open new menu only if previous menu was open if (itemp && itemp->getEnabled() && menu_open) { - itemp->doIt(); + itemp->onCommit(); } return TRUE; @@ -1554,7 +1462,7 @@ BOOL LLMenuItemBranchDownGL::handleKeyHere(KEY key, MASK mask) // open new menu only if previous menu was open if (itemp && itemp->getEnabled() && menu_open) { - itemp->doIt(); + itemp->onCommit(); } return TRUE; @@ -1566,7 +1474,7 @@ BOOL LLMenuItemBranchDownGL::handleKeyHere(KEY key, MASK mask) if (!isActive()) { - doIt(); + onCommit(); } getBranch()->highlightNextItem(NULL); return TRUE; @@ -1578,7 +1486,7 @@ BOOL LLMenuItemBranchDownGL::handleKeyHere(KEY key, MASK mask) if (!isActive()) { - doIt(); + onCommit(); } getBranch()->highlightPrevItem(NULL); return TRUE; @@ -1598,31 +1506,25 @@ void LLMenuItemBranchDownGL::draw( void ) if( getHighlight() ) { - gGL.color4fv( getHighlightBGColor().mV ); + gGL.color4fv( mHighlightBackground.get().mV ); gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); } - U8 font_style = getFontStyle(); - if (getEnabled() && !getDrawTextDisabled() ) - { - font_style |= LLFontGL::DROP_SHADOW_SOFT; - } - LLColor4 color; if (getHighlight()) { - color = getHighlightFGColor(); + color = mHighlightForeground.get(); } else if( getEnabled() ) { - color = getEnabledColor(); + color = mEnabledColor.get(); } else { - color = getDisabledColor(); + color = mDisabledColor.get(); } getFont()->render( mLabel.getWString(), 0, (F32)getRect().getWidth() / 2.f, (F32)LABEL_BOTTOM_PAD_PIXELS, color, - LLFontGL::HCENTER, LLFontGL::BOTTOM, font_style ); + LLFontGL::HCENTER, LLFontGL::BOTTOM, LLFontGL::NORMAL); // underline navigation key only when keyboard navigation has been initiated @@ -1645,22 +1547,116 @@ void LLMenuItemBranchDownGL::draw( void ) setHover(FALSE); } + +class LLMenuScrollItem : public LLMenuItemCallGL +{ +public: + enum EArrowType + { + ARROW_DOWN, + ARROW_UP + }; + struct ArrowTypes : public LLInitParam::TypeValuesHelper<EArrowType, ArrowTypes> + { + static void declareValues() + { + declare("up", ARROW_UP); + declare("down", ARROW_DOWN); + } + }; + + struct Params : public LLInitParam::Block<Params, LLMenuItemCallGL::Params> + { + Optional<EArrowType, ArrowTypes> arrow_type; + Optional<CommitCallbackParam> 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; + 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<LLButton>(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 ///============================================================================ -static LLRegisterWidget<LLMenuGL> r1("menu"); - -// Default constructor -LLMenuGL::LLMenuGL( const std::string& name, const std::string& label, LLHandle<LLFloater> parent_floater_handle ) -: LLUICtrl( name, LLRect(), FALSE, NULL, NULL ), - mBackgroundColor( sDefaultBackgroundColor ), - mBgVisible( TRUE ), - mParentMenuItem( NULL ), - mLabel( label ), - mDropShadowed( TRUE ), - mHorizontalLayout( FALSE ), - mKeepFixedSize( FALSE ), +LLMenuGL::LLMenuGL(const LLMenuGL::Params& p) +: LLUICtrl(p), + mBackgroundColor( p.bg_color() ), + mBgVisible( p.bg_visible ), + mDropShadowed( p.drop_shadow ), + 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), @@ -1668,38 +1664,41 @@ LLMenuGL::LLMenuGL( const std::string& name, const std::string& label, LLHandle< mTornOff(FALSE), mTearOffItem(NULL), mSpilloverBranch(NULL), + mFirstVisibleItem(NULL), + mArrowUpItem(NULL), + mArrowDownItem(NULL), mSpilloverMenu(NULL), - mParentFloaterHandle(parent_floater_handle), - mJumpKey(KEY_NONE) + mJumpKey(p.jump_key), + mCreateJumpKeys(p.create_jump_keys), + mParentFloaterHandle(p.parent_floater), + mNeedsArrange(FALSE), + mShortcutPad(p.shortcut_pad) { + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> 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(); - setCanTearOff(TRUE, parent_floater_handle); - setTabStop(FALSE); -} - -LLMenuGL::LLMenuGL( const std::string& label, LLHandle<LLFloater> parent_floater_handle ) -: LLUICtrl( label, LLRect(), FALSE, NULL, NULL ), - mBackgroundColor( sDefaultBackgroundColor ), - mBgVisible( TRUE ), - mParentMenuItem( NULL ), - mLabel( label ), - mDropShadowed( TRUE ), - mHorizontalLayout( FALSE ), - mKeepFixedSize( FALSE ), - mLastMouseX(0), - mLastMouseY(0), - mMouseVelX(0), - mMouseVelY(0), - mTornOff(FALSE), - mTearOffItem(NULL), - mSpilloverBranch(NULL), - mSpilloverMenu(NULL), - mParentFloaterHandle(parent_floater_handle), - mJumpKey(KEY_NONE) +} + +void LLMenuGL::initFromParams(const LLMenuGL::Params& p) { - mFadeTimer.stop(); - setCanTearOff(TRUE, parent_floater_handle); - setTabStop(FALSE); + LLUICtrl::initFromParams(p); + setCanTearOff(p.can_tear_off, p.parent_floater); } // Destroys the object @@ -1715,10 +1714,10 @@ void LLMenuGL::setCanTearOff(BOOL tear_off, LLHandle<LLFloater> parent_floater_h { if (tear_off && mTearOffItem == NULL) { - mTearOffItem = new LLMenuItemTearOffGL(parent_floater_handle); - mItems.insert(mItems.begin(), mTearOffItem); - addChildAtEnd(mTearOffItem); - arrange(); + LLMenuItemTearOffGL::Params p; + p.parent_floater_handle = parent_floater_handle; + mTearOffItem = LLUICtrlFactory::create<LLMenuItemTearOffGL>(p); + addChildInBack(mTearOffItem); } else if (!tear_off && mTearOffItem != NULL) { @@ -1726,313 +1725,45 @@ void LLMenuGL::setCanTearOff(BOOL tear_off, LLHandle<LLFloater> parent_floater_h removeChild(mTearOffItem); delete mTearOffItem; mTearOffItem = NULL; - arrange(); + needsArrange(); } } -// virtual -LLXMLNodePtr LLMenuGL::getXML(bool save_children) const +bool LLMenuGL::addChild(LLView* view, S32 tab_group) { - LLXMLNodePtr node = LLView::getXML(); - - // Attributes - - node->createChild("opaque", TRUE)->setBoolValue(mBgVisible); - - node->createChild("drop_shadow", TRUE)->setBoolValue(mDropShadowed); - - node->createChild("tear_off", TRUE)->setBoolValue((mTearOffItem != NULL)); - - if (mBgVisible) + if (LLMenuGL* menup = dynamic_cast<LLMenuGL*>(view)) { - // TomY TODO: this should save out the color control name - node->createChild("color", TRUE)->setFloatValue(4, mBackgroundColor.mV); + appendMenu(menup); + return true; } - - // Contents - item_list_t::const_iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) + else if (LLMenuItemGL* itemp = dynamic_cast<LLMenuItemGL*>(view)) { - LLView* child = (*item_iter); - LLMenuItemGL* item = (LLMenuItemGL*)child; - - LLXMLNodePtr child_node = item->getXML(); - - node->addChild(child_node); + append(itemp); + return true; } - - return node; + return false; } -void LLMenuGL::parseChildXML(LLXMLNodePtr child, LLView *parent, LLUICtrlFactory *factory) +void LLMenuGL::removeChild( LLView* ctrl) { - if (child->hasName(LL_MENU_GL_TAG)) + // 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<LLMenuItemGL*>(ctrl); + + item_list_t::iterator found_it = std::find(mItems.begin(), mItems.end(), (itemp)); + if (found_it != mItems.end()) { - // SUBMENU - LLMenuGL *submenu = (LLMenuGL*)LLMenuGL::fromXML(child, parent, factory); - appendMenu(submenu); - if (LLMenuGL::sMenuContainer != NULL) - { - submenu->updateParent(LLMenuGL::sMenuContainer); - } - else - { - submenu->updateParent(parent); - } + mItems.erase(found_it); } - else if (child->hasName(LL_MENU_ITEM_CALL_GL_TAG) || - child->hasName(LL_MENU_ITEM_CHECK_GL_TAG) || - child->hasName(LL_MENU_ITEM_SEPARATOR_GL_TAG)) - { - LLMenuItemGL *item = NULL; - - std::string type; - std::string item_name; - std::string source_label; - std::string item_label; - KEY jump_key = KEY_NONE; - - child->getAttributeString("type", type); - child->getAttributeString("name", item_name); - child->getAttributeString("label", source_label); - - // parse jump key out of label - typedef boost::tokenizer<boost::char_separator<char> > tokenizer; - boost::char_separator<char> sep("_"); - tokenizer tokens(source_label, sep); - tokenizer::iterator token_iter; - S32 token_count = 0; - for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) - { - item_label += (*token_iter); - if (token_count > 0) - { - jump_key = (*token_iter).c_str()[0]; - } - ++token_count; - } - - - if (child->hasName(LL_MENU_ITEM_SEPARATOR_GL_TAG)) - { - appendSeparator(item_name); - } - else - { - // ITEM - if (child->hasName(LL_MENU_ITEM_CALL_GL_TAG) || - child->hasName(LL_MENU_ITEM_CHECK_GL_TAG)) - { - MASK mask = 0; - -#ifdef LL_DARWIN - // See if this Mac accelerator should really use the ctrl key and not get mapped to cmd - BOOL useMacCtrl = FALSE; - child->getAttributeBOOL("useMacCtrl", useMacCtrl); -#endif // LL_DARWIN - - std::string shortcut; - child->getAttributeString("shortcut", shortcut); - if (shortcut.find("control") != shortcut.npos) - { -#ifdef LL_DARWIN - if ( useMacCtrl ) - { - mask |= MASK_MAC_CONTROL; - } -#endif // LL_DARWIN - mask |= MASK_CONTROL; - } - if (shortcut.find("alt") != shortcut.npos) - { - mask |= MASK_ALT; - } - if (shortcut.find("shift") != shortcut.npos) - { - mask |= MASK_SHIFT; - } - S32 pipe_pos = shortcut.rfind("|"); - std::string key_str = shortcut.substr(pipe_pos+1); - - KEY key = KEY_NONE; - LLKeyboard::keyFromString(key_str, &key); - - LLMenuItemCallGL *new_item; - LLXMLNodePtr call_child; - - if (child->hasName(LL_MENU_ITEM_CHECK_GL_TAG)) - { - std::string control_name; - child->getAttributeString("control_name", control_name); - - new_item = new LLMenuItemCheckGL(item_name, item_label, 0, 0, control_name, parent, 0, key, mask); - - for (call_child = child->getFirstChild(); call_child.notNull(); call_child = call_child->getNextSibling()) - { - if (call_child->hasName("on_check")) - { - std::string callback_name; - std::string control_name; - if (call_child->hasAttribute("function")) - { - call_child->getAttributeString("function", callback_name); - - control_name = callback_name; - - std::string callback_data = item_name; - if (call_child->hasAttribute("userdata")) - { - call_child->getAttributeString("userdata", callback_data); - if (!callback_data.empty()) - { - control_name = llformat("%s(%s)", callback_name.c_str(), callback_data.c_str()); - } - } - - LLSD userdata; - userdata["control"] = control_name; - userdata["data"] = callback_data; - - LLSimpleListener* callback = parent->getListenerByName(callback_name); - - if (!callback) continue; - - new_item->addListener(callback, "on_build", userdata); - } - else if (call_child->hasAttribute("control")) - { - call_child->getAttributeString("control", control_name); - } - else - { - continue; - } - LLControlVariable *control = parent->findControl(control_name); - if (!control) - { - parent->addBoolControl(control_name, FALSE); - } - ((LLMenuItemCheckGL*)new_item)->setCheckedControl(control_name, parent); - } - } - } - else - { - new_item = new LLMenuItemCallGL(item_name, item_label, 0, 0, 0, 0, key, mask); - } - - for (call_child = child->getFirstChild(); call_child.notNull(); call_child = call_child->getNextSibling()) - { - if (call_child->hasName("on_click")) - { - std::string callback_name; - call_child->getAttributeString("function", callback_name); - - std::string callback_data = item_name; - if (call_child->hasAttribute("userdata")) - { - call_child->getAttributeString("userdata", callback_data); - } - - LLSimpleListener* callback = parent->getListenerByName(callback_name); - - if (!callback) continue; - - new_item->addListener(callback, "on_click", callback_data); - } - if (call_child->hasName("on_enable")) - { - std::string callback_name; - std::string control_name; - if (call_child->hasAttribute("function")) - { - call_child->getAttributeString("function", callback_name); - - control_name = callback_name; - - std::string callback_data; - if (call_child->hasAttribute("userdata")) - { - call_child->getAttributeString("userdata", callback_data); - if (!callback_data.empty()) - { - control_name = llformat("%s(%s)", callback_name.c_str(), callback_data.c_str()); - } - } - - LLSD userdata; - userdata["control"] = control_name; - userdata["data"] = callback_data; - - LLSimpleListener* callback = parent->getListenerByName(callback_name); - - if (!callback) continue; - - new_item->addListener(callback, "on_build", userdata); - } - else if (call_child->hasAttribute("control")) - { - call_child->getAttributeString("control", control_name); - } - else - { - continue; - } - new_item->setEnabledControl(control_name, parent); - } - if (call_child->hasName("on_visible")) - { - std::string callback_name; - std::string control_name; - if (call_child->hasAttribute("function")) - { - call_child->getAttributeString("function", callback_name); - - control_name = callback_name; - - std::string callback_data; - if (call_child->hasAttribute("userdata")) - { - call_child->getAttributeString("userdata", callback_data); - if (!callback_data.empty()) - { - control_name = llformat("%s(%s)", callback_name.c_str(), callback_data.c_str()); - } - } - - LLSD userdata; - userdata["control"] = control_name; - userdata["data"] = callback_data; - LLSimpleListener* callback = parent->getListenerByName(callback_name); - - if (!callback) continue; - - new_item->addListener(callback, "on_build", userdata); - } - else if (call_child->hasAttribute("control")) - { - call_child->getAttributeString("control", control_name); - } - else - { - continue; - } - new_item->setVisibleControl(control_name, parent); - } - } - item = new_item; - item->setLabel(item_label); - if (jump_key != KEY_NONE) - item->setJumpKey(jump_key); - } + return LLUICtrl::removeChild(ctrl); +} - if (item != NULL) - { - append(item); - } - } - } +BOOL LLMenuGL::postBuild() +{ + createJumpKeys(); + return LLUICtrl::postBuild(); } // are we the childmost active menu and hence our jump keys should be enabled? @@ -2079,77 +1810,88 @@ BOOL LLMenuGL::isOpen() return getVisible(); } } -// static -LLView* LLMenuGL::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("menu"); - node->getAttributeString("name", name); - - std::string label = name; - node->getAttributeString("label", label); - // parse jump key out of label - std::string new_menu_label; - - typedef boost::tokenizer<boost::char_separator<char> > tokenizer; - boost::char_separator<char> sep("_"); - tokenizer tokens(label, sep); - tokenizer::iterator token_iter; +void LLMenuGL::scrollItemsUp() +{ + // Slowing down the items scrolling when arrow button is held + if (mScrollItemsTimer.hasExpired() && NULL != mFirstVisibleItem) + { + mScrollItemsTimer.setTimerExpirySec(.033f); + } + else + { + return; + } - KEY jump_key = KEY_NONE; - S32 token_count = 0; - for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + 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++) { - new_menu_label += (*token_iter); - if (token_count > 0) + if( (*cur_item_iter) == mFirstVisibleItem) { - jump_key = (*token_iter).c_str()[0]; + break; + } + if ((*cur_item_iter)->getVisible()) + { + prev_item_iter = cur_item_iter; } - ++token_count; } - BOOL opaque = FALSE; - node->getAttributeBOOL("opaque", opaque); - - LLMenuGL *menu = new LLMenuGL(name, new_menu_label); - - menu->setJumpKey(jump_key); - - BOOL tear_off = FALSE; - node->getAttributeBOOL("tear_off", tear_off); - menu->setCanTearOff(tear_off); + if ((*prev_item_iter)->getVisible()) + { + mFirstVisibleItem = *prev_item_iter; + } + + mNeedsArrange = TRUE; + arrangeAndClear(); +} - if (node->hasAttribute("drop_shadow")) +void LLMenuGL::scrollItemsDown() +{ + // Slowing down the items scrolling when arrow button is held + if (mScrollItemsTimer.hasExpired()) + { + mScrollItemsTimer.setTimerExpirySec(.033f); + } + else + { + return; + } + + if (NULL == mFirstVisibleItem) { - BOOL drop_shadow = FALSE; - node->getAttributeBOOL("drop_shadow", drop_shadow); - menu->setDropShadowed(drop_shadow); + mFirstVisibleItem = *mItems.begin(); } - menu->setBackgroundVisible(opaque); - LLColor4 color(0,0,0,1); - if (opaque && LLUICtrlFactory::getAttributeColor(node,"color", color)) + item_list_t::iterator cur_item_iter; + + for (cur_item_iter = mItems.begin(); cur_item_iter != mItems.end(); cur_item_iter++) { - menu->setBackgroundColor(color); + if( (*cur_item_iter) == mFirstVisibleItem) + { + break; + } } - BOOL create_jump_keys = FALSE; - node->getAttributeBOOL("create_jump_keys", create_jump_keys); + item_list_t::iterator next_item_iter; - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++) { - menu->parseChildXML(child, parent, factory); + if( (*next_item_iter)->getVisible()) + { + break; + } } - if (create_jump_keys) + if ((*next_item_iter)->getVisible()) { - menu->createJumpKeys(); + mFirstVisibleItem = *next_item_iter; } - return menu; + + mNeedsArrange = TRUE; + arrangeAndClear(); } - // rearrange the child rects so they fit the shape of the menu. void LLMenuGL::arrange( void ) { @@ -2167,39 +1909,59 @@ void LLMenuGL::arrange( void ) // 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(); + U32 max_height = U32_MAX; + if (!getTornOff()) + { + max_height = getRect().mTop - menu_region_rect.mBottom; + if (menu_region_rect.mTop - getRect().mTop > (S32)max_height) + { + max_height = menu_region_rect.mTop - getRect().mTop; + } + } + // *FIX: create the item first and then ask for its dimensions? - S32 spillover_item_width = PLAIN_PAD_PIXELS + LLFontGL::getFontSansSerif()->getWidth( std::string("More") ); + S32 spillover_item_width = PLAIN_PAD_PIXELS + LLFontGL::getFontSansSerif()->getWidth( std::string("More") ); // *TODO: Translate S32 spillover_item_height = llround(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 != mItems.begin() // Don't spillover the first item! + && *item_iter != mSpilloverBranch && width + (*item_iter)->getNominalWidth() > max_width - spillover_item_width) { // no room for any more items createSpilloverBranch(); - item_list_t::iterator spillover_iter; - for (spillover_iter = item_iter; spillover_iter != mItems.end(); ++spillover_iter) + std::vector<LLMenuItemGL*> items_to_remove; + std::copy(item_iter, mItems.end(), std::back_inserter(items_to_remove)); + std::vector<LLMenuItemGL*>::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->appendNoArrange(itemp); // *NOTE:Mani Favor addChild() in merge with skinning + mSpilloverMenu->addChild(itemp); } - mSpilloverMenu->arrange(); // *NOTE: Mani Remove line in merge with skinning/viewer2.0 branch - mSpilloverMenu->updateParent(LLMenuGL::sMenuContainer); // *NOTE: Mani Remove line in merge with skinning/viewer2.0 branch - mItems.erase(item_iter, mItems.end()); - mItems.push_back(mSpilloverBranch); + addChild(mSpilloverBranch); + height = llmax(height, mSpilloverBranch->getNominalHeight()); width += mSpilloverBranch->getNominalWidth(); + break; } else @@ -2214,31 +1976,38 @@ void LLMenuGL::arrange( void ) else { 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 != mItems.begin() // Don't spillover the first item! + && !mScrollable + && *item_iter != mSpilloverBranch && height + (*item_iter)->getNominalHeight() > max_height - spillover_item_height) { // no room for any more items createSpilloverBranch(); - item_list_t::iterator spillover_iter; - for (spillover_iter= item_iter; spillover_iter != mItems.end(); ++spillover_iter) + std::vector<LLMenuItemGL*> items_to_remove; + std::copy(item_iter, mItems.end(), std::back_inserter(items_to_remove)); + std::vector<LLMenuItemGL*>::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->appendNoArrange(itemp); // *NOTE:Mani Favor addChild() in merge with skinning + mSpilloverMenu->addChild(itemp); } - mSpilloverMenu->arrange(); // *NOTE: Mani Remove line in merge with skinning/viewer2.0 branch - mSpilloverMenu->updateParent(LLMenuGL::sMenuContainer); // *NOTE: Mani Remove line in merge with skinning/viewer2.0 branch - mItems.erase(item_iter, mItems.end()); - mItems.push_back(mSpilloverBranch); + + addChild(mSpilloverBranch); + height += mSpilloverBranch->getNominalHeight(); width = llmax( width, mSpilloverBranch->getNominalWidth() ); + break; } else @@ -2247,19 +2016,173 @@ void LLMenuGL::arrange( void ) 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--; + } + } } } - } - setRect(LLRect(getRect().mLeft, getRect().mBottom + height, getRect().mLeft + width, getRect().mBottom)); + 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::scrollItemsUp, this)); + + mArrowUpItem = LLUICtrlFactory::create<LLMenuScrollItem>(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::scrollItemsDown, this)); + + mArrowDownItem = LLUICtrlFactory::create<LLMenuScrollItem>(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) @@ -2269,11 +2192,13 @@ void LLMenuGL::arrange( void ) } else { - rect.setLeftTopAndSize( 0, cur_height, width, (*item_iter)->getNominalHeight()); - cur_height -= (*item_iter)->getNominalHeight(); + rect.setLeftTopAndSize( 0 + offset, cur_height, width, (*item_iter)->getNominalHeight()); + if (offset == 0) + { + cur_height -= (*item_iter)->getNominalHeight(); + } } (*item_iter)->setRect( rect ); - (*item_iter)->buildDrawLabel(); } } } @@ -2283,6 +2208,15 @@ void LLMenuGL::arrange( void ) } } +void LLMenuGL::arrangeAndClear( void ) +{ + if (mNeedsArrange) + { + arrange(); + mNeedsArrange = FALSE; + } +} + void LLMenuGL::createSpilloverBranch() { if (!mSpilloverBranch) @@ -2291,14 +2225,24 @@ void LLMenuGL::createSpilloverBranch() delete mSpilloverMenu; // technically, you can't tear off spillover menus, but we're passing the handle // along just to be safe - mSpilloverMenu = new LLMenuGL(std::string("More"), std::string("More"), mParentFloaterHandle); + LLMenuGL::Params p; + p.name("More"); + p.label("More"); // *TODO: Translate + p.parent_floater(mParentFloaterHandle); + p.bg_color(mBackgroundColor); + p.bg_visible(true); + p.can_tear_off(false); + mSpilloverMenu = new LLMenuGL(p); mSpilloverMenu->updateParent(LLMenuGL::sMenuContainer); - // Inherit colors - mSpilloverMenu->setBackgroundColor( mBackgroundColor ); - mSpilloverMenu->setCanTearOff(FALSE); - mSpilloverBranch = new LLMenuItemBranchGL(std::string("More"), std::string("More"), mSpilloverMenu->getHandle()); - mSpilloverBranch->setFontStyle(LLFontGL::ITALIC); + LLMenuItemBranchGL::Params branch_params; + branch_params.name = "More"; + branch_params.label = "More"; // *TODO: Translate + branch_params.branch = mSpilloverMenu; + branch_params.font.style = "italic"; + + + mSpilloverBranch = LLUICtrlFactory::create<LLMenuItemBranchGL>(branch_params); } } @@ -2309,25 +2253,15 @@ void LLMenuGL::cleanupSpilloverBranch() // head-recursion to propagate items back up to root menu mSpilloverMenu->cleanupSpilloverBranch(); - removeChild(mSpilloverBranch); - - item_list_t::iterator found_iter = std::find(mItems.begin(), mItems.end(), mSpilloverBranch); - if (found_iter != mItems.end()) - { - mItems.erase(found_iter); - } - // pop off spillover items while (mSpilloverMenu->getItemCount()) { LLMenuItemGL* itemp = mSpilloverMenu->getItem(0); mSpilloverMenu->removeChild(itemp); - mSpilloverMenu->mItems.erase(mSpilloverMenu->mItems.begin()); // put them at the end of our own list - mItems.push_back(itemp); addChild(itemp); } - + // Delete the branch, and since the branch will delete the menu, // set the menu* to null. delete mSpilloverBranch; @@ -2338,6 +2272,9 @@ void LLMenuGL::cleanupSpilloverBranch() void LLMenuGL::createJumpKeys() { + if (!mCreateJumpKeys) return; + mCreateJumpKeys = FALSE; + mJumpKeys.clear(); std::set<std::string> unique_words; @@ -2414,8 +2351,8 @@ void LLMenuGL::createJumpKeys() { char jump_key = uppercase_word[i]; - if (LLStringOps::isDigit(jump_key) || LLStringOps::isUpper(jump_key) && - mJumpKeys.find(jump_key) == mJumpKeys.end()) + if (LLStringOps::isDigit(jump_key) || (LLStringOps::isUpper(jump_key) && + mJumpKeys.find(jump_key) == mJumpKeys.end())) { mJumpKeys.insert(std::pair<KEY, LLMenuItemGL*>(jump_key, (*item_it))); (*item_it)->setJumpKey(jump_key); @@ -2438,16 +2375,18 @@ void LLMenuGL::empty( void ) cleanupSpilloverBranch(); mItems.clear(); + mFirstVisibleItem = NULL; + mArrowUpItem = NULL; + mArrowDownItem = NULL; deleteAllChildren(); - } // Adjust rectangle of the menu void LLMenuGL::setLeftAndBottom(S32 left, S32 bottom) { setRect(LLRect(left, getRect().mTop, getRect().mRight, bottom)); - arrange(); + needsArrange(); } BOOL LLMenuGL::handleJumpKey(KEY key) @@ -2460,12 +2399,9 @@ BOOL LLMenuGL::handleJumpKey(KEY key) // switch to keyboard navigation mode LLMenuGL::setKeyboardMode(TRUE); - // force highlight to close old menus and any open sub-menus - - //clearHoverItem(); // force highlight to close old menus and open and sub-menus found_it->second->setHighlight(TRUE); - found_it->second->doIt(); + found_it->second->onCommit(); } // if we are navigating the menus, we need to eat the keystroke @@ -2477,30 +2413,18 @@ BOOL LLMenuGL::handleJumpKey(KEY key) // Add the menu item to this menu. BOOL LLMenuGL::append( LLMenuItemGL* item ) { + if (!item) return FALSE; mItems.push_back( item ); - addChild( item ); - arrange(); - return TRUE; -} - -// *NOTE:Mani - appendNoArrange() should be removed when merging to skinning/viewer2.0 -// Its added as a fix to a viewer 1.23 bug that has already been address by skinning work. -BOOL LLMenuGL::appendNoArrange( LLMenuItemGL* item ) -{ - mItems.push_back( item ); - addChild( item ); + LLUICtrl::addChild(item); + needsArrange(); return TRUE; } // add a separator to this menu -BOOL LLMenuGL::appendSeparator( const std::string &separator_name ) +BOOL LLMenuGL::addSeparator() { - LLMenuItemGL* separator; - if (separator_name.empty()) - separator = new LLMenuItemSeparatorGL(std::string("separator")); - else - separator = new LLMenuItemSeparatorGL(separator_name); - return append( separator ); + LLMenuItemGL* separator = new LLMenuItemSeparatorGL(); + return addChild(separator); } // add a menu - this will create a cascading menu @@ -2513,14 +2437,22 @@ BOOL LLMenuGL::appendMenu( LLMenuGL* menu ) } BOOL success = TRUE; - LLMenuItemBranchGL* branch = NULL; - branch = new LLMenuItemBranchGL( menu->getName(), menu->getLabel(), menu->getHandle() ); - branch->setJumpKey(menu->getJumpKey()); + 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<LLMenuItemBranchGL>(p); success &= append( branch ); // Inherit colors menu->setBackgroundColor( mBackgroundColor ); - + menu->updateParent(LLMenuGL::sMenuContainer); return success; } @@ -2558,6 +2490,7 @@ void LLMenuGL::setItemVisible( const std::string& name, BOOL visible ) if( (*item_iter)->getName() == name ) { (*item_iter)->setVisible( visible ); + needsArrange(); break; } } @@ -2570,7 +2503,7 @@ void LLMenuGL::setItemLastSelected(LLMenuItemGL* item) LLMenuHolderGL::setActivatedItem(item); } - // fix the checkmarks + // update enabled and checkmark status item->buildDrawLabel(); } @@ -2668,7 +2601,7 @@ LLMenuItemGL* LLMenuGL::highlightNextItem(LLMenuItemGL* cur_item, BOOL skip_disa while(1) { // skip separators and disabled/invisible items - if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getVisible() && (*next_item_iter)->getType() != SEPARATOR_NAME) + if ((*next_item_iter)->getEnabled() && (*next_item_iter)->getVisible() && !dynamic_cast<LLMenuItemSeparatorGL*>(*next_item_iter)) { if (cur_item) { @@ -2775,7 +2708,10 @@ void LLMenuGL::updateParent(LLView* parentp) { getParent()->removeChild(this); } - parentp->addChild(this); + if (parentp) + { + parentp->addChild(this); + } item_list_t::iterator item_iter; for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) { @@ -2878,29 +2814,60 @@ BOOL LLMenuGL::handleHover( S32 x, S32 y, MASK mask ) } } 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-- ) + scrollItemsDown(); + } + else + { + while( clicks++ ) + scrollItemsUp(); + } + return TRUE; } + void LLMenuGL::draw( void ) { + if (mNeedsArrange) + { + arrange(); + mNeedsArrange = FALSE; + } if (mDropShadowed && !mTornOff) { + static LLUICachedControl<S32> drop_shadow_floater ("DropShadowFloater", 0); + static LLUIColor color_drop_shadow = LLUIColorTable::instance().getColor("ColorDropShadow"); gl_drop_shadow(0, getRect().getHeight(), getRect().getWidth(), 0, - LLUI::sColorsGroup->getColor("ColorDropShadow"), - LLUI::sConfigGroup->getS32("DropShadowFloater") ); + color_drop_shadow, drop_shadow_floater ); } - LLColor4 bg_color = mBackgroundColor; - if( mBgVisible ) { - gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0, mBackgroundColor ); + gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0, mBackgroundColor.get() ); } LLView::draw(); } -void LLMenuGL::drawBackground(LLMenuItemGL* itemp, LLColor4& color) +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); @@ -2929,9 +2896,9 @@ void LLMenuGL::setVisible(BOOL visible) } } -LLMenuGL* LLMenuGL::getChildMenuByName(const std::string& name, BOOL recurse) const +LLMenuGL* LLMenuGL::findChildMenuByName(const std::string& name, BOOL recurse) const { - LLView* view = getChildView(name, recurse, FALSE); + LLView* view = findChildView(name, recurse); if (view) { LLMenuItemBranchGL* branch = dynamic_cast<LLMenuItemBranchGL*>(view); @@ -2970,839 +2937,71 @@ void hide_top_view( LLView* view ) } +// 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) { - const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect(); - - const S32 HPAD = 2; - LLRect rect = menu->getRect(); - //LLView* cur_view = spawning_view; - S32 left = x + HPAD; - S32 top = y; - spawning_view->localPointToOtherView(left, top, &left, &top, menu->getParent()); - rect.setLeftTopAndSize( left, top, - rect.getWidth(), rect.getHeight() ); - + const S32 CURSOR_HEIGHT = 22; // Approximate "normal" cursor size + const S32 CURSOR_WIDTH = 12; - //rect.setLeftTopAndSize(x + HPAD, y, rect.getWidth(), rect.getHeight()); - menu->setRect( rect ); + // 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() + S32 mouse_x, mouse_y; - S32 bottom; - left = rect.mLeft; - bottom = rect.mBottom; - //menu->getParent()->localPointToScreen( rect.mLeft, rect.mBottom, - // &left, &bottom ); - S32 delta_x = 0; - S32 delta_y = 0; - if( bottom < menu_region_rect.mBottom ) + // Resetting scrolling position + if (menu->isScrollable()) { - // At this point, we need to move the context menu to the - // other side of the mouse. - //delta_y = menu_region_rect.mBottom - bottom; - delta_y = (rect.getHeight() + 2 * HPAD); + menu->mFirstVisibleItem = NULL; } - if( left > menu_region_rect.mRight - rect.getWidth() ) - { - // At this point, we need to move the context menu to the - // other side of the mouse. - //delta_x = (window_width - rect.getWidth()) - x; - delta_x = -(rect.getWidth() + 2 * HPAD); - } - menu->translate( delta_x, delta_y ); menu->setVisible( TRUE ); - menu->getParent()->sendChildToFront(menu); -} - -//----------------------------------------------------------------------------- -// class LLPieMenuBranch -// A branch to another pie menu -//----------------------------------------------------------------------------- -class LLPieMenuBranch : public LLMenuItemGL -{ -public: - LLPieMenuBranch(const std::string& name, const std::string& label, LLPieMenu* branch); - - // called to rebuild the draw label - virtual void buildDrawLabel( void ); - - // doIt() - do the primary funcationality of the menu item. - virtual void doIt( void ); - - LLPieMenu* getBranch() { return mBranch; } - -protected: - LLPieMenu* mBranch; -}; - -LLPieMenuBranch::LLPieMenuBranch(const std::string& name, - const std::string& label, - LLPieMenu* branch) -: LLMenuItemGL( name, label, KEY_NONE, MASK_NONE ), - mBranch( branch ) -{ - mBranch->hide(FALSE); - mBranch->setParentMenuItem(this); -} -// called to rebuild the draw label -void LLPieMenuBranch::buildDrawLabel( void ) -{ - { - // default enablement is this -- if any of the subitems are - // enabled, this item is enabled. JC - U32 sub_count = mBranch->getItemCount(); - U32 i; - BOOL any_enabled = FALSE; - for (i = 0; i < sub_count; i++) - { - LLMenuItemGL* item = mBranch->getItem(i); - item->buildDrawLabel(); - if (item->getEnabled() && !item->getDrawTextDisabled() ) - { - any_enabled = TRUE; - break; - } - } - setDrawTextDisabled(!any_enabled); - setEnabled(TRUE); - } + // Fix menu rect if needed. + menu->needsArrange(); + menu->arrangeAndClear(); - mDrawAccelLabel.clear(); - std::string st = mDrawAccelLabel; - appendAcceleratorString( st ); - mDrawAccelLabel = st; - - // No special branch suffix - mDrawBranchLabel.clear(); -} + LLUI::getMousePositionLocal(menu->getParent(), &mouse_x, &mouse_y); + LLMenuHolderGL::sContextMenuSpawnPos.set(mouse_x,mouse_y); -// doIt() - do the primary funcationality of the menu item. -void LLPieMenuBranch::doIt( void ) -{ - LLPieMenu *parent = (LLPieMenu *)getParent(); + const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getRect(); - LLRect rect = parent->getRect(); - S32 center_x; - S32 center_y; - parent->localPointToScreen(rect.getWidth() / 2, rect.getHeight() / 2, ¢er_x, ¢er_y); - - parent->hide(FALSE); - mBranch->show( center_x, center_y, FALSE ); -} - -//----------------------------------------------------------------------------- -// class LLPieMenu -// A circular menu of items, icons, etc. -//----------------------------------------------------------------------------- -LLPieMenu::LLPieMenu(const std::string& name, const std::string& label) -: LLMenuGL(name, label), - mFirstMouseDown(FALSE), - mUseInfiniteRadius(FALSE), - mHoverItem(NULL), - mHoverThisFrame(FALSE), - mHoveredAnyItem(FALSE), - mOuterRingAlpha(1.f), - mCurRadius(0.f), - mRightMouseDown(FALSE) -{ - LLMenuGL::setVisible(FALSE); - setCanTearOff(FALSE); -} - -LLPieMenu::LLPieMenu(const std::string& name) -: LLMenuGL(name, name), - mFirstMouseDown(FALSE), - mUseInfiniteRadius(FALSE), - mHoverItem(NULL), - mHoverThisFrame(FALSE), - mHoveredAnyItem(FALSE), - mOuterRingAlpha(1.f), - mCurRadius(0.f), - mRightMouseDown(FALSE) -{ - LLMenuGL::setVisible(FALSE); - setCanTearOff(FALSE); -} - - -void LLPieMenu::initXML(LLXMLNodePtr node, LLView *context, LLUICtrlFactory *factory) -{ - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - if (child->hasName(LL_PIE_MENU_TAG)) - { - // SUBMENU - std::string name("menu"); - child->getAttributeString("name", name); - std::string label(name); - child->getAttributeString("label", label); - - LLPieMenu *submenu = new LLPieMenu(name, label); - appendPieMenu(submenu); - submenu->initXML(child, context, factory); - } - else - { - parseChildXML(child, context, factory); - } - } -} - -// virtual -void LLPieMenu::setVisible(BOOL visible) -{ - if (!visible) - { - hide(FALSE); - } -} - -BOOL LLPieMenu::handleHover( S32 x, S32 y, MASK mask ) -{ - // This is mostly copied from the llview class, but it continues - // the hover handle code after a hover handler has been found. - BOOL handled = FALSE; - - // If we got a hover event, we've already moved the cursor - // for any menu shifts, so subsequent mouseup messages will be in the - // correct position. No need to correct them. - //mShiftHoriz = 0; - //mShiftVert = 0; - - // release mouse capture after short period of visibility if we're using a finite boundary - // so that right click outside of boundary will trigger new pie menu - if (hasMouseCapture() && - !mRightMouseDown && - mShrinkBorderTimer.getStarted() && - mShrinkBorderTimer.getElapsedTimeF32() >= PIE_SHRINK_TIME) - { - gFocusMgr.setMouseCapture(NULL); - mUseInfiniteRadius = FALSE; - } - - LLMenuItemGL *item = pieItemFromXY( x, y ); - - if (item && item->getEnabled()) - { - getWindow()->setCursor(UI_CURSOR_ARROW); - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; - handled = TRUE; - - if (item != mHoverItem) - { - if (mHoverItem) - { - mHoverItem->setHighlight( FALSE ); - } - mHoverItem = item; - mHoverItem->setHighlight( TRUE ); - - switch(pieItemIndexFromXY(x, y)) - { - case 0: - make_ui_sound("UISndPieMenuSliceHighlight0"); - break; - case 1: - make_ui_sound("UISndPieMenuSliceHighlight1"); - break; - case 2: - make_ui_sound("UISndPieMenuSliceHighlight2"); - break; - case 3: - make_ui_sound("UISndPieMenuSliceHighlight3"); - break; - case 4: - make_ui_sound("UISndPieMenuSliceHighlight4"); - break; - case 5: - make_ui_sound("UISndPieMenuSliceHighlight5"); - break; - case 6: - make_ui_sound("UISndPieMenuSliceHighlight6"); - break; - case 7: - make_ui_sound("UISndPieMenuSliceHighlight7"); - break; - default: - make_ui_sound("UISndPieMenuSliceHighlight0"); - break; - } - } - mHoveredAnyItem = TRUE; - } - else - { - // clear out our selection - if (mHoverItem) - { - mHoverItem->setHighlight(FALSE); - mHoverItem = NULL; - } - } - - if( !handled && pointInView( x, y ) ) - { - getWindow()->setCursor(UI_CURSOR_ARROW); - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; - handled = TRUE; - } - - mHoverThisFrame = TRUE; - - return handled; -} - -BOOL LLPieMenu::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - BOOL handled = FALSE; - // The click was somewhere within our rectangle - LLMenuItemGL *item = pieItemFromXY( x, y ); - - if (item) - { - // lie to the item about where the click happened - // to make sure it's within the item's rectangle - handled = item->handleMouseDown( 0, 0, mask ); - } - else if (!mRightMouseDown) - { - // call hidemenus to make sure transient selections get cleared - ((LLMenuHolderGL*)getParent())->hideMenus(); - } - - // always handle mouse down as mouse up will close open menus - return handled; -} - -BOOL LLPieMenu::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - BOOL handled = FALSE; - - mRightMouseDown = TRUE; - - // The click was somewhere within our rectangle - LLMenuItemGL *item = pieItemFromXY( x, y ); - S32 delta_x = x /*+ mShiftHoriz*/ - getLocalRect().getCenterX(); - S32 delta_y = y /*+ mShiftVert*/ - getLocalRect().getCenterY(); - BOOL clicked_in_pie = ((delta_x * delta_x) + (delta_y * delta_y) < mCurRadius*mCurRadius) || mUseInfiniteRadius; - - // grab mouse if right clicking anywhere within pie (even deadzone in middle), to detect drag outside of pie - if (clicked_in_pie) - { - // capture mouse cursor as if on initial menu show - gFocusMgr.setMouseCapture(this); - mShrinkBorderTimer.stop(); - mUseInfiniteRadius = TRUE; - 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 LLPieMenu::handleRightMouseUp( S32 x, S32 y, MASK mask ) -{ - // release mouse capture when right mouse button released, and we're past the shrink time - if (mShrinkBorderTimer.getStarted() && - mShrinkBorderTimer.getElapsedTimeF32() > PIE_SHRINK_TIME) - { - mUseInfiniteRadius = FALSE; - gFocusMgr.setMouseCapture(NULL); - } - - S32 delta_x = x /*+ mShiftHoriz*/ - getLocalRect().getCenterX(); - S32 delta_y = y /*+ mShiftVert*/ - getLocalRect().getCenterY(); - if (!mHoveredAnyItem && !mFirstMouseDown && (delta_x * delta_x) + (delta_y * delta_y) < PIE_CENTER_SIZE * PIE_CENTER_SIZE) - { - // user released right mouse button in middle of pie, interpret this as closing the menu - sMenuContainer->hideMenus(); - return TRUE; - } - - - BOOL result = handleMouseUp( x, y, mask ); - mRightMouseDown = FALSE; - mHoveredAnyItem = FALSE; - - return result; -} - -BOOL LLPieMenu::handleMouseUp( S32 x, S32 y, MASK mask ) -{ - BOOL handled = FALSE; - - // The click was somewhere within our rectangle - LLMenuItemGL *item = pieItemFromXY( x, y ); - - if (item) - { - // lie to the item about where the click happened - // to make sure it's within the item's rectangle - if (item->getEnabled()) - { - handled = item->handleMouseUp( 0, 0, mask ); - hide(TRUE); - } - } - else if (!mRightMouseDown) - { - // call hidemenus to make sure transient selections get cleared - ((LLMenuHolderGL*)getParent())->hideMenus(); - } - - if (handled) - { - make_ui_sound("UISndClickRelease"); - } - - if (!handled && !mUseInfiniteRadius) - { - // call hidemenus to make sure transient selections get cleared - sMenuContainer->hideMenus(); - } - - if (mFirstMouseDown) - { - make_ui_sound("UISndPieMenuAppear"); - mFirstMouseDown = FALSE; - } - - // *FIX: is this necessary? - if (!mShrinkBorderTimer.getStarted()) - { - mShrinkBorderTimer.start(); - } - - return handled; -} - - -// virtual -void LLPieMenu::draw() -{ - // clear hover if mouse moved away - if (!mHoverThisFrame && mHoverItem) - { - mHoverItem->setHighlight(FALSE); - mHoverItem = NULL; - } - - F32 width = (F32) getRect().getWidth(); - F32 height = (F32) getRect().getHeight(); - mCurRadius = PIE_SCALE_FACTOR * llmax( width/2, height/2 ); - - mOuterRingAlpha = mUseInfiniteRadius ? 0.f : 1.f; - if (mShrinkBorderTimer.getStarted()) - { - mOuterRingAlpha = clamp_rescale(mShrinkBorderTimer.getElapsedTimeF32(), 0.f, PIE_SHRINK_TIME, 0.f, 1.f); - mCurRadius *= clamp_rescale(mShrinkBorderTimer.getElapsedTimeF32(), 0.f, PIE_SHRINK_TIME, 1.f, 1.f / PIE_SCALE_FACTOR); - } - - // correct for non-square pixels - F32 center_x = width/2; - F32 center_y = height/2; - S32 steps = 100; - - gGL.pushMatrix(); - { - gGL.translatef(center_x, center_y, 0.f); - - F32 line_width = LLUI::sConfigGroup->getF32("PieMenuLineWidth"); - LLColor4 line_color = LLUI::sColorsGroup->getColor("PieMenuLineColor"); - LLColor4 bg_color = LLUI::sColorsGroup->getColor("PieMenuBgColor"); - LLColor4 selected_color = LLUI::sColorsGroup->getColor("PieMenuSelectedColor"); - - // main body - LLColor4 outer_color = bg_color; - outer_color.mV[VALPHA] *= mOuterRingAlpha; - gl_washer_2d( mCurRadius, (F32) PIE_CENTER_SIZE, steps, bg_color, outer_color ); - - // selected wedge - item_list_t::iterator item_iter; - S32 i = 0; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - if ((*item_iter)->getHighlight()) - { - F32 arc_size = F_PI * 0.25f; - - F32 start_radians = (i * arc_size) - (arc_size * 0.5f); - F32 end_radians = start_radians + arc_size; - - LLColor4 outer_color = selected_color; - outer_color.mV[VALPHA] *= mOuterRingAlpha; - gl_washer_segment_2d( mCurRadius, (F32)PIE_CENTER_SIZE, start_radians, end_radians, steps / 8, selected_color, outer_color ); - } - i++; - } - - LLUI::setLineWidth( line_width ); - - // inner lines - outer_color = line_color; - outer_color.mV[VALPHA] *= mOuterRingAlpha; - gl_washer_spokes_2d( mCurRadius, (F32)PIE_CENTER_SIZE, 8, line_color, outer_color ); - - // inner circle - gGL.color4fv( line_color.mV ); - gl_circle_2d( 0, 0, (F32)PIE_CENTER_SIZE, steps, FALSE ); - - // outer circle - gGL.color4fv( outer_color.mV ); - gl_circle_2d( 0, 0, mCurRadius, steps, FALSE ); - - LLUI::setLineWidth(1.0f); - } - gGL.popMatrix(); - - mHoverThisFrame = FALSE; - - LLView::draw(); -} - -void LLPieMenu::drawBackground(LLMenuItemGL* itemp, LLColor4& color) -{ - F32 width = (F32) getRect().getWidth(); - F32 height = (F32) getRect().getHeight(); - F32 center_x = width/2; - F32 center_y = height/2; - S32 steps = 100; - - gGL.color4fv( color.mV ); - gGL.pushMatrix(); - { - gGL.translatef(center_x - itemp->getRect().mLeft, center_y - itemp->getRect().mBottom, 0.f); - - item_list_t::iterator item_iter; - S32 i = 0; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - if ((*item_iter) == itemp) - { - F32 arc_size = F_PI * 0.25f; - - F32 start_radians = (i * arc_size) - (arc_size * 0.5f); - F32 end_radians = start_radians + arc_size; - - LLColor4 outer_color = color; - outer_color.mV[VALPHA] *= mOuterRingAlpha; - gl_washer_segment_2d( mCurRadius, (F32)PIE_CENTER_SIZE, start_radians, end_radians, steps / 8, color, outer_color ); - } - i++; - } - } - gGL.popMatrix(); -} - -// virtual -BOOL LLPieMenu::append(LLMenuItemGL *item) -{ - item->setBriefItem(TRUE); - item->setFont( LLFontGL::getFontSansSerifSmall() ); - return LLMenuGL::append(item); -} - -// virtual -BOOL LLPieMenu::appendSeparator(const std::string &separator_name) -{ - LLMenuItemGL* separator = new LLMenuItemBlankGL(); - separator->setFont( LLFontGL::getFontSansSerifSmall() ); - return append( separator ); -} - - -BOOL LLPieMenu::appendPieMenu(LLPieMenu *menu) -{ - if (menu == this) - { - llerrs << "Can't attach a pie menu to itself" << llendl; - } - LLPieMenuBranch *item; - item = new LLPieMenuBranch(menu->getName(), menu->getLabel(), menu); - getParent()->addChild(item->getBranch()); - item->setFont( LLFontGL::getFontSansSerifSmall() ); - return append( item ); -} - -// virtual -void LLPieMenu::arrange() -{ - const S32 rect_height = 190; - const S32 rect_width = 190; - - // all divide by 6 - const S32 CARD_X = 60; - const S32 DIAG_X = 48; - const S32 CARD_Y = 76; - const S32 DIAG_Y = 42; - - const S32 ITEM_CENTER_X[] = { CARD_X, DIAG_X, 0, -DIAG_X, -CARD_X, -DIAG_X, 0, DIAG_X }; - const S32 ITEM_CENTER_Y[] = { 0, DIAG_Y, CARD_Y, DIAG_Y, 0, -DIAG_Y, -CARD_Y, -DIAG_Y }; - - LLRect rect; - - S32 font_height = 0; - if( mItems.size() ) - { - font_height = (*mItems.begin())->getNominalHeight(); - } - S32 item_width = 0; - -// F32 sin_delta = OO_SQRT2; // sin(45 deg) -// F32 cos_delta = OO_SQRT2; // cos(45 deg) - - // TODO: Compute actual bounding rect for menu - - // HACK: casting away const. Should use setRect or some helper function instead. - const_cast<LLRect&>(getRect()).setOriginAndSize(getRect().mLeft, getRect().mBottom, rect_width, rect_height ); - - // place items around a circle, with item 0 at positive X, - // rotating counter-clockwise - item_list_t::iterator item_iter; - S32 i = 0; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - LLMenuItemGL *item = *item_iter; - - item_width = item->getNominalWidth(); - - // Put in the right place around a circle centered at 0,0 - rect.setCenterAndSize(ITEM_CENTER_X[i], - ITEM_CENTER_Y[i], - item_width, font_height ); - - // Correct for the actual rectangle size - rect.translate( rect_width/2, rect_height/2 ); - - item->setRect( rect ); - - // Make sure enablement is correct - item->buildDrawLabel(); - i++; - } -} - -LLMenuItemGL *LLPieMenu::pieItemFromXY(S32 x, S32 y) -{ - // We might have shifted this menu on draw. If so, we need - // to shift over mouseup events until we get a hover event. - //x += mShiftHoriz; - //y += mShiftVert; - - // An arc of the pie menu is 45 degrees - const F32 ARC_DEG = 45.f; - S32 delta_x = x - getRect().getWidth() / 2; - S32 delta_y = y - getRect().getHeight() / 2; - - // circle safe zone in the center - S32 dist_squared = delta_x*delta_x + delta_y*delta_y; - if (dist_squared < PIE_CENTER_SIZE*PIE_CENTER_SIZE) - { - return NULL; - } - - // infinite radius is only used with right clicks - S32 radius = llmax( getRect().getWidth()/2, getRect().getHeight()/2 ); - if (!(mUseInfiniteRadius && mRightMouseDown) && dist_squared > radius * radius) - { - return NULL; - } - - F32 angle = RAD_TO_DEG * (F32) atan2((F32)delta_y, (F32)delta_x); - - // rotate marks CCW so that east = [0, ARC_DEG) instead of - // [-ARC_DEG/2, ARC_DEG/2) - angle += ARC_DEG / 2.f; - - // make sure we're only using positive angles - if (angle < 0.f) angle += 360.f; - - S32 which = S32( angle / ARC_DEG ); - - if (0 <= which && which < (S32)mItems.size() ) - { - item_list_t::iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - if (which == 0) - { - return (*item_iter); - } - which--; - } - } - - return NULL; -} - -S32 LLPieMenu::pieItemIndexFromXY(S32 x, S32 y) -{ - // An arc of the pie menu is 45 degrees - const F32 ARC_DEG = 45.f; - // correct for non-square pixels - S32 delta_x = x - getRect().getWidth() / 2; - S32 delta_y = y - getRect().getHeight() / 2; - - // circle safe zone in the center - if (delta_x*delta_x + delta_y*delta_y < PIE_CENTER_SIZE*PIE_CENTER_SIZE) - { - return -1; - } - - F32 angle = RAD_TO_DEG * (F32) atan2((F32)delta_y, (F32)delta_x); - - // rotate marks CCW so that east = [0, ARC_DEG) instead of - // [-ARC_DEG/2, ARC_DEG/2) - angle += ARC_DEG / 2.f; - - // make sure we're only using positive angles - if (angle < 0.f) angle += 360.f; - - S32 which = S32( angle / ARC_DEG ); - return which; -} - -void LLPieMenu::show(S32 x, S32 y, BOOL mouse_down) -{ - S32 width = getRect().getWidth(); - S32 height = getRect().getHeight(); - - const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect(); - - LLView* parent_view = getParent(); - BOOL moved = FALSE; - - S32 local_x, local_y; - parent_view->screenPointToLocal(x, y, &local_x, &local_y); - - // HACK: casting away const. Should use setRect or some helper function instead. - const_cast<LLRect&>(getRect()).setCenterAndSize(local_x, local_y, width, height); - arrange(); - - // Adjust the pie rectangle to keep it on screen - if (getRect().mLeft < menu_region_rect.mLeft) - { - //mShiftHoriz = menu_region_rect.mLeft - getRect().mLeft; - //getRect().translate( mShiftHoriz, 0 ); - // HACK: casting away const. Should use setRect or some helper function instead. - const_cast<LLRect&>(getRect()).translate( menu_region_rect.mLeft - getRect().mLeft, 0 ); - moved = TRUE; - } - - if (getRect().mRight > menu_region_rect.mRight) - { - //mShiftHoriz = menu_region_rect.mRight - getRect().mRight; - //getRect().translate( mShiftHoriz, 0); - // HACK: casting away const. Should use setRect or some helper function instead. - const_cast<LLRect&>(getRect()).translate( menu_region_rect.mRight - getRect().mRight, 0 ); - moved = TRUE; - } - - if (getRect().mBottom < menu_region_rect.mBottom) - { - //mShiftVert = menu_region_rect.mBottom - getRect().mBottom; - //getRect().translate( 0, mShiftVert ); - // HACK: casting away const. Should use setRect or some helper function instead. - const_cast<LLRect&>(getRect()).translate( 0, menu_region_rect.mBottom - getRect().mBottom ); - moved = TRUE; - } - - - if (getRect().mTop > menu_region_rect.mTop) - { - //mShiftVert = menu_region_rect.mTop - getRect().mTop; - //getRect().translate( 0, mShiftVert ); - // HACK: casting away const. Should use setRect or some helper function instead. - const_cast<LLRect&>(getRect()).translate( 0, menu_region_rect.mTop - getRect().mTop ); - moved = TRUE; - } - - // If we had to relocate the pie menu, put the cursor in the - // center of its rectangle - if (moved) - { - LLCoordGL center; - center.mX = (getRect().mLeft + getRect().mRight) / 2; - center.mY = (getRect().mTop + getRect().mBottom) / 2; - - LLUI::setCursorPositionLocal(getParent(), center.mX, center.mY); - } - - // *FIX: what happens when mouse buttons reversed? - mRightMouseDown = mouse_down; - mFirstMouseDown = mouse_down; - mUseInfiniteRadius = TRUE; - mHoveredAnyItem = FALSE; - - if (!mFirstMouseDown) - { - make_ui_sound("UISndPieMenuAppear"); - } - - LLView::setVisible(TRUE); - - // we want all mouse events in case user does quick right click again off of pie menu - // rectangle, to support gestural menu traversal - gFocusMgr.setMouseCapture(this); - - if (mouse_down) - { - mShrinkBorderTimer.stop(); - } - else - { - mShrinkBorderTimer.start(); - } -} - -void LLPieMenu::hide(BOOL item_selected) -{ - if (!getVisible()) return; + 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 ); - if (mHoverItem) - { - mHoverItem->setHighlight( FALSE ); - mHoverItem = NULL; - } - make_ui_sound("UISndPieMenuHide"); + // 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, FALSE ); + menu->getParent()->sendChildToFront(menu); - mFirstMouseDown = FALSE; - mRightMouseDown = FALSE; - mUseInfiniteRadius = FALSE; - mHoveredAnyItem = FALSE; - LLView::setVisible(FALSE); - gFocusMgr.setMouseCapture(NULL); } ///============================================================================ /// Class LLMenuBarGL ///============================================================================ -static LLRegisterWidget<LLMenuBarGL> r2("menu_bar"); +static LLDefaultChildRegistry::Register<LLMenuBarGL> r2("menu_bar"); -// Default constructor -LLMenuBarGL::LLMenuBarGL( const std::string& name ) : LLMenuGL ( name, name ) -{ - mHorizontalLayout = TRUE; - setCanTearOff(FALSE); - mKeepFixedSize = TRUE; - mAltKeyTrigger = FALSE; -} +LLMenuBarGL::LLMenuBarGL( const Params& p ) +: LLMenuGL(p), + mAltKeyTrigger(FALSE) +{} // Default destructor LLMenuBarGL::~LLMenuBarGL() @@ -3811,108 +3010,6 @@ LLMenuBarGL::~LLMenuBarGL() mAccelerators.clear(); } -// virtual -LLXMLNodePtr LLMenuBarGL::getXML(bool save_children) const -{ - // Sorty of hacky: reparent items to this and then back at the end of the export - LLView *orig_parent = NULL; - item_list_t::const_iterator item_iter; - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - LLMenuItemGL* child = *item_iter; - LLMenuItemBranchGL* branch = (LLMenuItemBranchGL*)child; - LLMenuGL *menu = branch->getBranch(); - orig_parent = menu->getParent(); - menu->updateParent((LLView *)this); - } - - LLXMLNodePtr node = LLMenuGL::getXML(); - - for (item_iter = mItems.begin(); item_iter != mItems.end(); ++item_iter) - { - LLMenuItemGL* child = *item_iter; - LLMenuItemBranchGL* branch = (LLMenuItemBranchGL*)child; - LLMenuGL *menu = branch->getBranch(); - menu->updateParent(orig_parent); - } - - return node; -} - -LLView* LLMenuBarGL::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("menu"); - node->getAttributeString("name", name); - - BOOL opaque = FALSE; - node->getAttributeBOOL("opaque", opaque); - - LLMenuBarGL *menubar = new LLMenuBarGL(name); - - LLHandle<LLFloater> parent_handle; - LLFloater* parent_floater = dynamic_cast<LLFloater*>(parent); - if (parent_floater) - { - parent_handle = parent_floater->getHandle(); - } - - // We need to have the rect early so that it's around when building - // the menu items - LLRect view_rect; - createRect(node, view_rect, parent, menubar->getRequiredRect()); - menubar->setRect(view_rect); - - if (node->hasAttribute("drop_shadow")) - { - BOOL drop_shadow = FALSE; - node->getAttributeBOOL("drop_shadow", drop_shadow); - menubar->setDropShadowed(drop_shadow); - } - - menubar->setBackgroundVisible(opaque); - LLColor4 color(0,0,0,0); - if (opaque && LLUICtrlFactory::getAttributeColor(node,"color", color)) - { - menubar->setBackgroundColor(color); - } - - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - if (child->hasName("menu")) - { - LLMenuGL *menu = (LLMenuGL*)LLMenuGL::fromXML(child, parent, factory); - // because of lazy initialization, have to disable tear off functionality - // and then re-enable with proper parent handle - if (menu->getCanTearOff()) - { - menu->setCanTearOff(FALSE); - menu->setCanTearOff(TRUE, parent_handle); - } - menubar->appendMenu(menu); - if (LLMenuGL::sMenuContainer != NULL) - { - menu->updateParent(LLMenuGL::sMenuContainer); - } - else - { - menu->updateParent(parent); - } - } - } - - menubar->initFromXML(node, parent); - - BOOL create_jump_keys = FALSE; - node->getAttributeBOOL("create_jump_keys", create_jump_keys); - if (create_jump_keys) - { - menubar->createJumpKeys(); - } - - return menubar; -} - BOOL LLMenuBarGL::handleAcceleratorKey(KEY key, MASK mask) { if (getHighlightedItem() && mask == MASK_NONE) @@ -3949,7 +3046,8 @@ BOOL LLMenuBarGL::handleAcceleratorKey(KEY key, MASK mask) BOOL LLMenuBarGL::handleKeyHere(KEY key, MASK mask) { - if(key == KEY_ALT && !gKeyboard->getKeyRepeated(key) && LLUI::sConfigGroup->getBOOL("UseAltKeyForMenus")) + static LLUICachedControl<bool> use_altkey_for_menus ("UseAltKeyForMenus", 0); + if(key == KEY_ALT && !gKeyboard->getKeyRepeated(key) && use_altkey_for_menus) { mAltKeyTrigger = TRUE; } @@ -3982,7 +3080,7 @@ BOOL LLMenuBarGL::handleJumpKey(KEY key) LLMenuGL::setKeyboardMode(TRUE); found_it->second->setHighlight(TRUE); - found_it->second->doIt(); + found_it->second->onCommit(); } return TRUE; } @@ -3999,19 +3097,6 @@ BOOL LLMenuBarGL::handleMouseDown(S32 x, S32 y, MASK mask) return LLMenuGL::handleMouseDown(x, y, mask); } -BOOL LLMenuBarGL::handleRightMouseDown(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); -} - - void LLMenuBarGL::draw() { LLMenuItemGL* itemp = getHighlightedItem(); @@ -4029,6 +3114,7 @@ void LLMenuBarGL::draw() LLMenuGL::draw(); } + void LLMenuBarGL::checkMenuTrigger() { // has the ALT key been pressed and subsequently released? @@ -4036,7 +3122,8 @@ void LLMenuBarGL::checkMenuTrigger() { // if alt key was released quickly, treat it as a menu access key // otherwise it was probably an Alt-zoom or similar action - if (gKeyboard->getKeyElapsedTime(KEY_ALT) <= LLUI::sConfigGroup->getF32("MenuAccessKeyTime") || + static LLUICachedControl<F32> menu_access_key_time ("MenuAccessKeyTime", 0); + if (gKeyboard->getKeyElapsedTime(KEY_ALT) <= menu_access_key_time || gKeyboard->getKeyElapsedFrameCount(KEY_ALT) < 2) { if (getHighlightedItem()) @@ -4105,7 +3192,7 @@ S32 LLMenuBarGL::getRightmostMenuEdge() } // add a vertical separator to this menu -BOOL LLMenuBarGL::appendSeparator( const std::string &separator_name ) +BOOL LLMenuBarGL::addSeparator() { LLMenuItemGL* separator = new LLMenuItemVerticalSeparatorGL(); return append( separator ); @@ -4122,11 +3209,23 @@ BOOL LLMenuBarGL::appendMenu( LLMenuGL* menu ) BOOL success = TRUE; - LLMenuItemBranchGL* branch = NULL; - branch = new LLMenuItemBranchDownGL( menu->getName(), menu->getLabel(), menu->getHandle()); + // *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"); + + LLMenuItemBranchDownGL* branch = LLUICtrlFactory::create<LLMenuItemBranchDownGL>(p); success &= branch->addToAcceleratorList(&mAccelerators); success &= append( branch ); branch->setJumpKey(branch->getJumpKey()); + menu->updateParent(LLMenuGL::sMenuContainer); + return success; } @@ -4172,7 +3271,7 @@ BOOL LLMenuBarGL::handleHover( S32 x, S32 y, MASK mask ) handled = TRUE; if (active_menu && active_menu != viewp) { - ((LLMenuItemGL*)viewp)->doIt(); + ((LLMenuItemGL*)viewp)->onCommit(); LLMenuGL::setKeyboardMode(FALSE); } LLMenuGL::setKeyboardMode(FALSE); @@ -4203,23 +3302,15 @@ BOOL LLMenuBarGL::handleHover( S32 x, S32 y, MASK mask ) ///============================================================================ /// Class LLMenuHolderGL ///============================================================================ -LLMenuHolderGL::LLMenuHolderGL() - : LLPanel(std::string("Menu Holder")) -{ - setMouseOpaque(FALSE); - sItemActivationTimer.stop(); - mCanHide = TRUE; -} +LLCoordGL LLMenuHolderGL::sContextMenuSpawnPos(S32_MAX, S32_MAX); -LLMenuHolderGL::LLMenuHolderGL(const std::string& name, const LLRect& rect, BOOL mouse_opaque, U32 follows) -: LLPanel(name, rect, FALSE) +LLMenuHolderGL::LLMenuHolderGL(const LLMenuHolderGL::Params& p) + : LLPanel(p) { - setMouseOpaque(mouse_opaque); sItemActivationTimer.stop(); mCanHide = TRUE; } - void LLMenuHolderGL::draw() { LLView::draw(); @@ -4234,16 +3325,11 @@ void LLMenuHolderGL::draw() selecteditem->localRectToOtherView(selecteditem->getLocalRect(), &item_rect, this); F32 interpolant = sItemActivationTimer.getElapsedTimeF32() / ACTIVATE_HIGHLIGHT_TIME; - F32 alpha = lerp(LLMenuItemGL::getHighlightBGColor().mV[VALPHA], 0.f, interpolant); - LLColor4 bg_color(LLMenuItemGL::getHighlightBGColor().mV[VRED], - LLMenuItemGL::getHighlightBGColor().mV[VGREEN], - LLMenuItemGL::getHighlightBGColor().mV[VBLUE], - alpha); LLUI::pushMatrix(); { LLUI::translate((F32)item_rect.mLeft, (F32)item_rect.mBottom, 0.f); - selecteditem->getMenu()->drawBackground(selecteditem, bg_color); + selecteditem->getMenu()->drawBackground(selecteditem, interpolant); selecteditem->draw(); } LLUI::popMatrix(); @@ -4272,6 +3358,60 @@ BOOL LLMenuHolderGL::handleRightMouseDown( S32 x, S32 y, MASK mask ) 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<LLMenuGL*>(getVisibleMenu()); + + if (pMenu) + { + //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 + { + //highlight first enabled one + 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()) @@ -4281,17 +3421,17 @@ void LLMenuHolderGL::reshape(S32 width, S32 height, BOOL called_from_parent) LLView::reshape(width, height, called_from_parent); } -BOOL LLMenuHolderGL::hasVisibleMenu() const +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<LLMenuBarGL*>(viewp) == NULL) { - return TRUE; + return viewp; } } - return FALSE; + return NULL; } @@ -4334,51 +3474,60 @@ void LLMenuHolderGL::setActivatedItem(LLMenuItemGL* item) /// Class LLTearOffMenu ///============================================================================ LLTearOffMenu::LLTearOffMenu(LLMenuGL* menup) : - LLFloater(menup->getName(), LLRect(0, 100, 100, 0), menup->getLabel(), FALSE, DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT, FALSE, FALSE) + LLFloater(LLSD()) { + 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->arrange(); + 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 = (F32)(rect.getHeight() + LLFLOATER_HEADER_SIZE + 5); + mTargetHeight = (F32)(rect.getHeight() + floater_header_size); reshape(rect.getWidth(), rect.getHeight()); setRect(rect); // attach menu to floater - menup->setFollowsAll(); + menup->setFollows( FOLLOWS_LEFT | FOLLOWS_BOTTOM ); mOldParent = menup->getParent(); addChild(menup); menup->setVisible(TRUE); - menup->translate(-menup->getRect().mLeft + 1, -menup->getRect().mBottom + 1); + 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()); - mMenu->arrange(); + mMenu->needsArrange(); if (getRect().getHeight() != mTargetHeight) { // animate towards target height reshape(getRect().getWidth(), llceil(lerp((F32)getRect().getHeight(), mTargetHeight, LLCriticalDamp::getInterpolant(0.05f)))); } - else - { - // when in stasis, remain big enough to hold menu contents - mTargetHeight = (F32)(mMenu->getRect().getHeight() + LLFLOATER_HEADER_SIZE + 4); - reshape(mMenu->getRect().getWidth() + 3, mMenu->getRect().getHeight() + LLFLOATER_HEADER_SIZE + 5); - } LLFloater::draw(); } @@ -4455,12 +3604,12 @@ LLTearOffMenu* LLTearOffMenu::create(LLMenuGL* menup) LLTearOffMenu* tearoffp = new LLTearOffMenu(menup); // keep onscreen gFloaterView->adjustToFitScreen(tearoffp, FALSE); - tearoffp->open(); /* Flawfinder: ignore */ + tearoffp->openFloater(LLSD()); return tearoffp; } -void LLTearOffMenu::onClose(bool app_quitting) +void LLTearOffMenu::closeTearOff() { removeChild(mMenu); mOldParent->addChild(mMenu); @@ -4470,6 +3619,342 @@ void LLTearOffMenu::onClose(bool app_quitting) mMenu->setVisible(FALSE); mMenu->setTornOff(FALSE); mMenu->setDropShadowed(TRUE); - destroy(); +} + + +//----------------------------------------------------------------------------- +// class LLContextMenuBranch +// A branch to another context menu +//----------------------------------------------------------------------------- +class LLContextMenuBranch : public LLMenuItemGL +{ +public: + struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params> + { + Mandatory<LLContextMenu*> branch; + }; + + LLContextMenuBranch(const Params&); + + virtual ~LLContextMenuBranch() + { + delete mBranch; + } + + // 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; } + void setHighlight( BOOL highlight ); + +protected: + void showSubMenu(); + + LLContextMenu* mBranch; +}; + +LLContextMenuBranch::LLContextMenuBranch(const LLContextMenuBranch::Params& p) +: LLMenuItemGL(p), + mBranch( p.branch ) +{ + mBranch->hide(); + mBranch->setParentMenuItem(this); +} + +// called to rebuild the draw label +void LLContextMenuBranch::buildDrawLabel( void ) +{ + { + // default enablement is this -- if any of the subitems are + // enabled, this item is enabled. JC + U32 sub_count = mBranch->getItemCount(); + U32 i; + BOOL any_enabled = FALSE; + for (i = 0; i < sub_count; i++) + { + LLMenuItemGL* item = mBranch->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; + + // No special branch suffix + mDrawBranchLabel.clear(); +} + +void LLContextMenuBranch::showSubMenu() +{ + S32 center_x; + S32 center_y; + localPointToScreen(getRect().getWidth(), getRect().getHeight() , ¢er_x, ¢er_y); + mBranch->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); + if( highlight ) + { + showSubMenu(); + } + else + { + mBranch->hide(); + } +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////// +//----------------------------------------------------------------------------- +// class LLContextMenu +// A context menu +//----------------------------------------------------------------------------- +static LLDefaultChildRegistry::Register<LLContextMenu> context_menu_register("context_menu"); +static MenuRegistry::Register<LLContextMenu> 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) +{ + // 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(); + + 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; +} + +void LLContextMenu::draw() +{ + LLMenuGL::draw(); +} + +BOOL LLContextMenu::appendContextSubMenu(LLContextMenu *menu) +{ + + if (menu == this) + { + llerrs << "Can't attach a context menu to itself" << llendl; + } + + LLContextMenuBranch *item; + LLContextMenuBranch::Params p; + p.name = menu->getName(); + p.label = menu->getLabel(); + 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"); + + item = LLUICtrlFactory::create<LLContextMenuBranch>(p); + LLMenuGL::sMenuContainer->addChild(item->getBranch()); + item->setFont( LLFontGL::getFontSansSerif() ); + + return append( item ); +} + +bool LLContextMenu::addChild(LLView* view, S32 tab_group) +{ + LLContextMenu* context = dynamic_cast<LLContextMenu*>(view); + if (context) + return appendContextSubMenu(context); + + LLMenuItemSeparatorGL* separator = dynamic_cast<LLMenuItemSeparatorGL*>(view); + if (separator) + return append(separator); + + LLMenuItemGL* item = dynamic_cast<LLMenuItemGL*>(view); + if (item) + return append(item); + + return false; } diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 030b81b58c..61e06f9e5f 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -38,85 +38,80 @@ #include "llstring.h" #include "v4color.h" #include "llframetimer.h" -#include "llevent.h" #include "llkeyboard.h" #include "llfloater.h" #include "lluistring.h" #include "llview.h" - +#include <boost/function.hpp> extern S32 MENU_BAR_HEIGHT; extern S32 MENU_BAR_WIDTH; -// These callbacks are used by the LLMenuItemCallGL and LLMenuItemCheckGL -// classes during their work. -typedef void (*menu_callback)(void*); - -// These callbacks are used by the LLMenuItemCallGL -// classes during their work. -typedef void (*on_disabled_callback)(void*); - -// This callback is used by the LLMenuItemCallGL and LLMenuItemCheckGL -// to determine if the current menu is enabled. -typedef BOOL (*enabled_callback)(void*); - -// This callback is used by LLMenuItemCheckGL to determine it's -// 'checked' state. -typedef BOOL (*check_callback)(void*); - -// This callback is potentially used by LLMenuItemCallGL. If provided, -// this function is called whenever it's time to determine the label's -// contents. Put the contents of the label in the provided parameter. -typedef void (*label_callback)(std::string&,void*); - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLMenuItemGL // // The LLMenuItemGL represents a single menu item in a menu. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLMenuItemGL : public LLView +class LLMenuItemGL : public LLUICtrl { public: - // static functions to control the global color scheme. - static void setEnabledColor( const LLColor4& color ) { sEnabledColor = color; } - static const LLColor4& getEnabledColor() { return sEnabledColor; } - static void setDisabledColor( const LLColor4& color ) { sDisabledColor = color; } - static const LLColor4& getDisabledColor() { return sDisabledColor; } - static void setHighlightBGColor( const LLColor4& color ) { sHighlightBackground = color; } - static const LLColor4& getHighlightBGColor() { return sHighlightBackground; } - static void setHighlightFGColor( const LLColor4& color ) { sHighlightForeground = color; } - static const LLColor4& getHighlightFGColor() { return sHighlightForeground; } - - LLMenuItemGL( const std::string& name, const std::string& label, KEY key = KEY_NONE, MASK = MASK_NONE ); - virtual ~LLMenuItemGL() {}; - - virtual void setValue(const LLSD& value) { setLabel(value.asString()); } + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<std::string> shortcut; + Optional<KEY> jump_key; + Optional<bool> use_mac_ctrl; + + Ignored rect, + left, + top, + right, + bottom, + width, + height, + bottom_delta, + left_delta; + + Optional<LLUIColor> enabled_color, + disabled_color, + highlight_bg_color, + highlight_fg_color; + + + Params(); + }; - virtual LLXMLNodePtr getXML(bool save_children = true) const; - - virtual std::string getType() const { return "item"; } +protected: + LLMenuItemGL(const Params&); + friend class LLUICtrlFactory; +public: + // LLView overrides + /*virtual*/ void handleVisibilityChange(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); - virtual BOOL handleHover(S32 x, S32 y, MASK mask); + // LLUICtrl overrides + /*virtual*/ void setValue(const LLSD& value); 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; } - void setFontStyle(U8 style) { mStyle = style; } - U8 getFontStyle() const { return mStyle; } // 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) { mBriefItem = brief; } - virtual BOOL isBriefItem() const { return mBriefItem; } + virtual void setBriefItem(BOOL brief); + virtual BOOL isBriefItem() const; virtual BOOL addToAcceleratorList(std::list<LLKeyBinding*> *listp); void setAllowKeyRepeat(BOOL allow) { mAllowKeyRepeat = allow; } @@ -128,7 +123,7 @@ public: virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); // Get the parent menu for this item - virtual class LLMenuGL* getMenu(); + 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 @@ -140,7 +135,7 @@ public: // 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 - // doIt() function, the draw buffer will not be updated and will + // 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. @@ -149,8 +144,7 @@ public: // for branching menu items, bring sub menus up to root level of menu hierarchy virtual void updateBranchParent( LLView* parentp ){}; - // doIt() - do the primary funcationality of the menu item. - virtual void doIt( void ); + virtual void onCommit( void ); virtual void setHighlight( BOOL highlight ); virtual BOOL getHighlight() const { return mHighlight; } @@ -167,6 +161,7 @@ public: 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 draw( void ); BOOL getHover() const { return mGotHover; } @@ -180,7 +175,8 @@ protected: // This function appends the character string representation of // the current accelerator key and mask to the provided string. void appendAcceleratorString( std::string& st ) const; - + +protected: KEY mAcceleratorKey; MASK mAcceleratorMask; // mLabel contains the actual label specified by the user. @@ -193,13 +189,13 @@ protected: LLUIString mDrawAccelLabel; LLUIString mDrawBranchLabel; + LLUIColor mEnabledColor; + LLUIColor mDisabledColor; + LLUIColor mHighlightBackground; + LLUIColor mHighlightForeground; + BOOL mHighlight; private: - static LLColor4 sEnabledColor; - static LLColor4 sDisabledColor; - static LLColor4 sHighlightBackground; - static LLColor4 sHighlightForeground; - // Keyboard and mouse variables BOOL mAllowKeyRepeat; BOOL mGotHover; @@ -210,12 +206,32 @@ private: // Font for this item const LLFontGL* mFont; - U8 mStyle; BOOL mDrawTextDisabled; KEY mJumpKey; }; +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLMenuItemSeparatorGL +// +// This class represents a separator. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLMenuItemSeparatorGL : public LLMenuItemGL +{ +public: + struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params> + { + 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*/ U32 getNominalHeight( void ) const; +}; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLMenuItemCallGL @@ -224,78 +240,52 @@ private: // calls a user defined callback. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLMenuItemCallGL : public LLMenuItemGL, public LLObservable +class LLMenuItemCallGL : public LLMenuItemGL { public: - // normal constructor - LLMenuItemCallGL( const std::string& name, - menu_callback clicked_cb, - enabled_callback enabled_cb = NULL, - void* user_data = NULL, - KEY key = KEY_NONE, MASK mask = MASK_NONE, - BOOL enabled = TRUE, - on_disabled_callback on_disabled_cb = NULL); - LLMenuItemCallGL( const std::string& name, - const std::string& label, - menu_callback clicked_cb, - enabled_callback enabled_cb = NULL, - void* user_data = NULL, - KEY key = KEY_NONE, MASK mask = MASK_NONE, - BOOL enabled = TRUE, - on_disabled_callback on_disabled_cb = NULL); - - // constructor for when you want to trap the arrange method. - LLMenuItemCallGL( const std::string& name, - const std::string& label, - menu_callback clicked_cb, - enabled_callback enabled_cb, - label_callback label_cb, - void* user_data, - KEY key = KEY_NONE, MASK mask = MASK_NONE, - BOOL enabled = TRUE, - on_disabled_callback on_disabled_c = NULL); - LLMenuItemCallGL( const std::string& name, - menu_callback clicked_cb, - enabled_callback enabled_cb, - label_callback label_cb, - void* user_data, - KEY key = KEY_NONE, MASK mask = MASK_NONE, - BOOL enabled = TRUE, - on_disabled_callback on_disabled_c = NULL); - virtual LLXMLNodePtr getXML(bool save_children = true) const; - - virtual std::string getType() const { return "call"; } - - - void setEnabledControl(std::string enabled_control, LLView *context); - void setVisibleControl(std::string enabled_control, LLView *context); - - void setMenuCallback(menu_callback callback, void* data) { mCallback = callback; mUserData = data; }; - menu_callback getMenuCallback() const { return mCallback; } - - void setEnabledCallback(enabled_callback callback) { mEnabledCallback = callback; }; - - void setUserData(void *userdata) { mUserData = userdata; } - void* getUserData() const { return mUserData; } + struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params> + { + Optional<EnableCallbackParam > on_enable; + Optional<CommitCallbackParam > on_click; + Optional<EnableCallbackParam > 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 ); - // doIt() - do the primary funcationality of the menu item. - virtual void doIt( 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: - menu_callback mCallback; - // mEnabledCallback should return TRUE if the item should be enabled - enabled_callback mEnabledCallback; - label_callback mLabelCallback; - void* mUserData; - on_disabled_callback mOnDisabledCallback; + enable_signal_t mEnableSignal; + enable_signal_t mVisibleSignal; }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -312,78 +302,37 @@ class LLMenuItemCheckGL : public LLMenuItemCallGL { public: - LLMenuItemCheckGL( const std::string& name, - const std::string& label, - menu_callback callback, - enabled_callback enabled_cb, - check_callback check, - void* user_data, - KEY key = KEY_NONE, MASK mask = MASK_NONE ); - LLMenuItemCheckGL( const std::string& name, - menu_callback callback, - enabled_callback enabled_cb, - check_callback check, - void* user_data, - KEY key = KEY_NONE, MASK mask = MASK_NONE ); - LLMenuItemCheckGL( const std::string& name, - const std::string& label, - menu_callback callback, - enabled_callback enabled_cb, - std::string control_name, - LLView *context, - void* user_data, - KEY key = KEY_NONE, MASK mask = MASK_NONE ); - virtual LLXMLNodePtr getXML(bool save_children = true) const; - - void setCheckedControl(std::string checked_control, LLView *context); - - virtual void setValue(const LLSD& value); - - virtual std::string getType() const { return "check"; } + struct Params : public LLInitParam::Block<Params, LLMenuItemCallGL::Params> + { + Optional<EnableCallbackParam > on_check; + Params() + : on_check("on_check") + {} + }; - // called to rebuild the draw label - virtual void buildDrawLabel( void ); - -private: - check_callback mCheckCallback; - BOOL mChecked; -}; - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLMenuItemToggleGL -// -// The LLMenuItemToggleGL is a menu item that wraps around a user -// specified and controlled boolean. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMenuItemToggleGL : public LLMenuItemGL -{ +protected: + LLMenuItemCheckGL(const Params&); + friend class LLUICtrlFactory; public: - LLMenuItemToggleGL( const std::string& name, const std::string& label, - BOOL* toggle, - KEY key = KEY_NONE, MASK mask = MASK_NONE ); - - LLMenuItemToggleGL( const std::string& name, - BOOL* toggle, - KEY key = KEY_NONE, MASK mask = MASK_NONE ); + + void initFromParams(const Params& p); - virtual std::string getType() const { return "toggle"; } + virtual void onCommit( void ); + + virtual void setValue(const LLSD& value); // called to rebuild the draw label virtual void buildDrawLabel( void ); - - // doIt() - do the primary funcationality of the menu item. - virtual void doIt( void ); - - // LLView Functionality - //virtual void draw( void ); - + + boost::signals2::connection setCheckCallback( const enable_signal_t::slot_type& cb ) + { + return mCheckSignal.connect(cb); + } + private: - BOOL* mToggle; + enable_signal_t mCheckSignal; }; - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLMenuGL // @@ -395,33 +344,81 @@ private: // it in the appendMenu() method. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// child widget registry +struct MenuRegistry : public LLChildRegistry<MenuRegistry> +{}; + + class LLMenuGL : public LLUICtrl -// TODO: The menu and menu item classes share a great deal of functionality and perhaps should be united. -// I think it may make the most sense to make LLMenuGL be a subclass of LLMenuItemGL. -MG { +public: + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<LLHandle<LLFloater> > parent_floater; + Optional<KEY> jump_key; + Optional<bool> horizontal_layout, + can_tear_off, + drop_shadow, + bg_visible, + create_jump_keys, + keep_fixed_size, + scrollable; + Optional<U32> max_scrollable_items; + Optional<U32> preferred_width; + Optional<LLUIColor> bg_color; + Optional<S32> 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), + 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"); + + name = "menu"; + } + }; + + // my valid children are contained in MenuRegistry + typedef MenuRegistry child_registry_t; + + void initFromParams(const Params&); + +protected: + LLMenuGL(const LLMenuGL::Params& p); + friend class LLUICtrlFactory; // let branching menu items use my protected traversal methods friend class LLMenuItemBranchGL; public: - LLMenuGL( const std::string& name, const std::string& label, LLHandle<LLFloater> parent_floater = LLHandle<LLFloater>()); - LLMenuGL( const std::string& label, LLHandle<LLFloater> parent_floater = LLHandle<LLFloater>() ); virtual ~LLMenuGL( void ); - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - - void parseChildXML(LLXMLNodePtr child, LLView *parent, LLUICtrlFactory *factory); + void parseChildXML(LLXMLNodePtr child, LLView* parent); // LLView Functionality - virtual BOOL handleUnicodeCharHere( llwchar uni_char ); - virtual BOOL handleHover( S32 x, S32 y, MASK mask ); - virtual void draw( void ); - virtual void drawBackground(LLMenuItemGL* itemp, LLColor4& color); - virtual void setVisible(BOOL visible); + /*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 removeChild( LLView* ctrl); + /*virtual*/ BOOL postBuild(); virtual BOOL handleAcceleratorKey(KEY key, MASK mask); - LLMenuGL* getChildMenuByName(const std::string& name, BOOL recurse) const; + LLMenuGL* findChildMenuByName(const std::string& name, BOOL recurse) const; BOOL clearHoverItem(); @@ -430,24 +427,13 @@ public: void setLabel(const LLStringExplicit& label) { mLabel = label; } // background colors - static void setDefaultBackgroundColor( const LLColor4& color ) { sDefaultBackgroundColor = color; } - void setBackgroundColor( const LLColor4& color ) { mBackgroundColor = color; } - const LLColor4& getBackgroundColor() const { return mBackgroundColor; } + void setBackgroundColor( const LLUIColor& color ) { mBackgroundColor = color; } + const LLUIColor& getBackgroundColor() const { return mBackgroundColor; } void setBackgroundVisible( BOOL b ) { mBgVisible = b; } void setCanTearOff(BOOL tear_off, LLHandle<LLFloater> parent_floater_handle = LLHandle<LLFloater>()); - // Add the menu item to this menu. - virtual BOOL append( LLMenuItemGL* item ); - - // *NOTE:Mani - appendNoArrange() should be removed when merging to skinning/viewer2.0 - // Its added as a fix to a viewer 1.23 bug that has already been address by skinning work. - virtual BOOL appendNoArrange( LLMenuItemGL* item ); - // add a separator to this menu - virtual BOOL appendSeparator( const std::string &separator_name = LLStringUtil::null ); - - // add a menu - this will create a cascading menu - virtual BOOL appendMenu( LLMenuGL* menu ); + virtual BOOL addSeparator(); // for branching menu items, bring sub menus up to root level of menu hierarchy virtual void updateParent( LLView* parentp ); @@ -471,19 +457,17 @@ public: 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 ); - // Rearrange the components, and do the right thing if the menu doesn't - // fit in the bounds. - // virtual void arrangeWithBounds(LLRect bounds); - void setItemLastSelected(LLMenuItemGL* item); // must be in menu U32 getItemCount(); // number of menu items LLMenuItemGL* getItem(S32 number); // 0 = first item @@ -495,17 +479,14 @@ public: void buildDrawLabels(); void createJumpKeys(); - // Show popup in global screen space based on last mouse location. - static void showPopup(LLMenuGL* menu); - - // Show popup at a specific location. + // 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); // Whether to drop shadow menu bar void setDropShadowed( const BOOL shadowed ); - void setParentMenuItem( LLMenuItemGL* parent_menu_item ) { mParentMenuItem = parent_menu_item; } - LLMenuItemGL* getParentMenuItem() const { return mParentMenuItem; } + void setParentMenuItem( LLMenuItemGL* parent_menu_item ) { mParentMenuItem = parent_menu_item->getHandle(); } + LLMenuItemGL* getParentMenuItem() const { return dynamic_cast<LLMenuItemGL*>(mParentMenuItem.get()); } void setTornOff(BOOL torn_off); BOOL getTornOff() { return mTornOff; } @@ -518,41 +499,64 @@ public: static void setKeyboardMode(BOOL mode) { sKeyboardMode = mode; } static BOOL getKeyboardMode() { return sKeyboardMode; } + S32 getShortcutPad() { return mShortcutPad; } + + void scrollItemsUp(); + void scrollItemsDown(); + BOOL isScrollable() const { return mScrollable; } + static class LLMenuHolderGL* sMenuContainer; 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 ); // 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<KEY, LLMenuItemGL*> 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; private: + + static LLColor4 sDefaultBackgroundColor; static BOOL sKeyboardMode; - LLColor4 mBackgroundColor; + LLUIColor mBackgroundColor; BOOL mBgVisible; - LLMenuItemGL* mParentMenuItem; + LLHandle<LLView> 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; LLHandle<LLFloater> mParentFloaterHandle; KEY mJumpKey; + BOOL mCreateJumpKeys; + S32 mShortcutPad; }; // end class LLMenuGL @@ -567,15 +571,17 @@ private: class LLMenuItemBranchGL : public LLMenuItemGL { public: - LLMenuItemBranchGL( const std::string& name, const std::string& label, LLHandle<LLView> branch, - KEY key = KEY_NONE, MASK mask = MASK_NONE ); + struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params> + { + Optional<LLMenuGL*> branch; + }; +protected: + LLMenuItemBranchGL(const Params&); + friend class LLUICtrlFactory; +public: virtual ~LLMenuItemBranchGL(); - - virtual LLXMLNodePtr getXML(bool save_children = true) const; - - virtual std::string getType() const { return "menu"; } - + virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); virtual BOOL handleAcceleratorKey(KEY key, MASK mask); @@ -586,8 +592,7 @@ public: // called to rebuild the draw label virtual void buildDrawLabel( void ); - // doIt() - do the primary funcationality of the menu item. - virtual void doIt( 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); @@ -598,90 +603,79 @@ public: virtual BOOL handleKeyHere(KEY key, MASK mask); - virtual BOOL isActive() const { return isOpen() && getBranch()->getHighlightedItem(); } + virtual BOOL isActive() const; - virtual BOOL isOpen() const { return getBranch() && getBranch()->isOpen(); } + virtual BOOL isOpen() const; - LLMenuGL *getBranch() const { return (LLMenuGL*)(mBranch.get()); } + LLMenuGL* getBranch() const { return (LLMenuGL*)mBranchHandle.get(); } virtual void updateBranchParent( LLView* parentp ); // LLView Functionality - virtual void onVisibilityChange( BOOL curVisibilityIn ); + virtual void handleVisibilityChange( BOOL curVisibilityIn ); virtual void draw(); - virtual void setEnabledSubMenus(BOOL enabled) { if(getBranch()) getBranch()->setEnabledSubMenus(enabled); } + virtual void setEnabledSubMenus(BOOL enabled) { if (getBranch()) getBranch()->setEnabledSubMenus(enabled); } virtual void openMenu(); - virtual LLView* getChildView(const std::string& name, BOOL recurse = TRUE, BOOL create_if_missing = TRUE) const; + virtual LLView* getChildView(const std::string& name, BOOL recurse = TRUE) const; + virtual LLView* findChildView(const std::string& name, BOOL recurse = TRUE) const; private: - LLHandle<LLView> mBranch; + LLHandle<LLView> mBranchHandle; }; // end class LLMenuItemBranchGL - //----------------------------------------------------------------------------- -// class LLPieMenu -// A circular menu of items, icons, etc. +// class LLContextMenu +// A context menu //----------------------------------------------------------------------------- -class LLPieMenu +class LLContextMenu : public LLMenuGL { public: - LLPieMenu(const std::string& name, const std::string& label); - LLPieMenu(const std::string& name); - virtual ~LLPieMenu() {} + struct Params : public LLInitParam::Block<Params, LLMenuGL::Params> + { + Params() + { + visible = false; + } + }; - void initXML(LLXMLNodePtr node, LLView *context, LLUICtrlFactory *factory); +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 setVisible (BOOL visible); - virtual BOOL handleHover( 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 handleRightMouseUp( S32 x, S32 y, MASK mask ); - virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ); - virtual void draw(); - virtual void drawBackground(LLMenuItemGL* itemp, LLColor4& color); + virtual void draw (); + + virtual void show (S32 x, S32 y); + virtual void hide (); - virtual BOOL append(LLMenuItemGL* item); - virtual BOOL appendSeparator( const std::string &separator_name = LLStringUtil::null ); + 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 ); - BOOL appendPieMenu(LLPieMenu *menu); + virtual bool addChild (LLView* view, S32 tab_group = 0); - virtual void arrange( void ); + BOOL appendContextSubMenu(LLContextMenu *menu); - // Display the menu centered on this point on the screen. - void show(S32 x, S32 y, BOOL mouse_down); - void hide(BOOL item_selected); - -private: - LLMenuItemGL *pieItemFromXY(S32 x, S32 y); - S32 pieItemIndexFromXY(S32 x, S32 y); - - // These cause menu items to be spuriously selected by right-clicks - // near the window edge at low frame rates. I don't think they are - // needed unless you shift the menu position in the draw() function. JC - //S32 mShiftHoriz; // non-zero if menu had to shift this frame - //S32 mShiftVert; // non-zero if menu had to shift this frame - BOOL mFirstMouseDown; // true from show until mouse up - BOOL mUseInfiniteRadius; // allow picking pie menu items anywhere outside of center circle - LLMenuItemGL* mHoverItem; - BOOL mHoverThisFrame; +protected: BOOL mHoveredAnyItem; - LLFrameTimer mShrinkBorderTimer; - F32 mOuterRingAlpha; // for rendering pie menus as both bounded and unbounded - F32 mCurRadius; - BOOL mRightMouseDown; + LLMenuItemGL* mHoverItem; }; + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLMenuBarGL // @@ -691,28 +685,30 @@ private: class LLMenuBarGL : public LLMenuGL { public: - LLMenuBarGL( const std::string& name ); + struct Params : public LLInitParam::Block<Params, LLMenuGL::Params> + { + Params() + { + can_tear_off = false; + keep_fixed_size = true; + horizontal_layout = true; + visible = true; + drop_shadow = false; + } + }; + LLMenuBarGL( const Params& p ); virtual ~LLMenuBarGL(); - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - 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 handleRightMouseDown(S32 x, S32 y, MASK mask); + /*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); - // rearrange the child rects so they fit the shape of the menu - // bar. - virtual void arrange( void ); - virtual void draw(); - virtual BOOL jumpKeysActive(); + /*virtual*/ void draw(); + /*virtual*/ BOOL jumpKeysActive(); // add a vertical separator to this menu - virtual BOOL appendSeparator( const std::string &separator_name = LLStringUtil::null ); - - // add a menu - this will create a drop down menu. - virtual BOOL appendMenu( LLMenuGL* menu ); + virtual BOOL addSeparator(); // LLView Functionality virtual BOOL handleHover( S32 x, S32 y, MASK mask ); @@ -723,6 +719,12 @@ public: 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 <LLKeyBinding*> mAccelerators; @@ -737,8 +739,9 @@ private: class LLMenuHolderGL : public LLPanel { public: - LLMenuHolderGL(); - LLMenuHolderGL(const std::string& name, const LLRect& rect, BOOL mouse_opaque, U32 follows = FOLLOWS_NONE); + struct Params : public LLInitParam::Block<Params, LLPanel::Params> + {}; + LLMenuHolderGL(const Params& p); virtual ~LLMenuHolderGL() {} virtual BOOL hideMenus(); @@ -750,11 +753,20 @@ public: 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(); } - virtual BOOL hasVisibleMenu() const; + 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<LLView> sItemLastSelectedHandle; static LLFrameTimer sItemActivationTimer; @@ -772,8 +784,8 @@ class LLTearOffMenu : public LLFloater { public: static LLTearOffMenu* create(LLMenuGL* menup); - virtual ~LLTearOffMenu() {} - virtual void onClose(bool app_quitting); + virtual ~LLTearOffMenu(); + virtual void draw(void); virtual void onFocusReceived(); virtual void onFocusLost(); @@ -783,7 +795,9 @@ public: private: LLTearOffMenu(LLMenuGL* menup); - + + void closeTearOff(); + LLView* mOldParent; LLMenuGL* mMenu; F32 mTargetHeight; @@ -798,11 +812,19 @@ private: class LLMenuItemTearOffGL : public LLMenuItemGL { public: - LLMenuItemTearOffGL( LLHandle<LLFloater> parent_floater_handle = LLHandle<LLFloater>()); - - virtual std::string getType() const { return "tearoff_menu"; } - - virtual void doIt(void); + struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params> + { + Optional<LLHandle<LLFloater> > parent_floater_handle; + Params() + { + name = "tear off"; + label = "~~~~~~~~~~~"; + } + }; + + LLMenuItemTearOffGL( const Params& ); + + virtual void onCommit(void); virtual void draw(void); virtual U32 getNominalHeight() const; @@ -824,4 +846,31 @@ 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; + virtual ~view_listener_t() {} + + 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); + } +}; + #endif // LL_LLMENUGL_H diff --git a/indra/llui/llmodaldialog.cpp b/indra/llui/llmodaldialog.cpp index 1662ff7db6..387af05935 100644 --- a/indra/llui/llmodaldialog.cpp +++ b/indra/llui/llmodaldialog.cpp @@ -44,22 +44,20 @@ // static std::list<LLModalDialog*> LLModalDialog::sModalStack; -LLModalDialog::LLModalDialog( const std::string& title, S32 width, S32 height, BOOL modal ) - : LLFloater( std::string("modal container"), - LLRect( 0, height, width, 0 ), - title, - FALSE, // resizable - DEFAULT_MIN_WIDTH, DEFAULT_MIN_HEIGHT, - FALSE, // drag_on_left - modal ? FALSE : TRUE, // minimizable - modal ? FALSE : TRUE, // close button - TRUE), // bordered +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() @@ -69,15 +67,27 @@ LLModalDialog::~LLModalDialog() { gFocusMgr.unlockFocus(); } + + std::list<LLModalDialog*>::iterator iter = std::find(sModalStack.begin(), sModalStack.end(), this); + if (iter != sModalStack.end()) + { + llerrs << "Attempt to delete dialog while still in sModalStack!" << llendl; + } +} + +// virtual +BOOL LLModalDialog::postBuild() +{ + return LLFloater::postBuild(); } // virtual -void LLModalDialog::open() /* Flawfinder: ignore */ +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::open(); + LLFloater::openFloater(key); LLFloater::setFloaterHost(thost); } @@ -87,7 +97,8 @@ void LLModalDialog::reshape(S32 width, S32 height, BOOL called_from_parent) centerOnScreen(); } -void LLModalDialog::startModal() +// virtual +void LLModalDialog::onOpen(const LLSD& key) { if (mModal) { @@ -105,8 +116,6 @@ void LLModalDialog::startModal() sModalStack.push_front( this ); } - - setVisible( TRUE ); } void LLModalDialog::stopModal() @@ -229,48 +238,25 @@ BOOL LLModalDialog::handleKeyHere(KEY key, MASK mask ) BOOL enough_time_elapsed = mVisibleTime.getElapsedTimeF32() > 1.0f; if (enough_time_elapsed && key == KEY_ESCAPE) { - close(); + closeFloater(); return TRUE; } return FALSE; } } -void LLModalDialog::onClose(bool app_quitting) -{ - stopModal(); - LLFloater::onClose(app_quitting); -} - // virtual void LLModalDialog::draw() { - LLColor4 shadow_color = LLUI::sColorsGroup->getColor("ColorDropShadow"); - S32 shadow_lines = LLUI::sConfigGroup->getS32("DropShadowFloater"); + static LLUIColor shadow_color = LLUIColorTable::instance().getColor("ColorDropShadow"); + static LLUICachedControl<S32> shadow_lines ("DropShadowFloater", 0); gl_drop_shadow( 0, getRect().getHeight(), getRect().getWidth(), 0, shadow_color, shadow_lines); LLFloater::draw(); - - if (mModal) - { - // If we've lost focus to a non-child, get it back ASAP. - if( gFocusMgr.getTopCtrl() != this ) - { - gFocusMgr.setTopCtrl( this ); - } - - if( !gFocusMgr.childHasKeyboardFocus( this ) ) - { - setFocus(TRUE); - } - - if( !gFocusMgr.childHasMouseCapture( this ) ) - { - gFocusMgr.setMouseCapture( this ); - } - } + + // Focus retrieval moved to LLFloaterView::refresh() } void LLModalDialog::centerOnScreen() @@ -291,10 +277,7 @@ void LLModalDialog::onAppFocusLost() gFocusMgr.setMouseCapture( NULL ); } - if( gFocusMgr.childHasKeyboardFocus( instance ) ) - { - gFocusMgr.setKeyboardFocus( NULL ); - } + instance->setFocus(FALSE); } } @@ -314,5 +297,16 @@ void LLModalDialog::onAppFocusGained() } } - - +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 llerrs popup that + // simply makes our software look unreliable. + sModalStack.clear(); +} diff --git a/indra/llui/llmodaldialog.h b/indra/llui/llmodaldialog.h index f6abd0a7ac..863572fb5a 100644 --- a/indra/llui/llmodaldialog.h +++ b/indra/llui/llmodaldialog.h @@ -45,16 +45,16 @@ class LLModalDialog; class LLModalDialog : public LLFloater { public: - LLModalDialog( const std::string& title, S32 width, S32 height, BOOL modal = true ); + LLModalDialog( const LLSD& key, BOOL modal = true ); /*virtual*/ ~LLModalDialog(); - - /*virtual*/ void open(); /* Flawfinder: ignore */ + + /*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*/ void startModal(); - /*virtual*/ void stopModal(); - /*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); @@ -63,22 +63,23 @@ public: /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask ); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void setVisible(BOOL visible); /*virtual*/ void draw(); - BOOL isModal() const { return mModal; } + 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; diff --git a/indra/llui/llmultifloater.cpp b/indra/llui/llmultifloater.cpp new file mode 100644 index 0000000000..78738c826d --- /dev/null +++ b/indra/llui/llmultifloater.cpp @@ -0,0 +1,509 @@ +/** + * @file llmultifloater.cpp + * @brief LLFloater that hosts other floaters + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// 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(0), + mOrigMinHeight(0) +{ +} + +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<LLTabContainer>(p); + addChild(mTabContainer); + + if (isResizable()) + { + mTabContainer->setRightTabBtnOffset(RESIZE_HANDLE_WIDTH); + } +} + +void LLMultiFloater::onOpen(const LLSD& key) +{ +// if (mTabContainer->getTabCount() <= 0) +// { +// // for now, don't allow multifloaters +// // without any child floaters +// closeFloater(); +// } +} + +void LLMultiFloater::draw() +{ + if (mTabContainer->getTabCount() == 0) + { + //RN: could this potentially crash in draw hierarchy? + closeFloater(); + } + else + { + for (S32 i = 0; i < mTabContainer->getTabCount(); i++) + { + LLFloater* floaterp = (LLFloater*)mTabContainer->getPanelByIndex(i); + if (floaterp->getShortTitle() != mTabContainer->getPanelTitle(i)) + { + mTabContainer->setPanelTitle(i, floaterp->getShortTitle()); + } + } + 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<S32> 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) + { + llerrs << "Tab Container used without having been initialized." << llendl; + 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 it's 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(); + + // remove minimize and close buttons + floaterp->setCanMinimize(FALSE); + floaterp->setCanResize(FALSE); + floaterp->setCanDrag(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(); +} + +/** + 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); + 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(); + } + return TRUE; + } + + return LLFloater::handleKeyHere(key, mask); +} + +bool LLMultiFloater::addChild(LLView* child, S32 tab_group) +{ + LLTabContainer* tab_container = dynamic_cast<LLTabContainer*>(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<LLFloater*>(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<LLTabContainer>("Preview Tabs"); + + setCanResize(mResizable); + return TRUE; +} + +void LLMultiFloater::updateResizeLimits() +{ + static LLUICachedControl<S32> 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; + // initialize minimum size constraint to the original xml values. + S32 new_min_width = mOrigMinWidth; + S32 new_min_height = mOrigMinHeight; + // 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); + } + } + 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); + } +} diff --git a/indra/llui/llmultifloater.h b/indra/llui/llmultifloater.h new file mode 100644 index 0000000000..bbf2c56fe7 --- /dev/null +++ b/indra/llui/llmultifloater.h @@ -0,0 +1,106 @@ +/** + * @file llmultifloater.h + * @brief LLFloater that hosts other floaters + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// 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 onOpen(const LLSD& key); + /*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(); + +protected: + struct LLFloaterData + { + S32 mWidth; + S32 mHeight; + BOOL mCanMinimize; + BOOL mCanResize; + }; + + LLTabContainer* mTabContainer; + + typedef std::map<LLHandle<LLFloater>, LLFloaterData> floater_data_map_t; + floater_data_map_t mFloaterDataMap; + + LLTabContainer::TabPosition mTabPos; + BOOL mAutoResize; + S32 mOrigMinWidth, mOrigMinHeight; // logically const but initialized late +}; + +#endif // LL_MULTI_FLOATER_H + + + diff --git a/indra/llui/llmultislider.cpp b/indra/llui/llmultislider.cpp index c1487be553..27a727fdf5 100644 --- a/indra/llui/llmultislider.cpp +++ b/indra/llui/llmultislider.cpp @@ -40,67 +40,95 @@ #include "llfocusmgr.h" #include "llkeyboard.h" // for the MASK constants #include "llcontrol.h" -#include "llimagegl.h" +#include "lluictrlfactory.h" #include <sstream> -static LLRegisterWidget<LLMultiSlider> r("multi_slider_bar"); +static LLDefaultChildRegistry::Register<LLMultiSlider> r("multi_slider_bar"); -const S32 MULTI_THUMB_WIDTH = 8; -const S32 MULTI_TRACK_HEIGHT = 6; const F32 FLOAT_THRESHOLD = 0.00001f; -const S32 EXTRA_TRIANGLE_WIDTH = 2; -const S32 EXTRA_TRIANGLE_HEIGHT = -2; S32 LLMultiSlider::mNameCounter = 0; -LLMultiSlider::LLMultiSlider( - const std::string& name, - const LLRect& rect, - void (*on_commit_callback)(LLUICtrl* ctrl, void* userdata), - void* callback_userdata, - F32 initial_value, - F32 min_value, - F32 max_value, - F32 increment, - S32 max_sliders, - BOOL allow_overlap, - BOOL draw_track, - BOOL use_triangle, - const std::string& control_name) - : - LLUICtrl( name, rect, TRUE, on_commit_callback, callback_userdata, - FOLLOWS_LEFT | FOLLOWS_TOP), - - mInitialValue( initial_value ), - mMinValue( min_value ), - mMaxValue( max_value ), - mIncrement( increment ), - mMaxNumSliders(max_sliders), - mAllowOverlap(allow_overlap), - mDrawTrack(draw_track), - mUseTriangle(use_triangle), +LLMultiSlider::SliderParams::SliderParams() +: name("name"), + value("value", 0.f) +{ +} + +LLMultiSlider::Params::Params() +: max_sliders("max_sliders", 1), + allow_overlap("allow_overlap", false), + draw_track("draw_track", true), + use_triangle("use_triangle", false), + track_color("track_color"), + thumb_disabled_color("thumb_disabled_color"), + thumb_outline_color("thumb_outline_color"), + thumb_center_color("thumb_center_color"), + thumb_center_selected_color("thumb_center_selected_color"), + triangle_color("triangle_color"), + mouse_down_callback("mouse_down_callback"), + mouse_up_callback("mouse_up_callback"), + thumb_width("thumb_width"), + sliders("slider") +{ + name = "multi_slider_bar"; + mouse_opaque(true); + follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); +} + +LLMultiSlider::LLMultiSlider(const LLMultiSlider::Params& p) +: LLF32UICtrl(p), mMouseOffset( 0 ), - mDragStartThumbRect( 0, getRect().getHeight(), MULTI_THUMB_WIDTH, 0 ), - mTrackColor( LLUI::sColorsGroup->getColor( "MultiSliderTrackColor" ) ), - mThumbOutlineColor( LLUI::sColorsGroup->getColor( "MultiSliderThumbOutlineColor" ) ), - mThumbCenterColor( LLUI::sColorsGroup->getColor( "MultiSliderThumbCenterColor" ) ), - mThumbCenterSelectedColor( LLUI::sColorsGroup->getColor( "MultiSliderThumbCenterSelectedColor" ) ), - mDisabledThumbColor(LLUI::sColorsGroup->getColor( "MultiSliderDisabledThumbColor" ) ), - mTriangleColor(LLUI::sColorsGroup->getColor( "MultiSliderTriangleColor" ) ), - mMouseDownCallback( NULL ), - mMouseUpCallback( NULL ) + mDragStartThumbRect( 0, getRect().getHeight(), p.thumb_width, 0 ), + mMaxNumSliders(p.max_sliders), + mAllowOverlap(p.allow_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), + mMouseDownSignal(NULL), + mMouseUpSignal(NULL) { mValue.emptyMap(); mCurSlider = LLStringUtil::null; + + if (p.mouse_down_callback.isProvided()) + { + setMouseDownCallback(initCommitCallback(p.mouse_down_callback)); + } + if (p.mouse_up_callback.isProvided()) + { + setMouseUpCallback(initCommitCallback(p.mouse_up_callback)); + } - // properly handle setting the starting thumb rect - // do it this way to handle both the operating-on-settings - // and standalone ways of using this - setControlName(control_name, NULL); - setValue(getValue()); + for (LLInitParam::ParamIterator<SliderParams>::const_iterator it = p.sliders().begin(); + it != p.sliders().end(); + ++it) + { + if (it->name.isProvided()) + { + addSlider(it->value, it->name); + } + else + { + addSlider(it->value); + } + } +} + +LLMultiSlider::~LLMultiSlider() +{ + delete mMouseDownSignal; + delete mMouseUpSignal; } + void LLMultiSlider::setSliderValue(const std::string& name, F32 value, BOOL from_event) { // exit if not there @@ -152,12 +180,12 @@ void LLMultiSlider::setSliderValue(const std::string& name, F32 value, BOOL from F32 t = (newValue - mMinValue) / (mMaxValue - mMinValue); - S32 left_edge = MULTI_THUMB_WIDTH/2; - S32 right_edge = getRect().getWidth() - (MULTI_THUMB_WIDTH/2); + 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 - (MULTI_THUMB_WIDTH/2); - mThumbRects[name].mRight = x + (MULTI_THUMB_WIDTH/2); + mThumbRects[name].mLeft = x - (mThumbWidth/2); + mThumbRects[name].mRight = x + (mThumbWidth/2); } void LLMultiSlider::setValue(const LLSD& value) @@ -211,7 +239,7 @@ const std::string& LLMultiSlider::addSlider(F32 val) } // add a new thumb rect - mThumbRects[newName.str()] = LLRect( 0, getRect().getHeight(), MULTI_THUMB_WIDTH, 0 ); + mThumbRects[newName.str()] = LLRect( 0, getRect().getHeight(), mThumbWidth, 0 ); // add the value and set the current slider to this one mValue.insert(newName.str(), initVal); @@ -223,6 +251,30 @@ const std::string& LLMultiSlider::addSlider(F32 val) return mCurSlider; } +void LLMultiSlider::addSlider(F32 val, const std::string& name) +{ + F32 initVal = val; + + if(mValue.size() >= mMaxNumSliders) { + return; + } + + bool foundOne = findUnusedValue(initVal); + if(!foundOne) { + return; + } + + // add a new thumb rect + mThumbRects[name] = LLRect( 0, getRect().getHeight(), mThumbWidth, 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); +} + bool LLMultiSlider::findUnusedValue(F32& initVal) { bool firstTry = true; @@ -295,15 +347,15 @@ void LLMultiSlider::clear() deleteCurSlider(); } - LLUICtrl::clear(); + LLF32UICtrl::clear(); } BOOL LLMultiSlider::handleHover(S32 x, S32 y, MASK mask) { if( gFocusMgr.getMouseCapture() == this ) { - S32 left_edge = MULTI_THUMB_WIDTH/2; - S32 right_edge = getRect().getWidth() - (MULTI_THUMB_WIDTH/2); + S32 left_edge = mThumbWidth/2; + S32 right_edge = getRect().getWidth() - (mThumbWidth/2); x += mMouseOffset; x = llclamp( x, left_edge, right_edge ); @@ -331,10 +383,9 @@ BOOL LLMultiSlider::handleMouseUp(S32 x, S32 y, MASK mask) { gFocusMgr.setMouseCapture( NULL ); - if( mMouseUpCallback ) - { - mMouseUpCallback( this, mCallbackUserData ); - } + if (mMouseUpSignal) + (*mMouseUpSignal)( this, LLSD() ); + handled = TRUE; make_ui_sound("UISndClickRelease"); } @@ -353,10 +404,8 @@ BOOL LLMultiSlider::handleMouseDown(S32 x, S32 y, MASK mask) { setFocus(TRUE); } - if( mMouseDownCallback ) - { - mMouseDownCallback( this, mCallbackUserData ); - } + if (mMouseDownSignal) + (*mMouseDownSignal)( this, LLSD() ); if (MASK_CONTROL & mask) // if CTRL is modifying { @@ -379,7 +428,7 @@ BOOL LLMultiSlider::handleMouseDown(S32 x, S32 y, MASK mask) // Find the offset of the actual mouse location from the center of the thumb. if (mThumbRects[mCurSlider].pointInRect(x,y)) { - mMouseOffset = (mThumbRects[mCurSlider].mLeft + MULTI_THUMB_WIDTH/2) - x; + mMouseOffset = (mThumbRects[mCurSlider].mLeft + mThumbWidth/2) - x; } else { @@ -424,6 +473,8 @@ BOOL LLMultiSlider::handleKeyHere(KEY key, MASK mask) void LLMultiSlider::draw() { + static LLUICachedControl<S32> extra_triangle_height ("UIExtraTriangleHeight", 0); + static LLUICachedControl<S32> extra_triangle_width ("UIExtraTriangleWidth", 0); LLColor4 curThumbColor; std::map<std::string, LLRect>::iterator mIt; @@ -439,16 +490,17 @@ void LLMultiSlider::draw() F32 opacity = getEnabled() ? 1.f : 0.3f; // Track - LLUIImagePtr thumb_imagep = LLUI::sImageProvider->getUIImage("rounded_square.tga"); + LLUIImagePtr thumb_imagep = LLUI::getUIImage("Rounded_Square"); - S32 height_offset = (getRect().getHeight() - MULTI_TRACK_HEIGHT) / 2; + static LLUICachedControl<S32> multi_track_height ("UIMultiTrackHeight", 0); + S32 height_offset = (getRect().getHeight() - multi_track_height) / 2; LLRect track_rect(0, getRect().getHeight() - height_offset, getRect().getWidth(), height_offset ); if(mDrawTrack) { track_rect.stretch(-1); - thumb_imagep->draw(track_rect, mTrackColor % opacity); + thumb_imagep->draw(track_rect, mTrackColor.get() % opacity); } // if we're supposed to use a drawn triangle @@ -458,13 +510,13 @@ void LLMultiSlider::draw() 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 - 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, TRUE); + mIt->second.mBottom - extra_triangle_height, + mTriangleColor.get(), TRUE); } } else if (!thumb_imagep) @@ -474,7 +526,7 @@ void LLMultiSlider::draw() for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) { // choose the color - curThumbColor = mThumbCenterColor; + curThumbColor = mThumbCenterColor.get(); if(mIt->first == mCurSlider) { curSldrIt = mIt; @@ -488,19 +540,19 @@ void LLMultiSlider::draw() // now draw the current slider if(curSldrIt != mThumbRects.end()) { - gl_rect_2d(curSldrIt->second, mThumbCenterSelectedColor, TRUE); + gl_rect_2d(curSldrIt->second, mThumbCenterSelectedColor.get(), TRUE); } // and draw the drag start if (gFocusMgr.getMouseCapture() == this) { - gl_rect_2d(mDragStartThumbRect, mThumbCenterColor % opacity, FALSE); + gl_rect_2d(mDragStartThumbRect, mThumbCenterColor.get() % opacity, FALSE); } } else if( gFocusMgr.getMouseCapture() == this ) { // draw drag start - thumb_imagep->drawSolid(mDragStartThumbRect, mThumbCenterColor % 0.3f); + thumb_imagep->drawSolid(mDragStartThumbRect, mThumbCenterColor.get() % 0.3f); // draw the highlight if (hasFocus()) @@ -513,7 +565,7 @@ void LLMultiSlider::draw() for(mIt = mThumbRects.begin(); mIt != mThumbRects.end(); mIt++) { // choose the color - curThumbColor = mThumbCenterColor; + curThumbColor = mThumbCenterColor.get(); if(mIt->first == mCurSlider) { // don't draw now, draw last @@ -528,7 +580,7 @@ void LLMultiSlider::draw() // draw cur slider last if(curSldrIt != mThumbRects.end()) { - thumb_imagep->drawSolid(curSldrIt->second, mThumbCenterSelectedColor); + thumb_imagep->drawSolid(curSldrIt->second, mThumbCenterSelectedColor.get()); } } @@ -546,7 +598,7 @@ void LLMultiSlider::draw() { // choose the color - curThumbColor = mThumbCenterColor; + curThumbColor = mThumbCenterColor.get(); if(mIt->first == mCurSlider) { curSldrIt = mIt; @@ -559,74 +611,20 @@ void LLMultiSlider::draw() if(curSldrIt != mThumbRects.end()) { - thumb_imagep->drawSolid(curSldrIt->second, mThumbCenterSelectedColor % opacity); + thumb_imagep->drawSolid(curSldrIt->second, mThumbCenterSelectedColor.get() % opacity); } } - LLUICtrl::draw(); + LLF32UICtrl::draw(); } - -// virtual -LLXMLNodePtr LLMultiSlider::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - node->createChild("initial_val", TRUE)->setFloatValue(getInitialValue()); - node->createChild("min_val", TRUE)->setFloatValue(getMinValue()); - node->createChild("max_val", TRUE)->setFloatValue(getMaxValue()); - node->createChild("increment", TRUE)->setFloatValue(getIncrement()); - - return node; +boost::signals2::connection LLMultiSlider::setMouseDownCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t(); + return mMouseDownSignal->connect(cb); } - -//static -LLView* LLMultiSlider::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("multi_slider_bar"); - node->getAttributeString("name", name); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - F32 initial_value = 0.f; - node->getAttributeF32("initial_val", initial_value); - - F32 min_value = 0.f; - node->getAttributeF32("min_val", min_value); - - F32 max_value = 1.f; - node->getAttributeF32("max_val", max_value); - - F32 increment = 0.1f; - node->getAttributeF32("increment", increment); - - S32 max_sliders = 1; - node->getAttributeS32("max_sliders", max_sliders); - - BOOL allow_overlap = FALSE; - node->getAttributeBOOL("allow_overlap", allow_overlap); - - BOOL draw_track = TRUE; - node->getAttributeBOOL("draw_track", draw_track); - - BOOL use_triangle = FALSE; - node->getAttributeBOOL("use_triangle", use_triangle); - - LLMultiSlider* multiSlider = new LLMultiSlider(name, - rect, - NULL, - NULL, - initial_value, - min_value, - max_value, - increment, - max_sliders, - allow_overlap, - draw_track, - use_triangle); - - multiSlider->initFromXML(node, parent); - - return multiSlider; +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 cdbdb597f9..88576da85b 100644 --- a/indra/llui/llmultislider.h +++ b/indra/llui/llmultislider.h @@ -33,75 +33,79 @@ #ifndef LL_MULTI_SLIDER_H #define LL_MULTI_SLIDER_H -#include "lluictrl.h" +#include "llf32uictrl.h" #include "v4color.h" class LLUICtrlFactory; -class LLMultiSlider : public LLUICtrl +class LLMultiSlider : public LLF32UICtrl { public: - LLMultiSlider( - const std::string& name, - const LLRect& rect, - void (*on_commit_callback)(LLUICtrl* ctrl, void* userdata), - void* callback_userdata, - F32 initial_value, - F32 min_value, - F32 max_value, - F32 increment, - S32 max_sliders, - BOOL allow_overlap, - BOOL draw_track, - BOOL use_triangle, - const std::string& control_name = LLStringUtil::null ); - - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - - void setSliderValue(const std::string& name, F32 value, BOOL from_event = FALSE); - F32 getSliderValue(const std::string& name) const; - - const std::string& getCurSlider() const { return mCurSlider; } - F32 getCurSliderValue() const { return getSliderValue(mCurSlider); } - void setCurSlider(const std::string& name); - void setCurSliderValue(F32 val, BOOL from_event = false) { setSliderValue(mCurSlider, val, from_event); } - - virtual void setValue(const LLSD& value); - virtual LLSD getValue() const { return mValue; } - - virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } - virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } - - F32 getInitialValue() const { return mInitialValue; } - F32 getMinValue() const { return mMinValue; } - F32 getMaxValue() const { return mMaxValue; } - F32 getIncrement() const { return mIncrement; } - void setMinValue(F32 min_value) { mMinValue = min_value; } - void setMaxValue(F32 max_value) { mMaxValue = max_value; } - void setIncrement(F32 increment) { mIncrement = increment; } - void setMouseDownCallback( void (*cb)(LLUICtrl* ctrl, void* userdata) ) { mMouseDownCallback = cb; } - void setMouseUpCallback( void (*cb)(LLUICtrl* ctrl, void* userdata) ) { mMouseUpCallback = cb; } - - bool findUnusedValue(F32& initVal); + struct SliderParams : public LLInitParam::Block<SliderParams> + { + Optional<std::string> name; + Mandatory<F32> value; + SliderParams(); + }; + + struct Params : public LLInitParam::Block<Params, LLF32UICtrl::Params> + { + Optional<S32> max_sliders; + + Optional<bool> allow_overlap, + draw_track, + use_triangle; + + Optional<LLUIColor> track_color, + thumb_disabled_color, + thumb_outline_color, + thumb_center_color, + thumb_center_selected_color, + triangle_color; + + Optional<CommitCallbackParam> mouse_down_callback, + mouse_up_callback; + Optional<S32> thumb_width; + + Multiple<SliderParams> sliders; + Params(); + }; + +protected: + LLMultiSlider(const Params&); + friend class LLUICtrlFactory; +public: + virtual ~LLMultiSlider(); + void setSliderValue(const std::string& name, F32 value, BOOL from_event = FALSE); + F32 getSliderValue(const std::string& name) const; + + const std::string& getCurSlider() const { return mCurSlider; } + F32 getCurSliderValue() const { return getSliderValue(mCurSlider); } + void setCurSlider(const std::string& name); + void setCurSliderValue(F32 val, BOOL from_event = false) { setSliderValue(mCurSlider, val, from_event); } + + /*virtual*/ void setValue(const LLSD& value); + /*virtual*/ LLSD getValue() const { 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); - void deleteSlider(const std::string& name); - void deleteCurSlider() { deleteSlider(mCurSlider); } - void clear(); + void addSlider(F32 val, const std::string& name); + void deleteSlider(const std::string& name); + void deleteCurSlider() { deleteSlider(mCurSlider); } + void clear(); - 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 void draw(); + /*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*/ void draw(); protected: LLSD mValue; - F32 mInitialValue; - F32 mMinValue; - F32 mMaxValue; - F32 mIncrement; std::string mCurSlider; static S32 mNameCounter; @@ -112,17 +116,19 @@ protected: S32 mMouseOffset; LLRect mDragStartThumbRect; - - std::map<std::string, LLRect> mThumbRects; - LLColor4 mTrackColor; - LLColor4 mThumbOutlineColor; - LLColor4 mThumbCenterColor; - LLColor4 mThumbCenterSelectedColor; - LLColor4 mDisabledThumbColor; - LLColor4 mTriangleColor; + S32 mThumbWidth; + + std::map<std::string, LLRect> + mThumbRects; + LLUIColor mTrackColor; + LLUIColor mThumbOutlineColor; + LLUIColor mThumbCenterColor; + LLUIColor mThumbCenterSelectedColor; + LLUIColor mDisabledThumbColor; + LLUIColor mTriangleColor; - void (*mMouseDownCallback)(LLUICtrl* ctrl, void* userdata); - void (*mMouseUpCallback)(LLUICtrl* ctrl, void* userdata); + commit_signal_t* mMouseDownSignal; + commit_signal_t* mMouseUpSignal; }; -#endif // LL_LLSLIDER_H +#endif // LL_MULTI_SLIDER_H diff --git a/indra/llui/llmultisliderctrl.cpp b/indra/llui/llmultisliderctrl.cpp index 8bcf9f9b76..f4434a0f78 100644 --- a/indra/llui/llmultisliderctrl.cpp +++ b/indra/llui/llmultisliderctrl.cpp @@ -34,9 +34,6 @@ #include "llmultisliderctrl.h" -#include "audioengine.h" -#include "sound_ids.h" - #include "llmath.h" #include "llfontgl.h" #include "llgl.h" @@ -50,90 +47,101 @@ #include "llcontrol.h" #include "llfocusmgr.h" #include "llresmgr.h" +#include "lluictrlfactory.h" -static LLRegisterWidget<LLMultiSliderCtrl> r("multi_slider"); +static LLDefaultChildRegistry::Register<LLMultiSliderCtrl> 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), + 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 std::string& name, const LLRect& rect, - const std::string& label, - const LLFontGL* font, - S32 label_width, - S32 text_left, - BOOL show_text, - BOOL can_edit_text, - void (*commit_callback)(LLUICtrl*, void*), - void* callback_user_data, - F32 initial_value, F32 min_value, F32 max_value, F32 increment, - S32 max_sliders, BOOL allow_overlap, - BOOL draw_track, - BOOL use_triangle, - const std::string& control_which) - : LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data ), - mFont(font), - mShowText( show_text ), - mCanEditText( can_edit_text ), - mPrecision( 3 ), - mLabelBox( NULL ), - mLabelWidth( label_width ), - - mEditor( NULL ), - mTextBox( NULL ), - mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), - mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), - mSliderMouseUpCallback( NULL ), - mSliderMouseDownCallback( NULL ) +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<S32> 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( !label.empty() ) + if( !p.label().empty() ) { - if (label_width == 0) + if (p.label_width == 0) { - label_width = font->getWidth(label); + label_width = p.font()->getWidth(p.label); } LLRect label_rect( left, top, label_width, bottom ); - mLabelBox = new LLTextBox( std::string("MultiSliderCtrl Label"), label_rect, label, font ); + LLTextBox::Params params; + params.name("MultiSliderCtrl Label"); + params.rect(label_rect); + params.initial_value(p.label()); + params.font(p.font); + mLabelBox = LLUICtrlFactory::create<LLTextBox> (params); addChild(mLabelBox); } S32 slider_right = getRect().getWidth(); - if( show_text ) - { - slider_right = text_left - MULTI_SLIDERCTRL_SPACING; - } - S32 slider_left = label_width ? label_width + MULTI_SLIDERCTRL_SPACING : 0; - LLRect slider_rect( slider_left, top, slider_right, bottom ); - mMultiSlider = new LLMultiSlider( - std::string("multi_slider"), - slider_rect, - LLMultiSliderCtrl::onSliderCommit, this, - initial_value, min_value, max_value, increment, - max_sliders, allow_overlap, draw_track, - use_triangle, - control_which ); - addChild( mMultiSlider ); - mCurValue = mMultiSlider->getCurSliderValue(); - - if( show_text ) + 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( can_edit_text ) + if( p.can_edit_text ) { - mEditor = new LLLineEditor( std::string("MultiSliderCtrl Editor"), text_rect, - LLStringUtil::null, font, - MAX_STRING_LENGTH, - &LLMultiSliderCtrl::onEditorCommit, NULL, NULL, this, - &LLLineEditor::prevalidateFloat ); - mEditor->setFollowsLeft(); - mEditor->setFollowsBottom(); - mEditor->setFocusReceivedCallback( &LLMultiSliderCtrl::onEditorGainFocus ); - mEditor->setIgnoreTab(TRUE); + 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.prevalidate_callback(&LLLineEditor::prevalidateFloat); + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + mEditor = LLUICtrlFactory::create<LLLineEditor> (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); @@ -141,13 +149,38 @@ LLMultiSliderCtrl::LLMultiSliderCtrl(const std::string& name, const LLRect& rect } else { - mTextBox = new LLTextBox( std::string("MultiSliderCtrl Text"), text_rect, LLStringUtil::null, font); - mTextBox->setFollowsLeft(); - mTextBox->setFollowsBottom(); + 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<LLTextBox> (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.draw_track(p.draw_track); + params.use_triangle(p.use_triangle); + params.control_name(p.control_name); + mMultiSlider = LLUICtrlFactory::create<LLMultiSlider> (params); + addChild( mMultiSlider ); + mCurValue = mMultiSlider->getCurSliderValue(); + + updateText(); } @@ -203,7 +236,8 @@ BOOL LLMultiSliderCtrl::setLabelArg( const std::string& key, const LLStringExpli S32 delta = rect.mRight - prev_right; rect = mMultiSlider->getRect(); S32 left = rect.mLeft + delta; - left = llclamp(left, 0, rect.mRight-MULTI_SLIDERCTRL_SPACING); + static LLUICachedControl<S32> multi_slider_ctrl_spacing ("UIMultiSliderctrlSpacing", 0); + left = llclamp(left, 0, rect.mRight - multi_slider_ctrl_spacing); rect.mLeft = left; mMultiSlider->setRect(rect); } @@ -294,11 +328,12 @@ void LLMultiSliderCtrl::updateText() } // static -void LLMultiSliderCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) +void LLMultiSliderCtrl::onEditorCommit( LLUICtrl* ctrl, const LLSD& userdata) { - LLMultiSliderCtrl* self = (LLMultiSliderCtrl*) userdata; - llassert( caller == self->mEditor ); - + LLMultiSliderCtrl* self = dynamic_cast<LLMultiSliderCtrl*>(ctrl->getParent()); + if (!ctrl) + return; + BOOL success = FALSE; F32 val = self->mCurValue; F32 saved_val = self->mCurValue; @@ -310,17 +345,9 @@ void LLMultiSliderCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) val = (F32) atof( text.c_str() ); if( self->mMultiSlider->getMinValue() <= val && val <= self->mMultiSlider->getMaxValue() ) { - if( self->mValidateCallback ) - { - self->setCurSliderValue( val ); // set the value temporarily so that the callback can retrieve it. - if( self->mValidateCallback( self, self->mCallbackUserData ) ) - { - success = TRUE; - } - } - else + self->setCurSliderValue( val ); // set the value temporarily so that the callback can retrieve it. + if( !self->mValidateSignal || (*(self->mValidateSignal))( self, val ) ) { - self->setCurSliderValue( val ); success = TRUE; } } @@ -342,26 +369,19 @@ void LLMultiSliderCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) } // static -void LLMultiSliderCtrl::onSliderCommit( LLUICtrl* caller, void *userdata ) +void LLMultiSliderCtrl::onSliderCommit(LLUICtrl* ctrl, const LLSD& userdata) { - LLMultiSliderCtrl* self = (LLMultiSliderCtrl*) userdata; - //llassert( caller == self->mSlider ); - + LLMultiSliderCtrl* self = dynamic_cast<LLMultiSliderCtrl*>(ctrl->getParent()); + if (!self) + return; + BOOL success = FALSE; F32 saved_val = self->mCurValue; F32 new_val = self->mMultiSlider->getCurSliderValue(); - if( self->mValidateCallback ) + self->mCurValue = new_val; // set the value temporarily so that the callback can retrieve it. + if( !self->mValidateSignal || (*(self->mValidateSignal))( self, new_val ) ) { - self->mCurValue = new_val; // set the value temporarily so that the callback can retrieve it. - if( self->mValidateCallback( self, self->mCallbackUserData ) ) - { - success = TRUE; - } - } - else - { - self->mCurValue = new_val; success = TRUE; } @@ -382,11 +402,11 @@ void LLMultiSliderCtrl::onSliderCommit( LLUICtrl* caller, void *userdata ) void LLMultiSliderCtrl::setEnabled(BOOL b) { - LLUICtrl::setEnabled( b ); + LLF32UICtrl::setEnabled( b ); if( mLabelBox ) { - mLabelBox->setColor( b ? mTextEnabledColor : mTextDisabledColor ); + mLabelBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); } mMultiSlider->setEnabled( b ); @@ -398,7 +418,7 @@ void LLMultiSliderCtrl::setEnabled(BOOL b) if( mTextBox ) { - mTextBox->setColor( b ? mTextEnabledColor : mTextDisabledColor ); + mTextBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); } } @@ -409,7 +429,7 @@ void LLMultiSliderCtrl::setTentative(BOOL b) { mEditor->setTentative(b); } - LLUICtrl::setTentative(b); + LLF32UICtrl::setTentative(b); } @@ -422,7 +442,8 @@ void LLMultiSliderCtrl::onCommit() mEditor->setTentative(FALSE); } - LLUICtrl::onCommit(); + setControlValue(getValueF32()); + LLF32UICtrl::onCommit(); } @@ -438,37 +459,14 @@ void LLMultiSliderCtrl::setPrecision(S32 precision) updateText(); } -void LLMultiSliderCtrl::setSliderMouseDownCallback( void (*slider_mousedown_callback)(LLUICtrl* caller, void* userdata) ) +boost::signals2::connection LLMultiSliderCtrl::setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ) { - mSliderMouseDownCallback = slider_mousedown_callback; - mMultiSlider->setMouseDownCallback( LLMultiSliderCtrl::onSliderMouseDown ); + return mMultiSlider->setMouseDownCallback( cb ); } -// static -void LLMultiSliderCtrl::onSliderMouseDown(LLUICtrl* caller, void* userdata) +boost::signals2::connection LLMultiSliderCtrl::setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ) { - LLMultiSliderCtrl* self = (LLMultiSliderCtrl*) userdata; - if( self->mSliderMouseDownCallback ) - { - self->mSliderMouseDownCallback( self, self->mCallbackUserData ); - } -} - - -void LLMultiSliderCtrl::setSliderMouseUpCallback( void (*slider_mouseup_callback)(LLUICtrl* caller, void* userdata) ) -{ - mSliderMouseUpCallback = slider_mouseup_callback; - mMultiSlider->setMouseUpCallback( LLMultiSliderCtrl::onSliderMouseUp ); -} - -// static -void LLMultiSliderCtrl::onSliderMouseUp(LLUICtrl* caller, void* userdata) -{ - LLMultiSliderCtrl* self = (LLMultiSliderCtrl*) userdata; - if( self->mSliderMouseUpCallback ) - { - self->mSliderMouseUpCallback( self, self->mCallbackUserData ); - } + return mMultiSlider->setMouseUpCallback( cb ); } void LLMultiSliderCtrl::onTabInto() @@ -484,154 +482,9 @@ void LLMultiSliderCtrl::reportInvalidData() make_ui_sound("UISndBadKeystroke"); } -//virtual -std::string LLMultiSliderCtrl::getControlName() const -{ - return mMultiSlider->getControlName(); -} - // virtual void LLMultiSliderCtrl::setControlName(const std::string& control_name, LLView* context) { mMultiSlider->setControlName(control_name, context); } -// virtual -LLXMLNodePtr LLMultiSliderCtrl::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - node->createChild("show_text", TRUE)->setBoolValue(mShowText); - - node->createChild("can_edit_text", TRUE)->setBoolValue(mCanEditText); - - node->createChild("decimal_digits", TRUE)->setIntValue(mPrecision); - - if (mLabelBox) - { - node->createChild("label", TRUE)->setStringValue(mLabelBox->getText()); - } - - // TomY TODO: Do we really want to export the transient state of the slider? - node->createChild("value", TRUE)->setFloatValue(mCurValue); - - if (mMultiSlider) - { - node->createChild("initial_val", TRUE)->setFloatValue(mMultiSlider->getInitialValue()); - node->createChild("min_val", TRUE)->setFloatValue(mMultiSlider->getMinValue()); - node->createChild("max_val", TRUE)->setFloatValue(mMultiSlider->getMaxValue()); - node->createChild("increment", TRUE)->setFloatValue(mMultiSlider->getIncrement()); - } - addColorXML(node, mTextEnabledColor, "text_enabled_color", "LabelTextColor"); - addColorXML(node, mTextDisabledColor, "text_disabled_color", "LabelDisabledColor"); - - return node; -} - -LLView* LLMultiSliderCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("multi_slider"); - node->getAttributeString("name", name); - - std::string label; - node->getAttributeString("label", label); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - LLFontGL* font = LLView::selectFont(node); - - // HACK: Font might not be specified. - if (!font) - { - font = LLFontGL::getFontSansSerifSmall(); - } - - S32 label_width = 0; - node->getAttributeS32("label_width", label_width); - - BOOL show_text = TRUE; - node->getAttributeBOOL("show_text", show_text); - - BOOL can_edit_text = FALSE; - node->getAttributeBOOL("can_edit_text", can_edit_text); - - BOOL allow_overlap = FALSE; - node->getAttributeBOOL("allow_overlap", allow_overlap); - - BOOL draw_track = TRUE; - node->getAttributeBOOL("draw_track", draw_track); - - BOOL use_triangle = FALSE; - node->getAttributeBOOL("use_triangle", use_triangle); - - F32 initial_value = 0.f; - node->getAttributeF32("initial_val", initial_value); - - F32 min_value = 0.f; - node->getAttributeF32("min_val", min_value); - - F32 max_value = 1.f; - node->getAttributeF32("max_val", max_value); - - F32 increment = 0.1f; - node->getAttributeF32("increment", increment); - - U32 precision = 3; - node->getAttributeU32("decimal_digits", precision); - - S32 max_sliders = 1; - node->getAttributeS32("max_sliders", max_sliders); - - - S32 text_left = 0; - if (show_text) - { - // calculate the size of the text box (log max_value is number of digits - 1 so plus 1) - if ( max_value ) - text_left = font->getWidth(std::string("0")) * ( static_cast < S32 > ( log10 ( max_value ) ) + precision + 1 ); - - if ( increment < 1.0f ) - text_left += font->getWidth(std::string(".")); // (mostly) take account of decimal point in value - - if ( min_value < 0.0f || max_value < 0.0f ) - text_left += font->getWidth(std::string("-")); // (mostly) take account of minus sign - - // padding to make things look nicer - text_left += 8; - } - - LLUICtrlCallback callback = NULL; - - if (label.empty()) - { - label.assign(node->getTextContents()); - } - - LLMultiSliderCtrl* slider = new LLMultiSliderCtrl(name, - rect, - label, - font, - label_width, - rect.getWidth() - text_left, - show_text, - can_edit_text, - callback, - NULL, - initial_value, - min_value, - max_value, - increment, - max_sliders, - allow_overlap, - draw_track, - use_triangle); - - slider->setPrecision(precision); - - slider->initFromXML(node, parent); - - slider->updateText(); - - return slider; -} diff --git a/indra/llui/llmultisliderctrl.h b/indra/llui/llmultisliderctrl.h index b62b5ec323..e8366d09c2 100644 --- a/indra/llui/llmultisliderctrl.h +++ b/indra/llui/llmultisliderctrl.h @@ -33,17 +33,12 @@ #ifndef LL_MULTI_SLIDERCTRL_H #define LL_MULTI_SLIDERCTRL_H -#include "lluictrl.h" +#include "llf32uictrl.h" #include "v4color.h" #include "llmultislider.h" #include "lltextbox.h" #include "llrect.h" -// -// Constants -// -const S32 MULTI_SLIDERCTRL_SPACING = 4; // space between label, slider, and text -const S32 MULTI_SLIDERCTRL_HEIGHT = 16; // // Classes @@ -53,27 +48,37 @@ class LLLineEditor; class LLSlider; -class LLMultiSliderCtrl : public LLUICtrl +class LLMultiSliderCtrl : public LLF32UICtrl { public: - LLMultiSliderCtrl(const std::string& name, - const LLRect& rect, - const std::string& label, - const LLFontGL* font, - S32 slider_left, - S32 text_left, - BOOL show_text, - BOOL can_edit_text, - void (*commit_callback)(LLUICtrl*, void*), - void* callback_userdata, - F32 initial_value, F32 min_value, F32 max_value, F32 increment, - S32 max_sliders, BOOL allow_overlap, BOOL draw_track, - BOOL use_triangle, - const std::string& control_which = LLStringUtil::null ); - + struct Params : public LLInitParam::Block<Params, LLF32UICtrl::Params> + { + Optional<S32> label_width, + text_width; + Optional<bool> show_text, + can_edit_text; + Optional<S32> decimal_digits; + Optional<S32> max_sliders; + Optional<bool> allow_overlap, + draw_track, + use_triangle; + + Optional<LLUIColor> text_color, + text_disabled_color; + + Optional<CommitCallbackParam> mouse_down_callback, + mouse_up_callback; + + Multiple<LLMultiSlider::SliderParams> sliders; + + Params(); + }; + +protected: + LLMultiSliderCtrl(const Params&); + friend class LLUICtrlFactory; +public: virtual ~LLMultiSliderCtrl(); - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); F32 getSliderValue(const std::string& name) const; void setSliderValue(const std::string& name, F32 v, BOOL from_event = FALSE); @@ -87,8 +92,8 @@ public: void setCurSlider(const std::string& name); void setCurSliderValue(F32 val, BOOL from_event = false) { setSliderValue(mMultiSlider->getCurSlider(), val, from_event); } - virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } - virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } + 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(); @@ -105,15 +110,15 @@ public: void deleteSlider(const std::string& name); void deleteCurSlider() { deleteSlider(mMultiSlider->getCurSlider()); } - F32 getMinValue() { return mMultiSlider->getMinValue(); } - F32 getMaxValue() { return mMultiSlider->getMaxValue(); } + F32 getMinValue() const { return mMultiSlider->getMinValue(); } + F32 getMaxValue() const { return mMultiSlider->getMaxValue(); } 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; } - void setSliderMouseDownCallback( void (*slider_mousedown_callback)(LLUICtrl* caller, void* userdata) ); - void setSliderMouseUpCallback( void (*slider_mouseup_callback)(LLUICtrl* caller, void* userdata) ); + 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(); @@ -121,13 +126,10 @@ public: virtual void onCommit(); // mark not tentative, then commit virtual void setControlName(const std::string& control_name, LLView* context); - virtual std::string getControlName() const; - static void onSliderCommit(LLUICtrl* caller, void* userdata); - static void onSliderMouseDown(LLUICtrl* caller,void* userdata); - static void onSliderMouseUp(LLUICtrl* caller,void* userdata); - - static void onEditorCommit(LLUICtrl* caller, void* userdata); + 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); @@ -149,11 +151,8 @@ private: LLLineEditor* mEditor; LLTextBox* mTextBox; - LLColor4 mTextEnabledColor; - LLColor4 mTextDisabledColor; - - void (*mSliderMouseUpCallback)( LLUICtrl* ctrl, void* userdata ); - void (*mSliderMouseDownCallback)( LLUICtrl* ctrl, void* userdata ); + LLUIColor mTextEnabledColor; + LLUIColor mTextDisabledColor; }; #endif // LL_MULTI_SLIDERCTRL_H diff --git a/indra/llui/llnotificationptr.h b/indra/llui/llnotificationptr.h new file mode 100644 index 0000000000..0718f7d182 --- /dev/null +++ b/indra/llui/llnotificationptr.h @@ -0,0 +1,41 @@ +/** + * @file llnotificationptr.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#ifndef LLNOTIFICATIONPTR_H +#define LLNOTIFICATIONPTR_H + +// Many classes just store a single LLNotificationPtr +// and llnotifications.h is very large, so define this ligher header. +#include <boost/shared_ptr.hpp> + +class LLNotification; +typedef boost::shared_ptr<LLNotification> LLNotificationPtr; + +#endif diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 6b7dd0a3de..86989012ee 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -31,11 +31,16 @@ */ #include "linden_common.h" + +#include "llnotifications.h" + +#include "llxmlnode.h" +#include "lluictrl.h" #include "lluictrlfactory.h" #include "lldir.h" #include "llsdserialize.h" - -#include "llnotifications.h" +#include "lltrans.h" +#include "llnotificationslistener.h" #include <algorithm> #include <boost/regex.hpp> @@ -76,6 +81,11 @@ private: void savePersistentNotifications() { + /* NOTE: As of 2009-11-09 the reload of notifications on startup does not + work, and has not worked for months. Skip saving notifications until the + read can be fixed, because this hits the disk once per notification and + causes log spam. James + llinfos << "Saving open notifications to " << mFileName << llendl; llofstream notify_file(mFileName.c_str()); @@ -102,6 +112,7 @@ private: LLPointer<LLSDFormatter> formatter = new LLSDXMLFormatter(); formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY); + */ } void loadPersistentNotifications() @@ -157,11 +168,17 @@ private: bool filterIgnoredNotifications(LLNotificationPtr notification) { + // filter everything if we are to ignore ALL + if(LLNotifications::instance().getIgnoreAllNotifications()) + { + return false; + } + LLNotificationFormPtr form = notification->getForm(); // Check to see if the user wants to ignore this alert if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO) { - return LLUI::sConfigGroup->getWarning(notification->getName()); + return LLUI::sSettingGroups["ignores"]->getBOOL(notification->getName()); } return true; @@ -182,7 +199,7 @@ bool handleIgnoredNotification(const LLSD& payload) response = pNotif->getResponseTemplate(LLNotification::WITH_DEFAULT_BUTTON); break; case LLNotificationForm::IGNORE_WITH_LAST_RESPONSE: - response = LLUI::sIgnoresGroup->getLLSD("Default" + pNotif->getName()); + response = LLUI::sSettingGroups["ignores"]->getLLSD("Default" + pNotif->getName()); break; case LLNotificationForm::IGNORE_SHOW_AGAIN: break; @@ -228,7 +245,7 @@ LLNotificationForm::LLNotificationForm(const std::string& name, const LLXMLNodeP LLSD item_entry; std::string element_name = child->getName()->mString; - if (element_name == "ignore") + if (element_name == "ignore" ) { bool save_option = false; child->getAttribute_bool("save_option", save_option); @@ -240,10 +257,11 @@ LLNotificationForm::LLNotificationForm(const std::string& name, const LLXMLNodeP { // remember last option chosen by user and automatically respond with that in the future mIgnore = IGNORE_WITH_LAST_RESPONSE; - LLUI::sIgnoresGroup->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name)); + LLUI::sSettingGroups["ignores"]->declareLLSD(std::string("Default") + name, "", std::string("Default response for notification " + name)); } child->getAttributeString("text", mIgnoreMsg); - LLUI::sIgnoresGroup->addWarning(name); + BOOL show_notification = TRUE; + LLUI::sSettingGroups["ignores"]->declareBOOL(name, show_notification, "Ignore notification with this name", TRUE); } else { @@ -339,13 +357,13 @@ void LLNotificationForm::formatElements(const LLSD& substitutions) if ((*it).has("text")) { std::string text = (*it)["text"].asString(); - text = LLNotification::format(text, substitutions); + LLStringUtil::format(text, substitutions); (*it)["text"] = text; } if ((*it)["type"].asString() == "text" && (*it).has("value")) { std::string value = (*it)["value"].asString(); - value = LLNotification::format(value, substitutions); + LLStringUtil::format(value, substitutions); (*it)["value"] = value; } } @@ -366,6 +384,7 @@ LLNotificationTemplate::LLNotificationTemplate() : mExpireSeconds(0), mExpireOption(-1), mURLOption(-1), + mURLOpenExternally(-1), mUnique(false), mPriority(NOTIFICATION_PRIORITY_NORMAL) { @@ -373,17 +392,28 @@ LLNotificationTemplate::LLNotificationTemplate() : } LLNotification::LLNotification(const LLNotification::Params& p) : - mTimestamp(p.timestamp), + mTimestamp(p.time_stamp), mSubstitutions(p.substitutions), mPayload(p.payload), mExpiresAt(0), - mResponseFunctorName(p.functor_name), - mTemporaryResponder(p.mTemporaryResponder), + mTemporaryResponder(false), mRespondedTo(false), mPriority(p.priority), mCancelled(false), mIgnored(false) { + 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; + } + mId.generate(); init(p.name, p.form_elements); } @@ -540,10 +570,11 @@ void LLNotification::respond(const LLSD& response) if (mForm->getIgnoreType() != LLNotificationForm::IGNORE_NO) { - LLUI::sIgnoresGroup->setWarning(getName(), !mIgnored); + BOOL show_notification = mIgnored ? FALSE : TRUE; + LLUI::sSettingGroups["ignores"]->setBOOL(getName(), show_notification); if (mIgnored && mForm->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) { - LLUI::sIgnoresGroup->setLLSD("Default" + getName(), response); + LLUI::sSettingGroups["ignores"]->setLLSD("Default" + getName(), response); } } @@ -601,8 +632,12 @@ void LLNotification::init(const std::string& template_name, const LLSD& form_ele if (!mTemplatep) return; // add default substitutions - // TODO: change this to read from the translatable strings file! - mSubstitutions["SECOND_LIFE"] = "Second Life"; + 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: @@ -636,64 +671,6 @@ std::string LLNotification::summarize() const return s; } -//static -std::string LLNotification::format(const std::string& s, const LLSD& substitutions) -{ - if (!substitutions.isMap()) - { - return s; - } - - std::ostringstream output; - // match strings like [NAME] - const boost::regex key("\\[([0-9_A-Z]+)]"); - - std::string::const_iterator start = s.begin(); - std::string::const_iterator end = s.end(); - boost::smatch match; - - while (boost::regex_search(start, end, match, key, boost::match_default)) - { - bool found_replacement = false; - std::string replacement; - - // see if we have a replacement for the bracketed string (without the brackets) - // test first using has() because if we just look up with operator[] we get back an - // empty string even if the value is missing. We want to distinguish between - // missing replacements and deliberately empty replacement strings. - if (substitutions.has(std::string(match[1].first, match[1].second))) - { - replacement = substitutions[std::string(match[1].first, match[1].second)].asString(); - found_replacement = true; - } - // if not, see if there's one WITH brackets - else if (substitutions.has(std::string(match[0].first, match[0].second))) - { - replacement = substitutions[std::string(match[0].first, match[0].second)].asString(); - found_replacement = true; - } - - if (found_replacement) - { - // found a replacement - // "hello world" is output - output << std::string(start, match[0].first) << replacement; - } - else - { - // we had no replacement, so leave the string we searched for so that it gets noticed by QA - // "hello [NAME_NOT_FOUND]" is output - output << std::string(start, match[0].second); - } - - // update search position - start = match[0].second; - } - // send the remainder of the string (with no further matches for bracketed names) - output << std::string(start, end); - return output.str(); -} - std::string LLNotification::getMessage() const { // all our callers cache this result, so it gives us more flexibility @@ -701,20 +678,32 @@ std::string LLNotification::getMessage() const // cache it in the notification if (!mTemplatep) return std::string(); - return format(mTemplatep->mMessage, mSubstitutions); + + std::string message = mTemplatep->mMessage; + LLStringUtil::format(message, mSubstitutions); + return message; } std::string LLNotification::getLabel() const { - return (mTemplatep ? format(mTemplatep->mLabel, mSubstitutions) : ""); + 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 : ""); +} // ========================================================= // LLNotificationChannel implementation // --- -void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type& slot) +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 @@ -722,23 +711,32 @@ void LLNotificationChannelBase::connectChanged(const LLStandardSignal::slot_type // only about new notifications for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) { - slot.get_slot_function()(LLSD().insert("sigtype", "load").insert("id", (*it)->id())); + slot(LLSD().with("sigtype", "load").with("id", (*it)->id())); } // and then connect the signal so that all future notifications will also be // forwarded. - mChanged.connect(slot); + return mChanged.connect(slot); } -void LLNotificationChannelBase::connectPassedFilter(const LLStandardSignal::slot_type& 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. - mPassedFilter.connect(slot); + return mPassedFilter.connect(slot); } -void LLNotificationChannelBase::connectFailedFilter(const LLStandardSignal::slot_type& slot) +LLBoundListener LLNotificationChannelBase::connectFailedFilterImpl(const LLEventListener& slot) { - mFailedFilter.connect(slot); + return mFailedFilter.connect(slot); } // external call, conforms to our standard signature @@ -896,8 +894,7 @@ mParent(parent) else { LLNotificationChannelPtr p = LLNotifications::instance().getChannel(parent); - LLStandardSignal::slot_type f = boost::bind(&LLNotificationChannelBase::updateItem, this, _1); - p->connectChanged(f); + p->connectChanged(boost::bind(&LLNotificationChannelBase::updateItem, this, _1)); } } @@ -910,7 +907,7 @@ void LLNotificationChannel::setComparator(LLNotificationComparator comparator) mItems.swap(s2); // notify clients that we've been resorted - mChanged(LLSD().insert("sigtype", "sort")); + mChanged(LLSD().with("sigtype", "sort")); } bool LLNotificationChannel::isEmpty() const @@ -951,8 +948,12 @@ std::string LLNotificationChannel::summarize() // LLNotifications implementation // --- LLNotifications::LLNotifications() : LLNotificationChannelBase(LLNotificationFilters::includeEverything, - LLNotificationComparators::orderByUUID()) + LLNotificationComparators::orderByUUID()), + mIgnoreAllNotifications(false) { + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); + + mListener.reset(new LLNotificationsListener(*this)); } @@ -1093,18 +1094,18 @@ void LLNotifications::createDefaultChannels() // connect action methods to these channels LLNotifications::instance().getChannel("Expiration")-> - connectChanged(boost::bind(&LLNotifications::expirationHandler, this, _1)); + 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 LLNotifications::instance().getChannel("Unique")-> - connectChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); - LLNotifications::instance().getChannel("Unique")-> - connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); + connectAtFrontChanged(boost::bind(&LLNotifications::uniqueHandler, this, _1)); +// failedUniquenessTest slot isn't necessary +// LLNotifications::instance().getChannel("Unique")-> +// connectFailedFilter(boost::bind(&LLNotifications::failedUniquenessTest, this, _1)); LLNotifications::instance().getChannel("Ignore")-> connectFailedFilter(&handleIgnoredNotification); } -static std::string sStringSkipNextTime("Skip this dialog next time"); -static std::string sStringAlwaysChoose("Always choose this option"); - bool LLNotifications::addTemplate(const std::string &name, LLNotificationTemplatePtr theTemplate) { @@ -1325,8 +1326,17 @@ bool LLNotifications::loadTemplates() item->getAttributeString("sound", sound); if (!sound.empty()) { - // TODO: test for bad sound effect name / missing effect - pTemplate->mSoundEffect = LLUUID(LLUI::sConfigGroup->getString(sound.c_str())); + // test for bad sound effect name / missing effect + if (LLUI::sSettingGroups["config"]->controlExists(sound)) + { + pTemplate->mSoundEffect = + LLUUID(LLUI::sSettingGroups["config"]->getString(sound)); + } + else + { + llwarns << "Unknown sound effect control name " << sound + << llendl; + } } for (LLXMLNodePtr child = item->getFirstChild(); @@ -1339,6 +1349,7 @@ bool LLNotifications::loadTemplates() { pTemplate->mURL = child->getTextContents(); child->getAttributeU32("option", pTemplate->mURLOption); + child->getAttributeU32("openexternally", pTemplate->mURLOpenExternally); } if (child->hasName("unique")) @@ -1378,28 +1389,40 @@ bool LLNotifications::loadTemplates() return true; } -// we provide a couple of simple add notification functions so that it's reasonable to create notifications in one line -LLNotificationPtr LLNotifications::add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload) +// Add a simple notification (from XUI) +void LLNotifications::addFromCallback(const LLSD& name) { - return add(LLNotification::Params(name).substitutions(substitutions).payload(payload)); + add(LLNotification::Params().name(name.asString())); } LLNotificationPtr LLNotifications::add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload, - const std::string& functor_name) + const LLSD& substitutions, + const LLSD& payload) { - return add(LLNotification::Params(name).substitutions(substitutions).payload(payload).functor_name(functor_name)); + 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) { - return add(LLNotification::Params(name).substitutions(substitutions).payload(payload).functor(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 @@ -1420,7 +1443,7 @@ void LLNotifications::add(const LLNotificationPtr pNotif) llerrs << "Notification added a second time to the master notification channel." << llendl; } - updateItem(LLSD().insert("sigtype", "add").insert("id", pNotif->id()), pNotif); + updateItem(LLSD().with("sigtype", "add").with("id", pNotif->id()), pNotif); } void LLNotifications::cancel(LLNotificationPtr pNotif) @@ -1430,8 +1453,8 @@ void LLNotifications::cancel(LLNotificationPtr pNotif) { llerrs << "Attempted to delete nonexistent notification " << pNotif->getName() << llendl; } - updateItem(LLSD().insert("sigtype", "delete").insert("id", pNotif->id()), pNotif); pNotif->cancel(); + updateItem(LLSD().with("sigtype", "delete").with("id", pNotif->id()), pNotif); } void LLNotifications::update(const LLNotificationPtr pNotif) @@ -1439,7 +1462,7 @@ void LLNotifications::update(const LLNotificationPtr pNotif) LLNotificationSet::iterator it=mItems.find(pNotif); if (it != mItems.end()) { - updateItem(LLSD().insert("sigtype", "change").insert("id", pNotif->id()), pNotif); + updateItem(LLSD().with("sigtype", "change").with("id", pNotif->id()), pNotif); } } @@ -1479,6 +1502,14 @@ std::string LLNotifications::getGlobalString(const std::string& key) const } } +void LLNotifications::setIgnoreAllNotifications(bool setting) +{ + mIgnoreAllNotifications = setting; +} +bool LLNotifications::getIgnoreAllNotifications() +{ + return mIgnoreAllNotifications; +} // --- // END OF LLNotifications implementation diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index bb379121cc..aeb4cebf1b 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -91,48 +91,19 @@ #include <boost/utility.hpp> #include <boost/shared_ptr.hpp> -#include <boost/signal.hpp> +#include <boost/enable_shared_from_this.hpp> #include <boost/type_traits.hpp> // we want to minimize external dependencies, but this one is important #include "llsd.h" // and we need this to manage the notification callbacks +#include "llevents.h" #include "llfunctorregistry.h" -#include "llui.h" - -class LLNotification; -typedef boost::shared_ptr<LLNotification> LLNotificationPtr; - -/***************************************************************************** -* Signal and handler declarations -* Using a single handler signature means that we can have a common handler -* type, rather than needing a distinct one for each different handler. -*****************************************************************************/ - -/** - * A boost::signals Combiner that stops the first time a handler returns true - * We need this because we want to have our handlers return bool, so that - * we have the option to cause a handler to stop further processing. The - * default handler fails when the signal returns a value but has no slots. - */ -struct LLStopWhenHandled -{ - typedef bool result_type; - - template<typename InputIterator> - result_type operator()(InputIterator first, InputIterator last) const - { - for (InputIterator si = first; si != last; ++si) - { - if (*si) - { - return true; - } - } - return false; - } -}; +#include "llpointer.h" +#include "llinitparam.h" +#include "llnotificationslistener.h" +#include "llnotificationptr.h" typedef enum e_notification_priority @@ -144,27 +115,11 @@ typedef enum e_notification_priority NOTIFICATION_PRIORITY_CRITICAL } ENotificationPriority; -/** - * We want to have a standard signature for all signals; this way, - * we can easily document a protocol for communicating across - * dlls and into scripting languages someday. - * we want to return a bool to indicate whether the signal has been - * handled and should NOT be passed on to other listeners. - * Return true to stop further handling of the signal, and false - * to continue. - * We take an LLSD because this way the contents of the signal - * are independent of the API used to communicate it. - * It is const ref because then there's low cost to pass it; - * if you only need to inspect it, it's very cheap. - */ - typedef boost::function<void (const LLSD&, const LLSD&)> LLNotificationResponder; typedef LLFunctorRegistry<LLNotificationResponder> LLNotificationFunctorRegistry; typedef LLFunctorRegistration<LLNotificationResponder> LLNotificationFunctorRegistration; -typedef boost::signal<bool(const LLSD&), LLStopWhenHandled> LLStandardSignal; - // 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<LLNotificationContext, LLUUID> @@ -202,7 +157,8 @@ public: LLNotificationForm(); LLNotificationForm(const LLSD& sd); - LLNotificationForm(const std::string& name, const LLXMLNodePtr xml_node); + LLNotificationForm(const std::string& name, + const LLPointer<class LLXMLNode> xml_node); LLSD asLLSD() const; @@ -280,6 +236,11 @@ struct LLNotificationTemplate // that URL. Obsolete this and eliminate the buttons for affected // messages when we allow clickable URLs in the UI U32 mURLOption; + + U32 mURLOpenExternally; + //This is a flag that tells if the url needs to open externally dispite + //what the user setting is. + // does this notification persist across sessions? if so, it will be // serialized to disk on first receipt and read on startup bool mPersist; @@ -322,42 +283,53 @@ friend class LLNotifications; public: // parameter object used to instantiate a new notification - class Params : public LLParamBlock<Params> + struct Params : public LLInitParam::Block<Params> { friend class LLNotification; - public: - Params(const std::string& _name) - : name(_name), - mTemporaryResponder(false), - functor_name(_name), - priority(NOTIFICATION_PRIORITY_UNSPECIFIED), - timestamp(LLDate::now()) + + Mandatory<std::string> name; + + // optional + Optional<LLSD> substitutions; + Optional<LLSD> payload; + Optional<ENotificationPriority> priority; + Optional<LLSD> form_elements; + Optional<LLDate> time_stamp; + Optional<LLNotificationContext*> context; + + struct Functor : public LLInitParam::Choice<Functor> { + Alternative<std::string> name; + Alternative<LLNotificationFunctorRegistry::ResponseFunctor> function; + + Functor() + : name("functor_name"), + function("functor") + {} + }; + Optional<Functor> functor; + + Params() + : name("name"), + priority("priority", NOTIFICATION_PRIORITY_UNSPECIFIED), + time_stamp("time_stamp"), + payload("payload"), + form_elements("form_elements") + { + time_stamp = LLDate::now(); } - // pseudo-param - Params& functor(LLNotificationFunctorRegistry::ResponseFunctor f) - { - functor_name = LLUUID::generateNewID().asString(); - LLNotificationFunctorRegistry::instance().registerFunctor(functor_name, f); - - mTemporaryResponder = true; - return *this; + Params(const std::string& _name) + : name("name"), + priority("priority", NOTIFICATION_PRIORITY_UNSPECIFIED), + time_stamp("time_stamp"), + payload("payload"), + form_elements("form_elements") + { + functor.name = _name; + name = _name; + time_stamp = LLDate::now(); } - - LLMandatoryParam<std::string> name; - - // optional - LLOptionalParam<LLSD> substitutions; - LLOptionalParam<LLSD> payload; - LLOptionalParam<ENotificationPriority> priority; - LLOptionalParam<LLSD> form_elements; - LLOptionalParam<LLDate> timestamp; - LLOptionalParam<LLNotificationContext*> context; - LLOptionalParam<std::string> functor_name; - - private: - bool mTemporaryResponder; }; private: @@ -410,10 +382,6 @@ public: // constructor from a saved notification LLNotification(const LLSD& sd); - // This is a string formatter for substituting into the message directly - // from LLSD without going through the hopefully-to-be-obsoleted LLString - static std::string format(const std::string& text, const LLSD& substitutions); - void setResponseFunctor(std::string const &responseFunctorName); typedef enum e_response_template_type @@ -505,16 +473,21 @@ public: std::string getMessage() const; std::string getLabel() const; - std::string getURL() const - { - return (mTemplatep ? mTemplatep->mURL : ""); - } + std::string getURL() const; +// { +// return (mTemplatep ? mTemplatep->mURL : ""); +// } S32 getURLOption() const { return (mTemplatep ? mTemplatep->mURLOption : -1); } - + + S32 getURLOpenExternally() const + { + return(mTemplatep? mTemplatep->mURLOpenExternally : -1); + } + const LLNotificationFormPtr getForm(); const LLDate getExpiration() const @@ -699,7 +672,7 @@ typedef std::multimap<std::string, LLNotificationPtr> LLNotificationMap; // all of the built-in tests should attach to the "Visible" channel // class LLNotificationChannelBase : - public boost::signals::trackable + public LLEventTrackable { LOG_CLASS(LLNotificationChannelBase); public: @@ -709,15 +682,54 @@ public: virtual ~LLNotificationChannelBase() {} // you can also connect to a Channel, so you can be notified of // changes to this channel - virtual void connectChanged(const LLStandardSignal::slot_type& slot); - virtual void connectPassedFilter(const LLStandardSignal::slot_type& slot); - virtual void connectFailedFilter(const LLStandardSignal::slot_type& slot); + template <typename LISTENER> + LLBoundListener connectChanged(const LISTENER& slot) + { + // Examine slot to see if it binds an LLEventTrackable subclass, or a + // boost::shared_ptr to something, or a boost::weak_ptr to something. + // Call this->connectChangedImpl() to actually connect it. + return LLEventDetail::visit_and_connect(slot, + boost::bind(&LLNotificationChannelBase::connectChangedImpl, + this, + _1)); + } + template <typename LISTENER> + LLBoundListener connectAtFrontChanged(const LISTENER& slot) + { + return LLEventDetail::visit_and_connect(slot, + boost::bind(&LLNotificationChannelBase::connectAtFrontChangedImpl, + this, + _1)); + } + template <typename LISTENER> + LLBoundListener connectPassedFilter(const LISTENER& slot) + { + // see comments in connectChanged() + return LLEventDetail::visit_and_connect(slot, + boost::bind(&LLNotificationChannelBase::connectPassedFilterImpl, + this, + _1)); + } + template <typename LISTENER> + LLBoundListener connectFailedFilter(const LISTENER& slot) + { + // see comments in connectChanged() + return LLEventDetail::visit_and_connect(slot, + boost::bind(&LLNotificationChannelBase::connectFailedFilterImpl, + this, + _1)); + } // 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; @@ -799,9 +811,19 @@ private: LLNotificationComparator mComparator; }; - +// 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<LLNotifications>, public LLNotificationChannelBase { @@ -812,17 +834,21 @@ public: // load notification descriptions from file; // OK to call more than once because it will reload bool loadTemplates(); - LLXMLNodePtr checkForXMLTemplate(LLXMLNodePtr item); - - // we provide a collection of simple add notification functions so that it's reasonable to create notifications in one line + LLPointer<class LLXMLNode> checkForXMLTemplate(LLPointer<class LLXMLNode> item); + + // 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 = LLSD(), - const LLSD& payload = LLSD()); + const LLSD& substitutions, + const LLSD& payload); LLNotificationPtr add(const std::string& name, const LLSD& substitutions, const LLSD& payload, const std::string& functor_name); - LLNotificationPtr add(const std::string& name, + /* virtual */ LLNotificationPtr add(const std::string& name, const LLSD& substitutions, const LLSD& payload, LLNotificationFunctorRegistry::ResponseFunctor functor); @@ -868,6 +894,9 @@ public: std::string getGlobalString(const std::string& key) const; + void setIgnoreAllNotifications(bool ignore); + bool getIgnoreAllNotifications(); + private: // we're a singleton, so we don't have a public constructor LLNotifications(); @@ -889,13 +918,17 @@ private: std::string mFileName; - typedef std::map<std::string, LLXMLNodePtr> XMLTemplateMap; + typedef std::map<std::string, LLPointer<class LLXMLNode> > XMLTemplateMap; XMLTemplateMap mXmlTemplates; LLNotificationMap mUniqueNotifications; typedef std::map<std::string, std::string> GlobalStringMap; GlobalStringMap mGlobalStrings; + + bool mIgnoreAllNotifications; + + boost::scoped_ptr<LLNotificationsListener> mListener; }; diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp new file mode 100644 index 0000000000..ee6ec0f88f --- /dev/null +++ b/indra/llui/llnotificationslistener.cpp @@ -0,0 +1,332 @@ +/** + * @file llnotificationslistener.cpp + * @author Brad Kittenbrink + * @date 2009-07-08 + * @brief Implementation for llnotificationslistener. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llnotificationslistener.h" +#include "llnotifications.h" +#include "llsd.h" +#include "llui.h" + +LLNotificationsListener::LLNotificationsListener(LLNotifications & notifications) : + LLEventAPI("LLNotifications", + "LLNotifications listener to (e.g.) pop up a notification"), + mNotifications(notifications) +{ + add("requestAdd", + "Add a notification with specified [\"name\"], [\"substitutions\"] and [\"payload\"].\n" + "If optional [\"reply\"] specified, arrange to send user response on that LLEventPump.", + &LLNotificationsListener::requestAdd); + add("listChannels", + "Post to [\"reply\"] a map of info on existing channels", + &LLNotificationsListener::listChannels, + LLSD().with("reply", LLSD())); + add("listChannelNotifications", + "Post to [\"reply\"] an array of info on notifications in channel [\"channel\"]", + &LLNotificationsListener::listChannelNotifications, + LLSD().with("reply", LLSD()).with("channel", LLSD())); + add("respond", + "Respond to notification [\"uuid\"] with data in [\"response\"]", + &LLNotificationsListener::respond, + LLSD().with("uuid", LLSD())); + add("cancel", + "Cancel notification [\"uuid\"]", + &LLNotificationsListener::cancel, + LLSD().with("uuid", LLSD())); + add("ignore", + "Ignore future notification [\"name\"]\n" + "(from <notification name= > in notifications.xml)\n" + "according to boolean [\"ignore\"].\n" + "If [\"name\"] is omitted or undefined, [un]ignore all future notifications.\n" + "Note that ignored notifications are not forwarded unless intercepted before\n" + "the \"Ignore\" channel.", + &LLNotificationsListener::ignore); + add("forward", + "Forward to [\"pump\"] future notifications on channel [\"channel\"]\n" + "according to boolean [\"forward\"]. When enabled, only types matching\n" + "[\"types\"] are forwarded, as follows:\n" + "omitted or undefined: forward all notifications\n" + "string: forward only the specific named [sig]type\n" + "array of string: forward any notification matching any named [sig]type.\n" + "When boolean [\"respond\"] is true, we auto-respond to each forwarded\n" + "notification.", + &LLNotificationsListener::forward, + LLSD().with("channel", LLSD())); +} + +// This is here in the .cpp file so we don't need the definition of class +// Forwarder in the header file. +LLNotificationsListener::~LLNotificationsListener() +{ +} + +void LLNotificationsListener::requestAdd(const LLSD& event_data) const +{ + if(event_data.has("reply")) + { + mNotifications.add(event_data["name"], + event_data["substitutions"], + event_data["payload"], + boost::bind(&LLNotificationsListener::NotificationResponder, + this, + event_data["reply"].asString(), + _1, _2 + ) + ); + } + else + { + mNotifications.add(event_data["name"], + event_data["substitutions"], + event_data["payload"]); + } +} + +void LLNotificationsListener::NotificationResponder(const std::string& reply_pump, + const LLSD& notification, + const LLSD& response) const +{ + LLSD reponse_event; + reponse_event["notification"] = notification; + reponse_event["response"] = response; + LLEventPumps::getInstance()->obtain(reply_pump).post(reponse_event); +} + +void LLNotificationsListener::listChannels(const LLSD& params) const +{ + LLReqID reqID(params); + LLSD response(reqID.makeResponse()); + for (LLNotifications::ChannelMap::const_iterator cmi(mNotifications.mChannels.begin()), + cmend(mNotifications.mChannels.end()); + cmi != cmend; ++cmi) + { + LLSD channelInfo; + channelInfo["parent"] = cmi->second->getParentChannelName(); + response[cmi->first] = channelInfo; + } + LLEventPumps::instance().obtain(params["reply"]).post(response); +} + +void LLNotificationsListener::listChannelNotifications(const LLSD& params) const +{ + LLReqID reqID(params); + LLSD response(reqID.makeResponse()); + LLNotificationChannelPtr channel(mNotifications.getChannel(params["channel"])); + if (channel) + { + LLSD notifications(LLSD::emptyArray()); + for (LLNotificationChannel::Iterator ni(channel->begin()), nend(channel->end()); + ni != nend; ++ni) + { + notifications.append(asLLSD(*ni)); + } + response["notifications"] = notifications; + } + LLEventPumps::instance().obtain(params["reply"]).post(response); +} + +void LLNotificationsListener::respond(const LLSD& params) const +{ + LLNotificationPtr notification(mNotifications.find(params["uuid"])); + if (notification) + { + notification->respond(params["response"]); + } +} + +void LLNotificationsListener::cancel(const LLSD& params) const +{ + LLNotificationPtr notification(mNotifications.find(params["uuid"])); + if (notification) + { + mNotifications.cancel(notification); + } +} + +void LLNotificationsListener::ignore(const LLSD& params) const +{ + // Calling a method named "ignore", but omitting its "ignore" Boolean + // argument, should by default cause something to be ignored. Explicitly + // pass ["ignore"] = false to cancel ignore. + bool ignore = true; + if (params.has("ignore")) + { + ignore = params["ignore"].asBoolean(); + } + // This method can be used to affect either a single notification name or + // all future notifications. The two use substantially different mechanisms. + if (params["name"].isDefined()) + { + // ["name"] was passed: ignore just that notification + LLUI::sSettingGroups["ignores"]->setBOOL(params["name"], ignore); + } + else + { + // no ["name"]: ignore all future notifications + mNotifications.setIgnoreAllNotifications(ignore); + } +} + +class LLNotificationsListener::Forwarder: public LLEventTrackable +{ + LOG_CLASS(LLNotificationsListener::Forwarder); +public: + Forwarder(LLNotifications& llnotifications, const std::string& channel): + mNotifications(llnotifications), + mRespond(false) + { + // Connect to the specified channel on construction. Because + // LLEventTrackable is a base, we should automatically disconnect when + // destroyed. + LLNotificationChannelPtr channelptr(llnotifications.getChannel(channel)); + if (channelptr) + { + // Insert our processing as a "passed filter" listener. This way + // we get to run before all the "changed" listeners, and we get to + // swipe it (hide it from the other listeners) if desired. + channelptr->connectPassedFilter(boost::bind(&Forwarder::handle, this, _1)); + } + } + + void setPumpName(const std::string& name) { mPumpName = name; } + void setTypes(const LLSD& types) { mTypes = types; } + void setRespond(bool respond) { mRespond = respond; } + +private: + bool handle(const LLSD& notification) const; + bool matchType(const LLSD& filter, const std::string& type) const; + + LLNotifications& mNotifications; + std::string mPumpName; + LLSD mTypes; + bool mRespond; +}; + +void LLNotificationsListener::forward(const LLSD& params) +{ + std::string channel(params["channel"]); + // First decide whether we're supposed to start forwarding or stop it. + // Default to true. + bool forward = true; + if (params.has("forward")) + { + forward = params["forward"].asBoolean(); + } + if (! forward) + { + // This is a request to stop forwarding notifications on the specified + // channel. The rest of the params don't matter. + // Because mForwarders contains scoped_ptrs, erasing the map entry + // DOES delete the heap Forwarder object. Because Forwarder derives + // from LLEventTrackable, destroying it disconnects it from the + // channel. + mForwarders.erase(channel); + return; + } + // From here on, we know we're being asked to start (or modify) forwarding + // on the specified channel. Find or create an appropriate Forwarder. + ForwarderMap::iterator + entry(mForwarders.insert(ForwarderMap::value_type(channel, ForwarderMap::mapped_type())).first); + if (! entry->second) + { + entry->second.reset(new Forwarder(mNotifications, channel)); + } + // Now, whether this Forwarder is brand-new or not, update it with the new + // request info. + Forwarder& fwd(*entry->second); + fwd.setPumpName(params["pump"]); + fwd.setTypes(params["types"]); + fwd.setRespond(params["respond"]); +} + +bool LLNotificationsListener::Forwarder::handle(const LLSD& notification) const +{ + LL_INFOS("LLNotificationsListener") << "handle(" << notification << ")" << LL_ENDL; + if (notification["sigtype"].asString() == "delete") + { + LL_INFOS("LLNotificationsListener") << "ignoring delete" << LL_ENDL; + // let other listeners see the "delete" operation + return false; + } + LLNotificationPtr note(mNotifications.find(notification["id"])); + if (! note) + { + LL_INFOS("LLNotificationsListener") << notification["id"] << " not found" << LL_ENDL; + return false; + } + if (! matchType(mTypes, note->getType())) + { + LL_INFOS("LLNotificationsListener") << "didn't match types " << mTypes << LL_ENDL; + // We're not supposed to intercept this particular notification. Let + // other listeners process it. + return false; + } + LL_INFOS("LLNotificationsListener") << "sending via '" << mPumpName << "'" << LL_ENDL; + // This is a notification we care about. Forward it through specified + // LLEventPump. + LLEventPumps::instance().obtain(mPumpName).post(asLLSD(note)); + // Are we also being asked to auto-respond? + if (mRespond) + { + LL_INFOS("LLNotificationsListener") << "should respond" << LL_ENDL; + note->respond(LLSD::emptyMap()); + // Did that succeed in removing the notification? Only cancel() if + // it's still around -- otherwise we get an LL_ERRS crash! + note = mNotifications.find(notification["id"]); + if (note) + { + LL_INFOS("LLNotificationsListener") << "respond() didn't clear, canceling" << LL_ENDL; + mNotifications.cancel(note); + } + } + // If we've auto-responded to this notification, then it's going to be + // deleted. Other listeners would get the change operation, try to look it + // up and be baffled by lookup failure. So when we auto-respond, suppress + // this notification: don't pass it to other listeners. + return mRespond; +} + +bool LLNotificationsListener::Forwarder::matchType(const LLSD& filter, const std::string& type) const +{ + // Decide whether this notification matches filter: + // undefined: forward all notifications + if (filter.isUndefined()) + { + return true; + } + // array of string: forward any notification matching any named type + if (filter.isArray()) + { + for (LLSD::array_const_iterator ti(filter.beginArray()), tend(filter.endArray()); + ti != tend; ++ti) + { + if (ti->asString() == type) + { + return true; + } + } + // Didn't match any entry in the array + return false; + } + // string: forward only the specific named type + return (filter.asString() == type); +} + +LLSD LLNotificationsListener::asLLSD(LLNotificationPtr note) +{ + LLSD notificationInfo(note->asLLSD()); + // For some reason the following aren't included in LLNotification::asLLSD(). + notificationInfo["summary"] = note->summarize(); + notificationInfo["id"] = note->id(); + notificationInfo["type"] = note->getType(); + notificationInfo["message"] = note->getMessage(); + notificationInfo["label"] = note->getLabel(); + return notificationInfo; +} diff --git a/indra/llui/llnotificationslistener.h b/indra/llui/llnotificationslistener.h new file mode 100644 index 0000000000..de208b57f0 --- /dev/null +++ b/indra/llui/llnotificationslistener.h @@ -0,0 +1,52 @@ +/** + * @file llnotificationslistener.h + * @author Brad Kittenbrink + * @date 2009-07-08 + * @brief Wrap subset of LLNotifications API in event API for test scripts. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * Copyright (c) 2009, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#ifndef LL_LLNOTIFICATIONSLISTENER_H +#define LL_LLNOTIFICATIONSLISTENER_H + +#include "lleventapi.h" +#include "llnotificationptr.h" +#include <boost/shared_ptr.hpp> +#include <map> +#include <string> + +class LLNotifications; +class LLSD; + +class LLNotificationsListener : public LLEventAPI +{ +public: + LLNotificationsListener(LLNotifications & notifications); + ~LLNotificationsListener(); + +private: + void requestAdd(LLSD const & event_data) const; + + void NotificationResponder(const std::string& replypump, + const LLSD& notification, + const LLSD& response) const; + + void listChannels(const LLSD& params) const; + void listChannelNotifications(const LLSD& params) const; + void respond(const LLSD& params) const; + void cancel(const LLSD& params) const; + void ignore(const LLSD& params) const; + void forward(const LLSD& params); + + static LLSD asLLSD(LLNotificationPtr); + + class Forwarder; + typedef std::map<std::string, boost::shared_ptr<Forwarder> > ForwarderMap; + ForwarderMap mForwarders; + LLNotifications & mNotifications; +}; + +#endif // LL_LLNOTIFICATIONSLISTENER_H diff --git a/indra/llui/llnotificationsutil.cpp b/indra/llui/llnotificationsutil.cpp new file mode 100644 index 0000000000..f343d27cb4 --- /dev/null +++ b/indra/llui/llnotificationsutil.cpp @@ -0,0 +1,96 @@ +/** + * @file llnotificationsutil.cpp + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#include "linden_common.h" + +#include "llnotificationsutil.h" + +#include "llnotifications.h" +#include "llsd.h" +#include "llxmlnode.h" // apparently needed to call LLNotifications::instance() + +LLNotificationPtr LLNotificationsUtil::add(const std::string& name) +{ + LLNotification::Params::Functor functor_p; + functor_p.name = name; + return LLNotifications::instance().add( + LLNotification::Params().name(name).substitutions(LLSD()).payload(LLSD()).functor(functor_p)); +} + +LLNotificationPtr LLNotificationsUtil::add(const std::string& name, + const LLSD& substitutions) +{ + LLNotification::Params::Functor functor_p; + functor_p.name = name; + return LLNotifications::instance().add( + LLNotification::Params().name(name).substitutions(substitutions).payload(LLSD()).functor(functor_p)); +} + +LLNotificationPtr LLNotificationsUtil::add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload) +{ + LLNotification::Params::Functor functor_p; + functor_p.name = name; + return LLNotifications::instance().add( + LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); +} + +LLNotificationPtr LLNotificationsUtil::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 LLNotifications::instance().add( + LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); +} + +LLNotificationPtr LLNotificationsUtil::add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + boost::function<void (const LLSD&, const LLSD&)> functor) +{ + LLNotification::Params::Functor functor_p; + functor_p.function = functor; + return LLNotifications::instance().add( + LLNotification::Params().name(name).substitutions(substitutions).payload(payload).functor(functor_p)); +} + +S32 LLNotificationsUtil::getSelectedOption(const LLSD& notification, const LLSD& response) +{ + return LLNotification::getSelectedOption(notification, response); +} + +void LLNotificationsUtil::cancel(LLNotificationPtr pNotif) +{ + LLNotifications::instance().cancel(pNotif); +} diff --git a/indra/llui/llnotificationsutil.h b/indra/llui/llnotificationsutil.h new file mode 100644 index 0000000000..d552fa915b --- /dev/null +++ b/indra/llui/llnotificationsutil.h @@ -0,0 +1,70 @@ +/** + * @file llnotificationsutil.h + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#ifndef LLNOTIFICATIONSUTIL_H +#define LLNOTIFICATIONSUTIL_H + +// The vast majority of clients of the notifications system just want to add +// a notification to the screen, so define this lightweight public interface +// to avoid including the heavyweight llnotifications.h + +#include "llnotificationptr.h" + +#include <boost/function.hpp> + +class LLSD; + +namespace LLNotificationsUtil +{ + LLNotificationPtr add(const std::string& name); + + LLNotificationPtr add(const std::string& name, + const LLSD& substitutions); + + 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); + + LLNotificationPtr add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + boost::function<void (const LLSD&, const LLSD&)> functor); + + S32 getSelectedOption(const LLSD& notification, const LLSD& response); + + void cancel(LLNotificationPtr pNotif); +} + +#endif diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index 28780c7adb..7f23fe2671 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -34,15 +34,17 @@ #include "linden_common.h" +#define LLPANEL_CPP #include "llpanel.h" -#include "llalertdialog.h" #include "llfocusmgr.h" #include "llfontgl.h" #include "llrect.h" #include "llerror.h" #include "lltimer.h" +#include "llaccordionctrltab.h" +#include "llbutton.h" #include "llmenugl.h" //#include "llstatusbar.h" #include "llui.h" @@ -53,75 +55,78 @@ #include "lluictrl.h" #include "lluictrlfactory.h" #include "llviewborder.h" -#include "llbutton.h" - -// LLLayoutStack -#include "llresizebar.h" -#include "llcriticaldamp.h" - -const S32 RESIZE_BAR_OVERLAP = 1; -const S32 RESIZE_BAR_HEIGHT = 3; - -static LLRegisterWidget<LLPanel> r1("panel"); - -void LLPanel::init() -{ - // mRectControl - mBgColorAlpha = LLUI::sColorsGroup->getColor( "DefaultBackgroundColor" ); - mBgColorOpaque = LLUI::sColorsGroup->getColor( "FocusBackgroundColor" ); - mDefaultBtnHighlight = LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ); - mBgVisible = FALSE; - mBgOpaque = FALSE; - mBorder = NULL; - mDefaultBtn = NULL; - setIsChrome(FALSE); //is this a decorator to a live window or a form? - mLastTabGroup = 0; - - mPanelHandle.bind(this); - setTabStop(FALSE); -} - -LLPanel::LLPanel() -: mRectControl() -{ - init(); - setName(std::string("panel")); -} - -LLPanel::LLPanel(const std::string& name) -: LLUICtrl(name, LLRect(0, 0, 0, 0), TRUE, NULL, NULL), - mRectControl() -{ - init(); -} - - -LLPanel::LLPanel(const std::string& name, const LLRect& rect, BOOL bordered) -: LLUICtrl(name, rect, TRUE, NULL, NULL), - mRectControl() -{ - init(); - if (bordered) - { - addBorder(); - } -} - - -LLPanel::LLPanel(const std::string& name, const std::string& rect_control, BOOL bordered) -: LLUICtrl(name, LLUI::sConfigGroup->getRect(rect_control), TRUE, NULL, NULL), - mRectControl( rect_control ) -{ - init(); - if (bordered) - { - addBorder(); +#include "lltabcontainer.h" + +static LLDefaultChildRegistry::Register<LLPanel> r1("panel", &LLPanel::fromXML); + +// Compiler optimization, generate extern template +template class LLPanel* LLView::getChild<class LLPanel>( + const std::string& name, BOOL recurse) const; + +LLPanel::LocalizedString::LocalizedString() +: name("name"), + value("value") +{} + +const LLPanel::Params& LLPanel::getDefaultParams() +{ + return LLUICtrlFactory::getDefaultParams<LLPanel>(); +} + +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("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") +{ + name = "panel"; + addSynonym(background_visible, "bg_visible"); + addSynonym(has_border, "border_visible"); + addSynonym(label, "title"); +} + + +LLPanel::LLPanel(const LLPanel::Params& p) +: LLUICtrl(p), + mBgVisible(p.background_visible), + mBgOpaque(p.background_opaque), + mBgOpaqueColor(p.bg_opaque_color()), + mBgAlphaColor(p.bg_alpha_color()), + 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); } + + mPanelHandle.bind(this); } LLPanel::~LLPanel() { - storeRectControl(); + delete mVisibleSignal; } // virtual @@ -130,27 +135,31 @@ BOOL LLPanel::isPanel() const return TRUE; } -// virtual -BOOL LLPanel::postBuild() -{ - return TRUE; -} - -void LLPanel::addBorder(LLViewBorder::EBevel border_bevel, - LLViewBorder::EStyle border_style, S32 border_thickness) +void LLPanel::addBorder(LLViewBorder::Params p) { removeBorder(); - mBorder = new LLViewBorder( std::string("panel border"), - LLRect(0, getRect().getHeight(), getRect().getWidth(), 0), - border_bevel, border_style, border_thickness ); - mBorder->setSaveToXML(false); + p.rect = getLocalRect(); + + mBorder = LLUICtrlFactory::create<LLViewBorder>(p); addChild( mBorder ); } +void LLPanel::addBorder() +{ + LLViewBorder::Params p; + p.border_thickness(LLPANEL_BORDER_WIDTH); + addBorder(p); +} + + void LLPanel::removeBorder() { - delete mBorder; - mBorder = NULL; + if (mBorder) + { + removeChild(mBorder); + delete mBorder; + mBorder = NULL; + } } @@ -179,22 +188,36 @@ void LLPanel::setCtrlsEnabled( BOOL b ) void LLPanel::draw() { + F32 alpha = getDrawContext().mAlpha; + // draw background if( mBgVisible ) { - //RN: I don't see the point of this - S32 left = 0;//LLPANEL_BORDER_WIDTH; - S32 top = getRect().getHeight();// - LLPANEL_BORDER_WIDTH; - S32 right = getRect().getWidth();// - LLPANEL_BORDER_WIDTH; - S32 bottom = 0;//LLPANEL_BORDER_WIDTH; - + LLRect local_rect = getLocalRect(); if (mBgOpaque ) { - gl_rect_2d( left, top, right, bottom, mBgColorOpaque ); + // opaque, in-front look + if (mBgOpaqueImage.notNull()) + { + mBgOpaqueImage->draw( local_rect, UI_VERTEX_COLOR % alpha ); + } + else + { + // fallback to flat colors when there are no images + gl_rect_2d( local_rect, mBgOpaqueColor.get() % alpha); + } } else { - gl_rect_2d( left, top, right, bottom, mBgColorAlpha ); + // transparent, in-back look + if (mBgAlphaImage.notNull()) + { + mBgAlphaImage->draw( local_rect, UI_VERTEX_COLOR % alpha ); + } + else + { + gl_rect_2d( local_rect, mBgAlphaColor.get() % alpha ); + } } } @@ -205,16 +228,11 @@ void LLPanel::draw() void LLPanel::updateDefaultBtn() { - // This method does not call LLView::draw() so callers will need - // to take care of that themselves at the appropriate place in - // their rendering sequence - if( mDefaultBtn) { if (gFocusMgr.childHasKeyboardFocus( this ) && mDefaultBtn->getEnabled()) { - LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); - LLButton* buttonp = dynamic_cast<LLButton*>(focus_ctrl); + LLButton* buttonp = dynamic_cast<LLButton*>(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); @@ -258,30 +276,16 @@ void LLPanel::setDefaultBtn(const std::string& id) } } -void LLPanel::addCtrl( LLUICtrl* ctrl, S32 tab_group) -{ - mLastTabGroup = tab_group; - - LLView::addCtrl(ctrl, tab_group); -} - -void LLPanel::addCtrlAtEnd( LLUICtrl* ctrl, S32 tab_group) -{ - mLastTabGroup = tab_group; - - LLView::addCtrlAtEnd(ctrl, tab_group); -} - BOOL LLPanel::handleKeyHere( KEY key, MASK mask ) { BOOL handled = FALSE; - LLUICtrl* cur_focus = gFocusMgr.getKeyboardFocus(); + LLUICtrl* cur_focus = dynamic_cast<LLUICtrl*>(gFocusMgr.getKeyboardFocus()); // handle user hitting ESC to defocus if (key == KEY_ESCAPE) { - gFocusMgr.setKeyboardFocus(NULL); + setFocus(FALSE); return TRUE; } else if( (mask == MASK_SHIFT) && (KEY_TAB == key)) @@ -308,29 +312,26 @@ BOOL LLPanel::handleKeyHere( KEY key, MASK mask ) } } } - - // If we have a default button, click it when - // return is pressed, unless current focus is a return-capturing button - // in which case *that* button will handle the return key - LLButton* focused_button = dynamic_cast<LLButton*>(cur_focus); - if (cur_focus && !(focused_button && focused_button->getCommitOnReturn())) + + // If RETURN was pressed and something has focus, call onCommit() + if (!handled && cur_focus && key == KEY_RETURN && mask == MASK_NONE) { - // RETURN key means hit default button in this case - if (key == KEY_RETURN && mask == MASK_NONE - && mDefaultBtn != NULL - && mDefaultBtn->getVisible() - && mDefaultBtn->getEnabled()) + LLButton* focused_button = dynamic_cast<LLButton*>(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; } - } - - if (key == KEY_RETURN && mask == MASK_NONE) - { - // set keyboard focus to self to trigger commitOnFocusLost behavior on current ctrl - if (cur_focus && cur_focus->acceptsTextInput()) + else if (cur_focus->acceptsTextInput()) { + // call onCommit for text input handling control cur_focus->onCommit(); handled = TRUE; } @@ -339,55 +340,25 @@ BOOL LLPanel::handleKeyHere( KEY key, MASK mask ) return handled; } -BOOL LLPanel::checkRequirements() +void LLPanel::handleVisibilityChange ( BOOL new_visibility ) { - if (!mRequirementsError.empty()) - { - LLSD args; - args["COMPONENTS"] = mRequirementsError; - args["FLOATER"] = getName(); - - llwarns << getName() << " failed requirements check on: \n" - << mRequirementsError << llendl; - - LLNotifications::instance().add(LLNotification::Params("FailedRequirementsCheck").payload(args)); - mRequirementsError.clear(); - return FALSE; - } - - return TRUE; + LLUICtrl::handleVisibilityChange ( new_visibility ); + if (mVisibleSignal) + (*mVisibleSignal)(this, LLSD(new_visibility) ); // Pass BOOL as LLSD } void LLPanel::setFocus(BOOL b) { - if( b ) + if( b && !hasFocus()) { - if (!gFocusMgr.childHasKeyboardFocus(this)) - { - //refresh(); - if (!focusFirstItem()) - { - LLUICtrl::setFocus(TRUE); - } - onFocusReceived(); - } + // give ourselves focus preemptively, to avoid infinite loop + LLUICtrl::setFocus(TRUE); + // then try to pass to first valid child + focusFirstItem(); } else { - if( this == gFocusMgr.getKeyboardFocus() ) - { - gFocusMgr.setKeyboardFocus( NULL ); - } - else - { - //RN: why is this here? - LLView::ctrl_list_t ctrls = getCtrlList(); - for (LLView::ctrl_list_t::iterator ctrl_it = ctrls.begin(); ctrl_it != ctrls.end(); ++ctrl_it) - { - LLUICtrl* ctrl = *ctrl_it; - ctrl->setFocus( FALSE ); - } - } + LLUICtrl::setFocus(b); } } @@ -399,191 +370,208 @@ void LLPanel::setBorderVisible(BOOL b) } } -// virtual -LLXMLNodePtr LLPanel::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLView::getXML(); +LLFastTimer::DeclareTimer FTM_PANEL_CONSTRUCTION("Panel Construction"); - if (mBorder && mBorder->getVisible()) - { - node->createChild("border", TRUE)->setBoolValue(TRUE); - } - - if (!mRectControl.empty()) - { - node->createChild("rect_control", TRUE)->setStringValue(mRectControl); - } +LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parent, LLXMLNodePtr output_node) +{ + std::string name("panel"); + node->getAttributeString("name", name); - if (!mLabel.empty()) - { - node->createChild("label", TRUE)->setStringValue(mLabel); - } + std::string class_attr; + node->getAttributeString("class", class_attr); - if (save_children) + LLPanel* panelp = NULL; + { - LLView::child_list_const_reverse_iter_t rit; - for (rit = getChildList()->rbegin(); rit != getChildList()->rend(); ++rit) + LLFastTimer timer(FTM_PANEL_CONSTRUCTION); + + if(!class_attr.empty()) { - LLView* childp = *rit; - - if (childp->getSaveToXML()) + panelp = LLRegisterPanelClass::instance().createPanelClass(class_attr); + if (!panelp) { - LLXMLNodePtr xml_node = childp->getXML(); - - node->addChild(xml_node); + llwarns << "Panel class \"" << class_attr << "\" not registered." << llendl; } } - } - - return node; -} -LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parent, LLUICtrlFactory *factory) -{ - std::string name("panel"); - node->getAttributeString("name", name); + if (!panelp) + { + panelp = LLUICtrlFactory::getInstance()->createFactoryPanel(name); + } - LLPanel* panelp = factory->createFactoryPanel(name); - // Fall back on a default panel, if there was no special factory. - if (!panelp) - { - LLRect rect; - createRect(node, rect, parent, LLRect()); - // create a new panel without a border, by default - panelp = new LLPanel(name, rect, FALSE); - panelp->initPanelXML(node, parent, factory); - // preserve panel's width and height, but override the location - const LLRect& panelrect = panelp->getRect(); - S32 w = panelrect.getWidth(); - S32 h = panelrect.getHeight(); - rect.setLeftTopAndSize(rect.mLeft, rect.mTop, w, h); - panelp->setRect(rect); } - else + // factory panels may have registered their own factory maps + if (!panelp->getFactoryMap().empty()) + { + LLUICtrlFactory::instance().pushFactoryFunctions(&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); + + panelp->mCommitCallbackRegistrar.popScope(); + panelp->mEnableCallbackRegistrar.popScope(); + + if (panelp && !panelp->getFactoryMap().empty()) { - panelp->initPanelXML(node, parent, factory); + LLUICtrlFactory::instance().popFactoryFunctions(); } return panelp; } -BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +void LLPanel::initFromParams(const LLPanel::Params& p) { - std::string name = getName(); - node->getAttributeString("name", name); - setName(name); - - setPanelParameters(node, parent); - - initChildrenXML(node, factory); - - std::string xml_filename; - node->getAttributeString("filename", xml_filename); + //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); - BOOL didPost; + setSoundFlags(p.sound_flags); - if (!xml_filename.empty()) + // control_name, tab_stop, focus_lost_callback, initial_value, rect, enabled, visible + LLUICtrl::initFromParams(p); + + // visible callback + if (p.visible_callback.isProvided()) { - didPost = factory->buildPanel(this, xml_filename, NULL); - - LLRect new_rect = getRect(); - // override rectangle with embedding parameters as provided - createRect(node, new_rect, parent); - setOrigin(new_rect.mLeft, new_rect.mBottom); - reshape(new_rect.getWidth(), new_rect.getHeight()); - // optionally override follows flags from including nodes - parseFollowsFlags(node); + setVisibleCallback(initCommitCallback(p.visible_callback)); } - else + + for (LLInitParam::ParamIterator<LocalizedString>::const_iterator it = p.strings().begin(); + it != p.strings().end(); + ++it) { - didPost = FALSE; + 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 (!didPost) + if (p.has_border) { - postBuild(); - didPost = TRUE; + addBorder(p.border); } - - return didPost; + // 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(); } -void LLPanel::initChildrenXML(LLXMLNodePtr node, LLUICtrlFactory* factory) +static LLFastTimer::DeclareTimer FTM_PANEL_SETUP("Panel Setup"); +static LLFastTimer::DeclareTimer FTM_EXTERNAL_PANEL_LOAD("Load Extern Panel Reference"); +static LLFastTimer::DeclareTimer FTM_PANEL_POSTBUILD("Panel PostBuild"); + +BOOL LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node) { - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + const LLPanel::Params& default_params(LLUICtrlFactory::getDefaultParams<LLPanel>()); + Params params(default_params); + { - // look for string declarations for programmatic text - if (child->hasName("string")) + LLFastTimer timer(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()) { - std::string string_name; - child->getAttributeString("name", string_name); - if (!string_name.empty()) - { - mUIStrings[string_name] = child->getTextContents(); - } + node->getAttributeString("filename", xml_filename); } - else + + if (!xml_filename.empty()) { - factory->createWidget(this, child); - } - } -} + LLFastTimer timer(FTM_EXTERNAL_PANEL_LOAD); + if (output_node) + { + //if we are exporting, we want to export the current xml + //not the referenced xml + LLXUIParser::instance().readXUI(node, params, xml_filename); + Params output_params(params); + setupParamsForExport(output_params, parent); + output_node->setName(node->getName()->mString); + LLXUIParser::instance().writeXUI( + output_node, output_params, &default_params); + return TRUE; + } + + if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml)) + { + llwarns << "Couldn't parse panel from: " << xml_filename << llendl; -void LLPanel::setPanelParameters(LLXMLNodePtr node, LLView* parent) -{ - /////// Rect, follows, tool_tip, enabled, visible attributes /////// - initFromXML(node, parent); + return FALSE; + } - /////// Border attributes /////// - BOOL border = mBorder != NULL; - node->getAttributeBOOL("border", border); - if (border) - { - LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_OUT; - LLViewBorder::getBevelFromAttribute(node, bevel_style); + LLXUIParser::instance().readXUI(referenced_xml, params, xml_filename); - LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE; - std::string border_string; - node->getAttributeString("border_style", border_string); - LLStringUtil::toLower(border_string); + // add children using dimensions from referenced xml for consistent layout + setShape(params.rect); + LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance()); + + setXMLFilename(xml_filename); + } - if (border_string == "texture") + // ask LLUICtrlFactory for filename, since xml_filename might be empty + LLXUIParser::instance().readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + + if (output_node) { - border_style = LLViewBorder::STYLE_TEXTURE; + Params output_params(params); + setupParamsForExport(output_params, parent); + output_node->setName(node->getName()->mString); + LLXUIParser::instance().writeXUI( + output_node, output_params, &default_params); + } + + params.from_xui = true; + applyXUILayout(params, parent); + { + LLFastTimer timer(FTM_PANEL_CONSTRUCTION); + initFromParams(params); } - S32 border_thickness = LLPANEL_BORDER_WIDTH; - node->getAttributeS32("border_thickness", border_thickness); + // add children + LLUICtrlFactory::createChildren(this, node, child_registry_t::instance(), output_node); - addBorder(bevel_style, border_style, border_thickness); - } - else - { - removeBorder(); + // 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); + } + + { + LLFastTimer timer(FTM_PANEL_POSTBUILD); + postBuild(); + } } + return TRUE; +} - /////// Background attributes /////// - BOOL background_visible = mBgVisible; - node->getAttributeBOOL("background_visible", background_visible); - setBackgroundVisible(background_visible); - - BOOL background_opaque = mBgOpaque; - node->getAttributeBOOL("background_opaque", background_opaque); - setBackgroundOpaque(background_opaque); - - LLColor4 color; - color = mBgColorOpaque; - LLUICtrlFactory::getAttributeColor(node,"bg_opaque_color", color); - setBackgroundColor(color); - - color = mBgColorAlpha; - LLUICtrlFactory::getAttributeColor(node,"bg_alpha_color", color); - setTransparentColor(color); - - std::string label = getLabel(); - node->getAttributeString("label", label); - setLabel(label); +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 @@ -597,9 +585,7 @@ std::string LLPanel::getString(const std::string& name, const LLStringUtil::form return formatted_string.getString(); } std::string err_str("Failed to find string " + name + " in panel " + getName()); //*TODO: Translate - // *TODO: once the QAR-369 ui-cleanup work on settings is in we need to change the following line to be - //if(LLUI::sConfigGroup->getBOOL("QAMode")) - if(LLUI::sQAMode) + if(LLUI::sSettingGroups["config"]->getBOOL("QAMode")) { llerrs << err_str << llendl; } @@ -618,7 +604,7 @@ std::string LLPanel::getString(const std::string& name) const return found_it->second; } std::string err_str("Failed to find string " + name + " in panel " + getName()); //*TODO: Translate - if(LLUI::sQAMode) + if(LLUI::sSettingGroups["config"]->getBOOL("QAMode")) { llerrs << err_str << llendl; } @@ -632,7 +618,7 @@ std::string LLPanel::getString(const std::string& name) const void LLPanel::childSetVisible(const std::string& id, bool visible) { - LLView* child = getChild<LLView>(id); + LLView* child = findChild<LLView>(id); if (child) { child->setVisible(visible); @@ -641,7 +627,7 @@ void LLPanel::childSetVisible(const std::string& id, bool visible) bool LLPanel::childIsVisible(const std::string& id) const { - LLView* child = getChild<LLView>(id); + LLView* child = findChild<LLView>(id); if (child) { return (bool)child->getVisible(); @@ -651,7 +637,7 @@ bool LLPanel::childIsVisible(const std::string& id) const void LLPanel::childSetEnabled(const std::string& id, bool enabled) { - LLView* child = getChild<LLView>(id); + LLView* child = findChild<LLView>(id); if (child) { child->setEnabled(enabled); @@ -660,7 +646,7 @@ void LLPanel::childSetEnabled(const std::string& id, bool enabled) void LLPanel::childSetTentative(const std::string& id, bool tentative) { - LLView* child = getChild<LLView>(id); + LLView* child = findChild<LLView>(id); if (child) { child->setTentative(tentative); @@ -669,7 +655,7 @@ void LLPanel::childSetTentative(const std::string& id, bool tentative) bool LLPanel::childIsEnabled(const std::string& id) const { - LLView* child = getChild<LLView>(id); + LLView* child = findChild<LLView>(id); if (child) { return (bool)child->getEnabled(); @@ -680,7 +666,7 @@ bool LLPanel::childIsEnabled(const std::string& id) const void LLPanel::childSetToolTip(const std::string& id, const std::string& msg) { - LLView* child = getChild<LLView>(id); + LLView* child = findChild<LLView>(id); if (child) { child->setToolTip(msg); @@ -689,7 +675,7 @@ void LLPanel::childSetToolTip(const std::string& id, const std::string& msg) void LLPanel::childSetRect(const std::string& id, const LLRect& rect) { - LLView* child = getChild<LLView>(id); + LLView* child = findChild<LLView>(id); if (child) { child->setRect(rect); @@ -698,7 +684,7 @@ void LLPanel::childSetRect(const std::string& id, const LLRect& rect) bool LLPanel::childGetRect(const std::string& id, LLRect& rect) const { - LLView* child = getChild<LLView>(id); + LLView* child = findChild<LLView>(id); if (child) { rect = child->getRect(); @@ -709,7 +695,7 @@ bool LLPanel::childGetRect(const std::string& id, LLRect& rect) const void LLPanel::childSetFocus(const std::string& id, BOOL focus) { - LLUICtrl* child = getChild<LLUICtrl>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { child->setFocus(focus); @@ -718,72 +704,42 @@ void LLPanel::childSetFocus(const std::string& id, BOOL focus) BOOL LLPanel::childHasFocus(const std::string& id) { - LLUICtrl* child = getChild<LLUICtrl>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { return child->hasFocus(); } else { - childNotFound(id); return FALSE; } } - -void LLPanel::childSetFocusChangedCallback(const std::string& id, void (*cb)(LLFocusableElement*, void*), void* user_data) -{ - LLUICtrl* child = getChild<LLUICtrl>(id, true); - if (child) - { - child->setFocusChangedCallback(cb, user_data); - } -} - -void LLPanel::childSetCommitCallback(const std::string& id, void (*cb)(LLUICtrl*, void*), void *userdata ) +// *TODO: Deprecate; for backwards compatability only: +// Prefer getChild<LLUICtrl>("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<void (LLUICtrl*,void*)> cb, void* data) { - LLUICtrl* child = getChild<LLUICtrl>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { - child->setCommitCallback(cb); - child->setCallbackUserData(userdata); + child->setCommitCallback(boost::bind(cb, child, data)); } } -void LLPanel::childSetDoubleClickCallback(const std::string& id, void (*cb)(void*), void *userdata ) +void LLPanel::childSetValidate(const std::string& id, boost::function<bool (const LLSD& data)> cb) { - LLUICtrl* child = getChild<LLUICtrl>(id, true); - if (child) - { - child->setDoubleClickCallback(cb); - if (userdata) - { - child->setCallbackUserData(userdata); - } - } -} - -void LLPanel::childSetValidate(const std::string& id, BOOL (*cb)(LLUICtrl*, void*)) -{ - LLUICtrl* child = getChild<LLUICtrl>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { child->setValidateBeforeCommit(cb); } } -void LLPanel::childSetUserData(const std::string& id, void* userdata) -{ - LLUICtrl* child = getChild<LLUICtrl>(id, true); - if (child) - { - child->setCallbackUserData(userdata); - } -} - void LLPanel::childSetColor(const std::string& id, const LLColor4& color) { - LLUICtrl* child = getChild<LLUICtrl>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { child->setColor(color); @@ -792,7 +748,7 @@ void LLPanel::childSetColor(const std::string& id, const LLColor4& color) LLCtrlSelectionInterface* LLPanel::childGetSelectionInterface(const std::string& id) const { - LLUICtrl* child = getChild<LLUICtrl>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { return child->getSelectionInterface(); @@ -802,7 +758,7 @@ LLCtrlSelectionInterface* LLPanel::childGetSelectionInterface(const std::string& LLCtrlListInterface* LLPanel::childGetListInterface(const std::string& id) const { - LLUICtrl* child = getChild<LLUICtrl>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { return child->getListInterface(); @@ -812,7 +768,7 @@ LLCtrlListInterface* LLPanel::childGetListInterface(const std::string& id) const LLCtrlScrollInterface* LLPanel::childGetScrollInterface(const std::string& id) const { - LLUICtrl* child = getChild<LLUICtrl>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { return child->getScrollInterface(); @@ -822,7 +778,7 @@ LLCtrlScrollInterface* LLPanel::childGetScrollInterface(const std::string& id) c void LLPanel::childSetValue(const std::string& id, LLSD value) { - LLView* child = getChild<LLView>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { child->setValue(value); @@ -831,7 +787,7 @@ void LLPanel::childSetValue(const std::string& id, LLSD value) LLSD LLPanel::childGetValue(const std::string& id) const { - LLView* child = getChild<LLView>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { return child->getValue(); @@ -842,7 +798,7 @@ LLSD LLPanel::childGetValue(const std::string& id) const BOOL LLPanel::childSetTextArg(const std::string& id, const std::string& key, const LLStringExplicit& text) { - LLUICtrl* child = getChild<LLUICtrl>(id, true); + LLUICtrl* child = findChild<LLUICtrl>(id); if (child) { return child->setTextArg(key, text); @@ -852,7 +808,7 @@ BOOL LLPanel::childSetTextArg(const std::string& id, const std::string& key, con BOOL LLPanel::childSetLabelArg(const std::string& id, const std::string& key, const LLStringExplicit& text) { - LLView* child = getChild<LLView>(id); + LLView* child = findChild<LLView>(id); if (child) { return child->setLabelArg(key, text); @@ -862,7 +818,7 @@ BOOL LLPanel::childSetLabelArg(const std::string& id, const std::string& key, co BOOL LLPanel::childSetToolTipArg(const std::string& id, const std::string& key, const LLStringExplicit& text) { - LLView* child = getChildView(id, true, FALSE); + LLView* child = findChild<LLView>(id); if (child) { return child->setToolTipArg(key, text); @@ -870,27 +826,9 @@ BOOL LLPanel::childSetToolTipArg(const std::string& id, const std::string& key, return FALSE; } -void LLPanel::childSetMinValue(const std::string& id, LLSD min_value) -{ - LLUICtrl* child = getChild<LLUICtrl>(id, true); - if (child) - { - child->setMinValue(min_value); - } -} - -void LLPanel::childSetMaxValue(const std::string& id, LLSD max_value) -{ - LLUICtrl* child = getChild<LLUICtrl>(id, true); - if (child) - { - child->setMaxValue(max_value); - } -} - void LLPanel::childShowTab(const std::string& id, const std::string& tabname, bool visible) { - LLTabContainer* child = getChild<LLTabContainer>(id); + LLTabContainer* child = findChild<LLTabContainer>(id); if (child) { child->selectTabByName(tabname); @@ -899,7 +837,7 @@ void LLPanel::childShowTab(const std::string& id, const std::string& tabname, bo LLPanel *LLPanel::childGetVisibleTab(const std::string& id) const { - LLTabContainer* child = getChild<LLTabContainer>(id); + LLTabContainer* child = findChild<LLTabContainer>(id); if (child) { return child->getCurrentPanel(); @@ -907,742 +845,140 @@ LLPanel *LLPanel::childGetVisibleTab(const std::string& id) const return NULL; } -void LLPanel::childSetTabChangeCallback(const std::string& id, const std::string& tabname, void (*on_tab_clicked)(void*, bool), void *userdata, void (*on_precommit)(void*,bool)) +static LLPanel *childGetVisibleTabWithHelp(LLView *parent) { - LLTabContainer* child = getChild<LLTabContainer>(id); - if (child) - { - LLPanel *panel = child->getPanelByName(tabname); - if (panel) - { - child->setTabChangeCallback(panel, on_tab_clicked); - child->setTabUserData(panel, userdata); - if (on_precommit) - { - child->setTabPrecommitChangeCallback(panel, on_precommit); - } - } - } -} + LLView *child; -void LLPanel::childSetKeystrokeCallback(const std::string& id, void (*keystroke_callback)(LLLineEditor* caller, void* user_data), void *user_data) -{ - LLLineEditor* child = getChild<LLLineEditor>(id); - if (child) + // look through immediate children first for an active tab with help + for (child = parent->getFirstChild(); child; child = parent->findNextSibling(child)) { - child->setKeystrokeCallback(keystroke_callback); - if (user_data) - { - child->setCallbackUserData(user_data); - } - } -} + LLPanel *curTabPanel = NULL; -void LLPanel::childSetPrevalidate(const std::string& id, BOOL (*func)(const LLWString &) ) -{ - LLLineEditor* child = getChild<LLLineEditor>(id); - if (child) - { - child->setPrevalidate(func); - } -} - -void LLPanel::childSetWrappedText(const std::string& id, const std::string& text, bool visible) -{ - LLTextBox* child = getChild<LLTextBox>(id); - if (child) - { - child->setVisible(visible); - child->setWrappedText(text); - } -} - -void LLPanel::childSetAction(const std::string& id, void(*function)(void*), void* value) -{ - LLButton* button = getChild<LLButton>(id); - if (button) - { - button->setClickedCallback(function, value); - } -} - -void LLPanel::childSetActionTextbox(const std::string& id, void(*function)(void*), void* value) -{ - LLTextBox* textbox = getChild<LLTextBox>(id); - if (textbox) - { - textbox->setClickedCallback(function, value); - } -} - -void LLPanel::childSetControlName(const std::string& id, const std::string& control_name) -{ - LLView* view = getChild<LLView>(id); - if (view) - { - view->setControlName(control_name, NULL); - } -} - -//virtual -LLView* LLPanel::getChildView(const std::string& name, BOOL recurse, BOOL create_if_missing) const -{ - // just get child, don't try to create a dummy one - LLView* view = LLUICtrl::getChildView(name, recurse, FALSE); - if (!view && !recurse) - { - childNotFound(name); - } - if (!view && create_if_missing) - { - view = createDummyWidget<LLView>(name); - } - return view; -} - -void LLPanel::childNotFound(const std::string& id) const -{ - if (mExpectedMembers.find(id) == mExpectedMembers.end()) - { - mNewExpectedMembers.insert(id); - } -} - -void LLPanel::childDisplayNotFound() -{ - if (mNewExpectedMembers.empty()) - { - return; - } - std::string msg; - expected_members_list_t::iterator itor; - for (itor=mNewExpectedMembers.begin(); itor!=mNewExpectedMembers.end(); ++itor) - { - msg.append(*itor); - msg.append("\n"); - mExpectedMembers.insert(*itor); - } - mNewExpectedMembers.clear(); - LLSD args; - args["CONTROLS"] = msg; - LLNotifications::instance().add("FloaterNotFound", args); -} - -void LLPanel::storeRectControl() -{ - if( !mRectControl.empty() ) - { - LLUI::sConfigGroup->setRect( mRectControl, getRect() ); - } -} - - -// -// LLLayoutStack -// -struct LLLayoutStack::LLEmbeddedPanel -{ - LLEmbeddedPanel(LLPanel* panelp, eLayoutOrientation orientation, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize) : - mPanel(panelp), - mMinWidth(min_width), - mMinHeight(min_height), - mAutoResize(auto_resize), - mUserResize(user_resize), - mOrientation(orientation), - mCollapsed(FALSE), - mCollapseAmt(0.f), - mVisibleAmt(1.f) // default to fully visible - { - LLResizeBar::Side side = (orientation == HORIZONTAL) ? LLResizeBar::RIGHT : LLResizeBar::BOTTOM; - LLRect resize_bar_rect = panelp->getRect(); - - S32 min_dim; - if (orientation == HORIZONTAL) + // do we have a tab container? + LLTabContainer *tab = dynamic_cast<LLTabContainer *>(child); + if (tab && tab->getVisible()) { - min_dim = mMinHeight; + curTabPanel = tab->getCurrentPanel(); } - else + + // do we have an accordion tab? + LLAccordionCtrlTab* accordion = dynamic_cast<LLAccordionCtrlTab *>(child); + if (accordion && accordion->getDisplayChildren()) { - min_dim = mMinWidth; + curTabPanel = dynamic_cast<LLPanel *>(accordion->getAccordionView()); } - mResizeBar = new LLResizeBar(std::string("resizer"), mPanel, LLRect(), min_dim, S32_MAX, side); - mResizeBar->setEnableSnapping(FALSE); - // panels initialized as hidden should not start out partially visible - if (!mPanel->getVisible()) + + // if we found a valid tab, does it have a help topic? + if (curTabPanel && !curTabPanel->getHelpTopic().empty()) { - mVisibleAmt = 0.f; + return curTabPanel; } } - ~LLEmbeddedPanel() - { - // probably not necessary, but... - delete mResizeBar; - mResizeBar = NULL; - } - - F32 getCollapseFactor() + // then try a bit harder and recurse through all children + for (child = parent->getFirstChild(); child; child = parent->findNextSibling(child)) { - if (mOrientation == HORIZONTAL) - { - F32 collapse_amt = - clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinWidth / (F32)llmax(1, mPanel->getRect().getWidth())); - return mVisibleAmt * collapse_amt; - } - else + if (child->getVisible()) { - F32 collapse_amt = - clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, llmin(1.f, (F32)mMinHeight / (F32)llmax(1, mPanel->getRect().getHeight()))); - return mVisibleAmt * collapse_amt; + LLPanel* tab = ::childGetVisibleTabWithHelp(child); + if (tab) + { + return tab; + } } } - LLPanel* mPanel; - S32 mMinWidth; - S32 mMinHeight; - BOOL mAutoResize; - BOOL mUserResize; - BOOL mCollapsed; - LLResizeBar* mResizeBar; - eLayoutOrientation mOrientation; - F32 mVisibleAmt; - F32 mCollapseAmt; -}; - -static LLRegisterWidget<LLLayoutStack> r2("layout_stack"); - -LLLayoutStack::LLLayoutStack(eLayoutOrientation orientation) : - mOrientation(orientation), - mMinWidth(0), - mMinHeight(0), - mPanelSpacing(RESIZE_BAR_HEIGHT) -{ + // couldn't find any active tabs with a help topic string + return NULL; } -LLLayoutStack::~LLLayoutStack() +LLPanel *LLPanel::childGetVisibleTabWithHelp() { - std::for_each(mPanels.begin(), mPanels.end(), DeletePointer()); + // find a visible tab with a help topic (to determine help context) + return ::childGetVisibleTabWithHelp(this); } -void LLLayoutStack::draw() +static LLPanel *childGetVisiblePanelWithHelp(LLView *parent) { - updateLayout(); + LLView *child; - e_panel_list_t::iterator panel_it; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + // look through immediate children first for an active panel with help + for (child = parent->getFirstChild(); child; child = parent->findNextSibling(child)) { - // clip to layout rectangle, not bounding rectangle - LLRect clip_rect = (*panel_it)->mPanel->getRect(); - // scale clipping rectangle by visible amount - if (mOrientation == HORIZONTAL) - { - clip_rect.mRight = clip_rect.mLeft + llround((F32)clip_rect.getWidth() * (*panel_it)->getCollapseFactor()); - } - else + // do we have a panel with a help topic? + LLPanel *panel = dynamic_cast<LLPanel *>(child); + if (panel && panel->getVisible() && !panel->getHelpTopic().empty()) { - clip_rect.mBottom = clip_rect.mTop - llround((F32)clip_rect.getHeight() * (*panel_it)->getCollapseFactor()); + return panel; } - - LLPanel* panelp = (*panel_it)->mPanel; - - LLLocalClipRect clip(clip_rect); - // only force drawing invisible children if visible amount is non-zero - drawChild(panelp, 0, 0, !clip_rect.isNull()); - } -} - -void LLLayoutStack::removeCtrl(LLUICtrl* ctrl) -{ - LLEmbeddedPanel* embedded_panelp = findEmbeddedPanel((LLPanel*)ctrl); - - if (embedded_panelp) - { - mPanels.erase(std::find(mPanels.begin(), mPanels.end(), embedded_panelp)); - delete embedded_panelp; - } - - // need to update resizebars - - calcMinExtents(); - - LLView::removeCtrl(ctrl); -} - -LLXMLNodePtr LLLayoutStack::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLView::getXML(); - return node; -} - -//static -LLView* LLLayoutStack::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string orientation_string("vertical"); - node->getAttributeString("orientation", orientation_string); - - eLayoutOrientation orientation = VERTICAL; - - if (orientation_string == "horizontal") - { - orientation = HORIZONTAL; - } - else if (orientation_string == "vertical") - { - orientation = VERTICAL; } - else - { - llwarns << "Unknown orientation " << orientation_string << ", using vertical" << llendl; - } - - LLLayoutStack* layout_stackp = new LLLayoutStack(orientation); - - node->getAttributeS32("border_size", layout_stackp->mPanelSpacing); - // don't allow negative spacing values - layout_stackp->mPanelSpacing = llmax(layout_stackp->mPanelSpacing, 0); - - std::string name("stack"); - node->getAttributeString("name", name); - - layout_stackp->setName(name); - layout_stackp->initFromXML(node, parent); - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) + // then try a bit harder and recurse through all children + for (child = parent->getFirstChild(); child; child = parent->findNextSibling(child)) { - S32 min_width = 0; - S32 min_height = 0; - BOOL auto_resize = TRUE; - - child->getAttributeS32("min_width", min_width); - child->getAttributeS32("min_height", min_height); - child->getAttributeBOOL("auto_resize", auto_resize); - - if (child->hasName("layout_panel")) + if (child->getVisible()) { - BOOL user_resize = TRUE; - child->getAttributeBOOL("user_resize", user_resize); - LLPanel* panelp = (LLPanel*)LLPanel::fromXML(child, layout_stackp, factory); - if (panelp) + LLPanel* panel = ::childGetVisiblePanelWithHelp(child); + if (panel) { - panelp->setFollowsNone(); - layout_stackp->addPanel(panelp, min_width, min_height, auto_resize, user_resize); - } - } - else - { - BOOL user_resize = FALSE; - child->getAttributeBOOL("user_resize", user_resize); - - LLPanel* panelp = new LLPanel(std::string("auto_panel")); - LLView* new_child = factory->createWidget(panelp, child); - if (new_child) - { - // put child in new embedded panel - layout_stackp->addPanel(panelp, min_width, min_height, auto_resize, user_resize); - // resize panel to contain widget and move widget to be contained in panel - panelp->setRect(new_child->getRect()); - new_child->setOrigin(0, 0); - } - else - { - panelp->die(); + return panel; } } } - layout_stackp->updateLayout(); - return layout_stackp; + // couldn't find any active panels with a help topic string + return NULL; } -S32 LLLayoutStack::getDefaultHeight(S32 cur_height) +LLPanel *LLPanel::childGetVisiblePanelWithHelp() { - // if we are spanning our children (crude upward propagation of size) - // then don't enforce our size on our children - if (mOrientation == HORIZONTAL) - { - cur_height = llmax(mMinHeight, getRect().getHeight()); - } - - return cur_height; + // find a visible tab with a help topic (to determine help context) + return ::childGetVisiblePanelWithHelp(this); } -S32 LLLayoutStack::getDefaultWidth(S32 cur_width) +void LLPanel::childSetPrevalidate(const std::string& id, BOOL (*func)(const LLWString &) ) { - // if we are spanning our children (crude upward propagation of size) - // then don't enforce our size on our children - if (mOrientation == VERTICAL) + LLLineEditor* child = findChild<LLLineEditor>(id); + if (child) { - cur_width = llmax(mMinWidth, getRect().getWidth()); + child->setPrevalidate(func); } - - return cur_width; } -void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, EAnimate animate, S32 index) -{ - // panel starts off invisible (collapsed) - if (animate == ANIMATE) - { - panel->setVisible(FALSE); - } - LLEmbeddedPanel* embedded_panel = new LLEmbeddedPanel(panel, mOrientation, min_width, min_height, auto_resize, user_resize); - - mPanels.insert(mPanels.begin() + llclamp(index, 0, (S32)mPanels.size()), embedded_panel); - - addChild(panel); - addChild(embedded_panel->mResizeBar); - - // 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); - } - // start expanding panel animation - if (animate == ANIMATE) +void LLPanel::childSetAction(const std::string& id, boost::function<void(void*)> function, void* value) +{ + LLButton* button = findChild<LLButton>(id); + if (button) { - panel->setVisible(TRUE); + button->setClickedCallback(boost::bind(function, value)); } } -void LLLayoutStack::removePanel(LLPanel* panel) -{ - removeChild(panel); -} - -void LLLayoutStack::collapsePanel(LLPanel* panel, BOOL collapsed) -{ - LLEmbeddedPanel* panel_container = findEmbeddedPanel(panel); - if (!panel_container) return; - - panel_container->mCollapsed = collapsed; -} - -void LLLayoutStack::updateLayout(BOOL force_resize) +void LLPanel::childSetActionTextbox(const std::string& id, boost::function<void(void*)> function, void* value) { - calcMinExtents(); - - // calculate current extents - S32 total_width = 0; - S32 total_height = 0; - - const F32 ANIM_OPEN_TIME = 0.02f; - const F32 ANIM_CLOSE_TIME = 0.03f; - - e_panel_list_t::iterator panel_it; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) - { - LLPanel* panelp = (*panel_it)->mPanel; - if (panelp->getVisible()) - { - (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_OPEN_TIME)); - if ((*panel_it)->mVisibleAmt > 0.99f) - { - (*panel_it)->mVisibleAmt = 1.f; - } - } - else // not visible - { - (*panel_it)->mVisibleAmt = lerp((*panel_it)->mVisibleAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); - if ((*panel_it)->mVisibleAmt < 0.001f) - { - (*panel_it)->mVisibleAmt = 0.f; - } - } - - if ((*panel_it)->mCollapsed) - { - (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 1.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); - } - else - { - (*panel_it)->mCollapseAmt = lerp((*panel_it)->mCollapseAmt, 0.f, LLCriticalDamp::getInterpolant(ANIM_CLOSE_TIME)); - } - - if (mOrientation == HORIZONTAL) - { - // enforce minimize size constraint by default - if (panelp->getRect().getWidth() < (*panel_it)->mMinWidth) - { - panelp->reshape((*panel_it)->mMinWidth, panelp->getRect().getHeight()); - } - total_width += llround(panelp->getRect().getWidth() * (*panel_it)->getCollapseFactor()); - // want n-1 panel gaps for n panels - if (panel_it != mPanels.begin()) - { - total_width += mPanelSpacing; - } - } - else //VERTICAL - { - // enforce minimize size constraint by default - if (panelp->getRect().getHeight() < (*panel_it)->mMinHeight) - { - panelp->reshape(panelp->getRect().getWidth(), (*panel_it)->mMinHeight); - } - total_height += llround(panelp->getRect().getHeight() * (*panel_it)->getCollapseFactor()); - if (panel_it != mPanels.begin()) - { - total_height += mPanelSpacing; - } - } - } - - S32 num_resizable_panels = 0; - S32 shrink_headroom_available = 0; - S32 shrink_headroom_total = 0; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) - { - // panels that are not fully visible do not count towards shrink headroom - if ((*panel_it)->getCollapseFactor() < 1.f) - { - continue; - } - - // if currently resizing a panel or the panel is flagged as not automatically resizing - // only track total available headroom, but don't use it for automatic resize logic - if ((*panel_it)->mResizeBar->hasMouseCapture() - || (!(*panel_it)->mAutoResize - && !force_resize)) - { - if (mOrientation == HORIZONTAL) - { - shrink_headroom_total += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; - } - else //VERTICAL - { - shrink_headroom_total += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; - } - } - else - { - num_resizable_panels++; - if (mOrientation == HORIZONTAL) - { - shrink_headroom_available += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; - shrink_headroom_total += (*panel_it)->mPanel->getRect().getWidth() - (*panel_it)->mMinWidth; - } - else //VERTICAL - { - shrink_headroom_available += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; - shrink_headroom_total += (*panel_it)->mPanel->getRect().getHeight() - (*panel_it)->mMinHeight; - } - } - } - - // calculate how many pixels need to be distributed among layout panels - // positive means panels need to grow, negative means shrink - S32 pixels_to_distribute; - if (mOrientation == HORIZONTAL) - { - pixels_to_distribute = getRect().getWidth() - total_width; - } - else //VERTICAL - { - pixels_to_distribute = getRect().getHeight() - total_height; - } - - // now we distribute the pixels... - S32 cur_x = 0; - S32 cur_y = getRect().getHeight(); - - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) - { - LLPanel* panelp = (*panel_it)->mPanel; - - S32 cur_width = panelp->getRect().getWidth(); - S32 cur_height = panelp->getRect().getHeight(); - S32 new_width = llmax((*panel_it)->mMinWidth, cur_width); - S32 new_height = llmax((*panel_it)->mMinHeight, cur_height); - - S32 delta_size = 0; - - // if panel can automatically resize (not animating, and resize flag set)... - if ((*panel_it)->getCollapseFactor() == 1.f - && (force_resize || (*panel_it)->mAutoResize) - && !(*panel_it)->mResizeBar->hasMouseCapture()) - { - if (mOrientation == HORIZONTAL) - { - // if we're shrinking - if (pixels_to_distribute < 0) - { - // shrink proportionally to amount over minimum - // so we can do this in one pass - delta_size = (shrink_headroom_available > 0) ? llround((F32)pixels_to_distribute * ((F32)(cur_width - (*panel_it)->mMinWidth) / (F32)shrink_headroom_available)) : 0; - shrink_headroom_available -= (cur_width - (*panel_it)->mMinWidth); - } - else - { - // grow all elements equally - delta_size = llround((F32)pixels_to_distribute / (F32)num_resizable_panels); - num_resizable_panels--; - } - pixels_to_distribute -= delta_size; - new_width = llmax((*panel_it)->mMinWidth, cur_width + delta_size); - } - else - { - new_width = getDefaultWidth(new_width); - } - - if (mOrientation == VERTICAL) - { - if (pixels_to_distribute < 0) - { - // shrink proportionally to amount over minimum - // so we can do this in one pass - delta_size = (shrink_headroom_available > 0) ? llround((F32)pixels_to_distribute * ((F32)(cur_height - (*panel_it)->mMinHeight) / (F32)shrink_headroom_available)) : 0; - shrink_headroom_available -= (cur_height - (*panel_it)->mMinHeight); - } - else - { - delta_size = llround((F32)pixels_to_distribute / (F32)num_resizable_panels); - num_resizable_panels--; - } - pixels_to_distribute -= delta_size; - new_height = llmax((*panel_it)->mMinHeight, cur_height + delta_size); - } - else - { - new_height = getDefaultHeight(new_height); - } - } - else - { - if (mOrientation == HORIZONTAL) - { - new_height = getDefaultHeight(new_height); - } - else // VERTICAL - { - new_width = getDefaultWidth(new_width); - } - } - - // adjust running headroom count based on new sizes - shrink_headroom_total += delta_size; - - panelp->reshape(new_width, new_height); - panelp->setOrigin(cur_x, cur_y - new_height); - - LLRect panel_rect = panelp->getRect(); - LLRect resize_bar_rect = panel_rect; - if (mOrientation == HORIZONTAL) - { - resize_bar_rect.mLeft = panel_rect.mRight - RESIZE_BAR_OVERLAP; - resize_bar_rect.mRight = panel_rect.mRight + mPanelSpacing + RESIZE_BAR_OVERLAP; - } - else - { - resize_bar_rect.mTop = panel_rect.mBottom + RESIZE_BAR_OVERLAP; - resize_bar_rect.mBottom = panel_rect.mBottom - mPanelSpacing - RESIZE_BAR_OVERLAP; - } - (*panel_it)->mResizeBar->setRect(resize_bar_rect); - - if (mOrientation == HORIZONTAL) - { - cur_x += llround(new_width * (*panel_it)->getCollapseFactor()) + mPanelSpacing; - } - else //VERTICAL - { - cur_y -= llround(new_height * (*panel_it)->getCollapseFactor()) + mPanelSpacing; - } - } - - // update resize bars with new limits - LLResizeBar* last_resize_bar = NULL; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) - { - LLPanel* panelp = (*panel_it)->mPanel; - - if (mOrientation == HORIZONTAL) - { - (*panel_it)->mResizeBar->setResizeLimits( - (*panel_it)->mMinWidth, - (*panel_it)->mMinWidth + shrink_headroom_total); - } - else //VERTICAL - { - (*panel_it)->mResizeBar->setResizeLimits( - (*panel_it)->mMinHeight, - (*panel_it)->mMinHeight + shrink_headroom_total); - } - - // toggle resize bars based on panel visibility, resizability, etc - BOOL resize_bar_enabled = panelp->getVisible() && (*panel_it)->mUserResize; - (*panel_it)->mResizeBar->setVisible(resize_bar_enabled); - - if (resize_bar_enabled) - { - last_resize_bar = (*panel_it)->mResizeBar; - } - } - - // hide last resize bar as there is nothing past it - // resize bars need to be in between two resizable panels - if (last_resize_bar) - { - last_resize_bar->setVisible(FALSE); - } - - // not enough room to fit existing contents - if (force_resize == FALSE - // layout did not complete by reaching target position - && ((mOrientation == VERTICAL && cur_y != -mPanelSpacing) - || (mOrientation == HORIZONTAL && cur_x != getRect().getWidth() + mPanelSpacing))) + LLTextBox* textbox = findChild<LLTextBox>(id); + if (textbox) { - // do another layout pass with all stacked elements contributing - // even those that don't usually resize - llassert_always(force_resize == FALSE); - updateLayout(TRUE); + textbox->setClickedCallback(boost::bind(function, value)); } -} // end LLLayoutStack::updateLayout - +} -LLLayoutStack::LLEmbeddedPanel* LLLayoutStack::findEmbeddedPanel(LLPanel* panelp) const +void LLPanel::childSetControlName(const std::string& id, const std::string& control_name) { - e_panel_list_t::const_iterator panel_it; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + LLUICtrl* view = findChild<LLUICtrl>(id); + if (view) { - if ((*panel_it)->mPanel == panelp) - { - return *panel_it; - } + view->setControlName(control_name, NULL); } - return NULL; } -void LLLayoutStack::calcMinExtents() +boost::signals2::connection LLPanel::setVisibleCallback( const commit_signal_t::slot_type& cb ) { - mMinWidth = 0; - mMinHeight = 0; - - e_panel_list_t::iterator panel_it; - for (panel_it = mPanels.begin(); panel_it != mPanels.end(); ++panel_it) + if (!mVisibleSignal) { - if (mOrientation == HORIZONTAL) - { - mMinHeight = llmax( mMinHeight, - (*panel_it)->mMinHeight); - mMinWidth += (*panel_it)->mMinWidth; - if (panel_it != mPanels.begin()) - { - mMinWidth += mPanelSpacing; - } - } - else //VERTICAL - { - mMinWidth = llmax( mMinWidth, - (*panel_it)->mMinWidth); - mMinHeight += (*panel_it)->mMinHeight; - if (panel_it != mPanels.begin()) - { - mMinHeight += mPanelSpacing; - } - } + mVisibleSignal = new commit_signal_t(); } + + return mVisibleSignal->connect(cb); } diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index 756d02ef7d..6de83fe3a7 100644 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -37,8 +37,6 @@ #include "llcallbackmap.h" #include "lluictrl.h" -#include "llbutton.h" -#include "lllineeditor.h" #include "llviewborder.h" #include "lluistring.h" #include "v4color.h" @@ -49,6 +47,8 @@ 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. @@ -56,66 +56,87 @@ const BOOL BORDER_NO = FALSE; * With or without border, * Can contain LLUICtrls. */ -class LLPanel : public LLUICtrl, public boost::signals::trackable +class LLPanel : public LLUICtrl { public: + struct LocalizedString : public LLInitParam::Block<LocalizedString> + { + Mandatory<std::string> name; + Mandatory<std::string> value; + + LocalizedString(); + }; + + struct Params + : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<bool> has_border; + Optional<LLViewBorder::Params> border; + + Optional<bool> background_visible, + background_opaque; + + Optional<LLUIColor> bg_opaque_color, + bg_alpha_color; + // opaque image is for "panel in foreground" look + Optional<LLUIImage*> bg_opaque_image, + bg_alpha_image; + + Optional<S32> min_width, + min_height; + + Optional<std::string> filename; + Optional<std::string> class_name; + Optional<std::string> help_topic; - // minimal constructor for data-driven initialization - LLPanel(); - LLPanel(const std::string& name); + Multiple<LocalizedString> strings; + + Optional<CommitCallbackParam> visible_callback; + + Params(); + }; - // Position and size not saved - LLPanel(const std::string& name, const LLRect& rect, BOOL bordered = TRUE); + // valid children for LLPanel are stored in this registry + typedef LLDefaultChildRegistry child_registry_t; - // Position and size are saved to rect_control - LLPanel(const std::string& name, const std::string& rect_control, BOOL bordered = TRUE); +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: +// LLPanel(const std::string& name, const LLRect& rect = LLRect(), BOOL bordered = TRUE); /*virtual*/ ~LLPanel(); // LLView interface /*virtual*/ BOOL isPanel() const; /*virtual*/ void draw(); /*virtual*/ BOOL handleKeyHere( KEY key, MASK mask ); - /*virtual*/ LLXMLNodePtr getXML(bool save_children = true) const; - // Override to set not found list: - virtual LLView* getChildView(const std::string& name, BOOL recurse = TRUE, BOOL create_if_missing = TRUE) const; + /*virtual*/ void handleVisibilityChange ( BOOL new_visibility ); // From LLFocusableElement /*virtual*/ void setFocus( BOOL b ); // New virtuals virtual void refresh(); // called in setFocus() - virtual BOOL postBuild(); virtual void clearCtrls(); // overridden in LLPanelObject and LLPanelVolume // Border controls - void addBorder( LLViewBorder::EBevel border_bevel = LLViewBorder::BEVEL_OUT, - LLViewBorder::EStyle border_style = LLViewBorder::STYLE_LINE, - S32 border_thickness = LLPANEL_BORDER_WIDTH ); + void addBorder( LLViewBorder::Params p); + void addBorder(); void removeBorder(); BOOL hasBorder() const { return mBorder != NULL; } void setBorderVisible( BOOL b ); - template <class T> void requires(const std::string& name) - { - // check for widget with matching type and name - if (LLView::getChild<T>(name) == NULL) - { - mRequirementsError += name + "\n"; - } - } - - // requires LLView by default - void requires(const std::string& name) - { - requires<LLView>(name); - } - BOOL checkRequirements(); - - void setBackgroundColor( const LLColor4& color ) { mBgColorOpaque = color; } - const LLColor4& getBackgroundColor() const { return mBgColorOpaque; } - void setTransparentColor(const LLColor4& color) { mBgColorAlpha = color; } - const LLColor4& getTransparentColor() const { return mBgColorAlpha; } + 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; } + LLPointer<LLUIImage> getBackgroundImage() const { return mBgOpaqueImage; } + LLPointer<LLUIImage> getTransparentImage() const { return mBgAlphaImage; } void setBackgroundVisible( BOOL b ) { mBgVisible = b; } BOOL isBackgroundVisible() const { return mBgVisible; } void setBackgroundOpaque(BOOL b) { mBgOpaque = b; } @@ -125,23 +146,22 @@ public: 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 setRectControl(const std::string& rect_control) { mRectControl.assign(rect_control); } - const std::string& getRectControl() const { return mRectControl; } - void storeRectControl(); - void setCtrlsEnabled(BOOL b); LLHandle<LLPanel> getHandle() const { return mPanelHandle; } - S32 getLastTabGroup() const { return mLastTabGroup; } - const LLCallbackMap::map_t& getFactoryMap() const { return mFactoryMap; } - - BOOL initPanelXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - void initChildrenXML(LLXMLNodePtr node, LLUICtrlFactory* factory); - void setPanelParameters(LLXMLNodePtr node, LLView *parentp); - + + CommitCallbackRegistry::ScopedRegistrar& getCommitCallbackRegistrar() { return mCommitCallbackRegistrar; } + EnableCallbackRegistry::ScopedRegistrar& getEnableCallbackRegistrar() { return mEnableCallbackRegistrar; } + + void initFromParams(const Params& p); + BOOL initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node = NULL); + + 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; @@ -166,12 +186,14 @@ public: // LLUICtrl void childSetFocus(const std::string& id, BOOL focus = TRUE); BOOL childHasFocus(const std::string& id); - void childSetFocusChangedCallback(const std::string& id, void (*cb)(LLFocusableElement*, void*), void* user_data = NULL); - void childSetCommitCallback(const std::string& id, void (*cb)(LLUICtrl*, void*), void* userdata = NULL ); - void childSetDoubleClickCallback(const std::string& id, void (*cb)(void*), void* userdata = NULL ); - void childSetValidate(const std::string& id, BOOL (*cb)(LLUICtrl*, void*) ); - void childSetUserData(const std::string& id, void* userdata); + // *TODO: Deprecate; for backwards compatability only: + // Prefer getChild<LLUICtrl>("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<void (LLUICtrl*,void*)> cb, void* data); + + void childSetValidate(const std::string& id, boost::function<bool (const LLSD& data)> cb ); void childSetColor(const std::string& id, const LLColor4& color); @@ -189,124 +211,76 @@ public: BOOL childSetLabelArg(const std::string& id, const std::string& key, const LLStringExplicit& text); BOOL childSetToolTipArg(const std::string& id, const std::string& key, const LLStringExplicit& text); - // LLSlider / LLMultiSlider / LLSpinCtrl - void childSetMinValue(const std::string& id, LLSD min_value); - void childSetMaxValue(const std::string& id, LLSD max_value); - // LLTabContainer void childShowTab(const std::string& id, const std::string& tabname, bool visible = true); LLPanel *childGetVisibleTab(const std::string& id) const; - void childSetTabChangeCallback(const std::string& id, const std::string& tabname, void (*on_tab_clicked)(void*, bool), void *userdata, void (*on_precommit)(void*,bool) = NULL); - // LLTextBox - void childSetWrappedText(const std::string& id, const std::string& text, bool visible = true); + // Find a child with a nonempty Help topic + LLPanel *childGetVisibleTabWithHelp(); + LLPanel *childGetVisiblePanelWithHelp(); // LLTextBox/LLTextEditor/LLLineEditor void childSetText(const std::string& id, const LLStringExplicit& text) { childSetValue(id, LLSD(text)); } + + // *NOTE: Does not return text from <string> tags, use getString() std::string childGetText(const std::string& id) const { return childGetValue(id).asString(); } // LLLineEditor - void childSetKeystrokeCallback(const std::string& id, void (*keystroke_callback)(LLLineEditor* caller, void* user_data), void *user_data); void childSetPrevalidate(const std::string& id, BOOL (*func)(const LLWString &) ); // LLButton - void childSetAction(const std::string& id, void(*function)(void*), void* value); - void childSetActionTextbox(const std::string& id, void(*function)(void*), void* value = NULL); + void childSetAction(const std::string& id, boost::function<void(void*)> function, void* value = NULL); + + // LLTextBox + void childSetActionTextbox(const std::string& id, boost::function<void(void*)> function, void* value = NULL); + void childSetControlName(const std::string& id, const std::string& control_name); - // Error reporting - void childNotFound(const std::string& id) const; - void childDisplayNotFound(); + 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) {} - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + 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 + private: - // common construction logic - void init(); - - // From LLView - virtual void addCtrl( LLUICtrl* ctrl, S32 tab_group ); - virtual void addCtrlAtEnd( LLUICtrl* ctrl, S32 tab_group); - - // Unified error reporting for the child* functions - typedef std::set<std::string> expected_members_list_t; - mutable expected_members_list_t mExpectedMembers; - mutable expected_members_list_t mNewExpectedMembers; - - std::string mRectControl; - LLColor4 mBgColorAlpha; - LLColor4 mBgColorOpaque; - LLColor4 mDefaultBtnHighlight; - BOOL mBgVisible; - BOOL mBgOpaque; + BOOL mBgVisible; // any background at all? + BOOL mBgOpaque; // use opaque color or image + LLUIColor mBgOpaqueColor; + LLUIColor mBgAlphaColor; + LLPointer<LLUIImage> mBgOpaqueImage; // "panel in front" look + LLPointer<LLUIImage> mBgAlphaImage; // "panel in back" look LLViewBorder* mBorder; LLButton* mDefaultBtn; - std::string mLabel; - S32 mLastTabGroup; + LLUIString mLabel; LLRootHandle<LLPanel> mPanelHandle; typedef std::map<std::string, std::string> ui_string_map_t; ui_string_map_t mUIStrings; - std::string mRequirementsError; + // for setting the xml filename when building panel in context dependent cases + std::string mXMLFilename; }; // end class LLPanel - -class LLLayoutStack : public LLView -{ -public: - typedef enum e_layout_orientation - { - HORIZONTAL, - VERTICAL - } eLayoutOrientation; - - LLLayoutStack(eLayoutOrientation orientation); - virtual ~LLLayoutStack(); - - /*virtual*/ void draw(); - /*virtual*/ LLXMLNodePtr getXML(bool save_children = true) const; - /*virtual*/ void removeCtrl(LLUICtrl* ctrl); - - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - - S32 getMinWidth() const { return mMinWidth; } - S32 getMinHeight() const { return mMinHeight; } - - typedef enum e_animate - { - NO_ANIMATE, - ANIMATE - } EAnimate; - - void addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, EAnimate animate = NO_ANIMATE, S32 index = S32_MAX); - void removePanel(LLPanel* panel); - void collapsePanel(LLPanel* panel, BOOL collapsed = TRUE); - S32 getNumPanels() { return mPanels.size(); } - -private: - struct LLEmbeddedPanel; - - void updateLayout(BOOL force_resize = FALSE); - void calcMinExtents(); - S32 getDefaultHeight(S32 cur_height); - S32 getDefaultWidth(S32 cur_width); - - const eLayoutOrientation mOrientation; - - typedef std::vector<LLEmbeddedPanel*> e_panel_list_t; - e_panel_list_t mPanels; - LLEmbeddedPanel* findEmbeddedPanel(LLPanel* panelp) const; - - S32 mMinWidth; - S32 mMinHeight; - S32 mPanelSpacing; -}; // end class LLLayoutStack +// Build time optimization, generate once in .cpp file +#ifndef LLPANEL_CPP +extern template class LLPanel* LLView::getChild<class LLPanel>( + const std::string& name, BOOL recurse) const; +#endif #endif diff --git a/indra/llui/llprogressbar.cpp b/indra/llui/llprogressbar.cpp index 8833494af8..62ca569e6c 100644 --- a/indra/llui/llprogressbar.cpp +++ b/indra/llui/llprogressbar.cpp @@ -39,31 +39,30 @@ #include "llgl.h" #include "llui.h" #include "llfontgl.h" -#include "llimagegl.h" #include "lltimer.h" #include "llglheaders.h" #include "llfocusmgr.h" +#include "lluictrlfactory.h" -static LLRegisterWidget<LLProgressBar> r("progress_bar"); +static LLDefaultChildRegistry::Register<LLProgressBar> r("progress_bar"); -LLProgressBar::LLProgressBar(const std::string& name, const LLRect &rect) - : LLView(name, rect, FALSE), - mImageBar( NULL ), - mImageShadow( NULL ) -{ - mPercentDone = 0.f; - - // Defaults: +LLProgressBar::Params::Params() +: image_bar("image_bar"), + image_fill("image_fill"), + color_bar("color_bar"), + color_bg("color_bg") +{} - setImageBar("rounded_square.tga"); - setImageShadow("rounded_square_soft.tga"); - mColorBackground = LLColor4(0.3254f, 0.4000f, 0.5058f, 1.0f); - mColorBar = LLColor4(0.5764f, 0.6627f, 0.8352f, 1.0f); - mColorBar2 = LLColor4(0.5764f, 0.6627f, 0.8352f, 1.0f); - mColorShadow = LLColor4(0.2000f, 0.2000f, 0.4000f, 1.0f); -} +LLProgressBar::LLProgressBar(const LLProgressBar::Params& p) +: LLView(p), + mImageBar(p.image_bar), + mImageFill(p.image_fill), + mColorBackground(p.color_bg()), + mColorBar(p.color_bar()), + mPercentDone(0.f) +{} LLProgressBar::~LLProgressBar() { @@ -73,109 +72,21 @@ LLProgressBar::~LLProgressBar() void LLProgressBar::draw() { static LLTimer timer; - - LLUIImagePtr shadow_imagep = LLUI::getUIImage("rounded_square_soft.tga"); - LLUIImagePtr bar_fg_imagep = LLUI::getUIImage("progressbar_fill.tga"); - LLUIImagePtr bar_bg_imagep = LLUI::getUIImage("progressbar_track.tga"); - LLUIImagePtr bar_imagep = LLUI::getUIImage("rounded_square.tga"); - LLColor4 background_color = LLUI::sColorsGroup->getColor("LoginProgressBarBgColor"); + F32 alpha = getDrawContext().mAlpha; - bar_bg_imagep->draw(getLocalRect(), - background_color); + LLColor4 image_bar_color = mColorBackground.get(); + image_bar_color.setAlpha(alpha); + mImageBar->draw(getLocalRect(), image_bar_color); - F32 alpha = 0.5f + 0.5f*0.5f*(1.f + (F32)sin(3.f*timer.getElapsedTimeF32())); - LLColor4 bar_color = LLUI::sColorsGroup->getColor("LoginProgressBarFgColor"); - bar_color.mV[3] = alpha; + alpha *= 0.5f + 0.5f*0.5f*(1.f + (F32)sin(3.f*timer.getElapsedTimeF32())); + LLColor4 bar_color = mColorBar.get(); + bar_color.mV[VALPHA] *= alpha; // modulate alpha LLRect progress_rect = getLocalRect(); progress_rect.mRight = llround(getRect().getWidth() * (mPercentDone / 100.f)); - bar_fg_imagep->draw(progress_rect); + mImageFill->draw(progress_rect, bar_color); } void LLProgressBar::setPercent(const F32 percent) { mPercentDone = llclamp(percent, 0.f, 100.f); } - -void LLProgressBar::setImageBar( const std::string &bar_name ) -{ - mImageBar = LLUI::sImageProvider->getUIImage(bar_name)->getImage(); -} - -void LLProgressBar::setImageShadow(const std::string &shadow_name) -{ - mImageShadow = LLUI::sImageProvider->getUIImage(shadow_name)->getImage(); -} - -void LLProgressBar::setColorBar(const LLColor4 &c) -{ - mColorBar = c; -} -void LLProgressBar::setColorBar2(const LLColor4 &c) -{ - mColorBar2 = c; -} -void LLProgressBar::setColorShadow(const LLColor4 &c) -{ - mColorShadow = c; -} -void LLProgressBar::setColorBackground(const LLColor4 &c) -{ - mColorBackground = c; -} - - -// static -LLView* LLProgressBar::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("progress_bar"); - node->getAttributeString("name", name); - - LLProgressBar *progress = new LLProgressBar(name, LLRect()); - - - std::string image_bar; - if (node->hasAttribute("image_bar")) node->getAttributeString("image_bar",image_bar); - if (image_bar != LLStringUtil::null) progress->setImageBar(image_bar); - - - std::string image_shadow; - if (node->hasAttribute("image_shadow")) node->getAttributeString("image_shadow",image_shadow); - if (image_shadow != LLStringUtil::null) progress->setImageShadow(image_shadow); - - - LLColor4 color_bar; - if (node->hasAttribute("color_bar")) - { - node->getAttributeColor4("color_bar",color_bar); - progress->setColorBar(color_bar); - } - - - LLColor4 color_bar2; - if (node->hasAttribute("color_bar2")) - { - node->getAttributeColor4("color_bar2",color_bar2); - progress->setColorBar2(color_bar2); - } - - - LLColor4 color_shadow; - if (node->hasAttribute("color_shadow")) - { - node->getAttributeColor4("color_shadow",color_shadow); - progress->setColorShadow(color_shadow); - } - - - LLColor4 color_bg; - if (node->hasAttribute("color_bg")) - { - node->getAttributeColor4("color_bg",color_bg); - progress->setColorBackground(color_bg); - } - - - progress->initFromXML(node, parent); - - return progress; -} diff --git a/indra/llui/llprogressbar.h b/indra/llui/llprogressbar.h index 00ad61d540..b6a5b0400d 100644 --- a/indra/llui/llprogressbar.h +++ b/indra/llui/llprogressbar.h @@ -40,37 +40,32 @@ class LLProgressBar : public LLView { public: - LLProgressBar(const std::string& name, const LLRect &rect); - virtual ~LLProgressBar(); - - void setPercent(const F32 percent); + struct Params : public LLInitParam::Block<Params, LLView::Params> + { + Optional<LLUIImage*> image_bar, + image_fill; - void setImageBar(const std::string &bar_name); - void setImageShadow(const std::string &shadow_name); + Optional<LLUIColor> color_bar, + color_bg; - void setColorBar(const LLColor4 &c); - void setColorBar2(const LLColor4 &c); - void setColorShadow(const LLColor4 &c); - void setColorBackground(const LLColor4 &c); + Params(); + }; + LLProgressBar(const Params&); + virtual ~LLProgressBar(); - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + void setPercent(const F32 percent); /*virtual*/ void draw(); -protected: +private: F32 mPercentDone; - LLPointer<LLImageGL> mImageBar; - //LLUUID mImageBarID; - //LLString mImageBarName; - LLColor4 mColorBar; - LLColor4 mColorBar2; + LLPointer<LLUIImage> mImageBar; + LLUIColor mColorBar; - LLPointer<LLImageGL> mImageShadow; - //LLUUID mImageShadowID; - //LLString mImageShadowName; - LLColor4 mColorShadow; - LLColor4 mColorBackground; + LLUIColor mColorBackground; + + LLPointer<LLUIImage> mImageFill; }; #endif // LL_LLPROGRESSBAR_H diff --git a/indra/llui/llradiogroup.cpp b/indra/llui/llradiogroup.cpp index 33b93985d7..4087b484aa 100644 --- a/indra/llui/llradiogroup.cpp +++ b/indra/llui/llradiogroup.cpp @@ -42,63 +42,107 @@ #include "llcontrol.h" #include "llui.h" #include "llfocusmgr.h" +#include "lluictrlfactory.h" +#include "llsdutil.h" -static LLRegisterWidget<LLRadioGroup> r("radio_group"); +static LLDefaultChildRegistry::Register<LLRadioGroup> r1("radio_group"); -LLRadioGroup::LLRadioGroup(const std::string& name, const LLRect& rect, - const std::string& control_name, - LLUICtrlCallback callback, - void* userdata, - BOOL border) -: LLUICtrl(name, rect, TRUE, callback, userdata, FOLLOWS_LEFT | FOLLOWS_TOP), - mSelectedIndex(0) +/* + * 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 { - setControlName(control_name, NULL); - init(border); -} +public: + typedef LLRadioGroup::ItemParams Params; + /*virtual*/ ~LLRadioCtrl(); + /*virtual*/ void setValue(const LLSD& value); + + /*virtual*/ BOOL postBuild(); + + 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::LLRadioGroup(const std::string& name, const LLRect& rect, - S32 initial_index, - LLUICtrlCallback callback, - void* userdata, - BOOL border) : - LLUICtrl(name, rect, TRUE, callback, userdata, FOLLOWS_LEFT | FOLLOWS_TOP), - mSelectedIndex(initial_index) +LLRadioGroup::Params::Params() +: has_border("draw_border"), + items("item") { - init(border); + addSynonym(items, "radio_item"); + + name = "radio_group"; + mouse_opaque = true; + follows.flags = FOLLOWS_LEFT | FOLLOWS_TOP; + // radio items are not tabbable until they are selected + tab_stop = false; } -void LLRadioGroup::init(BOOL border) -{ - if (border) +LLRadioGroup::LLRadioGroup(const LLRadioGroup::Params& p) +: LLUICtrl(p), + mFont(p.font.isProvided() ? p.font() : LLFontGL::getFontSansSerifSmall()), + mSelectedIndex(-1), + mHasBorder(p.has_border) +{ + if (mHasBorder) { - addChild( new LLViewBorder( std::string("radio group border"), - LLRect(0, getRect().getHeight(), getRect().getWidth(), 0), - LLViewBorder::BEVEL_NONE, - LLViewBorder::STYLE_LINE, - 1 ) ); + LLViewBorder::Params params; + params.name("radio group border"); + params.rect(LLRect(0, getRect().getHeight(), getRect().getWidth(), 0)); + params.bevel_style(LLViewBorder::BEVEL_NONE); + LLViewBorder * vb = LLUICtrlFactory::create<LLViewBorder> (params); + addChild (vb); } - mHasBorder = border; } +void LLRadioGroup::initFromParams(const Params& p) +{ + LLUICtrl::initFromParams(p); + for (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items().begin(); + it != p.items().end(); + ++it) + { + LLRadioGroup::ItemParams item_params(*it); + + item_params.font.setIfNotProvided(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<LLRadioCtrl>(item_params, this); + mRadioButtons.push_back(item); + } +} LLRadioGroup::~LLRadioGroup() { } - // virtual -void LLRadioGroup::setEnabled(BOOL enabled) +BOOL LLRadioGroup::postBuild() { - for (child_list_const_iter_t child_iter = getChildList()->begin(); - child_iter != getChildList()->end(); ++child_iter) + if (!mRadioButtons.empty()) { - LLView *child = *child_iter; - child->setEnabled(enabled); + mRadioButtons[0]->setTabStop(true); } - LLView::setEnabled(enabled); + if (mControlVariable) + { + setSelectedIndex(mControlVariable->getValue().asInteger()); + } + return TRUE; } void LLRadioGroup::setIndexEnabled(S32 index, BOOL enabled) @@ -148,16 +192,36 @@ void LLRadioGroup::setIndexEnabled(S32 index, BOOL enabled) BOOL LLRadioGroup::setSelectedIndex(S32 index, BOOL from_event) { - if (index < 0 || index >= (S32)mRadioButtons.size()) + if (index < 0 || (S32)mRadioButtons.size() <= index ) { 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; + LLRadioCtrl* radio_item = mRadioButtons[mSelectedIndex]; + radio_item->setTabStop(true); + radio_item->setValue( TRUE ); + + if (hasFocus()) + { + mRadioButtons[mSelectedIndex]->focusFirstItem(FALSE, FALSE); + } + if (!from_event) { - setControlValue(getSelectedIndex()); + setControlValue(getValue()); } return TRUE; @@ -222,76 +286,45 @@ BOOL LLRadioGroup::handleKeyHere(KEY key, MASK mask) return handled; } -void LLRadioGroup::draw() +BOOL LLRadioGroup::handleMouseDown(S32 x, S32 y, MASK mask) { - S32 current_button = 0; - - BOOL take_focus = FALSE; - if (gFocusMgr.childHasKeyboardFocus(this)) - { - take_focus = TRUE; - } - - for (button_list_t::iterator iter = mRadioButtons.begin(); - iter != mRadioButtons.end(); ++iter) + // grab focus preemptively, before child button takes mousecapture + // + if (hasTabStop()) { - LLRadioCtrl* radio = *iter; - BOOL selected = (current_button == mSelectedIndex); - radio->setValue( selected ); - if (take_focus && selected && !gFocusMgr.childHasKeyboardFocus(radio)) - { - // don't flash keyboard focus when navigating via keyboard - BOOL DONT_FLASH = FALSE; - radio->focusFirstItem(FALSE, DONT_FLASH); - } - current_button++; + focusFirstItem(FALSE, FALSE); } - LLView::draw(); + return LLUICtrl::handleMouseDown(x, y, mask); } -// When adding a button, we need to ensure that the radio -// group gets a message when the button is clicked. -LLRadioCtrl* LLRadioGroup::addRadioButton(const std::string& name, const std::string& label, const LLRect& rect, const LLFontGL* font ) -{ - // Highlight will get fixed in draw method above - LLRadioCtrl* radio = new LLRadioCtrl(name, rect, label, font, - onClickButton, this); - addChild(radio); - mRadioButtons.push_back(radio); - return radio; -} - // Handle one button being clicked. All child buttons must have this // function as their callback function. -// static -void LLRadioGroup::onClickButton(LLUICtrl* ui_ctrl, void* userdata) +void LLRadioGroup::onClickButton(LLUICtrl* ctrl) { // llinfos << "LLRadioGroup::onClickButton" << llendl; - - LLRadioCtrl* clickedRadio = (LLRadioCtrl*) ui_ctrl; - LLRadioGroup* self = (LLRadioGroup*) userdata; - - S32 counter = 0; - for (button_list_t::iterator iter = self->mRadioButtons.begin(); - iter != self->mRadioButtons.end(); ++iter) + LLRadioCtrl* clicked_radio = dynamic_cast<LLRadioCtrl*>(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 == clickedRadio) + if (radio == clicked_radio) { - // llinfos << "clicked button " << counter << llendl; - self->setSelectedIndex(counter); - self->setControlValue(counter); + // llinfos << "clicked button " << index << llendl; + setSelectedIndex(index); // BUG: Calls click callback even if button didn't actually change - self->onCommit(); + onCommit(); return; } - counter++; + index++; } llwarns << "LLRadioGroup::onClickButton - clicked button that isn't a child" << llendl; @@ -299,13 +332,12 @@ void LLRadioGroup::onClickButton(LLUICtrl* ui_ctrl, void* userdata) void LLRadioGroup::setValue( const LLSD& value ) { - std::string value_name = value.asString(); int idx = 0; for (button_list_t::const_iterator iter = mRadioButtons.begin(); iter != mRadioButtons.end(); ++iter) { LLRadioCtrl* radio = *iter; - if (radio->getName() == value_name) + if (radio->getPayload().asString() == value.asString()) { setSelectedIndex(idx); idx = -1; @@ -322,7 +354,7 @@ void LLRadioGroup::setValue( const LLSD& value ) } else { - llwarns << "LLRadioGroup::setValue: value not found: " << value_name << llendl; + llwarns << "LLRadioGroup::setValue: value not found: " << value.asString() << llendl; } } } @@ -334,113 +366,12 @@ LLSD LLRadioGroup::getValue() const for (button_list_t::const_iterator iter = mRadioButtons.begin(); iter != mRadioButtons.end(); ++iter) { - if (idx == index) return LLSD((*iter)->getName()); + if (idx == index) return LLSD((*iter)->getPayload()); ++idx; } return LLSD(); } -// virtual -LLXMLNodePtr LLRadioGroup::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - // Attributes - - node->createChild("draw_border", TRUE)->setBoolValue(mHasBorder); - - // Contents - - for (button_list_t::const_iterator iter = mRadioButtons.begin(); - iter != mRadioButtons.end(); ++iter) - { - LLRadioCtrl* radio = *iter; - - LLXMLNodePtr child_node = radio->LLView::getXML(); - child_node->setStringValue(radio->getLabel()); - child_node->setName(std::string("radio_item")); - - node->addChild(child_node); - } - - return node; -} - -// static -LLView* LLRadioGroup::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("radio_group"); - node->getAttributeString("name", name); - - U32 initial_value = 0; - node->getAttributeU32("initial_value", initial_value); - - BOOL draw_border = TRUE; - node->getAttributeBOOL("draw_border", draw_border); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - LLRadioGroup* radio_group = new LLRadioGroup(name, - rect, - initial_value, - NULL, - NULL, - draw_border); - - const std::string& contents = node->getValue(); - - LLRect group_rect = radio_group->getRect(); - - LLFontGL *font = LLView::selectFont(node); - - if (contents.find_first_not_of(" \n\t") != contents.npos) - { - // ...old school default vertical layout - typedef boost::tokenizer<boost::char_separator<char> > tokenizer; - boost::char_separator<char> sep("\t\n"); - tokenizer tokens(contents, sep); - tokenizer::iterator token_iter = tokens.begin(); - - const S32 HPAD = 4, VPAD = 4; - S32 cur_y = group_rect.getHeight() - VPAD; - - while(token_iter != tokens.end()) - { - const std::string& line = *token_iter; - LLRect rect(HPAD, cur_y, group_rect.getWidth() - (2 * HPAD), cur_y - 15); - cur_y -= VPAD + 15; - radio_group->addRadioButton(std::string("radio"), line, rect, font); - ++token_iter; - } - llwarns << "Legacy radio group format used! Please convert to use <radio_item> tags!" << llendl; - } - else - { - // ...per pixel layout - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - if (child->hasName("radio_item")) - { - LLRect item_rect; - createRect(child, item_rect, radio_group, rect); - - std::string radioname("radio"); - child->getAttributeString("name", radioname); - std::string item_label = child->getTextContents(); - LLRadioCtrl* radio = radio_group->addRadioButton(radioname, item_label, item_rect, font); - - radio->initFromXML(child, radio_group); - } - } - } - - radio_group->initFromXML(node, parent); - - return radio_group; -} - // LLCtrlSelectionInterface functions BOOL LLRadioGroup::setCurrentByID( const LLUUID& id ) { @@ -455,11 +386,10 @@ LLUUID LLRadioGroup::getCurrentID() const BOOL LLRadioGroup::setSelectedByValue(const LLSD& value, BOOL selected) { S32 idx = 0; - std::string value_string = value.asString(); for (button_list_t::const_iterator iter = mRadioButtons.begin(); iter != mRadioButtons.end(); ++iter) { - if((*iter)->getName() == value_string) + if((*iter)->getPayload().asString() == value.asString()) { setSelectedIndex(idx); return TRUE; @@ -478,11 +408,10 @@ LLSD LLRadioGroup::getSelectedValue() BOOL LLRadioGroup::isSelected(const LLSD& value) const { S32 idx = 0; - std::string value_string = value.asString(); for (button_list_t::const_iterator iter = mRadioButtons.begin(); iter != mRadioButtons.end(); ++iter) { - if((*iter)->getName() == value_string) + if((*iter)->getPayload().asString() == value.asString()) { if (idx == mSelectedIndex) { @@ -504,6 +433,34 @@ 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; +} LLRadioCtrl::~LLRadioCtrl() { @@ -515,3 +472,19 @@ void LLRadioCtrl::setValue(const LLSD& 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 3410b74104..b178bb36ca 100644 --- a/indra/llui/llradiogroup.h +++ b/indra/llui/llradiogroup.h @@ -37,25 +37,6 @@ #include "llcheckboxctrl.h" #include "llctrlselectioninterface.h" - -/* - * A checkbox control with use_radio_style == true. - */ -class LLRadioCtrl : public LLCheckBoxCtrl -{ -public: - LLRadioCtrl(const std::string& name, const LLRect& rect, const std::string& label, const LLFontGL* font = NULL, - void (*commit_callback)(LLUICtrl*, void*) = NULL, void* callback_userdata = NULL) : - LLCheckBoxCtrl(name, rect, label, font, commit_callback, callback_userdata, FALSE, RADIO_STYLE) - { - setTabStop(FALSE); - } - /*virtual*/ ~LLRadioCtrl(); - - /*virtual*/ void setValue(const LLSD& value); -}; - - /* * An invisible view containing multiple mutually exclusive toggling * buttons (usually radio buttons). Automatically handles the mutex @@ -65,32 +46,37 @@ class LLRadioGroup : public LLUICtrl, public LLCtrlSelectionInterface { public: - // Build a radio group. The number (0...n-1) of the currently selected - // element will be stored in the named control. After the control is - // changed the callback will be called. - LLRadioGroup(const std::string& name, const LLRect& rect, - const std::string& control_name, - LLUICtrlCallback callback = NULL, - void* userdata = NULL, - BOOL border = TRUE); - - // Another radio group constructor, but this one doesn't rely on - // needing a control - LLRadioGroup(const std::string& name, const LLRect& rect, - S32 initial_index, - LLUICtrlCallback callback = NULL, - void* userdata = NULL, - BOOL border = TRUE); - virtual ~LLRadioGroup(); + struct ItemParams : public LLInitParam::Block<ItemParams, LLCheckBoxCtrl::Params> + { + Optional<LLSD> value; + ItemParams(); + }; + + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<bool> has_border; + Multiple<ItemParams, AtLeast<1> > items; + Params(); + }; + +protected: + LLRadioGroup(const Params&); + friend class LLUICtrlFactory; + +public: + /*virtual*/ void initFromParams(const Params&); + + virtual ~LLRadioGroup(); + + virtual BOOL postBuild(); + + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + virtual BOOL handleKeyHere(KEY key, MASK mask); - virtual void setEnabled(BOOL enabled); - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); void setIndexEnabled(S32 index, BOOL enabled); - // return the index value of the selected item S32 getSelectedIndex() const { return mSelectedIndex; } @@ -101,16 +87,8 @@ public: virtual void setValue(const LLSD& value ); virtual LLSD getValue() const; - // Draw the group, but also fix the highlighting based on the control. - void draw(); - - // You must use this method to add buttons to a radio group. - // Don't use addChild -- it won't set the callback function - // correctly. - LLRadioCtrl* addRadioButton(const std::string& name, const std::string& label, const LLRect& rect, const LLFontGL* font); - LLRadioCtrl* getRadioButton(const S32& index) { return mRadioButtons[index]; } // Update the control as needed. Userdata must be a pointer to the button. - static void onClickButton(LLUICtrl* radio, void* userdata); + void onClickButton(LLUICtrl* clicked_radio); //======================================================================== LLCtrlSelectionInterface* getSelectionInterface() { return (LLCtrlSelectionInterface*)this; }; @@ -131,15 +109,12 @@ public: /*virtual*/ BOOL operateOnAll(EOperation op); private: - // protected function shared by the two constructors. - void init(BOOL border); - + const LLFontGL* mFont; S32 mSelectedIndex; - typedef std::vector<LLRadioCtrl*> button_list_t; + typedef std::vector<class LLRadioCtrl*> button_list_t; button_list_t mRadioButtons; BOOL mHasBorder; }; - #endif diff --git a/indra/llui/llresizebar.cpp b/indra/llui/llresizebar.cpp index 5b9fe72e99..0c46edf300 100644 --- a/indra/llui/llresizebar.cpp +++ b/indra/llui/llresizebar.cpp @@ -40,22 +40,22 @@ #include "llfocusmgr.h" #include "llwindow.h" -LLResizeBar::LLResizeBar( const std::string& name, LLView* resizing_view, const LLRect& rect, S32 min_size, S32 max_size, Side side ) - : - LLView( name, rect, TRUE ), +LLResizeBar::LLResizeBar(const LLResizeBar::Params& p) +: LLView(p), mDragLastScreenX( 0 ), mDragLastScreenY( 0 ), mLastMouseScreenX( 0 ), mLastMouseScreenY( 0 ), - mMinSize( min_size ), - mMaxSize( max_size ), - mSide( side ), - mSnappingEnabled(TRUE), - mAllowDoubleClickSnapping(TRUE), - mResizingView(resizing_view) + 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) { + setFollowsNone(); // set up some generically good follow code. - switch( side ) + switch( mSide ) { case LEFT: setFollowsLeft(); @@ -80,8 +80,6 @@ LLResizeBar::LLResizeBar( const std::string& name, LLView* resizing_view, const default: break; } - // this is just a decorator - setSaveToXML(FALSE); } @@ -145,6 +143,13 @@ BOOL LLResizeBar::handleHover(S32 x, S32 y, MASK mask) if( valid_rect.localPointInRect( screen_x, screen_y ) && mResizingView ) { + // undock floater when user resize it + LLFloater* parent = dynamic_cast<LLFloater*>( getParent()); + if (parent && parent->isDocked()) + { + parent->setDocked( false, false); + } + // Resize the parent LLRect orig_rect = mResizingView->getRect(); LLRect scaled_rect = orig_rect; @@ -185,30 +190,31 @@ BOOL LLResizeBar::handleHover(S32 x, S32 y, MASK mask) if (mSnappingEnabled) { + static LLUICachedControl<S32> snap_margin ("SnapMargin", 0); switch( mSide ) { case LEFT: - snap_view = mResizingView->findSnapEdge(scaled_rect.mLeft, mouse_dir, SNAP_LEFT, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + 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, LLUI::sConfigGroup->getS32("SnapMargin")); + 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, LLUI::sConfigGroup->getS32("SnapMargin")); + 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, LLUI::sConfigGroup->getS32("SnapMargin")); + 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->snappedTo(snap_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->userSetShape(scaled_rect); + mResizingView->setShape(scaled_rect, true); // update last valid mouse cursor position based on resized view's actual size LLRect new_rect = mResizingView->getRect(); @@ -284,7 +290,7 @@ BOOL LLResizeBar::handleDoubleClick(S32 x, S32 y, MASK mask) break; } - mResizingView->userSetShape(scaled_rect); + mResizingView->setShape(scaled_rect, true); } return TRUE; diff --git a/indra/llui/llresizebar.h b/indra/llui/llresizebar.h index b9fc40593d..a7bc3c60f5 100644 --- a/indra/llui/llresizebar.h +++ b/indra/llui/llresizebar.h @@ -41,7 +41,31 @@ class LLResizeBar : public LLView public: enum Side { LEFT, TOP, RIGHT, BOTTOM }; - LLResizeBar(const std::string& name, LLView* resizing_view, const LLRect& rect, S32 min_size, S32 max_size, Side side ); + struct Params : public LLInitParam::Block<Params, LLView::Params> + { + Mandatory<LLView*> resizing_view; + Mandatory<Side> side; + + Optional<S32> min_size; + Optional<S32> max_size; + Optional<bool> snapping_enabled; + Optional<bool> allow_double_click_snapping; + + 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"; + } + }; + +protected: + LLResizeBar(const LLResizeBar::Params& p); + friend class LLUICtrlFactory; +public: // virtual void draw(); No appearance virtual BOOL handleHover(S32 x, S32 y, MASK mask); diff --git a/indra/llui/llresizehandle.cpp b/indra/llui/llresizehandle.cpp index c5d57d8d6c..3df09d124a 100644 --- a/indra/llui/llresizehandle.cpp +++ b/indra/llui/llresizehandle.cpp @@ -44,37 +44,39 @@ const S32 RESIZE_BORDER_WIDTH = 3; -LLResizeHandle::LLResizeHandle( const std::string& name, const LLRect& rect, S32 min_width, S32 min_height, ECorner corner ) - : - LLView( name, rect, TRUE ), +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( min_width ), - mMinHeight( min_height ), - mCorner( corner ) + mMinWidth( p.min_width ), + mMinHeight( p.min_height ), + mCorner( p.corner ) { - setSaveToXML(false); - if( RIGHT_BOTTOM == mCorner) { - mImage = LLUI::sImageProvider->getUIImage("UIImgResizeBottomRightUUID"); + mImage = LLUI::getUIImage("Resize_Corner"); } - - switch( mCorner ) + 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; + 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; } - - // decorator object, don't serialize - setSaveToXML(FALSE); } + BOOL LLResizeHandle::handleMouseDown(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; @@ -122,7 +124,7 @@ BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) { // 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); @@ -133,108 +135,108 @@ BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) LLView* resizing_view = getParent(); if( resizing_view ) { + // undock floater when user resize it + if (((LLFloater*)getParent())->isDocked()) + { + ((LLFloater*)getParent())->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; + + if(delta_x == 0 && delta_y == 0) + return FALSE; + 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_width = orig_rect.getWidth(); + S32 new_height = orig_rect.getHeight(); - 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()); - } + S32 new_pos_x = orig_rect.mLeft; + S32 new_pos_y = orig_rect.mTop; switch( mCorner ) { - case LEFT_TOP: - scaled_rect.translate(delta_x, 0); + case LEFT_TOP: + new_width-=delta_x; + new_height+=delta_y; + new_pos_x+=delta_x; + new_pos_y+=delta_y; break; case LEFT_BOTTOM: - scaled_rect.translate(delta_x, delta_y); + new_width-=delta_x; + new_height-=delta_y; + new_pos_x+=delta_x; break; case RIGHT_TOP: + new_width+=delta_x; + new_height+=delta_y; + new_pos_y+=delta_y; break; case RIGHT_BOTTOM: - scaled_rect.translate(0, delta_y); + new_width+=delta_x; + new_height-=delta_y; break; } + new_width = llmax(new_width,mMinWidth); + new_height = llmax(new_height,mMinHeight); + + LLRect::tCoordType screen_width = resizing_view->getParent()->getSnapRect().getWidth(); + LLRect::tCoordType screen_height = resizing_view->getParent()->getSnapRect().getHeight(); + + new_width = llmin(new_width, screen_width); + new_height = llmin(new_height, screen_height); + // temporarily set new parent rect - scaled_rect.mRight = scaled_rect.mLeft + new_width; - scaled_rect.mTop = scaled_rect.mBottom + new_height; + scaled_rect.setLeftTopAndSize(new_pos_x,new_pos_y,new_width,new_height); + resizing_view->setRect(scaled_rect); LLView* snap_view = NULL; LLView* test_view = NULL; + static LLUICachedControl<S32> 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, LLUI::sConfigGroup->getS32("SnapMargin")); - test_view = resizing_view->findSnapEdge(scaled_rect.mTop, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + 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, LLUI::sConfigGroup->getS32("SnapMargin")); - test_view = resizing_view->findSnapEdge(scaled_rect.mBottom, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + 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, LLUI::sConfigGroup->getS32("SnapMargin")); - test_view = resizing_view->findSnapEdge(scaled_rect.mTop, mouse_dir, SNAP_TOP, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + 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, LLUI::sConfigGroup->getS32("SnapMargin")); - test_view = resizing_view->findSnapEdge(scaled_rect.mBottom, mouse_dir, SNAP_BOTTOM, SNAP_PARENT_AND_SIBLINGS, LLUI::sConfigGroup->getS32("SnapMargin")); + 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; @@ -243,13 +245,17 @@ BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) } // register "snap" behavior with snapped view - resizing_view->snappedTo(snap_view); + resizing_view->setSnappedTo(snap_view); // reset parent rect resizing_view->setRect(orig_rect); // translate and scale to new shape - resizing_view->userSetShape(scaled_rect); + resizing_view->reshape(scaled_rect.getWidth(),scaled_rect.getHeight()); + resizing_view->setRect(scaled_rect); + //set shape to handle dependent floaters... + resizing_view->handleReshape(scaled_rect, false); + // update last valid mouse cursor position based on resized view's actual size LLRect new_rect = resizing_view->getRect(); diff --git a/indra/llui/llresizehandle.h b/indra/llui/llresizehandle.h index 0e23d526fa..1c09cbb1bd 100644 --- a/indra/llui/llresizehandle.h +++ b/indra/llui/llresizehandle.h @@ -35,7 +35,6 @@ #include "stdtypes.h" #include "llview.h" -#include "llimagegl.h" #include "llcoord.h" @@ -44,9 +43,18 @@ class LLResizeHandle : public LLView public: enum ECorner { LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM }; - - LLResizeHandle(const std::string& name, const LLRect& rect, S32 min_width, S32 min_height, ECorner corner = RIGHT_BOTTOM ); + struct Params : public LLInitParam::Block<Params, LLView::Params> + { + Mandatory<ECorner> corner; + Optional<S32> min_width; + Optional<S32> min_height; + Params(); + }; +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); @@ -68,8 +76,8 @@ private: const ECorner mCorner; }; -const S32 RESIZE_HANDLE_HEIGHT = 16; -const S32 RESIZE_HANDLE_WIDTH = 16; +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 141b08c39d..ed870d46d5 100644 --- a/indra/llui/llresmgr.cpp +++ b/indra/llui/llresmgr.cpp @@ -43,106 +43,8 @@ LLResMgr::LLResMgr() { - U32 i; - - // Init values for each locale. - // Note: This is only the most bare-bones version. In the future, load these dynamically, on demand. - - ////////////////////////////////////////////////////////////////////////////// - // USA - // USA Fonts - for( i=0; i<LLFONT_COUNT; i++ ) - { - mUSAFonts[i] = NULL; - } - mUSAFonts[ LLFONT_OCRA ] = LLFontGL::getFontMonospace(); - mUSAFonts[ LLFONT_SANSSERIF ] = LLFontGL::getFontSansSerif(); - mUSAFonts[ LLFONT_SANSSERIF_SMALL ] = LLFontGL::getFontSansSerifSmall(); - mUSAFonts[ LLFONT_SANSSERIF_BIG ] = LLFontGL::getFontSansSerifBig(); - mUSAFonts[ LLFONT_SMALL ] = LLFontGL::getFontMonospace(); -/* - // USA Strings - for( i=0; i<LLSTR_COUNT; i++ ) - { - mUSAStrings[i] = ""; - } - mUSAStrings[ LLSTR_HELLO ] = "hello"; - mUSAStrings[ LLSTR_GOODBYE ] = "goodbye"; - mUSAStrings[ LLSTR_CHAT_LABEL ] = "Chat"; - mUSAStrings[ LLSTR_STATUS_LABEL ] = "Properties"; - mUSAStrings[ LLSTR_X ] = "X"; - mUSAStrings[ LLSTR_Y ] = "Y"; - mUSAStrings[ LLSTR_Z ] = "Z"; - mUSAStrings[ LLSTR_POSITION ] = "Position (meters)"; - mUSAStrings[ LLSTR_SCALE ] = "Size (meters)"; - mUSAStrings[ LLSTR_ROTATION ] = "Rotation (degrees)"; - mUSAStrings[ LLSTR_HAS_PHYSICS ] = "Has Physics"; - mUSAStrings[ LLSTR_SCRIPT ] = "Script"; - mUSAStrings[ LLSTR_HELP ] = "Help"; - mUSAStrings[ LLSTR_REMOVE ] = "Remove"; - mUSAStrings[ LLSTR_CLEAR ] = "Clear"; - mUSAStrings[ LLSTR_APPLY ] = "Apply"; - mUSAStrings[ LLSTR_CANCEL ] = "Cancel"; - mUSAStrings[ LLSTR_MATERIAL ] = "Material"; - mUSAStrings[ LLSTR_FACE ] = "Face"; - mUSAStrings[ LLSTR_TEXTURE ] = "Texture"; - mUSAStrings[ LLSTR_TEXTURE_SIZE ] = "Repeats per Face"; - mUSAStrings[ LLSTR_TEXTURE_OFFSET ] = "Offset"; - mUSAStrings[ LLSTR_TEXTURE_ROTATION ] = "Rotation (degrees)"; - mUSAStrings[ LLSTR_U ] = "U"; - mUSAStrings[ LLSTR_V ] = "V"; - mUSAStrings[ LLSTR_OWNERSHIP ] = "Ownership"; - mUSAStrings[ LLSTR_PUBLIC ] = "Public"; - mUSAStrings[ LLSTR_PRIVATE ] = "Private"; - mUSAStrings[ LLSTR_REVERT ] = "Revert"; - mUSAStrings[ LLSTR_INSERT_SAMPLE ] = "Insert Sample"; - mUSAStrings[ LLSTR_SET_TEXTURE ] = "Set Texture"; - mUSAStrings[ LLSTR_EDIT_SCRIPT ] = "Edit Script..."; - mUSAStrings[ LLSTR_MOUSELOOK_INSTRUCTIONS ] = "Press ESC to leave Mouselook."; - mUSAStrings[ LLSTR_EDIT_FACE_INSTRUCTIONS ] = "Click on face to select part. Click and hold on a picture to look more like that. Press ESC to leave Face Edit Mode."; - mUSAStrings[ LLSTR_CLOSE ] = "Close"; - mUSAStrings[ LLSTR_MOVE ] = "Move"; - mUSAStrings[ LLSTR_ROTATE ] = "Rotate"; - mUSAStrings[ LLSTR_RESIZE ] = "Resize"; - mUSAStrings[ LLSTR_PLACE_BOX ] = "Place Box"; - mUSAStrings[ LLSTR_PLACE_PRISM ] = "Place Prism"; - mUSAStrings[ LLSTR_PLACE_PYRAMID ] = "Place Pyramid"; - mUSAStrings[ LLSTR_PLACE_TETRAHEDRON ] = "Place Tetrahedron"; - mUSAStrings[ LLSTR_PLACE_CYLINDER ] = "Place Cylinder"; - mUSAStrings[ LLSTR_PLACE_HALF_CYLINDER ] = "Place Half-Cylinder"; - mUSAStrings[ LLSTR_PLACE_CONE ] = "Place Cone"; - mUSAStrings[ LLSTR_PLACE_HALF_CONE ] = "Place Half-Cone"; - mUSAStrings[ LLSTR_PLACE_SPHERE ] = "Place Sphere"; - mUSAStrings[ LLSTR_PLACE_HALF_SPHERE ] = "Place Half-Sphere"; - mUSAStrings[ LLSTR_PLACE_BIRD ] = "Place Bird"; - mUSAStrings[ LLSTR_PLACE_SNAKE ] = "Place Silly Snake"; - mUSAStrings[ LLSTR_PLACE_ROCK ] = "Place Rock"; - mUSAStrings[ LLSTR_PLACE_TREE ] = "Place Tree"; - mUSAStrings[ LLSTR_PLACE_GRASS ] = "Place Grass"; - mUSAStrings[ LLSTR_MODIFY_LAND ] = "Modify Land"; -*/ - ////////////////////////////////////////////////////////////////////////////// - // UK - // The Brits are a lot like us Americans, so initially assume we're the same and only code the exceptions. - - // UK Fonts - for( i=0; i<LLFONT_COUNT; i++ ) - { - mUKFonts[i] = mUSAFonts[i]; - } -/* - // UK Strings - for( i=0; i<LLSTR_COUNT; i++ ) - { - mUKStrings[i] = mUSAStrings[i]; - } - mUKStrings[ LLSTR_HELLO ] = "hullo"; - mUKStrings[ LLSTR_GOODBYE ] = "cheerio"; -*/ - ////////////////////////////////////////////////////////////////////////////// // Set default setLocale( LLLOCALE_USA ); - } @@ -151,9 +53,9 @@ void LLResMgr::setLocale( LLLOCALE_ID locale_id ) mLocale = locale_id; //RN: for now, use normal 'C' locale for everything but specific UI input/output routines - switch( locale_id ) - { - case LLLOCALE_USA: +// switch( locale_id ) +// { +// case LLLOCALE_USA: //#if LL_WINDOWS // // Windows doesn't use ISO country codes. // llinfos << "Setting locale to " << setlocale( LC_ALL, "english-usa" ) << llendl; @@ -161,11 +63,8 @@ void LLResMgr::setLocale( LLLOCALE_ID locale_id ) // // posix version should work everywhere else. // llinfos << "Setting locale to " << setlocale( LC_ALL, "en_US" ) << llendl; //#endif - -// mStrings = mUSAStrings; - mFonts = mUSAFonts; - break; - case LLLOCALE_UK: +// break; +// case LLLOCALE_UK: //#if LL_WINDOWS // // Windows doesn't use ISO country codes. // llinfos << "Setting locale to " << setlocale( LC_ALL, "english-uk" ) << llendl; @@ -173,15 +72,12 @@ void LLResMgr::setLocale( LLLOCALE_ID locale_id ) // // posix version should work everywhere else. // llinfos << "Setting locale to " << setlocale( LC_ALL, "en_GB" ) << llendl; //#endif - -// mStrings = mUKStrings; - mFonts = mUKFonts; - break; - default: - llassert(0); - setLocale(LLLOCALE_USA); - break; - } +// break; +// default: +// llassert(0); +// setLocale(LLLOCALE_USA); +// break; +// } } char LLResMgr::getDecimalPoint() const @@ -383,6 +279,14 @@ std::string LLResMgr::getMonetaryString( S32 input ) const 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; @@ -418,27 +322,6 @@ void LLResMgr::getIntegerString( std::string& output, S32 input ) const } } -const std::string LLFONT_ID_NAMES[] = -{ - std::string("OCRA"), - std::string("SANSSERIF"), - std::string("SANSSERIF_SMALL"), - std::string("SANSSERIF_BIG"), - std::string("SMALL"), -}; - -const LLFontGL* LLResMgr::getRes( std::string font_id ) const -{ - for (S32 i=0; i<LLFONT_COUNT; ++i) - { - if (LLFONT_ID_NAMES[i] == font_id) - { - return getRes((LLFONT_ID)i); - } - } - return NULL; -} - #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"); diff --git a/indra/llui/llresmgr.h b/indra/llui/llresmgr.h index d54505c503..c8fa340990 100644 --- a/indra/llui/llresmgr.h +++ b/indra/llui/llresmgr.h @@ -37,7 +37,7 @@ #include "locale.h" #include "stdtypes.h" #include "llstring.h" -#include "llmemory.h" +#include "llsingleton.h" enum LLLOCALE_ID { @@ -46,18 +46,6 @@ enum LLLOCALE_ID LLLOCALE_COUNT // Number of values in this enum. Keep at end. }; -enum LLFONT_ID -{ - LLFONT_OCRA, - LLFONT_SANSSERIF, - LLFONT_SANSSERIF_SMALL, - LLFONT_SANSSERIF_BIG, - LLFONT_SMALL, - LLFONT_COUNT // Number of values in this enum. Keep at end. -}; - -class LLFontGL; - class LLResMgr : public LLSingleton<LLResMgr> { public: @@ -74,20 +62,9 @@ public: std::string getMonetaryString( S32 input ) const; void getIntegerString( std::string& output, S32 input ) const; -// const char* getRes( LLSTR_ID string_id ) const { return mStrings[ string_id ]; } - const LLFontGL* getRes( LLFONT_ID font_id ) const { return mFonts[ font_id ]; } - const LLFontGL* getRes( std::string font_id ) const; private: LLLOCALE_ID mLocale; -// const char** mStrings; - const LLFontGL** mFonts; - -// const char* mUSAStrings[LLSTR_COUNT]; - const LLFontGL* mUSAFonts[LLFONT_COUNT]; - -// const char* mUKStrings[LLSTR_COUNT]; - const LLFontGL* mUKFonts[LLFONT_COUNT]; }; class LLLocale diff --git a/indra/llui/llrngwriter.cpp b/indra/llui/llrngwriter.cpp new file mode 100644 index 0000000000..7e3d4b92d3 --- /dev/null +++ b/indra/llui/llrngwriter.cpp @@ -0,0 +1,316 @@ +/** + * @file llrngwriter.cpp + * @brief Generates Relax NG schema from param blocks + * + * $LicenseInfo:firstyear=2003&license=viewergpl$ + * + * Copyright (c) 2003-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llrngwriter.h" +#include "lluicolor.h" +#include "lluictrlfactory.h" + +// +// LLRNGWriter - writes Relax NG schema files based on a param block +// +LLRNGWriter::LLRNGWriter() +{ + // register various callbacks for inspecting the contents of a param block + registerInspectFunc<bool>(boost::bind(&LLRNGWriter::writeAttribute, this, "boolean", _1, _2, _3, _4)); + registerInspectFunc<std::string>(boost::bind(&LLRNGWriter::writeAttribute, this, "string", _1, _2, _3, _4)); + registerInspectFunc<U8>(boost::bind(&LLRNGWriter::writeAttribute, this, "unsignedByte", _1, _2, _3, _4)); + registerInspectFunc<S8>(boost::bind(&LLRNGWriter::writeAttribute, this, "signedByte", _1, _2, _3, _4)); + registerInspectFunc<U16>(boost::bind(&LLRNGWriter::writeAttribute, this, "unsignedShort", _1, _2, _3, _4)); + registerInspectFunc<S16>(boost::bind(&LLRNGWriter::writeAttribute, this, "signedShort", _1, _2, _3, _4)); + registerInspectFunc<U32>(boost::bind(&LLRNGWriter::writeAttribute, this, "unsignedInt", _1, _2, _3, _4)); + registerInspectFunc<S32>(boost::bind(&LLRNGWriter::writeAttribute, this, "integer", _1, _2, _3, _4)); + registerInspectFunc<F32>(boost::bind(&LLRNGWriter::writeAttribute, this, "float", _1, _2, _3, _4)); + registerInspectFunc<F64>(boost::bind(&LLRNGWriter::writeAttribute, this, "double", _1, _2, _3, _4)); + registerInspectFunc<LLColor4>(boost::bind(&LLRNGWriter::writeAttribute, this, "string", _1, _2, _3, _4)); + registerInspectFunc<LLUIColor>(boost::bind(&LLRNGWriter::writeAttribute, this, "string", _1, _2, _3, _4)); + registerInspectFunc<LLUUID>(boost::bind(&LLRNGWriter::writeAttribute, this, "string", _1, _2, _3, _4)); + registerInspectFunc<LLSD>(boost::bind(&LLRNGWriter::writeAttribute, this, "string", _1, _2, _3, _4)); +} + +void LLRNGWriter::writeRNG(const std::string& type_name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace) +{ + mGrammarNode = node; + mGrammarNode->setName("grammar"); + mGrammarNode->createChild("xmlns", true)->setStringValue("http://relaxng.org/ns/structure/1.0"); + mGrammarNode->createChild("datatypeLibrary", true)->setStringValue("http://www.w3.org/2001/XMLSchema-datatypes"); + mGrammarNode->createChild("ns", true)->setStringValue(xml_namespace); + + node = mGrammarNode->createChild("start", false); + node = node->createChild("ref", false); + node->createChild("name", true)->setStringValue(type_name); + + addDefinition(type_name, block); +} + +void LLRNGWriter::addDefinition(const std::string& type_name, const LLInitParam::BaseBlock& block) +{ + if (mDefinedElements.find(type_name) != mDefinedElements.end()) return; + mDefinedElements.insert(type_name); + + LLXMLNodePtr node = mGrammarNode->createChild("define", false); + node->createChild("name", true)->setStringValue(type_name); + + mElementNode = node->createChild("element", false); + mElementNode->createChild("name", true)->setStringValue(type_name); + mChildrenNode = mElementNode->createChild("zeroOrMore", false)->createChild("choice", false); + + mAttributesWritten.first = mElementNode; + mAttributesWritten.second.clear(); + mElementsWritten.clear(); + + block.inspectBlock(*this); + + // 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 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 child_name = it->first; + if (child_name == type_name) + { + continue; + } + + LLXMLNodePtr old_element_node = mElementNode; + LLXMLNodePtr old_child_node = mChildrenNode; + //FIXME: add LLDefaultParamBlockRegistry back when working on schema generation + //addDefinition(child_name, (*LLDefaultParamBlockRegistry::instance().getValue(type))()); + mElementNode = old_element_node; + mChildrenNode = old_child_node; + + mChildrenNode->createChild("ref", false)->createChild("name", true)->setStringValue(child_name); + } + + if (mChildrenNode->mChildren.isNull()) + { + // remove unused children node + mChildrenNode->mParent->mParent->deleteChild(mChildrenNode->mParent); + } +} + +void LLRNGWriter::writeAttribute(const std::string& type, const Parser::name_stack_t& stack, S32 min_count, S32 max_count, const std::vector<std::string>* possible_values) +{ + if (max_count == 0) return; + + 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); + } + } + + if (non_empty_names.empty()) return; + + 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; + } + + // singular attribute, e.g. <foo bar="1"/> + if (non_empty_names.size() == 1 && max_count == 1) + { + if (mAttributesWritten.second.find(attribute_name) == mAttributesWritten.second.end()) + { + LLXMLNodePtr node = createCardinalityNode(mElementNode, min_count, max_count)->createChild("attribute", false); + node->createChild("name", true)->setStringValue(attribute_name); + node->createChild("data", false)->createChild("type", true)->setStringValue(type); + + mAttributesWritten.second.insert(attribute_name); + } + } + // compound attribute + else + { + 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; + } + + elements_map_t::iterator found_it = mElementsWritten.find(element_name); + // <choice> + // <group> + // <optional> + // <attribute name="foo.bar"><data type="string"/></attribute> + // </optional> + // <optional> + // <attribute name="foo.baz"><data type="integer"/></attribute> + // </optional> + // </group> + // <element name="foo"> + // <optional> + // <attribute name="bar"><data type="string"/></attribute> + // </optional> + // <optional> + // <attribute name="baz"><data type="string"/></attribute> + // </optional> + // </element> + // <element name="outer.foo"> + // <ref name="foo"/> + // </element> + // </choice> + + if (found_it != mElementsWritten.end()) + { + // reuse existing element + LLXMLNodePtr choice_node = found_it->second.first; + + // attribute with this name not already written? + if (found_it->second.second.find(attribute_name) == found_it->second.second.end()) + { + // append to <group> + LLXMLNodePtr node = choice_node->mChildren->head; + node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false); + node->createChild("name", true)->setStringValue(attribute_name); + addTypeNode(node, type, possible_values); + + // append to <element> + node = choice_node->mChildren->head->mNext->mChildren->head; + node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false); + node->createChild("name", true)->setStringValue(non_empty_names.back().first); + addTypeNode(node, type, possible_values); + + // append to <element> + //node = choice_node->mChildren->head->mNext->mNext->mChildren->head; + //node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false); + //node->createChild("name", true)->setStringValue(non_empty_names.back().first); + //addTypeNode(node, type, possible_values); + + found_it->second.second.insert(attribute_name); + } + } + else + { + LLXMLNodePtr choice_node = mElementNode->createChild("choice", false); + + LLXMLNodePtr node = choice_node->createChild("group", false); + node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false); + node->createChild("name", true)->setStringValue(attribute_name); + addTypeNode(node, type, possible_values); + + node = choice_node->createChild("optional", false); + node = node->createChild("element", false); + node->createChild("name", true)->setStringValue(element_name); + node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false); + node->createChild("name", true)->setStringValue(non_empty_names.back().first); + addTypeNode(node, type, possible_values); + + //node = choice_node->createChild("optional", false); + //node = node->createChild("element", false); + //node->createChild("name", true)->setStringValue(mDefinitionName + "." + element_name); + //node = createCardinalityNode(node, min_count, max_count)->createChild("attribute", false); + //node->createChild("name", true)->setStringValue(non_empty_names.back().first); + //addTypeNode(node, type, possible_values); + + attribute_data_t& attribute_data = mElementsWritten[element_name]; + attribute_data.first = choice_node; + attribute_data.second.insert(attribute_name); + } + } +} + +void LLRNGWriter::addTypeNode(LLXMLNodePtr parent_node, const std::string& type, const std::vector<std::string>* possible_values) +{ + if (possible_values) + { + LLXMLNodePtr enum_node = parent_node->createChild("choice", false); + for (std::vector<std::string>::const_iterator it = possible_values->begin(); + it != possible_values->end(); + ++it) + { + enum_node->createChild("value", false)->setStringValue(*it); + } + } + else + { + parent_node->createChild("data", false)->createChild("type", true)->setStringValue(type); + } +} + +LLXMLNodePtr LLRNGWriter::createCardinalityNode(LLXMLNodePtr parent_node, S32 min_count, S32 max_count) +{ + // unlinked by default, meaning this attribute is forbidden + LLXMLNodePtr count_node = new LLXMLNode(); + if (min_count == 0) + { + if (max_count == 1) + { + count_node = parent_node->createChild("optional", false); + } + else if (max_count > 1) + { + count_node = parent_node->createChild("zeroOrMore", false); + } + } + else if (min_count >= 1) + { + if (max_count == 1 && min_count == 1) + { + // just add raw element, will count as 1 and only 1 + count_node = parent_node; + } + else + { + count_node = parent_node->createChild("oneOrMore", false); + } + } + return count_node; +} diff --git a/indra/llui/llrngwriter.h b/indra/llui/llrngwriter.h new file mode 100644 index 0000000000..66807577b5 --- /dev/null +++ b/indra/llui/llrngwriter.h @@ -0,0 +1,69 @@ +/** + * @file llrngwriter.h + * @brief Generates Relax NG schema files from a param block + * + * $LicenseInfo:firstyear=2003&license=viewergpl$ + * + * Copyright (c) 2003-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LLRNGWRITER_H +#define LLRNGWRITER_H + +#include "llinitparam.h" +#include "llxmlnode.h" + +class LLRNGWriter : public LLInitParam::Parser +{ + LOG_CLASS(LLRNGWriter); +public: + void writeRNG(const std::string& name, LLXMLNodePtr node, const LLInitParam::BaseBlock& block, const std::string& xml_namespace); + void addDefinition(const std::string& type_name, const LLInitParam::BaseBlock& block); + + /*virtual*/ std::string getCurrentElementName() { return LLStringUtil::null; } + + LLRNGWriter(); + +private: + LLXMLNodePtr createCardinalityNode(LLXMLNodePtr parent_node, S32 min_count, S32 max_count); + void addTypeNode(LLXMLNodePtr parent_node, const std::string& type, const std::vector<std::string>* possible_values); + + void writeAttribute(const std::string& type, const Parser::name_stack_t&, S32 min_count, S32 max_count, const std::vector<std::string>* possible_values); + LLXMLNodePtr mElementNode; + LLXMLNodePtr mChildrenNode; + LLXMLNodePtr mGrammarNode; + std::string mDefinitionName; + + typedef std::pair<LLXMLNodePtr, std::set<std::string> > attribute_data_t; + typedef std::map<std::string, attribute_data_t> elements_map_t; + typedef std::set<std::string> defined_elements_t; + + defined_elements_t mDefinedElements; + attribute_data_t mAttributesWritten; + elements_map_t mElementsWritten; +}; + +#endif //LLRNGWRITER_H diff --git a/indra/llui/llscrollbar.cpp b/indra/llui/llscrollbar.cpp index 65086d833d..dfd315d451 100644 --- a/indra/llui/llscrollbar.cpp +++ b/indra/llui/llscrollbar.cpp @@ -46,101 +46,92 @@ #include "llwindow.h" #include "llcontrol.h" #include "llrender.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register<LLScrollbar> 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) +{ + tab_stop = false; +} -LLScrollbar::LLScrollbar( - const std::string& name, LLRect rect, - LLScrollbar::ORIENTATION orientation, - S32 doc_size, S32 doc_pos, S32 page_size, - void (*change_callback)( S32 new_pos, LLScrollbar* self, void* userdata ), - void* callback_user_data, - S32 step_size) -: LLUICtrl( name, rect, TRUE, NULL, NULL ), - - mChangeCallback( change_callback ), - mCallbackUserData( callback_user_data ), - mOrientation( orientation ), - mDocSize( doc_size ), - mDocPos( doc_pos ), - mPageSize( page_size ), - mStepSize( step_size ), +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( LLUI::sColorsGroup->getColor("ScrollbarTrackColor") ), - mThumbColor ( LLUI::sColorsGroup->getColor("ScrollbarThumbColor") ), - mHighlightColor ( LLUI::sColorsGroup->getColor("DefaultHighlightLight") ), - mShadowColor ( LLUI::sColorsGroup->getColor("DefaultShadowLight") ), - mOnScrollEndCallback( NULL ), - mOnScrollEndData( NULL ) + 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::sSettingGroups["config"]->getS32("UIScrollbarSize")), + mBGVisible(p.bg_visible), + mBGColor(p.bg_color) { - //llassert( 0 <= mDocSize ); - //llassert( 0 <= mDocPos && mDocPos <= mDocSize ); - - setTabStop(FALSE); updateThumbRect(); // Page up and page down buttons LLRect line_up_rect; - std::string line_up_img; - std::string line_up_selected_img; - std::string line_down_img; - std::string line_down_selected_img; - LLRect line_down_rect; - if( LLScrollbar::VERTICAL == mOrientation ) + if( VERTICAL == mOrientation ) { - line_up_rect.setLeftTopAndSize( 0, getRect().getHeight(), SCROLLBAR_SIZE, SCROLLBAR_SIZE ); - line_up_img="UIImgBtnScrollUpOutUUID"; - line_up_selected_img="UIImgBtnScrollUpInUUID"; - - line_down_rect.setOriginAndSize( 0, 0, SCROLLBAR_SIZE, SCROLLBAR_SIZE ); - line_down_img="UIImgBtnScrollDownOutUUID"; - line_down_selected_img="UIImgBtnScrollDownInUUID"; + line_up_rect.setLeftTopAndSize( 0, getRect().getHeight(), mThickness, mThickness ); + line_down_rect.setOriginAndSize( 0, 0, mThickness, mThickness ); } - else + else // HORIZONTAL { - // Horizontal - line_up_rect.setOriginAndSize( 0, 0, SCROLLBAR_SIZE, SCROLLBAR_SIZE ); - line_up_img="UIImgBtnScrollLeftOutUUID"; - line_up_selected_img="UIImgBtnScrollLeftInUUID"; - - line_down_rect.setOriginAndSize( getRect().getWidth() - SCROLLBAR_SIZE, 0, SCROLLBAR_SIZE, SCROLLBAR_SIZE ); - line_down_img="UIImgBtnScrollRightOutUUID"; - line_down_selected_img="UIImgBtnScrollRightInUUID"; + line_up_rect.setOriginAndSize( 0, 0, mThickness, mThickness ); + line_down_rect.setOriginAndSize( getRect().getWidth() - mThickness, 0, mThickness, mThickness ); } - LLButton* line_up_btn = new LLButton(std::string("Line Up"), line_up_rect, - line_up_img, line_up_selected_img, LLStringUtil::null, - &LLScrollbar::onLineUpBtnPressed, this, LLFontGL::getFontSansSerif() ); - if( LLScrollbar::VERTICAL == mOrientation ) - { - line_up_btn->setFollowsRight(); - line_up_btn->setFollowsTop(); - } - else - { - // horizontal - line_up_btn->setFollowsLeft(); - line_up_btn->setFollowsBottom(); - } - line_up_btn->setHeldDownCallback( &LLScrollbar::onLineUpBtnPressed ); - line_up_btn->setTabStop(FALSE); - line_up_btn->setScaleImage(TRUE); - - addChild(line_up_btn); - - LLButton* line_down_btn = new LLButton(std::string("Line Down"), line_down_rect, - line_down_img, line_down_selected_img, LLStringUtil::null, - &LLScrollbar::onLineDownBtnPressed, this, LLFontGL::getFontSansSerif() ); - line_down_btn->setFollowsRight(); - line_down_btn->setFollowsBottom(); - line_down_btn->setHeldDownCallback( &LLScrollbar::onLineDownBtnPressed ); - line_down_btn->setTabStop(FALSE); - line_down_btn->setScaleImage(TRUE); - addChild(line_down_btn); + 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<LLButton>(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<LLButton>(down_btn)); } @@ -158,7 +149,8 @@ void LLScrollbar::setDocParams( S32 size, S32 pos ) updateThumbRect(); } -void LLScrollbar::setDocPos(S32 pos, BOOL update_thumb) +// returns true if document position really changed +bool LLScrollbar::setDocPos(S32 pos, BOOL update_thumb) { pos = llclamp(pos, 0, getDocPosMax()); if (pos != mDocPos) @@ -168,14 +160,16 @@ void LLScrollbar::setDocPos(S32 pos, BOOL update_thumb) if( mChangeCallback ) { - mChangeCallback( mDocPos, this, mCallbackUserData ); + mChangeCallback( mDocPos, this ); } if( update_thumb ) { updateThumbRect(); } + return true; } + return false; } void LLScrollbar::setDocSize(S32 size) @@ -221,7 +215,7 @@ void LLScrollbar::updateThumbRect() 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 * SCROLLBAR_SIZE); + 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; @@ -229,32 +223,27 @@ void LLScrollbar::updateThumbRect() if( mOrientation == LLScrollbar::VERTICAL ) { - S32 thumb_start_max = thumb_bg_length + SCROLLBAR_SIZE; - S32 thumb_start_min = SCROLLBAR_SIZE + THUMB_MIN_LENGTH; + 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 = SCROLLBAR_SIZE; + mThumbRect.mRight = mThickness; mThumbRect.mBottom = thumb_start - thumb_length; } else { // Horizontal - S32 thumb_start_max = thumb_bg_length + SCROLLBAR_SIZE - thumb_length; - S32 thumb_start_min = SCROLLBAR_SIZE; + 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 = SCROLLBAR_SIZE; + mThumbRect.mTop = mThickness; mThumbRect.mRight = thumb_start + thumb_length; mThumbRect.mBottom = 0; } - - if (mOnScrollEndCallback && mOnScrollEndData && (mDocPos == getDocPosMax())) - { - mOnScrollEndCallback(mOnScrollEndData); - } } BOOL LLScrollbar::handleMouseDown(S32 x, S32 y, MASK mask) @@ -318,21 +307,21 @@ BOOL LLScrollbar::handleHover(S32 x, S32 y, MASK mask) // S32 old_pos = mThumbRect.mTop; S32 delta_pixels = y - mDragStartY; - if( mOrigRect.mBottom + delta_pixels < SCROLLBAR_SIZE ) + if( mOrigRect.mBottom + delta_pixels < mThickness ) { - delta_pixels = SCROLLBAR_SIZE - mOrigRect.mBottom - 1; + delta_pixels = mThickness - mOrigRect.mBottom - 1; } else - if( mOrigRect.mTop + delta_pixels > height - SCROLLBAR_SIZE ) + if( mOrigRect.mTop + delta_pixels > height - mThickness ) { - delta_pixels = height - SCROLLBAR_SIZE - mOrigRect.mTop + 1; + 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 * SCROLLBAR_SIZE; + S32 thumb_track_length = height - 2 * mThickness; if( delta_pixels != mLastDelta || mDocChanged) @@ -343,7 +332,7 @@ BOOL LLScrollbar::handleHover(S32 x, S32 y, MASK mask) { S32 variable_lines = getDocPosMax(); S32 pos = mThumbRect.mTop; - F32 ratio = F32(pos - SCROLLBAR_SIZE - thumb_length) / usable_track_length; + 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 @@ -362,21 +351,21 @@ BOOL LLScrollbar::handleHover(S32 x, S32 y, MASK mask) S32 delta_pixels = x - mDragStartX; - if( mOrigRect.mLeft + delta_pixels < SCROLLBAR_SIZE ) + if( mOrigRect.mLeft + delta_pixels < mThickness ) { - delta_pixels = SCROLLBAR_SIZE - mOrigRect.mLeft - 1; + delta_pixels = mThickness - mOrigRect.mLeft - 1; } else - if( mOrigRect.mRight + delta_pixels > width - SCROLLBAR_SIZE ) + if( mOrigRect.mRight + delta_pixels > width - mThickness ) { - delta_pixels = width - SCROLLBAR_SIZE - mOrigRect.mRight + 1; + 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 * SCROLLBAR_SIZE; + S32 thumb_track_length = width - 2 * mThickness; if( delta_pixels != mLastDelta || mDocChanged) { @@ -386,7 +375,7 @@ BOOL LLScrollbar::handleHover(S32 x, S32 y, MASK mask) { S32 variable_lines = getDocPosMax(); S32 pos = mThumbRect.mLeft; - F32 ratio = F32(pos - SCROLLBAR_SIZE) / usable_track_length; + F32 ratio = F32(pos - mThickness) / usable_track_length; S32 new_pos = llclamp( S32(ratio * variable_lines + 0.5f), 0, variable_lines); @@ -405,7 +394,7 @@ BOOL LLScrollbar::handleHover(S32 x, S32 y, MASK mask) } else { - handled = childrenHandleMouseUp( x, y, mask ) != NULL; + handled = childrenHandleHover( x, y, mask ) != NULL; } // Opaque @@ -423,8 +412,8 @@ BOOL LLScrollbar::handleHover(S32 x, S32 y, MASK mask) BOOL LLScrollbar::handleScrollWheel(S32 x, S32 y, S32 clicks) { - changeLine( clicks * mStepSize, TRUE ); - return TRUE; + BOOL handled = changeLine( clicks * mStepSize, TRUE ); + return handled; } BOOL LLScrollbar::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, @@ -466,6 +455,13 @@ BOOL LLScrollbar::handleMouseUp(S32 x, S32 y, MASK 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; @@ -475,14 +471,14 @@ void LLScrollbar::reshape(S32 width, S32 height, BOOL called_from_parent) if (mOrientation == VERTICAL) { - up_button->reshape(up_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, SCROLLBAR_SIZE)); - down_button->reshape(down_button->getRect().getWidth(), llmin(getRect().getHeight() / 2, SCROLLBAR_SIZE)); + 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(up_button->getRect().mLeft, getRect().getHeight() - up_button->getRect().getHeight()); } else { - up_button->reshape(llmin(getRect().getWidth() / 2, SCROLLBAR_SIZE), up_button->getRect().getHeight()); - down_button->reshape(llmin(getRect().getWidth() / 2, SCROLLBAR_SIZE), down_button->getRect().getHeight()); + up_button->reshape(llmin(getRect().getWidth() / 2, mThickness), up_button->getRect().getHeight()); + down_button->reshape(llmin(getRect().getWidth() / 2, mThickness), down_button->getRect().getHeight()); down_button->setOrigin(getRect().getWidth() - down_button->getRect().getWidth(), down_button->getRect().mBottom); } updateThumbRect(); @@ -493,9 +489,14 @@ 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::getCursorPositionLocal(this, &local_mouse_x, &local_mouse_y); + LLUI::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) @@ -507,52 +508,67 @@ void LLScrollbar::draw() mCurGlowStrength = lerp(mCurGlowStrength, 0.f, LLCriticalDamp::getInterpolant(0.05f)); } - // Draw background and thumb. - LLUIImage* rounded_rect_imagep = LLUI::sImageProvider->getUIImage("rounded_square.tga"); - - if (!rounded_rect_imagep) + if ( ( mOrientation == VERTICAL&&(mThumbImageV.isNull() || mThumbImageH.isNull()) ) + || (mOrientation == HORIZONTAL&&(mTrackImageH.isNull() || mTrackImageV.isNull()) )) { - gl_rect_2d(mOrientation == HORIZONTAL ? SCROLLBAR_SIZE : 0, - mOrientation == VERTICAL ? getRect().getHeight() - 2 * SCROLLBAR_SIZE : getRect().getHeight(), - mOrientation == HORIZONTAL ? getRect().getWidth() - 2 * SCROLLBAR_SIZE : getRect().getWidth(), - mOrientation == VERTICAL ? SCROLLBAR_SIZE : 0, mTrackColor, TRUE); + 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, TRUE); + gl_rect_2d(mThumbRect, mThumbColor.get(), TRUE); } else { - // Background - rounded_rect_imagep->drawSolid(mOrientation == HORIZONTAL ? SCROLLBAR_SIZE : 0, - mOrientation == VERTICAL ? SCROLLBAR_SIZE : 0, - mOrientation == HORIZONTAL ? getRect().getWidth() - 2 * SCROLLBAR_SIZE : getRect().getWidth(), - mOrientation == VERTICAL ? getRect().getHeight() - 2 * SCROLLBAR_SIZE : getRect().getHeight(), - mTrackColor); - // Thumb LLRect outline_rect = mThumbRect; outline_rect.stretch(2); - - if (gFocusMgr.getKeyboardFocus() == this) + // Background + + if(mOrientation == HORIZONTAL) { - rounded_rect_imagep->draw(outline_rect, gFocusMgr.getFocusColor()); + 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); + } + } - - rounded_rect_imagep->draw(mThumbRect, mThumbColor); - if (mCurGlowStrength > 0.01f) + else if(mOrientation == VERTICAL) { - gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); - rounded_rect_imagep->drawSolid(mThumbRect, LLColor4(1.f, 1.f, 1.f, mCurGlowStrength)); - gGL.setSceneBlendType(LLRender::BT_ALPHA); + 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); + } } - - } - - BOOL was_scrolled_to_bottom = (getDocPos() == getDocPosMax()); - if (mOnScrollEndCallback && was_scrolled_to_bottom) - { - mOnScrollEndCallback(mOnScrollEndData); } // Draw children @@ -560,9 +576,9 @@ void LLScrollbar::draw() } // end draw -void LLScrollbar::changeLine( S32 delta, BOOL update_thumb ) +bool LLScrollbar::changeLine( S32 delta, BOOL update_thumb ) { - setDocPos(mDocPos + delta, update_thumb); + return setDocPos(mDocPos + delta, update_thumb); } void LLScrollbar::setValue(const LLSD& value) @@ -625,19 +641,12 @@ void LLScrollbar::pageDown(S32 overlap) } } -// static -void LLScrollbar::onLineUpBtnPressed( void* userdata ) +void LLScrollbar::onLineUpBtnPressed( const LLSD& data ) { - LLScrollbar* self = (LLScrollbar*) userdata; - - self->changeLine( - self->mStepSize, TRUE ); + changeLine( -mStepSize, TRUE ); } -// static -void LLScrollbar::onLineDownBtnPressed( void* userdata ) +void LLScrollbar::onLineDownBtnPressed( const LLSD& data ) { - LLScrollbar* self = (LLScrollbar*) userdata; - self->changeLine( self->mStepSize, TRUE ); + changeLine( mStepSize, TRUE ); } - - diff --git a/indra/llui/llscrollbar.h b/indra/llui/llscrollbar.h index 0bbf8662aa..a9f028f9ae 100644 --- a/indra/llui/llscrollbar.h +++ b/indra/llui/llscrollbar.h @@ -36,12 +36,7 @@ #include "stdtypes.h" #include "lluictrl.h" #include "v4color.h" - -// -// Constants -// -const S32 SCROLLBAR_SIZE = 16; - +#include "llbutton.h" // // Classes @@ -50,15 +45,46 @@ class LLScrollbar : public LLUICtrl { public: - enum ORIENTATION { HORIZONTAL, VERTICAL }; - LLScrollbar(const std::string& name, LLRect rect, - ORIENTATION orientation, - S32 doc_size, S32 doc_pos, S32 page_size, - void(*change_callback)( S32 new_pos, LLScrollbar* self, void* userdata ), - void* callback_user_data = NULL, - S32 step_size = 1); + enum ORIENTATION { HORIZONTAL, VERTICAL }; + + typedef boost::function<void (S32, LLScrollbar*)> callback_t; + struct Params + : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Mandatory<ORIENTATION> orientation; + Mandatory<S32> doc_size; + Mandatory<S32> doc_pos; + Mandatory<S32> page_size; + + Optional<callback_t> change_callback; + Optional<S32> step_size; + Optional<S32> thickness; + + Optional<LLUIImage*> thumb_image_vertical, + thumb_image_horizontal, + track_image_horizontal, + track_image_vertical; + + Optional<bool> bg_visible; + + Optional<LLUIColor> track_color, + thumb_color, + bg_color; + + Optional<LLButton::Params> up_button; + Optional<LLButton::Params> down_button; + Optional<LLButton::Params> left_button; + Optional<LLButton::Params> right_button; + + Params(); + }; + +protected: + LLScrollbar (const Params & p); + friend class LLUICtrlFactory; +public: virtual ~LLScrollbar(); virtual void setValue(const LLSD& value); @@ -67,6 +93,7 @@ public: 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 handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, @@ -82,7 +109,7 @@ public: // How many "lines" the "document" has scrolled. // 0 <= DocPos <= DocSize - DocVisibile - void setDocPos( S32 pos, BOOL update_thumb = TRUE ); + bool setDocPos( S32 pos, BOOL update_thumb = TRUE ); S32 getDocPos() const { return mDocPos; } BOOL isAtBeginning(); @@ -101,22 +128,14 @@ public: void pageUp(S32 overlap); void pageDown(S32 overlap); - static void onLineUpBtnPressed(void* userdata); - static void onLineDownBtnPressed(void* userdata); - - void setTrackColor( const LLColor4& color ) { mTrackColor = color; } - void setThumbColor( const LLColor4& color ) { mThumbColor = color; } - void setHighlightColor( const LLColor4& color ) { mHighlightColor = color; } - void setShadowColor( const LLColor4& color ) { mShadowColor = color; } - - void setOnScrollEndCallback(void (*callback)(void*), void* userdata) { mOnScrollEndCallback = callback; mOnScrollEndData = userdata;} + void onLineUpBtnPressed(const LLSD& data); + void onLineDownBtnPressed(const LLSD& data); private: void updateThumbRect(); - void changeLine(S32 delta, BOOL update_thumb ); + bool changeLine(S32 delta, BOOL update_thumb ); - void (*mChangeCallback)( S32 new_pos, LLScrollbar* self, void* userdata ); - void* mCallbackUserData; + callback_t mChangeCallback; const ORIENTATION mOrientation; S32 mDocSize; // Size of the document that the scrollbar is modeling. Units depend on the user. 0 <= mDocSize. @@ -134,16 +153,19 @@ private: LLRect mOrigRect; S32 mLastDelta; - LLColor4 mTrackColor; - LLColor4 mThumbColor; - LLColor4 mFocusColor; - LLColor4 mHighlightColor; - LLColor4 mShadowColor; + LLUIColor mTrackColor; + LLUIColor mThumbColor; + LLUIColor mBGColor; - void (*mOnScrollEndCallback)(void*); - void *mOnScrollEndData; -}; + 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 cf03259879..94465a67ce 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -1,6 +1,6 @@ /** * @file llscrollcontainer.cpp - * @brief LLScrollableContainerView base class + * @brief LLScrollContainer base class * * $LicenseInfo:firstyear=2001&license=viewergpl$ * @@ -33,8 +33,13 @@ #include "linden_common.h" -#include "llrender.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" @@ -42,6 +47,7 @@ #include "llfocusmgr.h" #include "llframetimer.h" #include "lluictrlfactory.h" +#include "llpanel.h" #include "llfontgl.h" ///---------------------------------------------------------------------------- @@ -50,103 +56,97 @@ static const S32 HORIZONTAL_MULTIPLE = 8; static const S32 VERTICAL_MULTIPLE = 16; -static const F32 MIN_AUTO_SCROLL_RATE = 120.f; -static const F32 MAX_AUTO_SCROLL_RATE = 500.f; static const F32 AUTO_SCROLL_RATE_ACCEL = 120.f; ///---------------------------------------------------------------------------- -/// Class LLScrollableContainerView +/// Class LLScrollContainer ///---------------------------------------------------------------------------- -static LLRegisterWidget<LLScrollableContainerView> r("scroll_container"); +static LLDefaultChildRegistry::Register<LLScrollContainer> r("scroll_container"); -// Default constructor -LLScrollableContainerView::LLScrollableContainerView( const std::string& name, - const LLRect& rect, - LLView* scrolled_view, - BOOL is_opaque, - const LLColor4& bg_color ) : - LLUICtrl( name, rect, FALSE, NULL, NULL ), - mScrolledView( scrolled_view ), - mIsOpaque( is_opaque ), - mBackgroundColor( bg_color ), - mReserveScrollCorner( FALSE ), - mAutoScrolling( FALSE ), - mAutoScrollRate( 0.f ) -{ - if( mScrolledView ) - { - addChild( mScrolledView ); - } +#include "llscrollingpanellist.h" +#include "llcontainerview.h" +#include "llpanel.h" - init(); -} +static ScrollContainerRegistry::Register<LLScrollingPanelList> r1("scrolling_panel_list"); +static ScrollContainerRegistry::Register<LLContainerView> r2("container_view"); +static ScrollContainerRegistry::Register<LLPanel> r3("panel", &LLPanel::fromXML); -// LLUICtrl constructor -LLScrollableContainerView::LLScrollableContainerView( const std::string& name, const LLRect& rect, - LLUICtrl* scrolled_ctrl, BOOL is_opaque, - const LLColor4& bg_color) : - LLUICtrl( name, rect, FALSE, NULL, NULL ), - mScrolledView( scrolled_ctrl ), - mIsOpaque( is_opaque ), - mBackgroundColor( bg_color ), - mReserveScrollCorner( FALSE ), - mAutoScrolling( FALSE ), - mAutoScrollRate( 0.f ) +LLScrollContainer::Params::Params() +: is_opaque("opaque"), + bg_color("color"), + border_visible("border_visible"), + hide_scrollbar("hide_scrollbar"), + min_auto_scroll_rate("min_auto_scroll_rate", 100), + max_auto_scroll_rate("max_auto_scroll_rate", 1000), + reserve_scroll_corner("reserve_scroll_corner", false) { - if( scrolled_ctrl ) - { - addChild( scrolled_ctrl ); - } - - init(); + name = "scroll_container"; + mouse_opaque(true); + tab_stop(false); } -void LLScrollableContainerView::init() + +// 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), + mReserveScrollCorner(p.reserve_scroll_corner), + mMinAutoScrollRate(p.min_auto_scroll_rate), + mMaxAutoScrollRate(p.max_auto_scroll_rate), + mScrolledView(NULL) { + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); LLRect border_rect( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - mBorder = new LLViewBorder( std::string("scroll border"), border_rect, LLViewBorder::BEVEL_IN ); - addChild( mBorder ); + 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<LLViewBorder> (params); + LLView::addChild( mBorder ); mInnerRect.set( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - mInnerRect.stretch( -mBorder->getBorderWidth() ); + mInnerRect.stretch( -getBorderWidth() ); LLRect vertical_scroll_rect = mInnerRect; - vertical_scroll_rect.mLeft = vertical_scroll_rect.mRight - SCROLLBAR_SIZE; - mScrollbar[VERTICAL] = new LLScrollbar( std::string("scrollable vertical"), - vertical_scroll_rect, - LLScrollbar::VERTICAL, - mInnerRect.getHeight(), - 0, - mInnerRect.getHeight(), - NULL, this, - VERTICAL_MULTIPLE); - addChild( mScrollbar[VERTICAL] ); - mScrollbar[VERTICAL]->setVisible( FALSE ); - mScrollbar[VERTICAL]->setFollowsRight(); - mScrollbar[VERTICAL]->setFollowsTop(); - mScrollbar[VERTICAL]->setFollowsBottom(); + 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<LLScrollbar> (sbparams); + LLView::addChild( mScrollbar[VERTICAL] ); LLRect horizontal_scroll_rect = mInnerRect; - horizontal_scroll_rect.mTop = horizontal_scroll_rect.mBottom + SCROLLBAR_SIZE; - mScrollbar[HORIZONTAL] = new LLScrollbar( std::string("scrollable horizontal"), - horizontal_scroll_rect, - LLScrollbar::HORIZONTAL, - mInnerRect.getWidth(), - 0, - mInnerRect.getWidth(), - NULL, this, - HORIZONTAL_MULTIPLE); - addChild( mScrollbar[HORIZONTAL] ); - mScrollbar[HORIZONTAL]->setVisible( FALSE ); - mScrollbar[HORIZONTAL]->setFollowsLeft(); - mScrollbar[HORIZONTAL]->setFollowsRight(); - - setTabStop(FALSE); + horizontal_scroll_rect.mTop = horizontal_scroll_rect.mBottom + scrollbar_size; + 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); + sbparams.change_callback(p.scroll_callback); + mScrollbar[HORIZONTAL] = LLUICtrlFactory::create<LLScrollbar> (sbparams); + LLView::addChild( mScrollbar[HORIZONTAL] ); } // Destroys the object -LLScrollableContainerView::~LLScrollableContainerView( void ) +LLScrollContainer::~LLScrollContainer( void ) { // mScrolledView and mScrollbar are child views, so the LLView // destructor takes care of memory deallocation. @@ -159,9 +159,9 @@ LLScrollableContainerView::~LLScrollableContainerView( void ) // internal scrollbar handlers // virtual -void LLScrollableContainerView::scrollHorizontal( S32 new_pos ) +void LLScrollContainer::scrollHorizontal( S32 new_pos ) { - //llinfos << "LLScrollableContainerView::scrollHorizontal()" << llendl; + //llinfos << "LLScrollContainer::scrollHorizontal()" << llendl; if( mScrolledView ) { LLRect doc_rect = mScrolledView->getRect(); @@ -171,9 +171,9 @@ void LLScrollableContainerView::scrollHorizontal( S32 new_pos ) } // virtual -void LLScrollableContainerView::scrollVertical( S32 new_pos ) +void LLScrollContainer::scrollVertical( S32 new_pos ) { - // llinfos << "LLScrollableContainerView::scrollVertical() " << new_pos << llendl; + // llinfos << "LLScrollContainer::scrollVertical() " << new_pos << llendl; if( mScrolledView ) { LLRect doc_rect = mScrolledView->getRect(); @@ -183,13 +183,13 @@ void LLScrollableContainerView::scrollVertical( S32 new_pos ) } // LLView functionality -void LLScrollableContainerView::reshape(S32 width, S32 height, +void LLScrollContainer::reshape(S32 width, S32 height, BOOL called_from_parent) { LLUICtrl::reshape( width, height, called_from_parent ); - mInnerRect.set( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - mInnerRect.stretch( -mBorder->getBorderWidth() ); + mInnerRect = getLocalRect(); + mInnerRect.stretch( -getBorderWidth() ); if (mScrolledView) { @@ -199,22 +199,33 @@ void LLScrollableContainerView::reshape(S32 width, S32 height, S32 visible_height = 0; BOOL show_v_scrollbar = FALSE; BOOL show_h_scrollbar = FALSE; - calcVisibleSize( scrolled_rect, &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + 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(); } } -BOOL LLScrollableContainerView::handleKeyHere(KEY key, MASK mask) +BOOL LLScrollContainer::handleKeyHere(KEY key, MASK mask) { + // 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 < SCROLLBAR_COUNT; i++ ) { if( mScrollbar[i]->handleKeyHere(key, mask) ) { + updateScroll(); return TRUE; } } @@ -222,203 +233,194 @@ BOOL LLScrollableContainerView::handleKeyHere(KEY key, MASK mask) return FALSE; } -BOOL LLScrollableContainerView::handleScrollWheel( S32 x, S32 y, S32 clicks ) +BOOL LLScrollContainer::handleScrollWheel( S32 x, S32 y, S32 clicks ) { - for( S32 i = 0; i < SCROLLBAR_COUNT; i++ ) + // 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()) { - // Note: tries vertical and then horizontal - // Pretend the mouse is over the scrollbar - if( mScrollbar[i]->handleScrollWheel( 0, 0, clicks ) ) + if (vertical->handleScrollWheel( 0, 0, clicks ) ) { - return TRUE; + updateScroll(); } + // Always eat the event + return TRUE; } - // Eat scroll wheel event (to avoid scrolling nested containers?) - return TRUE; -} - -BOOL LLScrollableContainerView::needsToScroll(S32 x, S32 y, LLScrollableContainerView::SCROLL_ORIENTATION axis) const -{ - if(mScrollbar[axis]->getVisible()) + LLScrollbar* horizontal = mScrollbar[HORIZONTAL]; + // Test enablement and visibility for consistency with + // LLView::childrenHandleScrollWheel(). + if (horizontal->getVisible() + && horizontal->getEnabled() + && horizontal->handleScrollWheel( 0, 0, clicks ) ) { - LLRect inner_rect_local( 0, mInnerRect.getHeight(), mInnerRect.getWidth(), 0 ); - const S32 AUTOSCROLL_SIZE = 10; - if(mScrollbar[axis]->getVisible()) - { - inner_rect_local.mRight -= SCROLLBAR_SIZE; - inner_rect_local.mTop += AUTOSCROLL_SIZE; - inner_rect_local.mBottom = inner_rect_local.mTop - AUTOSCROLL_SIZE; - } - if( inner_rect_local.pointInRect( x, y ) && (mScrollbar[axis]->getDocPos() > 0) ) - { - return TRUE; - } - + updateScroll(); + return TRUE; } return FALSE; } -BOOL LLScrollableContainerView::handleDragAndDrop(S32 x, S32 y, MASK mask, +BOOL LLScrollContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) { + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); // Scroll folder view if needed. Never accepts a drag or drop. *accept = ACCEPT_NO; - BOOL handled = FALSE; + 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::autoScroll(S32 x, S32 y) +{ + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + + bool scrolling = false; if( mScrollbar[HORIZONTAL]->getVisible() || mScrollbar[VERTICAL]->getVisible() ) { - const S32 AUTOSCROLL_SIZE = 10; - S32 auto_scroll_speed = llround(mAutoScrollRate * LLFrameTimer::getFrameDeltaTimeF32()); - + LLRect screen_local_extents; + screenRectToLocal(getRootView()->getLocalRect(), &screen_local_extents); + LLRect inner_rect_local( 0, mInnerRect.getHeight(), mInnerRect.getWidth(), 0 ); if( mScrollbar[HORIZONTAL]->getVisible() ) { - inner_rect_local.mBottom += SCROLLBAR_SIZE; + inner_rect_local.mBottom += scrollbar_size; } if( mScrollbar[VERTICAL]->getVisible() ) { - inner_rect_local.mRight -= SCROLLBAR_SIZE; + inner_rect_local.mRight -= scrollbar_size; } + // clip rect against root view + inner_rect_local.intersectWith(screen_local_extents); + + S32 auto_scroll_speed = llround(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, 10); + S32 auto_scroll_region_height = llmin(inner_rect_local.getHeight() / 3, 10); + if( mScrollbar[HORIZONTAL]->getVisible() ) { - LLRect left_scroll_rect = inner_rect_local; - left_scroll_rect.mRight = AUTOSCROLL_SIZE; + 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) ) { mScrollbar[HORIZONTAL]->setDocPos( mScrollbar[HORIZONTAL]->getDocPos() - auto_scroll_speed ); mAutoScrolling = TRUE; - handled = TRUE; + scrolling = true; } - LLRect right_scroll_rect = inner_rect_local; - right_scroll_rect.mLeft = inner_rect_local.mRight - AUTOSCROLL_SIZE; + 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()) ) { mScrollbar[HORIZONTAL]->setDocPos( mScrollbar[HORIZONTAL]->getDocPos() + auto_scroll_speed ); mAutoScrolling = TRUE; - handled = TRUE; + scrolling = true; } } if( mScrollbar[VERTICAL]->getVisible() ) { - LLRect bottom_scroll_rect = inner_rect_local; - bottom_scroll_rect.mTop = AUTOSCROLL_SIZE + bottom_scroll_rect.mBottom; + 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()) ) { mScrollbar[VERTICAL]->setDocPos( mScrollbar[VERTICAL]->getDocPos() + auto_scroll_speed ); mAutoScrolling = TRUE; - handled = TRUE; + scrolling = true; } - LLRect top_scroll_rect = inner_rect_local; - top_scroll_rect.mBottom = inner_rect_local.mTop - AUTOSCROLL_SIZE; + 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) ) { mScrollbar[VERTICAL]->setDocPos( mScrollbar[VERTICAL]->getDocPos() - auto_scroll_speed ); mAutoScrolling = TRUE; - handled = TRUE; + scrolling = true; } } } - - if( !handled ) - { - handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, - cargo_data, accept, tooltip_msg) != NULL; - } - - return TRUE; -} - - -BOOL LLScrollableContainerView::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect) -{ - S32 local_x, local_y; - for( S32 i = 0; i < SCROLLBAR_COUNT; i++ ) - { - local_x = x - mScrollbar[i]->getRect().mLeft; - local_y = y - mScrollbar[i]->getRect().mBottom; - if( mScrollbar[i]->handleToolTip(local_x, local_y, msg, sticky_rect) ) - { - return TRUE; - } - } - // Handle 'child' view. - if( mScrolledView ) - { - local_x = x - mScrolledView->getRect().mLeft; - local_y = y - mScrolledView->getRect().mBottom; - if( mScrolledView->handleToolTip(local_x, local_y, msg, sticky_rect) ) - { - return TRUE; - } - } - - // Opaque - return TRUE; -} - -void LLScrollableContainerView::calcVisibleSize( S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) const -{ - const LLRect& rect = mScrolledView->getRect(); - calcVisibleSize(rect, visible_width, visible_height, show_h_scrollbar, show_v_scrollbar); + return scrolling; } -void LLScrollableContainerView::calcVisibleSize( const LLRect& doc_rect, S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) const +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<S32> scrollbar_size ("UIScrollbarSize", 0); S32 doc_width = doc_rect.getWidth(); S32 doc_height = doc_rect.getHeight(); - *visible_width = getRect().getWidth() - 2 * mBorder->getBorderWidth(); - *visible_height = getRect().getHeight() - 2 * mBorder->getBorderWidth(); + S32 border_width = getBorderWidth(); + *visible_width = getRect().getWidth() - 2 * border_width; + *visible_height = getRect().getHeight() - 2 * border_width; *show_v_scrollbar = FALSE; - if( *visible_height < doc_height ) - { - *show_v_scrollbar = TRUE; - *visible_width -= SCROLLBAR_SIZE; - } - *show_h_scrollbar = FALSE; - if( *visible_width < doc_width ) - { - *show_h_scrollbar = TRUE; - *visible_height -= SCROLLBAR_SIZE; - // Must retest now that visible_height has changed - if( !*show_v_scrollbar && (*visible_height < doc_height) ) + if (!mHideScrollbar) + { + if( *visible_height < doc_height ) { *show_v_scrollbar = TRUE; - *visible_width -= SCROLLBAR_SIZE; + *visible_width -= scrollbar_size; + } + + if( *visible_width < doc_width ) + { + *show_h_scrollbar = TRUE; + *visible_height -= scrollbar_size; + + // Must retest now that visible_height has changed + if( !*show_v_scrollbar && (*visible_height < doc_height) ) + { + *show_v_scrollbar = TRUE; + *visible_width -= scrollbar_size; + } } } } + -void LLScrollableContainerView::draw() +void LLScrollContainer::draw() { + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); if (mAutoScrolling) { // add acceleration to autoscroll - mAutoScrollRate = llmin(mAutoScrollRate + (LLFrameTimer::getFrameDeltaTimeF32() * AUTO_SCROLL_RATE_ACCEL), MAX_AUTO_SCROLL_RATE); + mAutoScrollRate = llmin(mAutoScrollRate + (LLFrameTimer::getFrameDeltaTimeF32() * AUTO_SCROLL_RATE_ACCEL), mMaxAutoScrollRate); } else { - // reset to minimum - mAutoScrollRate = MIN_AUTO_SCROLL_RATE; + // reset to minimum for next time + mAutoScrollRate = mMinAutoScrollRate; } - // clear this flag to be set on next call to handleDragAndDrop + // 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 (!gFocusMgr.childHasKeyboardFocus(this) && - (mScrollbar[VERTICAL]->hasMouseCapture() || mScrollbar[HORIZONTAL]->hasMouseCapture())) + if (!hasFocus() + && (mScrollbar[VERTICAL]->hasMouseCapture() || mScrollbar[HORIZONTAL]->hasMouseCapture())) { focusFirstItem(); } @@ -427,7 +429,7 @@ void LLScrollableContainerView::draw() if( mIsOpaque ) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.color4fv( mBackgroundColor.mV ); + gGL.color4fv( mBackgroundColor.get().mV ); gl_rect_2d( mInnerRect ); } @@ -445,12 +447,12 @@ void LLScrollableContainerView::draw() S32 visible_height = 0; BOOL show_v_scrollbar = FALSE; BOOL show_h_scrollbar = FALSE; - calcVisibleSize( mScrolledView->getRect(), &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + 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, - visible_width, - mInnerRect.mBottom + (show_h_scrollbar ? SCROLLBAR_SIZE : 0) + 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); } @@ -481,16 +483,32 @@ void LLScrollableContainerView::draw() sDepth--; } } +} // end draw - if (sDebugRects) +bool LLScrollContainer::addChild(LLView* view, S32 tab_group) +{ + if (!mScrolledView) { - drawDebugRect(); + // Use the first panel or container as the scrollable view (bit of a hack) + mScrolledView = view; } -} // end draw + bool ret_val = LLView::addChild(view, tab_group); -void LLScrollableContainerView::updateScroll() + //bring the scrollbars to the front + sendChildToFront( mScrollbar[HORIZONTAL] ); + sendChildToFront( mScrollbar[VERTICAL] ); + + return ret_val; +} + +void LLScrollContainer::updateScroll() { + if (!mScrolledView) + { + return; + } + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); LLRect doc_rect = mScrolledView->getRect(); S32 doc_width = doc_rect.getWidth(); S32 doc_height = doc_rect.getHeight(); @@ -498,9 +516,9 @@ void LLScrollableContainerView::updateScroll() S32 visible_height = 0; BOOL show_v_scrollbar = FALSE; BOOL show_h_scrollbar = FALSE; - calcVisibleSize( doc_rect, &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); + calcVisibleSize( &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); - S32 border_width = mBorder->getBorderWidth(); + S32 border_width = getBorderWidth(); if( show_v_scrollbar ) { if( doc_rect.mTop < getRect().getHeight() - border_width ) @@ -514,15 +532,15 @@ void LLScrollableContainerView::updateScroll() S32 v_scrollbar_height = visible_height; if( !show_h_scrollbar && mReserveScrollCorner ) { - v_scrollbar_height -= SCROLLBAR_SIZE; + v_scrollbar_height -= scrollbar_size; } - mScrollbar[VERTICAL]->reshape( SCROLLBAR_SIZE, v_scrollbar_height, TRUE ); + 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; + v_scrollbar_offset = scrollbar_size; } LLRect r = mScrollbar[VERTICAL]->getRect(); r.translate( 0, mInnerRect.mBottom - r.mBottom + v_scrollbar_offset ); @@ -552,9 +570,9 @@ void LLScrollableContainerView::updateScroll() S32 h_scrollbar_width = visible_width; if( !show_v_scrollbar && mReserveScrollCorner ) { - h_scrollbar_width -= SCROLLBAR_SIZE; + h_scrollbar_width -= scrollbar_size; } - mScrollbar[HORIZONTAL]->reshape( h_scrollbar_width, SCROLLBAR_SIZE, TRUE ); + mScrollbar[HORIZONTAL]->reshape( h_scrollbar_width, scrollbar_size, TRUE ); } else { @@ -571,104 +589,119 @@ void LLScrollableContainerView::updateScroll() mScrollbar[VERTICAL]->setPageSize( visible_height ); } // end updateScroll -void LLScrollableContainerView::setBorderVisible(BOOL b) +void LLScrollContainer::setBorderVisible(BOOL b) { mBorder->setVisible( b ); + // Recompute inner rect, as border visibility changes it + mInnerRect = getLocalRect(); + mInnerRect.stretch( -getBorderWidth() ); } -// Scroll so that as much of rect as possible is showing (where rect is defined in the space of scroller view, not scrolled) -void LLScrollableContainerView::scrollToShowRect(const LLRect& rect, const LLCoordGL& desired_offset) +LLRect LLScrollContainer::getVisibleContentRect() { - if (!mScrolledView) - { - llwarns << "LLScrollableContainerView::scrollToShowRect with no view!" << llendl; - return; - } + 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_v_scrollbar = FALSE; BOOL show_h_scrollbar = FALSE; - const LLRect& scrolled_rect = mScrolledView->getRect(); - calcVisibleSize( scrolled_rect, &visible_width, &visible_height, &show_h_scrollbar, &show_v_scrollbar ); - - // can't be so far left that right side of rect goes off screen, or so far right that left side does - S32 horiz_offset = llclamp(desired_offset.mX, llmin(0, -visible_width + rect.getWidth()), 0); - // can't be so high that bottom of rect goes off screen, or so low that top does - S32 vert_offset = llclamp(desired_offset.mY, 0, llmax(0, visible_height - rect.getHeight())); - - // Vertical - // 1. First make sure the top is visible - // 2. Then, if possible without hiding the top, make the bottom visible. - S32 vert_pos = mScrollbar[VERTICAL]->getDocPos(); - - // find scrollbar position to get top of rect on screen (scrolling up) - S32 top_offset = scrolled_rect.mTop - rect.mTop - vert_offset; - // find scrollbar position to get bottom of rect on screen (scrolling down) - S32 bottom_offset = vert_offset == 0 ? scrolled_rect.mTop - rect.mBottom - visible_height : top_offset; - // scroll up far enough to see top or scroll down just enough if item is bigger than visual area - if( vert_pos >= top_offset || visible_height < rect.getHeight()) - { - vert_pos = top_offset; - } - // else scroll down far enough to see bottom - else - if( vert_pos <= bottom_offset ) + 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) { - vert_pos = bottom_offset; + llwarns << "LLScrollContainer::scrollToShowRect with no view!" << llendl; + 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( visible_height ); + mScrollbar[VERTICAL]->setPageSize( content_window_rect.getHeight() ); mScrollbar[VERTICAL]->setDocPos( vert_pos ); - // Horizontal - // 1. First make sure left side is visible - // 2. Then, if possible without hiding the left side, make the right side visible. - S32 horiz_pos = mScrollbar[HORIZONTAL]->getDocPos(); - S32 left_offset = rect.mLeft - scrolled_rect.mLeft + horiz_offset; - S32 right_offset = horiz_offset == 0 ? rect.mRight - scrolled_rect.mLeft - visible_width : left_offset; + S32 horizontal_pos = llclamp(mScrollbar[HORIZONTAL]->getDocPos(), + allowable_scroll_rect.mLeft, + allowable_scroll_rect.mRight); - if( horiz_pos >= left_offset || visible_width < rect.getWidth() ) - { - horiz_pos = left_offset; - } - else if( horiz_pos <= right_offset ) - { - horiz_pos = right_offset; - } - mScrollbar[HORIZONTAL]->setDocSize( scrolled_rect.getWidth() ); - mScrollbar[HORIZONTAL]->setPageSize( visible_width ); - mScrollbar[HORIZONTAL]->setDocPos( horiz_pos ); + 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 LLScrollableContainerView::pageUp(S32 overlap) +void LLScrollContainer::pageUp(S32 overlap) { mScrollbar[VERTICAL]->pageUp(overlap); + updateScroll(); } -void LLScrollableContainerView::pageDown(S32 overlap) +void LLScrollContainer::pageDown(S32 overlap) { mScrollbar[VERTICAL]->pageDown(overlap); + updateScroll(); } -void LLScrollableContainerView::goToTop() +void LLScrollContainer::goToTop() { mScrollbar[VERTICAL]->setDocPos(0); + updateScroll(); } -void LLScrollableContainerView::goToBottom() +void LLScrollContainer::goToBottom() { mScrollbar[VERTICAL]->setDocPos(mScrollbar[VERTICAL]->getDocSize()); + updateScroll(); } -S32 LLScrollableContainerView::getBorderWidth() const +S32 LLScrollContainer::getBorderWidth() const { - if (mBorder) + if (mBorder->getVisible()) { return mBorder->getBorderWidth(); } @@ -676,73 +709,3 @@ S32 LLScrollableContainerView::getBorderWidth() const return 0; } -// virtual -LLXMLNodePtr LLScrollableContainerView::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLView::getXML(); - - // Attributes - - node->createChild("opaque", TRUE)->setBoolValue(mIsOpaque); - - if (mIsOpaque) - { - node->createChild("color", TRUE)->setFloatValue(4, mBackgroundColor.mV); - } - - // Contents - - LLXMLNodePtr child_node = mScrolledView->getXML(); - - node->addChild(child_node); - - return node; -} - -LLView* LLScrollableContainerView::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("scroll_container"); - node->getAttributeString("name", name); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - BOOL opaque = FALSE; - node->getAttributeBOOL("opaque", opaque); - - LLColor4 color(0,0,0,0); - LLUICtrlFactory::getAttributeColor(node,"color", color); - - // Create the scroll view - LLScrollableContainerView *ret = new LLScrollableContainerView(name, rect, (LLPanel*)NULL, opaque, color); - - LLPanel* panelp = NULL; - - // Find a child panel to add - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - LLView *control = factory->createCtrlWidget(panelp, child); - if (control && control->isPanel()) - { - if (panelp) - { - llinfos << "Warning! Attempting to put multiple panels into a scrollable container view!" << llendl; - delete control; - } - else - { - panelp = (LLPanel*)control; - } - } - } - - if (panelp == NULL) - { - panelp = new LLPanel(std::string("dummy"), LLRect::null, FALSE); - } - - ret->mScrolledView = panelp; - - return ret; -} diff --git a/indra/llui/llscrollcontainer.h b/indra/llui/llscrollcontainer.h index 70fc9087d7..25dcd071ab 100644 --- a/indra/llui/llscrollcontainer.h +++ b/indra/llui/llscrollcontainer.h @@ -1,6 +1,6 @@ /** * @file llscrollcontainer.h - * @brief LLScrollableContainerView class header file. + * @brief LLScrollContainer class header file. * * $LicenseInfo:firstyear=2001&license=viewergpl$ * @@ -53,40 +53,59 @@ class LLUICtrlFactory; * the width and height of the view you're scrolling. * *****************************************************************************/ -class LLScrollableContainerView : public LLUICtrl + +struct ScrollContainerRegistry : public LLChildRegistry<ScrollContainerRegistry> +{}; + +class LLScrollContainer : public LLUICtrl { public: // Note: vertical comes before horizontal because vertical // scrollbars have priority for mouse and keyboard events. enum SCROLL_ORIENTATION { VERTICAL, HORIZONTAL, SCROLLBAR_COUNT }; - LLScrollableContainerView( const std::string& name, const LLRect& rect, - LLView* scrolled_view, BOOL is_opaque = FALSE, - const LLColor4& bg_color = LLColor4(0,0,0,0) ); - LLScrollableContainerView( const std::string& name, const LLRect& rect, - LLUICtrl* scrolled_ctrl, BOOL is_opaque = FALSE, - const LLColor4& bg_color = LLColor4(0,0,0,0) ); - virtual ~LLScrollableContainerView( void ); - - void setScrolledView(LLView* view) { mScrolledView = view; } + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<bool> is_opaque, + reserve_scroll_corner, + border_visible, + hide_scrollbar; + Optional<F32> min_auto_scroll_rate, + max_auto_scroll_rate; + Optional<LLUIColor> bg_color; + Optional<LLScrollbar::callback_t> scroll_callback; + + 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); } + virtual void setValue(const LLSD& value) { mInnerRect.setValue(value); } - void calcVisibleSize( S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) const; - void calcVisibleSize( const LLRect& doc_rect, S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) const; void setBorderVisible( BOOL b ); - void scrollToShowRect( const LLRect& rect, const LLCoordGL& desired_offset ); + 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; } - const LLRect& getScrolledViewRect() const { return mScrolledView->getRect(); } + LLRect getVisibleContentRect(); + LLRect getContentWindowRect(); + 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() { return mScrollbar[VERTICAL]->isAtBeginning(); } + bool isAtBottom() { return mScrollbar[VERTICAL]->isAtEnd(); } S32 getBorderWidth() const; - BOOL needsToScroll(S32 x, S32 y, SCROLL_ORIENTATION axis) const; - // LLView functionality virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); virtual BOOL handleKeyHere(KEY key, MASK mask); @@ -97,30 +116,31 @@ public: EAcceptance* accept, std::string& tooltip_msg); - virtual BOOL handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect); virtual void draw(); - - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + virtual bool addChild(LLView* view, S32 tab_group = 0); + + bool autoScroll(S32 x, S32 y); private: - void init(); - // internal scrollbar handlers virtual void scrollHorizontal( S32 new_pos ); virtual void scrollVertical( S32 new_pos ); void updateScroll(); + void calcVisibleSize( S32 *visible_width, S32 *visible_height, BOOL* show_h_scrollbar, BOOL* show_v_scrollbar ) const; LLScrollbar* mScrollbar[SCROLLBAR_COUNT]; LLView* mScrolledView; S32 mSize; BOOL mIsOpaque; - LLColor4 mBackgroundColor; + LLUIColor mBackgroundColor; LLRect mInnerRect; LLViewBorder* mBorder; BOOL mReserveScrollCorner; BOOL mAutoScrolling; F32 mAutoScrollRate; + F32 mMinAutoScrollRate; + F32 mMaxAutoScrollRate; + bool mHideScrollbar; }; diff --git a/indra/llui/llscrollingpanellist.cpp b/indra/llui/llscrollingpanellist.cpp index 05d0c6f753..4f55c0507c 100644 --- a/indra/llui/llscrollingpanellist.cpp +++ b/indra/llui/llscrollingpanellist.cpp @@ -35,7 +35,7 @@ #include "llscrollingpanellist.h" -static LLRegisterWidget<LLScrollingPanelList> r("scrolling_panel_list"); +static LLDefaultChildRegistry::Register<LLScrollingPanelList> r("scrolling_panel_list"); ///////////////////////////////////////////////////////////////////// @@ -50,11 +50,73 @@ void LLScrollingPanelList::clearPanels() reshape( 1, 1, FALSE ); } -void LLScrollingPanelList::addPanel( LLScrollingPanel* panel ) +S32 LLScrollingPanelList::addPanel( LLScrollingPanel* panel ) { - addChildAtEnd( panel ); + addChildInBack( panel ); mPanelList.push_front( panel ); - + + // Resize this view + S32 total_height = 0; + S32 max_width = 0; + S32 cur_gap = 0; + for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + total_height += childp->getRect().getHeight() + cur_gap; + max_width = llmax( max_width, childp->getRect().getWidth() ); + cur_gap = GAP_BETWEEN_PANELS; + } + reshape( max_width, total_height, FALSE ); + + // Reposition each of the child views + S32 cur_y = total_height; + for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); + iter != mPanelList.end(); ++iter) + { + LLScrollingPanel *childp = *iter; + cur_y -= childp->getRect().getHeight(); + childp->translate( -childp->getRect().mLeft, cur_y - childp->getRect().mBottom); + cur_y -= GAP_BETWEEN_PANELS; + } + + return total_height; +} + +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() ) + { + llwarns << "Panel index " << panel_index << " is out of range!" << llendl; + return; + } + else + { + removeChild( mPanelList.at(panel_index) ); + mPanelList.erase( mPanelList.begin() + panel_index ); + } + const S32 GAP_BETWEEN_PANELS = 6; // Resize this view @@ -82,7 +144,7 @@ void LLScrollingPanelList::addPanel( LLScrollingPanel* panel ) cur_y -= GAP_BETWEEN_PANELS; } } - + void LLScrollingPanelList::updatePanels(BOOL allow_modify) { for (std::deque<LLScrollingPanel*>::iterator iter = mPanelList.begin(); @@ -138,18 +200,3 @@ void LLScrollingPanelList::draw() LLUICtrl::draw(); } - -// static -LLView* LLScrollingPanelList::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("scrolling_panel_list"); - node->getAttributeString("name", name); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - LLScrollingPanelList* scrolling_panel_list = new LLScrollingPanelList(name, rect); - scrolling_panel_list->initFromXML(node, parent); - return scrolling_panel_list; -} - diff --git a/indra/llui/llscrollingpanellist.h b/indra/llui/llscrollingpanellist.h index b9d730978f..3abfbcbbe7 100644 --- a/indra/llui/llscrollingpanellist.h +++ b/indra/llui/llscrollingpanellist.h @@ -29,6 +29,9 @@ * $/LicenseInfo$ */ +#ifndef LL_LLSCROLLINGPANELLIST_H +#define LL_LLSCROLLINGPANELLIST_H + #include <vector> #include "llui.h" @@ -42,7 +45,7 @@ class LLScrollingPanel : public LLPanel { public: - LLScrollingPanel(const std::string& name, const LLRect& rect) : LLPanel(name, rect) { } + LLScrollingPanel(const LLPanel::Params& params) : LLPanel(params) {} virtual void updatePanel(BOOL allow_modify) = 0; }; @@ -53,23 +56,37 @@ public: class LLScrollingPanelList : public LLUICtrl { public: - LLScrollingPanelList(const std::string& name, const LLRect& rect) - : LLUICtrl(name, rect, TRUE, NULL, NULL, FOLLOWS_LEFT | FOLLOWS_BOTTOM ) {} + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Params() + { + name = "scrolling_panel_list"; + follows.flags = FOLLOWS_LEFT | FOLLOWS_BOTTOM; + } + }; + LLScrollingPanelList(const Params& p) + : LLUICtrl(p) + {} + + static const S32 GAP_BETWEEN_PANELS = 6; + + typedef std::deque<LLScrollingPanel*> panel_list_t; virtual void setValue(const LLSD& value) {}; - virtual LLXMLNodePtr getXML(bool save_children) const { return LLUICtrl::getXML(); } - virtual void draw(); void clearPanels(); - void addPanel( LLScrollingPanel* panel ); + S32 addPanel( LLScrollingPanel* panel ); + void removePanel( LLScrollingPanel* panel ); + void removePanel( U32 panel_index ); void updatePanels(BOOL allow_modify); + const panel_list_t& getPanelList() { return mPanelList; } - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - private: void updatePanelVisiblilty(); - std::deque<LLScrollingPanel*> mPanelList; + panel_list_t mPanelList; }; + +#endif //LL_LLSCROLLINGPANELLIST_H diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp new file mode 100644 index 0000000000..3cc92baa8d --- /dev/null +++ b/indra/llui/llscrolllistcell.cpp @@ -0,0 +1,441 @@ +/** + * @file llscrolllistcell.cpp + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "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 // 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; +} + +// +// 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; + } + } +} + +// +// LLScrollListText +// +U32 LLScrollListText::sCount = 0; + +LLScrollListText::LLScrollListText(const LLScrollListCell::Params& p) +: LLScrollListCell(p), + mText(p.value().asString()), + mFont(p.font), + mColor(p.color), + mUseColor(p.color.isProvided()), + mFontAlignment(p.font_halign), + mVisible(p.visible), + mHighlightCount( 0 ), + mHighlightOffset( 0 ) +{ + sCount++; + + // initialize rounded rect image + if (!mRoundedRectImage) + { + mRoundedRectImage = LLUI::getUIImage("Rounded_Square"); + } +} + +//virtual +void LLScrollListText::highlightText(S32 offset, S32 num_chars) +{ + mHighlightOffset = offset; + mHighlightCount = 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 llround(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 +const LLSD LLScrollListText::getValue() const +{ + return LLSD(mText.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) + { + S32 left = 0; + switch(mFontAlignment) + { + case LLFontGL::LEFT: + left = mFont->getWidth(mText.getString(), 0, 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, + llround(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 = 0.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, 2.f, + display_color, + mFontAlignment, + LLFontGL::BOTTOM, + 0, + LLFontGL::NO_SHADOW, + string_chars, + getWidth(), + &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<LLCheckBoxCtrl>(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; +} diff --git a/indra/llui/llscrolllistcell.h b/indra/llui/llscrolllistcell.h new file mode 100644 index 0000000000..5fecf5aade --- /dev/null +++ b/indra/llui/llscrolllistcell.h @@ -0,0 +1,227 @@ +/** + * @file llscrolllistcell.h + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LLSCROLLLISTCELL_H +#define LLSCROLLLISTCELL_H + +#include "llfontgl.h" // HAlign +#include "llpointer.h" // LLPointer<> +#include "lluistring.h" +#include "v4color.h" +#include "llui.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<Params> + { + Optional<std::string> type, + column; + + Optional<S32> width; + Optional<bool> enabled, + visible; + + Optional<void*> userdata; + Optional<LLSD> value; + Optional<std::string> tool_tip; + + Optional<const LLFontGL*> font; + Optional<LLColor4> font_color; + Optional<LLFontGL::HAlign> font_halign; + + Optional<LLColor4> color; + + Params() + : type("type", "text"), + column("column"), + width("width"), + enabled("enabled", true), + visible("visible", true), + value("value"), + tool_tip("tool_tip", ""), + font("font", LLFontGL::getFontSansSerifSmall()), + 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 void setValue(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*/ const LLSD getValue() 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; + + void setText(const LLStringExplicit& text); + void setFontStyle(const U8 font_style); + +private: + LLUIString mText; + const LLFontGL* mFont; + LLColor4 mColor; + U8 mUseColor; + LLFontGL::HAlign mFontAlignment; + BOOL mVisible; + S32 mHighlightCount; + S32 mHighlightOffset; + + LLPointer<LLUIImage> mRoundedRectImage; + + static U32 sCount; +}; + +/* + * Cell displaying an 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<LLUIImage> mIcon; + LLColor4 mColor; + LLFontGL::HAlign mAlignment; +}; + +/* + * 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; +}; + +#endif diff --git a/indra/llui/llscrolllistcolumn.cpp b/indra/llui/llscrolllistcolumn.cpp new file mode 100644 index 0000000000..d281341202 --- /dev/null +++ b/indra/llui/llscrolllistcolumn.cpp @@ -0,0 +1,338 @@ +/** + * @file llscrollcolumnheader.cpp + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "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") +{ + name = "column_header"; + tab_stop(false); +} + + +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<LLResizeBar>(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(); + setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, draw_arrow ? LLColor4::white : LLColor4::transparent); + + // 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 + 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(); + + 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 + mColumn->mParentCtrl->updateColumns(); + } +} + +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->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->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::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 new file mode 100644 index 0000000000..5aef6e8e94 --- /dev/null +++ b/indra/llui/llscrolllistcolumn.h @@ -0,0 +1,178 @@ +/** + * @file llscrollcolumnheader.h + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#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<Params, LLButton::Params> + { + Mandatory<LLScrollListColumn*> 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<LLScrollListColumn::ESortDirection, SortNames> + { + static void declareValues(); + }; + + struct Params : public LLInitParam::Block<Params> + { + Optional<std::string> name, + tool_tip; + Optional<std::string> sort_column; + Optional<ESortDirection, SortNames> sort_direction; + Optional<bool> sort_ascending; + + struct Width : public LLInitParam::Choice<Width> + { + Alternative<bool> dynamic_width; + Alternative<S32> pixel_width; + Alternative<F32> relative_width; + + Width() + : dynamic_width("dynamic_width", false), + pixel_width("width"), + relative_width("relative_width", -1.f) + { + addSynonym(relative_width, "relwidth"); + } + }; + Optional<Width> width; + + // either an image or label is used in column header + struct Header : public LLInitParam::Choice<Header> + { + Alternative<std::string> label; + Alternative<LLUIImage*> image; + + Header() + : label("label"), + image("image") + {} + }; + Optional<Header> header; + + Optional<LLFontGL::HAlign> 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" + 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 5dd4e2d0ee..4e84013db0 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -1,6 +1,7 @@ /** * @file llscrolllistctrl.cpp - * @brief LLScrollListCtrl base class + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). * * $LicenseInfo:firstyear=2001&license=viewergpl$ * @@ -30,34 +31,42 @@ * $/LicenseInfo$ */ -#include <algorithm> - #include "linden_common.h" -#include "llstl.h" -#include "llboost.h" #include "llscrolllistctrl.h" -#include "indra_constants.h" +#include <algorithm> + +#include "llstl.h" +#include "llboost.h" +//#include "indra_constants.h" #include "llcheckboxctrl.h" #include "llclipboard.h" #include "llfocusmgr.h" -#include "llrender.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 "llresizebar.h" +#include "llviewborder.h" +#include "lltextbox.h" +#include "llsdparam.h" +#include "llcachename.h" +#include "llmenugl.h" +#include "llurlaction.h" +#include "lltooltip.h" -const S32 MIN_COLUMN_WIDTH = 20; -const S32 LIST_SNAP_PADDING = 5; +#include <boost/bind.hpp> -static LLRegisterWidget<LLScrollListCtrl> r("scroll_list"); +static LLDefaultChildRegistry::Register<LLScrollListCtrl> r("scroll_list"); // local structures & classes. struct SortScrollListItem @@ -96,510 +105,95 @@ struct SortScrollListItem const sort_order_t& mSortOrders; }; - -// -// LLScrollListIcon -// -LLScrollListIcon::LLScrollListIcon(LLUIImagePtr icon, S32 width) - : LLScrollListCell(width), - mIcon(icon), - mColor(LLColor4::white) -{ -} - -LLScrollListIcon::LLScrollListIcon(const LLSD& value, S32 width) - : LLScrollListCell(width), - mColor(LLColor4::white) -{ - setValue(value); -} - - -LLScrollListIcon::~LLScrollListIcon() -{ -} - -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::sImageProvider->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) - { - mIcon->draw(0, 0, mColor); - } -} - -// -// LLScrollListCheck -// -LLScrollListCheck::LLScrollListCheck(LLCheckBoxCtrl* check_box, S32 width) -{ - mCheckBox = check_box; - LLRect rect(mCheckBox->getRect()); - if (width) - { - - rect.mRight = rect.mLeft + width; - mCheckBox->setRect(rect); - setWidth(width); - } - else - { - setWidth(rect.getWidth()); //check_box->getWidth(); - } -} - -LLScrollListCheck::~LLScrollListCheck() -{ - delete mCheckBox; -} - -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; -} - -// -// LLScrollListSeparator -// -LLScrollListSeparator::LLScrollListSeparator(S32 width) : LLScrollListCell(width) -{ -} - -//virtual -S32 LLScrollListSeparator::getHeight() const -{ - return 5; -} - - -void LLScrollListSeparator::draw(const LLColor4& color, const LLColor4& highlight_color) const -{ - //*FIXME: use dynamic item heights and make separators narrow, and inactive - gl_line_2d(5, 8, llmax(5, getWidth() - 5), 8, color); -} - -// -// LLScrollListText -// -U32 LLScrollListText::sCount = 0; - -LLScrollListText::LLScrollListText( const std::string& text, const LLFontGL* font, S32 width, U8 font_style, LLFontGL::HAlign font_alignment, LLColor4& color, BOOL use_color, BOOL visible) -: LLScrollListCell(width), - mText( text ), - mFont( font ), - mColor(color), - mUseColor(use_color), - mFontStyle( font_style ), - mFontAlignment( font_alignment ), - mVisible( visible ), - mHighlightCount( 0 ), - mHighlightOffset( 0 ) -{ - sCount++; - - // initialize rounded rect image - if (!mRoundedRectImage) - { - mRoundedRectImage = LLUI::sImageProvider->getUIImage("rounded_square.tga"); - } -} -//virtual -void LLScrollListText::highlightText(S32 offset, S32 num_chars) -{ - mHighlightOffset = offset; - mHighlightCount = num_chars; -} - -//virtual -BOOL LLScrollListText::isText() const -{ - return TRUE; -} - -//virtual -BOOL LLScrollListText::getVisible() const -{ - return mVisible; -} - -//virtual -S32 LLScrollListText::getHeight() const -{ - return llround(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; -} - -//virtual -void LLScrollListText::setValue(const LLSD& text) -{ - setText(text.asString()); -} - -//virtual -const LLSD LLScrollListText::getValue() const -{ - return LLSD(mText.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) - { - S32 left = 0; - switch(mFontAlignment) - { - case LLFontGL::LEFT: - left = mFont->getWidth(mText.getString(), 0, 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, - llround(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 = 0.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, 2.f, - display_color, - mFontAlignment, - LLFontGL::BOTTOM, - mFontStyle, - string_chars, - getWidth(), - &right_x, - FALSE, - TRUE); -} - -LLScrollListDate::LLScrollListDate( const LLDate& date, const LLFontGL* font, S32 width, U8 font_style, LLFontGL::HAlign font_alignment, LLColor4& color, BOOL use_color, BOOL visible) -: LLScrollListText(date.asRFC1123(), font, width, font_style, font_alignment, color, use_color, visible), - mDate(date) -{ -} - -void LLScrollListDate::setValue(const LLSD& value) -{ - mDate = value.asDate(); - LLScrollListText::setValue(mDate.asRFC1123()); -} - -const LLSD LLScrollListDate::getValue() const -{ - return mDate; -} - -LLScrollListItem::~LLScrollListItem() -{ - std::for_each(mColumns.begin(), mColumns.end(), DeletePointer()); -} - -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 - { - llerrs << "LLScrollListItem::setColumn: bad column: " << column << llendl; - } -} - -std::string LLScrollListItem::getContentsCSV() const -{ - std::string ret; - - S32 count = getNumColumns(); - for (S32 i=0; i<count; ++i) - { - ret += getColumn(i)->getValue().asString(); - if (i < count-1) - { - ret += ", "; - } - } - - return ret; -} - -void LLScrollListItem::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding) -{ - // draw background rect - LLRect bg_rect = rect; - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.color4fv(bg_color.mV); - gl_rect_2d( bg_rect ); - } - - 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, 0.0f); - - cell->draw( fg_color, highlight_color ); - } - LLUI::popMatrix(); - - cur_x += cell->getWidth() + column_padding; - } -} - - -void LLScrollListItem::setEnabled(BOOL b) -{ - mEnabled = b; -} - -//--------------------------------------------------------------------------- -// LLScrollListItemComment -//--------------------------------------------------------------------------- -LLScrollListItemComment::LLScrollListItemComment(const std::string& comment_string, const LLColor4& color) -: LLScrollListItem(FALSE), - mColor(color) -{ - addColumn( comment_string, LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ) ); -} - -void LLScrollListItemComment::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding) -{ - LLScrollListCell* cell = getColumn(0); - if (cell) - { - // Two ways a cell could be hidden - if (cell->getWidth() < 0 - || !cell->getVisible()) return; - - LLUI::pushMatrix(); - { - LLUI::translate((F32)rect.mLeft, (F32)rect.mBottom, 0.0f); - - // force first cell to be width of entire item - cell->setWidth(rect.getWidth()); - cell->draw( mColor, highlight_color ); - } - LLUI::popMatrix(); - } -} - -//--------------------------------------------------------------------------- -// LLScrollListItemSeparator -//--------------------------------------------------------------------------- -LLScrollListItemSeparator::LLScrollListItemSeparator() -: LLScrollListItem(FALSE) -{ - LLScrollListSeparator* cell = new LLScrollListSeparator(0); - setNumColumns(1); - setColumn(0, cell); -} - -void LLScrollListItemSeparator::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding) -{ - //TODO* move LLScrollListSeparator::draw into here and get rid of it - LLScrollListCell* cell = getColumn(0); - if (cell) - { - // Two ways a cell could be hidden - if (cell->getWidth() < 0 - || !cell->getVisible()) return; - - LLUI::pushMatrix(); - { - LLUI::translate((F32)rect.mLeft, (F32)rect.mBottom, 0.0f); - - // force first cell to be width of entire item - cell->setWidth(rect.getWidth()); - cell->draw( fg_color, highlight_color ); - } - LLUI::popMatrix(); - } -} - //--------------------------------------------------------------------------- // LLScrollListCtrl //--------------------------------------------------------------------------- -LLScrollListCtrl::LLScrollListCtrl(const std::string& name, const LLRect& rect, - void (*commit_callback)(LLUICtrl* ctrl, void* userdata), - void* callback_user_data, - BOOL allow_multiple_selection, - BOOL show_border - ) - : LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data), +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), + sort_column("sort_column", -1), + sort_ascending("sort_ascending", true), + commit_on_keyboard_movement("commit_on_keyboard_movement", true), + heading_height("heading_height"), + page_lines("page_lines", 0), + background_visible("background_visible"), + draw_stripes("draw_stripes"), + column_padding("column_padding"), + 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") +{ + name = "scroll_list"; + mouse_opaque = true; +} + +LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) +: LLUICtrl(p), mLineHeight(0), mScrollLines(0), - mPageLines(0), - mHeadingHeight(20), + mPageLines(p.page_lines), mMaxSelectable(0), - mAllowMultipleSelection( allow_multiple_selection ), mAllowKeyboardMovement(TRUE), - mCommitOnKeyboardMovement(TRUE), + mCommitOnKeyboardMovement(p.commit_on_keyboard_movement), mCommitOnSelectionChange(FALSE), mSelectionChanged(FALSE), mNeedsScroll(FALSE), mCanSelect(TRUE), - mDisplayColumnHeaders(FALSE), mColumnsDirty(FALSE), mMaxItemCount(INT_MAX), mMaxContentWidth(0), - mBackgroundVisible( TRUE ), - mDrawStripes(TRUE), - mBgWriteableColor( LLUI::sColorsGroup->getColor( "ScrollBgWriteableColor" ) ), - mBgReadOnlyColor( LLUI::sColorsGroup->getColor( "ScrollBgReadOnlyColor" ) ), - mBgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedBGColor") ), - mBgStripeColor( LLUI::sColorsGroup->getColor("ScrollBGStripeColor") ), - mFgSelectedColor( LLUI::sColorsGroup->getColor("ScrollSelectedFGColor") ), - mFgUnselectedColor( LLUI::sColorsGroup->getColor("ScrollUnselectedColor") ), - mFgDisabledColor( LLUI::sColorsGroup->getColor("ScrollDisabledColor") ), - mHighlightedColor( LLUI::sColorsGroup->getColor("ScrollHighlightedColor") ), mBorderThickness( 2 ), mOnDoubleClickCallback( NULL ), mOnMaximumSelectCallback( NULL ), mOnSortChangedCallback( NULL ), mHighlightedItem(-1), mBorder(NULL), - mSearchColumn(0), + mPopupMenu(NULL), mNumDynamicWidthColumns(0), mTotalStaticColumnWidth(0), mTotalColumnPadding(0), - mSorted(TRUE), + mSorted(false), mDirty(FALSE), mOriginalSelection(-1), - mDrewSelected(FALSE) + 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), + mContextMenuType(MENU_NONE) { mItemListRect.setOriginAndSize( mBorderThickness, @@ -609,40 +203,74 @@ LLScrollListCtrl::LLScrollListCtrl(const std::string& name, const LLRect& rect, updateLineHeight(); - mPageLines = mLineHeight? (mItemListRect.getHeight()) / mLineHeight : 0; - // Init the scrollbar + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + LLRect scroll_rect; scroll_rect.setOriginAndSize( - getRect().getWidth() - mBorderThickness - SCROLLBAR_SIZE, + getRect().getWidth() - mBorderThickness - scrollbar_size, mItemListRect.mBottom, - SCROLLBAR_SIZE, + scrollbar_size, mItemListRect.getHeight()); - mScrollbar = new LLScrollbar( std::string("Scrollbar"), scroll_rect, - LLScrollbar::VERTICAL, - getItemCount(), - mScrollLines, - mPageLines, - &LLScrollListCtrl::onScrollChange, this ); - mScrollbar->setFollowsRight(); - mScrollbar->setFollowsTop(); - mScrollbar->setFollowsBottom(); - mScrollbar->setEnabled( TRUE ); - // scrollbar is visible only when needed - mScrollbar->setVisible(FALSE); + + 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<LLScrollbar> (sbparams); addChild(mScrollbar); // Border - if (show_border) + if (p.has_border) { - LLRect border_rect( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - mBorder = new LLViewBorder( std::string("dlg border"), border_rect, LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, 1 ); + LLRect border_rect = getLocalRect(); + LLViewBorder::Params params = p.border; + params.rect(border_rect); + mBorder = LLUICtrlFactory::create<LLViewBorder> (params); addChild(mBorder); } - mColumnPadding = 5; + // set border *after* rect is fully initialized + if (mBorder) + { + mBorder->setRect(getLocalRect()); + mBorder->reshape(getRect().getWidth(), getRect().getHeight()); + } - mLastSelected = NULL; + if (p.sort_column >= 0) + { + sortByColumnIndex(p.sort_column, p.sort_ascending); + } + + + for (LLInitParam::ParamIterator<LLScrollListColumn::Params>::const_iterator row_it = p.contents.columns().begin(); + row_it != p.contents.columns().end(); + ++row_it) + { + addColumn(*row_it); + } + + for (LLInitParam::ParamIterator<LLScrollListItem::Params>::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); + addChild(LLUICtrlFactory::create<LLTextBox>(text_p)); } S32 LLScrollListCtrl::getSearchColumn() @@ -666,6 +294,18 @@ S32 LLScrollListCtrl::getSearchColumn() } 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() { @@ -746,6 +386,10 @@ std::vector<LLScrollListItem*> LLScrollListCtrl::getAllSelected() const 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++) { @@ -818,6 +462,7 @@ void LLScrollListCtrl::reshape( S32 width, S32 height, BOOL called_from_parent ) void LLScrollListCtrl::updateLayout() { + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); // reserve room for column headers, if needed S32 heading_size = (mDisplayColumnHeaders ? mHeadingHeight : 0); mItemListRect.setOriginAndSize( @@ -826,23 +471,21 @@ void LLScrollListCtrl::updateLayout() getRect().getWidth() - 2 * mBorderThickness, getRect().getHeight() - (2 * mBorderThickness ) - heading_size ); + getChildView("comment_text")->setShape(mItemListRect); + // how many lines of content in a single "page" - mPageLines = mLineHeight? mItemListRect.getHeight() / mLineHeight : 0; - BOOL scrollbar_visible = getItemCount() > mPageLines; + 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; - } - - // don't allow scrolling off bottom - if (mScrollLines + mPageLines > getItemCount()) - { - setScrollPos(llmax(0, getItemCount() - mPageLines)); + mItemListRect.mRight = getRect().getWidth() - mBorderThickness - scrollbar_size; } - mScrollbar->reshape(SCROLLBAR_SIZE, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0)); - mScrollbar->setPageSize( mPageLines ); + 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); @@ -854,6 +497,9 @@ void LLScrollListCtrl::updateLayout() void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height) { S32 height = llmin( getRequiredRect().getHeight(), max_height ); + if(mPageLines) + height = llmin( mPageLines * mLineHeight + (mDisplayColumnHeaders ? mHeadingHeight : 0), height ); + S32 width = getRect().getWidth(); reshape( width, height ); @@ -881,7 +527,7 @@ BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL r { case ADD_TOP: mItemList.push_front(item); - setSorted(FALSE); + setNeedsSort(); break; case ADD_SORTED: @@ -898,29 +544,29 @@ BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL r // ADD_SORTED just sorts by first column... // this might not match user sort criteria, so flag list as being in unsorted state - setSorted(FALSE); + setNeedsSort(); break; } case ADD_BOTTOM: mItemList.push_back(item); - setSorted(FALSE); + setNeedsSort(); break; default: llassert(0); mItemList.push_back(item); - setSorted(FALSE); + setNeedsSort(); break; } // create new column on demand if (mColumns.empty() && requires_column) { - LLSD new_column; - new_column["name"] = "default_column"; - new_column["label"] = ""; - new_column["dynamicwidth"] = TRUE; - addColumn(new_column); + LLScrollListColumn::Params col_params; + col_params.name = "default_column"; + col_params.header.label = ""; + col_params.width.dynamic_width = true; + addColumn(col_params); } updateLineHeightInsert(item); @@ -1017,7 +663,7 @@ void LLScrollListCtrl::updateColumns() // update column headers std::vector<LLScrollListColumn*>::iterator column_ordered_it; S32 left = mItemListRect.mLeft; - LLColumnHeader* last_header = NULL; + LLScrollColumnHeader* last_header = NULL; for (column_ordered_it = mColumnsIndexed.begin(); column_ordered_it != mColumnsIndexed.end(); ++column_ordered_it) { if ((*column_ordered_it)->getWidth() < 0) @@ -1052,7 +698,7 @@ void LLScrollListCtrl::updateColumns() } // expand last column header we encountered to full list width - if (last_header && last_header->canResize()) + if (last_header) { S32 new_width = llmax(0, mItemListRect.mRight - last_header->getRect().mLeft); last_header->reshape(new_width, last_header->getRect().getHeight()); @@ -1077,13 +723,6 @@ void LLScrollListCtrl::updateColumns() } -void LLScrollListCtrl::setDisplayHeading(BOOL display) -{ - mDisplayColumnHeaders = display; - - updateLayout(); -} - void LLScrollListCtrl::setHeadingHeight(S32 heading_height) { mHeadingHeight = heading_height; @@ -1091,6 +730,12 @@ void LLScrollListCtrl::setHeadingHeight(S32 heading_height) updateLayout(); } +void LLScrollListCtrl::setPageLines(S32 new_page_lines) +{ + mPageLines = new_page_lines; + + updateLayout(); +} BOOL LLScrollListCtrl::selectFirstItem() { @@ -1140,6 +785,9 @@ BOOL LLScrollListCtrl::selectItemRange( S32 first_index, S32 last_index ) return FALSE; } + // make sure sort is up to date + updateSort(); + S32 listlen = (S32)mItemList.size(); first_index = llclamp(first_index, 0, listlen-1); @@ -1193,6 +841,7 @@ void LLScrollListCtrl::swapWithNext(S32 index) // 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; @@ -1206,6 +855,7 @@ void LLScrollListCtrl::swapWithPrevious(S32 index) // At beginning of list, don't do anything } + updateSort(); LLScrollListItem *cur_itemp = mItemList[index]; mItemList[index] = mItemList[index - 1]; mItemList[index - 1] = cur_itemp; @@ -1219,6 +869,8 @@ void LLScrollListCtrl::deleteSingleItem(S32 target_index) return; } + updateSort(); + LLScrollListItem *itemp; itemp = mItemList[target_index]; if (itemp == mLastSelected) @@ -1275,7 +927,15 @@ void LLScrollListCtrl::deleteSelectedItems() dirtyColumns(); } -void LLScrollListCtrl::highlightNthItem(S32 target_index) +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) { @@ -1283,14 +943,14 @@ void LLScrollListCtrl::highlightNthItem(S32 target_index) } } -S32 LLScrollListCtrl::selectMultiple( LLDynamicArray<LLUUID> ids ) +S32 LLScrollListCtrl::selectMultiple( std::vector<LLUUID> ids ) { item_list::iterator iter; S32 count = 0; for (iter = mItemList.begin(); iter != mItemList.end(); iter++) { LLScrollListItem* item = *iter; - LLDynamicArray<LLUUID>::iterator iditr; + std::vector<LLUUID>::iterator iditr; for(iditr = ids.begin(); iditr != ids.end(); ++iditr) { if (item->getEnabled() && (item->getUUID() == (*iditr))) @@ -1312,6 +972,8 @@ S32 LLScrollListCtrl::selectMultiple( LLDynamicArray<LLUUID> ids ) S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const { + updateSort(); + S32 index = 0; item_list::const_iterator iter; for (iter = mItemList.begin(); iter != mItemList.end(); iter++) @@ -1328,6 +990,8 @@ S32 LLScrollListCtrl::getItemIndex( LLScrollListItem* target_item ) const 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++) @@ -1353,6 +1017,8 @@ void LLScrollListCtrl::selectPrevItem( BOOL extend_selection) } else { + updateSort(); + item_list::iterator iter; for (iter = mItemList.begin(); iter != mItemList.end(); iter++) { @@ -1395,6 +1061,8 @@ void LLScrollListCtrl::selectNextItem( BOOL extend_selection) } else { + updateSort(); + item_list::reverse_iterator iter; for (iter = mItemList.rbegin(); iter != mItemList.rend(); iter++) { @@ -1446,35 +1114,51 @@ void LLScrollListCtrl::deselectAllItems(BOOL no_commit_on_change) /////////////////////////////////////////////////////////////////////////////////////////////////// // Use this to add comment text such as "Searching", which ignores column settings of list -LLScrollListItem* LLScrollListCtrl::addCommentText(const std::string& comment_text, EAddPosition pos) +void LLScrollListCtrl::setCommentText(const std::string& comment_text) { - LLScrollListItem* item = NULL; - if (getItemCount() < mMaxItemCount) - { - // always draw comment text with "enabled" color - item = new LLScrollListItemComment( comment_text, mFgUnselectedColor ); - addItem( item, pos, FALSE ); - } - return item; + getChild<LLTextBox>("comment_text")->setValue(comment_text); } LLScrollListItem* LLScrollListCtrl::addSeparator(EAddPosition pos) { - LLScrollListItem* item = new LLScrollListItemSeparator(); - addItem(item, pos, FALSE); - return item; + 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) { - // ensure that no stale items are selected, even if we don't find a match - deselectAllItems(TRUE); - //RN: assume no empty items - if (label.empty()) + deselectAllItems(TRUE); // ensure that no stale items are selected, even if we don't find a match + LLScrollListItem* item = getItemByLabel(label, case_sensitive); + + bool found = NULL != item; + if(found) { - return FALSE; + selectItem(item); + } + + 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; @@ -1483,34 +1167,21 @@ BOOL LLScrollListCtrl::selectItemByLabel(const std::string& label, BOOL case_sen LLStringUtil::toLower(target_text); } - BOOL found = FALSE; - item_list::iterator iter; - S32 index = 0; for (iter = mItemList.begin(); iter != mItemList.end(); iter++) { LLScrollListItem* item = *iter; - // Only select enabled items with matching names - std::string item_text = item->getColumn(0)->getValue().asString(); + std::string item_text = item->getColumn(column)->getValue().asString(); // Only select enabled items with matching names if (!case_sensitive) { LLStringUtil::toLower(item_text); } - BOOL select = !found && item->getEnabled() && item_text == target_text; - if (select) + if(item_text == target_text) { - selectItem(item); + return item; } - found = found || select; - index++; } - - if (mCommitOnSelectionChange) - { - commitIfChanged(); - } - - return found; + return NULL; } @@ -1612,16 +1283,18 @@ const std::string LLScrollListCtrl::getSelectedItemLabel(S32 column) const // "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, S32 column_width) +LLScrollListItem* LLScrollListCtrl::addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos, BOOL enabled) { - LLScrollListItem* item = NULL; if (getItemCount() < mMaxItemCount) { - item = new LLScrollListItem( enabled, NULL, id ); - item->addColumn(item_text, LLResMgr::getInstance()->getRes(LLFONT_SANSSERIF_SMALL), column_width); - addItem( item, pos ); + 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 item; + return NULL; } // Select the line or lines that match this UUID @@ -1709,7 +1382,7 @@ void LLScrollListCtrl::drawItems() S32 y = mItemListRect.mTop - mLineHeight; // allow for partial line at bottom - S32 num_page_lines = mPageLines + 1; + S32 num_page_lines = getLinesPerPage(); LLRect item_rect; @@ -1720,13 +1393,11 @@ void LLScrollListCtrl::drawItems() S32 cur_y = y; - mDrewSelected = FALSE; - S32 line = 0; S32 max_columns = 0; LLColor4 highlight_color = LLColor4::white; - F32 type_ahead_timeout = LLUI::sConfigGroup->getF32("TypeAheadTimeout"); + static LLUICachedControl<F32> 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); item_list::iterator iter; @@ -1739,14 +1410,10 @@ void LLScrollListCtrl::drawItems() cur_y, mItemListRect.getWidth(), mLineHeight ); + item->setRect(item_rect); //llinfos << item_rect.getWidth() << llendl; - if (item->getSelected()) - { - mDrewSelected = TRUE; - } - max_columns = llmax(max_columns, item->getNumColumns()); LLColor4 fg_color; @@ -1754,27 +1421,46 @@ void LLScrollListCtrl::drawItems() if( mScrollLines <= line && line < mScrollLines + num_page_lines ) { - fg_color = (item->getEnabled() ? mFgUnselectedColor : mFgDisabledColor); + fg_color = (item->getEnabled() ? mFgUnselectedColor.get() : mFgDisabledColor.get()); if( item->getSelected() && mCanSelect) { - bg_color = mBgSelectedColor; - fg_color = (item->getEnabled() ? mFgSelectedColor : mFgDisabledColor); + if(item->getHighlighted()) // if it's highlighted, average the colors + { + bg_color = lerp(mBgSelectedColor.get(), mHighlightedColor.get(), 0.5f); + } + else // otherwise just select-highlight it + { + bg_color = mBgSelectedColor.get(); + } + + fg_color = (item->getEnabled() ? mFgSelectedColor.get() : mFgDisabledColor.get()); } else if (mHighlightedItem == line && mCanSelect) { - bg_color = mHighlightedColor; + if(item->getHighlighted()) // if it's highlighted, average the colors + { + bg_color = lerp(mHoveredColor.get(), mHighlightedColor.get(), 0.5f); + } + else // otherwise just hover-highlight it + { + bg_color = mHoveredColor.get(); + } + } + else if (item->getHighlighted()) + { + bg_color = mHighlightedColor.get(); } else { if (mDrawStripes && (line % 2 == 0) && (max_columns > 1)) { - bg_color = mBgStripeColor; + bg_color = mBgStripeColor.get(); } } if (!item->getEnabled()) { - bg_color = mBgReadOnlyColor; + bg_color = mBgReadOnlyColor.get(); } item->draw(item_rect, fg_color, bg_color, highlight_color, mColumnPadding); @@ -1792,10 +1478,7 @@ void LLScrollListCtrl::draw() LLLocalClipRect clip(getLocalRect()); // if user specifies sort, make sure it is maintained - if (needsSorting() && !isSorted()) - { - sortItems(); - } + updateSort(); if (mNeedsScroll) { @@ -1807,8 +1490,7 @@ void LLScrollListCtrl::draw() if (mBackgroundVisible) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.color4fv( getEnabled() ? mBgWriteableColor.mV : mBgReadOnlyColor.mV ); - gl_rect_2d(background); + gl_rect_2d(background, getEnabled() ? mBgWriteableColor.get() : mBgReadOnlyColor.get() ); } if (mColumnsDirty) @@ -1817,11 +1499,13 @@ void LLScrollListCtrl::draw() mColumnsDirty = FALSE; } + getChildView("comment_text")->setVisible(mItemList.empty()); + drawItems(); if (mBorder) { - mBorder->setKeyboardFocusHighlight(gFocusMgr.getKeyboardFocus() == this); + mBorder->setKeyboardFocusHighlight(hasFocus()); } LLUICtrl::draw(); @@ -1842,7 +1526,19 @@ BOOL LLScrollListCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) return handled; } -BOOL LLScrollListCtrl::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen) +// *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); @@ -1856,34 +1552,32 @@ BOOL LLScrollListCtrl::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sti { LLScrollListCell* hit_cell = hit_item->getColumn(column_index); if (!hit_cell) return FALSE; - //S32 cell_required_width = hit_cell->getContentWidth(); if (hit_cell - && hit_cell->isText()) + && hit_cell->isText() + && hit_cell->needsToolTip()) { - - S32 rect_left = getColumnOffsetFromIndex(column_index) + mItemListRect.mLeft; - S32 rect_bottom = getRowOffsetFromIndex(getItemIndex(hit_item)); - LLRect cell_rect; - cell_rect.setOriginAndSize(rect_left, rect_bottom, rect_left + columnp->getWidth(), mLineHeight); + S32 row_index = getItemIndex(hit_item); + LLRect cell_rect = getCellRect(row_index, column_index); // Convert rect local to screen coordinates - localPointToScreen( - cell_rect.mLeft, cell_rect.mBottom, - &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); - localPointToScreen( - cell_rect.mRight, cell_rect.mTop, - &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) ); + LLRect sticky_rect; + localRectToScreen(cell_rect, &sticky_rect); - msg = hit_cell->getValue().asString(); + // display tooltip exactly over original cell, in same font + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(hit_cell->getToolTip()) + .font(LLFontGL::getFontSansSerifSmall()) + .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 - LLColumnHeader* headerp = columnp->mHeader; + LLScrollColumnHeader* headerp = columnp->mHeader; if (headerp && !handled) { - headerp->handleToolTip(x, y, msg, sticky_rect_screen); - handled = !msg.empty(); + handled = headerp->handleToolTip(x, y, mask); } return handled; @@ -1922,7 +1616,7 @@ BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask) { if(mOnMaximumSelectCallback) { - mOnMaximumSelectCallback(mCallbackUserData); + mOnMaximumSelectCallback(); } break; } @@ -1960,7 +1654,7 @@ BOOL LLScrollListCtrl::selectItemAt(S32 x, S32 y, MASK mask) { if(mOnMaximumSelectCallback) { - mOnMaximumSelectCallback(mCallbackUserData); + mOnMaximumSelectCallback(); } } } @@ -2014,7 +1708,7 @@ BOOL LLScrollListCtrl::handleMouseDown(S32 x, S32 y, MASK mask) } BOOL LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask) -{ +{ if (hasMouseCapture()) { // release mouse capture immediately so @@ -2038,6 +1732,72 @@ BOOL LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask) 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.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"; + delete mPopupMenu; + mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>( + menu_name, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance()); + if (mPopupMenu) + { + mPopupMenu->show(x, y); + LLMenuGL::showPopup(this, mPopupMenu, x, y); + return TRUE; + } + } + } + return FALSE; +} + +void LLScrollListCtrl::showNameDetails(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::clickAction(slurl); +} + +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 + { + gCacheName->getFullName(LLUUID(id), name); + } + 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; @@ -2049,9 +1809,10 @@ BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask) // so the scroll bars will work. if (NULL == LLView::childrenHandleDoubleClick(x, y, mask)) { - if( mCanSelect && mOnDoubleClickCallback ) + // Run the callback only if an item is being double-clicked. + if( mCanSelect && hitItem(x, y) && mOnDoubleClickCallback ) { - mOnDoubleClickCallback( mCallbackUserData ); + mOnDoubleClickCallback(); } } } @@ -2118,6 +1879,8 @@ LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y ) // Excludes disabled items. LLScrollListItem* hit_item = NULL; + updateSort(); + LLRect item_rect; item_rect.setLeftTopAndSize( mItemListRect.mLeft, @@ -2126,7 +1889,7 @@ LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y ) mLineHeight ); // allow for partial line at bottom - S32 num_page_lines = mPageLines + 1; + S32 num_page_lines = getLinesPerPage(); S32 line = 0; item_list::iterator iter; @@ -2197,8 +1960,7 @@ S32 LLScrollListCtrl::getColumnOffsetFromIndex(S32 index) S32 LLScrollListCtrl::getRowOffsetFromIndex(S32 index) { - S32 row_bottom = ((mItemListRect.mTop - (index - mScrollLines)) * mLineHeight) - - mLineHeight; + S32 row_bottom = (mItemListRect.mTop - ((index - mScrollLines + 1) * mLineHeight) ); return row_bottom; } @@ -2221,11 +1983,11 @@ BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask) LLScrollListItem* item = hitItem(x, y); if (item) { - highlightNthItem(getItemIndex(item)); + mouseOverHighlightNthItem(getItemIndex(item)); } else { - highlightNthItem(-1); + mouseOverHighlightNthItem(-1); } } @@ -2234,6 +1996,11 @@ BOOL LLScrollListCtrl::handleHover(S32 x,S32 y,MASK mask) return handled; } +void LLScrollListCtrl::onMouseLeave(S32 x, S32 y, MASK mask) +{ + // clear mouse highlight + mouseOverHighlightNthItem(-1); +} BOOL LLScrollListCtrl::handleKeyHere(KEY key,MASK mask ) { @@ -2378,7 +2145,8 @@ BOOL LLScrollListCtrl::handleUnicodeCharHere(llwchar uni_char) } // perform incremental search based on keyboard input - if (mSearchTimer.getElapsedTimeF32() > LLUI::sConfigGroup->getF32("TypeAheadTimeout")) + static LLUICachedControl<F32> type_ahead_timeout ("TypeAheadTimeout", 0); + if (mSearchTimer.getElapsedTimeF32() > type_ahead_timeout) { mSearchString.clear(); } @@ -2554,9 +2322,11 @@ BOOL LLScrollListCtrl::setSort(S32 column_idx, BOOL ascending) LLScrollListColumn* sort_column = getColumn(column_idx); if (!sort_column) return FALSE; - sort_column->mSortAscending = ascending; + sort_column->mSortDirection = ascending ? LLScrollListColumn::ASCENDING : LLScrollListColumn::DESCENDING; sort_column_t new_sort_column(column_idx, ascending); + + setNeedsSort(); if (mSortColumns.empty()) { @@ -2578,12 +2348,24 @@ BOOL LLScrollListCtrl::setSort(S32 column_idx, BOOL ascending) } } +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 -//static -void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar, void* userdata ) +void LLScrollListCtrl::onScrollChange( S32 new_pos, LLScrollbar* scrollbar ) { - LLScrollListCtrl* self = (LLScrollListCtrl*) userdata; - self->mScrollLines = new_pos; + mScrollLines = new_pos; } @@ -2599,21 +2381,22 @@ void LLScrollListCtrl::sortByColumn(const std::string& name, BOOL ascending) // First column is column 0 void LLScrollListCtrl::sortByColumnIndex(U32 column, BOOL ascending) { - if (setSort(column, ascending)) - { - sortItems(); - } + setSort(column, ascending); + updateSort(); } -void LLScrollListCtrl::sortItems() +void LLScrollListCtrl::updateSort() const { - // do stable sort to preserve any previous sorts - std::stable_sort( - mItemList.begin(), - mItemList.end(), - SortScrollListItem(mSortColumns)); + if (hasSortOrder() && !isSorted()) + { + // do stable sort to preserve any previous sorts + std::stable_sort( + mItemList.begin(), + mItemList.end(), + SortScrollListItem(mSortColumns)); - setSorted(TRUE); + mSorted = true; + } } // for one-shot sorts, does not save sort column/order @@ -2656,7 +2439,7 @@ void LLScrollListCtrl::setScrollPos( S32 pos ) { mScrollbar->setDocPos( pos ); - onScrollChange(mScrollbar->getDocPos(), mScrollbar, this); + onScrollChange(mScrollbar->getDocPos(), mScrollbar); } @@ -2669,10 +2452,7 @@ void LLScrollListCtrl::scrollToShowSelected() return; } - if (needsSorting() && !isSorted()) - { - sortItems(); - } + updateSort(); S32 index = getFirstSelectedIndex(); if (index < 0) @@ -2688,7 +2468,8 @@ void LLScrollListCtrl::scrollToShowSelected() } S32 lowest = mScrollLines; - S32 highest = mScrollLines + mPageLines; + S32 page_lines = getLinesPerPage(); + S32 highest = mScrollLines + page_lines; if (index < lowest) { @@ -2697,7 +2478,7 @@ void LLScrollListCtrl::scrollToShowSelected() } else if (highest <= index) { - setScrollPos(index - mPageLines + 1); + setScrollPos(index - page_lines + 1); } } @@ -2706,293 +2487,6 @@ void LLScrollListCtrl::updateStaticColumnWidth(LLScrollListColumn* col, S32 new_ mTotalStaticColumnWidth += llmax(0, new_width) - llmax(0, col->getWidth()); } - -// virtual -LLXMLNodePtr LLScrollListCtrl::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - // Attributes - - node->createChild("multi_select", TRUE)->setBoolValue(mAllowMultipleSelection); - - node->createChild("draw_border", TRUE)->setBoolValue((mBorder != NULL)); - - node->createChild("draw_heading", TRUE)->setBoolValue(mDisplayColumnHeaders); - - node->createChild("background_visible", TRUE)->setBoolValue(mBackgroundVisible); - - node->createChild("draw_stripes", TRUE)->setBoolValue(mDrawStripes); - - node->createChild("column_padding", TRUE)->setIntValue(mColumnPadding); - - addColorXML(node, mBgWriteableColor, "bg_writeable_color", "ScrollBgWriteableColor"); - addColorXML(node, mBgReadOnlyColor, "bg_read_only_color", "ScrollBgReadOnlyColor"); - addColorXML(node, mBgSelectedColor, "bg_selected_color", "ScrollSelectedBGColor"); - addColorXML(node, mBgStripeColor, "bg_stripe_color", "ScrollBGStripeColor"); - addColorXML(node, mFgSelectedColor, "fg_selected_color", "ScrollSelectedFGColor"); - addColorXML(node, mFgUnselectedColor, "fg_unselected_color", "ScrollUnselectedColor"); - addColorXML(node, mFgDisabledColor, "fg_disable_color", "ScrollDisabledColor"); - addColorXML(node, mHighlightedColor, "highlighted_color", "ScrollHighlightedColor"); - - // Contents - - std::map<std::string, LLScrollListColumn>::const_iterator itor; - std::vector<const LLScrollListColumn*> sorted_list; - sorted_list.resize(mColumns.size()); - for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) - { - sorted_list[itor->second.mIndex] = &itor->second; - } - - std::vector<const LLScrollListColumn*>::iterator itor2; - for (itor2 = sorted_list.begin(); itor2 != sorted_list.end(); ++itor2) - { - LLXMLNodePtr child_node = node->createChild("column", FALSE); - const LLScrollListColumn *column = *itor2; - - child_node->createChild("name", TRUE)->setStringValue(column->mName); - child_node->createChild("label", TRUE)->setStringValue(column->mLabel); - child_node->createChild("width", TRUE)->setIntValue(column->getWidth()); - } - - return node; -} - -void LLScrollListCtrl::setScrollListParameters(LLXMLNodePtr node) -{ - // James: This is not a good way to do colors. We need a central "UI style" - // manager that sets the colors for ALL scroll lists, buttons, etc. - - LLColor4 color; - if(node->hasAttribute("fg_unselected_color")) - { - LLUICtrlFactory::getAttributeColor(node,"fg_unselected_color", color); - setFgUnselectedColor(color); - } - if(node->hasAttribute("fg_selected_color")) - { - LLUICtrlFactory::getAttributeColor(node,"fg_selected_color", color); - setFgSelectedColor(color); - } - if(node->hasAttribute("bg_selected_color")) - { - LLUICtrlFactory::getAttributeColor(node,"bg_selected_color", color); - setBgSelectedColor(color); - } - if(node->hasAttribute("fg_disable_color")) - { - LLUICtrlFactory::getAttributeColor(node,"fg_disable_color", color); - setFgDisableColor(color); - } - if(node->hasAttribute("bg_writeable_color")) - { - LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color); - setBgWriteableColor(color); - } - if(node->hasAttribute("bg_read_only_color")) - { - LLUICtrlFactory::getAttributeColor(node,"bg_read_only_color", color); - setReadOnlyBgColor(color); - } - if (LLUICtrlFactory::getAttributeColor(node,"bg_stripe_color", color)) - { - setBgStripeColor(color); - } - if (LLUICtrlFactory::getAttributeColor(node,"highlighted_color", color)) - { - setHighlightedColor(color); - } - - if(node->hasAttribute("background_visible")) - { - BOOL background_visible; - node->getAttributeBOOL("background_visible", background_visible); - setBackgroundVisible(background_visible); - } - - if(node->hasAttribute("draw_stripes")) - { - BOOL draw_stripes; - node->getAttributeBOOL("draw_stripes", draw_stripes); - setDrawStripes(draw_stripes); - } - - if(node->hasAttribute("column_padding")) - { - S32 column_padding; - node->getAttributeS32("column_padding", column_padding); - setColumnPadding(column_padding); - } -} - -// static -LLView* LLScrollListCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("scroll_list"); - node->getAttributeString("name", name); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - BOOL multi_select = FALSE; - node->getAttributeBOOL("multi_select", multi_select); - - BOOL draw_border = TRUE; - node->getAttributeBOOL("draw_border", draw_border); - - BOOL draw_heading = FALSE; - node->getAttributeBOOL("draw_heading", draw_heading); - - S32 search_column = 0; - node->getAttributeS32("search_column", search_column); - - S32 sort_column = -1; - node->getAttributeS32("sort_column", sort_column); - - BOOL sort_ascending = TRUE; - node->getAttributeBOOL("sort_ascending", sort_ascending); - - LLUICtrlCallback callback = NULL; - - LLScrollListCtrl* scroll_list = new LLScrollListCtrl( - name, - rect, - callback, - NULL, - multi_select, - draw_border); - - scroll_list->setDisplayHeading(draw_heading); - if (node->hasAttribute("heading_height")) - { - S32 heading_height; - node->getAttributeS32("heading_height", heading_height); - scroll_list->setHeadingHeight(heading_height); - } - - scroll_list->setScrollListParameters(node); - - scroll_list->initFromXML(node, parent); - - scroll_list->setSearchColumn(search_column); - - LLSD columns; - S32 index = 0; - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - if (child->hasName("column")) - { - std::string labelname(""); - child->getAttributeString("label", labelname); - - std::string columnname(labelname); - child->getAttributeString("name", columnname); - - std::string sortname(columnname); - child->getAttributeString("sort", sortname); - - BOOL sort_ascending = TRUE; - child->getAttributeBOOL("sort_ascending", sort_ascending); - - std::string imagename; - child->getAttributeString("image", imagename); - - BOOL columndynamicwidth = FALSE; - child->getAttributeBOOL("dynamicwidth", columndynamicwidth); - - S32 columnwidth = -1; - child->getAttributeS32("width", columnwidth); - - std::string tooltip; - child->getAttributeString("tool_tip", tooltip); - - F32 columnrelwidth = 0.f; - child->getAttributeF32("relwidth", columnrelwidth); - - LLFontGL::HAlign h_align = LLFontGL::LEFT; - h_align = LLView::selectFontHAlign(child); - - columns[index]["name"] = columnname; - columns[index]["sort"] = sortname; - columns[index]["sort_ascending"] = sort_ascending; - columns[index]["image"] = imagename; - columns[index]["label"] = labelname; - columns[index]["width"] = columnwidth; - columns[index]["relwidth"] = columnrelwidth; - columns[index]["dynamicwidth"] = columndynamicwidth; - columns[index]["halign"] = (S32)h_align; - columns[index]["tool_tip"] = tooltip; - - index++; - } - } - scroll_list->setColumnHeadings(columns); - - if (sort_column >= 0) - { - scroll_list->sortByColumnIndex(sort_column, sort_ascending); - } - - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - if (child->hasName("row")) - { - LLUUID id; - child->getAttributeUUID("id", id); - - LLSD row; - - row["id"] = id; - - S32 column_idx = 0; - LLXMLNodePtr row_child; - for (row_child = child->getFirstChild(); row_child.notNull(); row_child = row_child->getNextSibling()) - { - if (row_child->hasName("column")) - { - std::string value = row_child->getTextContents(); - - std::string columnname(""); - row_child->getAttributeString("name", columnname); - - std::string font(""); - row_child->getAttributeString("font", font); - - std::string font_style(""); - row_child->getAttributeString("font-style", font_style); - - row["columns"][column_idx]["column"] = columnname; - row["columns"][column_idx]["value"] = value; - row["columns"][column_idx]["font"] = font; - row["columns"][column_idx]["font-style"] = font_style; - column_idx++; - } - } - scroll_list->addElement(row); - } - } - - std::string contents = node->getTextContents(); - if (!contents.empty()) - { - typedef boost::tokenizer<boost::char_separator<char> > tokenizer; - boost::char_separator<char> sep("\t\n"); - tokenizer tokens(contents, sep); - tokenizer::iterator token_iter = tokens.begin(); - - while(token_iter != tokens.end()) - { - const std::string& line = *token_iter; - scroll_list->addSimpleElement(line); - ++token_iter; - } - } - - return scroll_list; -} - // LLEditMenuHandler functions // virtual @@ -3068,20 +2562,27 @@ BOOL LLScrollListCtrl::canDeselect() const void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) { - std::string name = column["name"].asString(); + LLScrollListColumn::Params p; + LLParamSDParser::instance().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()) { - std::ostringstream new_name; - new_name << mColumnsIndexed.size(); - name = new_name.str(); + name = llformat("%d", mColumnsIndexed.size()); } + if (mColumns.find(name) == mColumns.end()) { // Add column - mColumns[name] = LLScrollListColumn(column, this); + mColumns[name] = LLScrollListColumn(column_params, this); LLScrollListColumn* new_column = &mColumns[name]; - new_column->mParentCtrl = this; new_column->mIndex = mColumns.size()-1; // Add button @@ -3101,44 +2602,47 @@ void LLScrollListCtrl::addColumn(const LLSD& column, EAddPosition pos) new_column->setWidth((mItemListRect.getWidth() - mTotalStaticColumnWidth - mTotalColumnPadding) / mNumDynamicWidthColumns); } S32 top = mItemListRect.mTop; + S32 left = mItemListRect.mLeft; + for (std::map<std::string, LLScrollListColumn>::iterator itor = mColumns.begin(); + itor != mColumns.end(); + ++itor) { - std::map<std::string, LLScrollListColumn>::iterator itor; - for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) + if (itor->second.mIndex < new_column->mIndex && + itor->second.getWidth() > 0) { - if (itor->second.mIndex < new_column->mIndex && - itor->second.getWidth() > 0) - { - left += itor->second.getWidth() + mColumnPadding; - } + left += itor->second.getWidth() + mColumnPadding; } } - std::string button_name = "btn_" + name; + 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); - new_column->mHeader = new LLColumnHeader(button_name, temp_rect, new_column); - if(column["image"].asString() != "") + + LLScrollColumnHeader::Params params(LLUICtrlFactory::getDefaultParams<LLScrollColumnHeader>()); + 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()) { - //new_column->mHeader->setScaleImage(false); - new_column->mHeader->setImage(column["image"].asString()); + params.image_selected = column_params.header.image; + params.image_unselected = column_params.header.image; } else { - new_column->mHeader->setLabel(new_column->mLabel); - //new_column->mHeader->setLabel(new_column->mLabel); + params.label = column_params.header.label; } - new_column->mHeader->setToolTip(column["tool_tip"].asString()); - - //RN: although it might be useful to change sort order with the keyboard, - // mixing tab stops on child items along with the parent item is not supported yet - new_column->mHeader->setTabStop(FALSE); + new_column->mHeader = LLUICtrlFactory::create<LLScrollColumnHeader>(params); addChild(new_column->mHeader); - new_column->mHeader->setVisible(mDisplayColumnHeaders); sendChildToFront(mScrollbar); } @@ -3159,7 +2663,7 @@ void LLScrollListCtrl::onClickColumn(void *userdata) S32 column_index = info->mIndex; LLScrollListColumn* column = parent->mColumnsIndexed[info->mIndex]; - bool ascending = column->mSortAscending; + bool ascending = column->mSortDirection == LLScrollListColumn::ASCENDING; if (column->mSortingColumn != column->mName && parent->mColumns.find(column->mSortingColumn) != parent->mColumns.end()) { @@ -3178,7 +2682,7 @@ void LLScrollListCtrl::onClickColumn(void *userdata) if (parent->mOnSortChangedCallback) { - parent->mOnSortChangedCallback(parent->getCallbackUserData()); + parent->mOnSortChangedCallback(); } } @@ -3190,7 +2694,7 @@ std::string LLScrollListCtrl::getSortColumnName() else return ""; } -BOOL LLScrollListCtrl::needsSorting() +BOOL LLScrollListCtrl::hasSortOrder() const { return !mSortColumns.empty(); } @@ -3200,7 +2704,7 @@ void LLScrollListCtrl::clearColumns() std::map<std::string, LLScrollListColumn>::iterator itor; for (itor = mColumns.begin(); itor != mColumns.end(); ++itor) { - LLColumnHeader *header = itor->second.mHeader; + LLScrollColumnHeader *header = itor->second.mHeader; if (header) { removeChild(header); @@ -3215,13 +2719,13 @@ void LLScrollListCtrl::clearColumns() void LLScrollListCtrl::setColumnLabel(const std::string& column, const std::string& label) { - std::map<std::string, LLScrollListColumn>::iterator itor = mColumns.find(column); - if (itor != mColumns.end()) + LLScrollListColumn* columnp = getColumn(column); + if (columnp) { - itor->second.mLabel = label; - if (itor->second.mHeader) + columnp->mLabel = label; + if (columnp->mHeader) { - itor->second.mHeader->setLabel(label); + columnp->mHeader->setLabel(label); } } } @@ -3235,74 +2739,62 @@ LLScrollListColumn* LLScrollListCtrl::getColumn(S32 index) return mColumnsIndexed[index]; } -void LLScrollListCtrl::setColumnHeadings(LLSD headings) +LLScrollListColumn* LLScrollListCtrl::getColumn(const std::string& name) { - mColumns.clear(); - LLSD::array_const_iterator itor; - for (itor = headings.beginArray(); itor != headings.endArray(); ++itor) + column_map_t::iterator column_itor = mColumns.find(name); + if (column_itor != mColumns.end()) { - addColumn(*itor); + return &column_itor->second; } + return NULL; } -LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition pos, void* userdata) + +LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata) { - // ID - LLSD id = value["id"]; + LLScrollListItem::Params item_params; + LLParamSDParser::instance().readSD(element, item_params); + item_params.userdata = userdata; + return addRow(item_params, pos); +} - LLScrollListItem *new_item = new LLScrollListItem(id, userdata); - if (value.has("enabled")) - { - new_item->setEnabled( value["enabled"].asBoolean() ); - } +LLScrollListItem* LLScrollListCtrl::addRow(const LLScrollListItem::Params& item_p, EAddPosition pos) +{ + if (!item_p.validateBlock()) return NULL; + LLScrollListItem *new_item = new LLScrollListItem(item_p); new_item->setNumColumns(mColumns.size()); // Add any columns we don't already have - LLSD columns = value["columns"]; - LLSD::array_const_iterator itor; - S32 col_index = 0 ; - for (itor = columns.beginArray(); itor != columns.endArray(); ++itor) - { - if (itor->isUndefined()) - { - // skip unused columns in item passed in - continue; - } - std::string column = (*itor)["column"].asString(); + S32 col_index = 0; - LLScrollListColumn* columnp = NULL; + for(LLInitParam::ParamIterator<LLScrollListCell::Params>::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()) { - std::ostringstream new_name; - new_name << col_index; - column = new_name.str(); + column = llformat("%d", col_index); } - std::map<std::string, LLScrollListColumn>::iterator column_itor; - column_itor = mColumns.find(column); - if (column_itor != mColumns.end()) - { - columnp = &column_itor->second; - } + LLScrollListColumn* columnp = getColumn(column); // create new column on demand if (!columnp) { - LLSD new_column; - new_column["name"] = column; - new_column["label"] = column; + 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 (itor->has("width")) + if (cell_p.width.isProvided()) { - new_column["width"] = (*itor)["width"]; - } - else - { - new_column["dynamicwidth"] = true; + new_column.width.pixel_width = cell_p.width; } addColumn(new_column); columnp = &mColumns[column]; @@ -3310,91 +2802,48 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p } S32 index = columnp->mIndex; - S32 width = columnp->getWidth(); - LLFontGL::HAlign font_alignment = columnp->mFontAlignment; - LLColor4 fcolor = LLColor4::black; - - LLSD value = (*itor)["value"]; - std::string fontname = (*itor)["font"].asString(); - std::string fontstyle = (*itor)["font-style"].asString(); - std::string type = (*itor)["type"].asString(); - - if ((*itor).has("font-color")) - { - LLSD sd_color = (*itor)["font-color"]; - fcolor.setValue(sd_color); - } - - BOOL has_color = (*itor).has("color"); - LLColor4 color = ((*itor)["color"]); - BOOL enabled = !(*itor).has("enabled") || (*itor)["enabled"].asBoolean() == true; + cell_p.width.setIfNotProvided(columnp->getWidth()); - const LLFontGL *font = LLResMgr::getInstance()->getRes(fontname); - if (!font) - { - font = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ); - } - U8 font_style = LLFontGL::getStyleFromString(fontstyle); + LLScrollListCell* cell = LLScrollListCell::create(cell_p); - if (type == "icon") - { - LLScrollListIcon* cell = new LLScrollListIcon(value, width); - if (has_color) - { - cell->setColor(color); - } - new_item->setColumn(index, cell); - } - else if (type == "checkbox") + if (cell) { - LLCheckBoxCtrl* ctrl = new LLCheckBoxCtrl(std::string("check"), - LLRect(0, width, width, 0), std::string(" ")); - ctrl->setEnabled(enabled); - ctrl->setValue(value); - LLScrollListCheck* cell = new LLScrollListCheck(ctrl,width); - if (has_color) - { - cell->setColor(color); - } new_item->setColumn(index, cell); - } - else if (type == "separator") - { - LLScrollListSeparator* cell = new LLScrollListSeparator(width); - if (has_color) + if (columnp->mHeader + && cell->isText() + && !cell->getValue().asString().empty()) { - cell->setColor(color); + columnp->mHeader->setHasResizableElement(TRUE); } - new_item->setColumn(index, cell); } - else if (type == "date") + + col_index++; + } + + if (item_p.columns().empty()) + { + if (mColumns.empty()) { - LLScrollListDate* cell = new LLScrollListDate(value.asDate(), font, width, font_style, font_alignment); - if (has_color) - { - cell->setColor(color); - } - new_item->setColumn(index, cell); - if (columnp->mHeader && !value.asString().empty()) - { - columnp->mHeader->setHasResizableElement(TRUE); - } + LLScrollListColumn::Params new_column; + new_column.name = "0"; + + addColumn(new_column); + new_item->setNumColumns(mColumns.size()); } - else + + LLScrollListCell* cell = LLScrollListCell::create(LLScrollListCell::Params().value(item_p.value)); + if (cell) { - LLScrollListText* cell = new LLScrollListText(value.asString(), font, width, font_style, font_alignment, fcolor, TRUE); - if (has_color) - { - cell->setColor(color); - } - new_item->setColumn(index, cell); - if (columnp->mHeader && !value.asString().empty()) + LLScrollListColumn* columnp = &(mColumns.begin()->second); + + new_item->setColumn(0, cell); + if (columnp->mHeader + && cell->isText() + && !cell->getValue().asString().empty()) { columnp->mHeader->setHasResizableElement(TRUE); } } - - col_index++; } // add dummy cells for missing columns @@ -3404,12 +2853,14 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& value, EAddPosition p if (new_item->getColumn(column_idx) == NULL) { LLScrollListColumn* column_ptr = &column_it->second; - new_item->setColumn(column_idx, new LLScrollListText(LLStringUtil::null, LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ), column_ptr->getWidth(), LLFontGL::NORMAL)); + 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; } @@ -3422,14 +2873,13 @@ LLScrollListItem* LLScrollListCtrl::addSimpleElement(const std::string& value, E entry_id = value; } - LLScrollListItem *new_item = new LLScrollListItem(entry_id); - - const LLFontGL *font = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ); - - new_item->addColumn(value, font, getRect().getWidth()); - - addItem(new_item, pos); - return new_item; + LLScrollListItem::Params item_params; + item_params.value(entry_id); + item_params.columns.add() + .value(value) + .font(LLFontGL::getFontSansSerifSmall()); + + return addRow(item_params, pos); } void LLScrollListCtrl::setValue(const LLSD& value ) @@ -3530,484 +2980,3 @@ void LLScrollListCtrl::onFocusLost() LLUICtrl::onFocusLost(); } -LLColumnHeader::LLColumnHeader(const std::string& label, const LLRect &rect, LLScrollListColumn* column, const LLFontGL* fontp) : - LLComboBox(label, rect, label, NULL, NULL), - mColumn(column), - mOrigLabel(label), - mShowSortOptions(FALSE), - mHasResizableElement(FALSE) -{ - mListPosition = LLComboBox::ABOVE; - setCommitCallback(onSelectSort); - setCallbackUserData(this); - mButton->setTabStop(FALSE); - // require at least two frames between mouse down and mouse up event to capture intentional "hold" not just bad framerate - mButton->setHeldDownDelay(LLUI::sConfigGroup->getF32("ColumnHeaderDropDownDelay"), 2); - mButton->setHeldDownCallback(onHeldDown); - mButton->setClickedCallback(onClick); - mButton->setMouseDownCallback(onMouseDown); - - mButton->setCallbackUserData(this); - mButton->setToolTip(label); - - mAscendingText = std::string("[LOW]...[HIGH](Ascending)"); // *TODO: Translate - mDescendingText = std::string("[HIGH]...[LOW](Descending)"); // *TODO: Translate - - mList->reshape(llmax(mList->getRect().getWidth(), 110, getRect().getWidth()), mList->getRect().getHeight()); - - // resize handles on left and right - const S32 RESIZE_BAR_THICKNESS = 3; - mResizeBar = new LLResizeBar( - std::string("resizebar"), - this, - LLRect( getRect().getWidth() - RESIZE_BAR_THICKNESS, getRect().getHeight(), getRect().getWidth(), 0), - MIN_COLUMN_WIDTH, S32_MAX, LLResizeBar::RIGHT ); - addChild(mResizeBar); - - mResizeBar->setEnabled(FALSE); -} - -LLColumnHeader::~LLColumnHeader() -{ -} - -void LLColumnHeader::draw() -{ - BOOL draw_arrow = !mColumn->mLabel.empty() && mColumn->mParentCtrl->isSorted() && mColumn->mParentCtrl->getSortColumnName() == mColumn->mSortingColumn; - - BOOL is_ascending = mColumn->mParentCtrl->getSortAscending(); - mButton->setImageOverlay(is_ascending ? "up_arrow.tga" : "down_arrow.tga", LLFontGL::RIGHT, draw_arrow ? LLColor4::white : LLColor4::transparent); - mArrowImage = mButton->getImageOverlay(); - - //BOOL clip = getRect().mRight > mColumn->mParentCtrl->getItemListRect().getWidth(); - //LLGLEnable scissor_test(clip ? GL_SCISSOR_TEST : GL_FALSE); - - //LLRect column_header_local_rect(-getRect().mLeft, getRect().getHeight(), mColumn->mParentCtrl->getItemListRect().getWidth() - getRect().mLeft, 0); - //LLUI::setScissorRegionLocal(column_header_local_rect); - - // Draw children - LLComboBox::draw(); - - if (mList->getVisible()) - { - // sync sort order with list selection every frame - mColumn->mParentCtrl->sortByColumn(mColumn->mSortingColumn, getCurrentIndex() == 0); - } -} - -BOOL LLColumnHeader::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if (canResize() && mResizeBar->getRect().pointInRect(x, y)) - { - // reshape column to max content width - LLRect column_rect = getRect(); - column_rect.mRight = column_rect.mLeft + mColumn->mMaxContentWidth; - userSetShape(column_rect); - } - else - { - onClick(this); - } - return TRUE; -} - -void LLColumnHeader::setImage(const std::string &image_name) -{ - if (mButton) - { - mButton->setImageSelected(image_name); - mButton->setImageUnselected(image_name); - } -} - -//static -void LLColumnHeader::onClick(void* user_data) -{ - LLColumnHeader* headerp = (LLColumnHeader*)user_data; - if (!headerp) return; - - LLScrollListColumn* column = headerp->mColumn; - if (!column) return; - - if (headerp->mList->getVisible()) - { - headerp->hideList(); - } - - LLScrollListCtrl::onClickColumn(column); - - // propagate new sort order to sort order list - headerp->mList->selectNthItem(column->mParentCtrl->getSortAscending() ? 0 : 1); -} - -//static -void LLColumnHeader::onMouseDown(void* user_data) -{ - // for now, do nothing but block the normal showList() behavior - return; -} - -//static -void LLColumnHeader::onHeldDown(void* user_data) -{ - LLColumnHeader* headerp = (LLColumnHeader*)user_data; - headerp->showList(); -} - -void LLColumnHeader::showList() -{ - if (mShowSortOptions) - { - //LLSD item_val = mColumn->mParentCtrl->getFirstData()->getValue(); - mOrigLabel = mButton->getLabelSelected(); - - // move sort column over to this column and do initial sort - mColumn->mParentCtrl->sortByColumn(mColumn->mSortingColumn, mColumn->mParentCtrl->getSortAscending()); - - std::string low_item_text; - std::string high_item_text; - - LLScrollListItem* itemp = mColumn->mParentCtrl->getFirstData(); - if (itemp) - { - LLScrollListCell* cell = itemp->getColumn(mColumn->mIndex); - if (cell && cell->isText()) - { - if (mColumn->mParentCtrl->getSortAscending()) - { - low_item_text = cell->getValue().asString(); - } - else - { - high_item_text = cell->getValue().asString(); - } - } - } - - itemp = mColumn->mParentCtrl->getLastData(); - if (itemp) - { - LLScrollListCell* cell = itemp->getColumn(mColumn->mIndex); - if (cell && cell->isText()) - { - if (mColumn->mParentCtrl->getSortAscending()) - { - high_item_text = cell->getValue().asString(); - } - else - { - low_item_text = cell->getValue().asString(); - } - } - } - - LLStringUtil::truncate(low_item_text, 3); - LLStringUtil::truncate(high_item_text, 3); - - std::string ascending_string; - std::string descending_string; - - if (low_item_text.empty() || high_item_text.empty()) - { - ascending_string = "Ascending"; - descending_string = "Descending"; - } - else - { - mAscendingText.setArg("[LOW]", low_item_text); - mAscendingText.setArg("[HIGH]", high_item_text); - mDescendingText.setArg("[LOW]", low_item_text); - mDescendingText.setArg("[HIGH]", high_item_text); - ascending_string = mAscendingText.getString(); - descending_string = mDescendingText.getString(); - } - - S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(ascending_string); - text_width = llmax(text_width, LLFontGL::getFontSansSerifSmall()->getWidth(descending_string)) + 10; - text_width = llmax(text_width, getRect().getWidth() - 30); - - mList->getColumn(0)->setWidth(text_width); - ((LLScrollListText*)mList->getFirstData()->getColumn(0))->setText(ascending_string); - ((LLScrollListText*)mList->getLastData()->getColumn(0))->setText(descending_string); - - mList->reshape(llmax(text_width + 30, 110, getRect().getWidth()), mList->getRect().getHeight()); - - LLComboBox::showList(); - } -} - -//static -void LLColumnHeader::onSelectSort(LLUICtrl* ctrl, void* user_data) -{ - LLColumnHeader* headerp = (LLColumnHeader*)user_data; - if (!headerp) return; - - LLScrollListColumn* column = headerp->mColumn; - if (!column) return; - LLScrollListCtrl *parent = column->mParentCtrl; - if (!parent) return; - - if (headerp->getCurrentIndex() == 0) - { - // ascending - parent->sortByColumn(column->mSortingColumn, TRUE); - } - else - { - // descending - parent->sortByColumn(column->mSortingColumn, FALSE); - } - - // restore original column header - headerp->setLabel(headerp->mOrigLabel); -} - -LLView* LLColumnHeader::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(); - - 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 LLColumnHeader::userSetShape(const LLRect& new_rect) -{ - 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->mDynamicWidth && */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->mDynamicWidth && */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 - mColumn->mParentCtrl->updateColumns(); - } -} - -void LLColumnHeader::setHasResizableElement(BOOL resizable) -{ - // for now, dynamically spaced columns can't be resized -// if (mColumn->mDynamicWidth) return; - - if (mHasResizableElement != resizable) - { - mColumn->mParentCtrl->dirtyColumns(); - mHasResizableElement = resizable; - } -} - -void LLColumnHeader::updateResizeBars() -{ - S32 num_resizable_columns = 0; - S32 col; - for (col = 0; col < mColumn->mParentCtrl->getNumColumns(); col++) - { - LLScrollListColumn* columnp = mColumn->mParentCtrl->getColumn(col); - if (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->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 LLColumnHeader::enableResizeBar(BOOL enable) -{ - // for now, dynamically spaced columns can't be resized - //if (!mColumn->mDynamicWidth) - { - mResizeBar->setEnabled(enable); - } -} - -BOOL LLColumnHeader::canResize() -{ - return getVisible() && (mHasResizableElement || mColumn->mDynamicWidth); -} - -void LLScrollListColumn::setWidth(S32 width) -{ - if (!mDynamicWidth && mRelWidth <= 0.f) - { - mParentCtrl->updateStaticColumnWidth(this, width); - } - mWidth = width; -} - -// Default constructor -LLScrollListColumn::LLScrollListColumn() : - mName(), - mSortingColumn(), - mSortAscending(TRUE), - mLabel(), - mWidth(-1), - mRelWidth(-1.0), - mDynamicWidth(FALSE), - mMaxContentWidth(0), - mIndex(-1), - mParentCtrl(NULL), - mHeader(NULL), - mFontAlignment(LLFontGL::LEFT) -{ } - -LLScrollListColumn::LLScrollListColumn(const LLSD &sd, LLScrollListCtrl* parent) : - mWidth(0), - mIndex (-1), - mParentCtrl(parent), - mHeader(NULL), - mMaxContentWidth(0), - mDynamicWidth(FALSE), - mRelWidth(-1.f) -{ - mName = sd.get("name").asString(); - mSortingColumn = mName; - if (sd.has("sort")) - { - mSortingColumn = sd.get("sort").asString(); - } - mSortAscending = TRUE; - if (sd.has("sort_ascending")) - { - mSortAscending = sd.get("sort_ascending").asBoolean(); - } - mLabel = sd.get("label").asString(); - if (sd.has("relwidth") && (F32)sd.get("relwidth").asReal() > 0) - { - mRelWidth = (F32)sd.get("relwidth").asReal(); - if (mRelWidth < 0) mRelWidth = 0; - if (mRelWidth > 1) mRelWidth = 1; - mDynamicWidth = FALSE; - } - else if(sd.has("dynamicwidth") && (BOOL)sd.get("dynamicwidth").asBoolean() == TRUE) - { - mDynamicWidth = TRUE; - mRelWidth = -1; - } - else - { - - setWidth(sd.get("width").asInteger()); - } - - if (sd.has("halign")) - { - mFontAlignment = (LLFontGL::HAlign)llclamp(sd.get("halign").asInteger(), (S32)LLFontGL::LEFT, (S32)LLFontGL::HCENTER); - } - else - { - mFontAlignment = LLFontGL::LEFT; - } - -} diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index 72d8894afa..907dc90bea 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -1,5 +1,8 @@ /** * @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=viewergpl$ * @@ -37,346 +40,115 @@ #include "lluictrl.h" #include "llctrlselectioninterface.h" -#include "lldarray.h" +//#include "lldarray.h" #include "llfontgl.h" #include "llui.h" -#include "llstring.h" -#include "llimagegl.h" +#include "llstring.h" // LLWString #include "lleditmenuhandler.h" #include "llframetimer.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" + #include "llscrollbar.h" -#include "llresizebar.h" #include "lldate.h" +#include "llscrolllistitem.h" +#include "llscrolllistcolumn.h" +#include "llviewborder.h" -/* - * 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: - LLScrollListCell(S32 width = 0) : mWidth(width) {}; - virtual ~LLScrollListCell() {}; - virtual void draw(const LLColor4& color, const LLColor4& highlight_color) const = 0; // truncate to given width, if possible - virtual S32 getWidth() const {return mWidth;} - virtual S32 getContentWidth() const { return 0; } - virtual S32 getHeight() const = 0; - virtual const LLSD getValue() const { return LLStringUtil::null; } - virtual void setValue(const LLSD& value) { } - 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 = 0; - virtual void setColor(const LLColor4&) {} - virtual void onCommit() {}; - - virtual BOOL handleClick() { return FALSE; } - virtual void setEnabled(BOOL enable) { } - -private: - S32 mWidth; -}; - -/* - * Draws a horizontal line. - */ -class LLScrollListSeparator : public LLScrollListCell -{ -public: - LLScrollListSeparator(S32 width); - virtual ~LLScrollListSeparator() {}; - virtual void draw(const LLColor4& color, const LLColor4& highlight_color) const; // truncate to given width, if possible - virtual S32 getHeight() const; - virtual BOOL isText() const { return FALSE; } -}; - -/* - * Cell displaying a text label. - */ -class LLScrollListText : public LLScrollListCell -{ -public: - LLScrollListText( const std::string& text, const LLFontGL* font, S32 width = 0, U8 font_style = LLFontGL::NORMAL, LLFontGL::HAlign font_alignment = LLFontGL::LEFT, LLColor4& color = LLColor4::black, BOOL use_color = FALSE, BOOL visible = TRUE); - /*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 const LLSD getValue() const; - virtual BOOL getVisible() const; - virtual void highlightText(S32 offset, S32 num_chars); - - virtual void setColor(const LLColor4&); - virtual BOOL isText() const; - - void setText(const LLStringExplicit& text); - void setFontStyle(const U8 font_style) { mFontStyle = font_style; } - -private: - LLUIString mText; - const LLFontGL* mFont; - LLColor4 mColor; - U8 mUseColor; - U8 mFontStyle; - LLFontGL::HAlign mFontAlignment; - BOOL mVisible; - S32 mHighlightCount; - S32 mHighlightOffset; - - LLPointer<LLUIImage> mRoundedRectImage; - - static U32 sCount; -}; - - -class LLScrollListDate : public LLScrollListText -{ -public: - LLScrollListDate( const LLDate& date, const LLFontGL* font, S32 width=0, U8 font_style = LLFontGL::NORMAL, LLFontGL::HAlign font_alignment = LLFontGL::LEFT, LLColor4& color = LLColor4::black, BOOL use_color = FALSE, BOOL visible = TRUE); - virtual void setValue(const LLSD& value); - virtual const LLSD getValue() const; - -private: - LLDate mDate; -}; - -/* - * Cell displaying an image. - */ -class LLScrollListIcon : public LLScrollListCell -{ -public: - LLScrollListIcon( LLUIImagePtr icon, S32 width = 0); - LLScrollListIcon(const LLSD& value, S32 width = 0); - /*virtual*/ ~LLScrollListIcon(); - virtual void draw(const LLColor4& color, const LLColor4& highlight_color) const; - virtual S32 getWidth() const; - virtual S32 getHeight() const { return mIcon ? mIcon->getHeight() : 0; } - virtual const LLSD getValue() const { return mIcon.isNull() ? LLStringUtil::null : mIcon->getName(); } - virtual void setColor(const LLColor4&); - virtual BOOL isText()const { return FALSE; } - virtual void setValue(const LLSD& value); - -private: - LLUIImagePtr mIcon; - LLColor4 mColor; -}; - -/* - * An interactive cell containing a check box. - */ -class LLScrollListCheck : public LLScrollListCell -{ -public: - LLScrollListCheck( LLCheckBoxCtrl* check_box, S32 width = 0); - /*virtual*/ ~LLScrollListCheck(); - virtual void draw(const LLColor4& color, const LLColor4& highlight_color) const; - virtual S32 getHeight() const { return 0; } - virtual const LLSD getValue() const { return mCheckBox->getValue(); } - virtual void setValue(const LLSD& value) { mCheckBox->setValue(value); } - virtual void onCommit() { mCheckBox->onCommit(); } - - virtual BOOL handleClick(); - virtual void setEnabled(BOOL enable) { mCheckBox->setEnabled(enable); } - - LLCheckBoxCtrl* getCheckBox() { return mCheckBox; } - virtual BOOL isText() const { return FALSE; } - -private: - LLCheckBoxCtrl* mCheckBox; -}; - -/* - * A simple data class describing a column within a scroll list. - */ -class LLScrollListColumn -{ -public: - LLScrollListColumn(); - LLScrollListColumn(const LLSD &sd, LLScrollListCtrl* parent); - - void setWidth(S32 width); - S32 getWidth() const { return mWidth; } - - // 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; - BOOL mSortAscending; - std::string mLabel; - F32 mRelWidth; - BOOL mDynamicWidth; - S32 mMaxContentWidth; - S32 mIndex; - LLScrollListCtrl* mParentCtrl; - class LLColumnHeader* mHeader; - LLFontGL::HAlign mFontAlignment; - -private: - S32 mWidth; - -}; +class LLScrollListCell; +class LLTextBox; +class LLContextMenu; -class LLColumnHeader : public LLComboBox -{ -public: - LLColumnHeader(const std::string& label, const LLRect &rect, LLScrollListColumn* column, const LLFontGL *font = NULL); - ~LLColumnHeader(); - - /*virtual*/ void draw(); - /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); - - /*virtual*/ void showList(); - /*virtual*/ LLView* findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESnapEdge snap_edge, ESnapType snap_type, S32 threshold, S32 padding); - /*virtual*/ void userSetShape(const LLRect& new_rect); - - void setImage(const std::string &image_name); - LLScrollListColumn* getColumn() { return mColumn; } - void setHasResizableElement(BOOL resizable); - void updateResizeBars(); - BOOL canResize(); - void enableResizeBar(BOOL enable); - std::string getLabel() { return mOrigLabel; } - - static void onSelectSort(LLUICtrl* ctrl, void* user_data); - static void onClick(void* user_data); - static void onMouseDown(void* user_data); - static void onHeldDown(void* user_data); - -private: - LLScrollListColumn* mColumn; - LLResizeBar* mResizeBar; - std::string mOrigLabel; - LLUIString mAscendingText; - LLUIString mDescendingText; - BOOL mShowSortOptions; - BOOL mHasResizableElement; -}; - -class LLScrollListItem +class LLScrollListCtrl : public LLUICtrl, public LLEditMenuHandler, + public LLCtrlListInterface, public LLCtrlScrollInterface { public: - LLScrollListItem( BOOL enabled = TRUE, void* userdata = NULL, const LLUUID& uuid = LLUUID::null ) - : mSelected(FALSE), mEnabled( enabled ), mUserdata( userdata ), mItemValue( uuid ), mColumns() {} - LLScrollListItem( LLSD item_value, void* userdata = NULL ) - : mSelected(FALSE), mEnabled( TRUE ), mUserdata( userdata ), mItemValue( item_value ), mColumns() {} - - virtual ~LLScrollListItem(); - - void setSelected( BOOL b ) { mSelected = b; } - BOOL getSelected() const { return mSelected; } - - void setEnabled( BOOL b ); - BOOL getEnabled() const { return mEnabled; } + struct Contents : public LLInitParam::Block<Contents> + { + Multiple<LLScrollListColumn::Params> columns; + Multiple<LLScrollListItem::Params> rows; - void setUserdata( void* userdata ) { mUserdata = userdata; } - void* getUserdata() const { return mUserdata; } + //Multiple<Contents> groups; - LLUUID getUUID() const { return mItemValue.asUUID(); } - LLSD getValue() const { return mItemValue; } + Contents(); + }; - // If width = 0, just use the width of the text. Otherwise override with - // specified width in pixels. - void addColumn( const std::string& text, const LLFontGL* font, S32 width = 0 , U8 font_style = LLFontGL::NORMAL, LLFontGL::HAlign font_alignment = LLFontGL::LEFT, BOOL visible = TRUE) - { mColumns.push_back( new LLScrollListText(text, font, width, font_style, font_alignment, LLColor4::black, FALSE, visible) ); } - - void addColumn( LLUIImagePtr icon, S32 width = 0 ) - { mColumns.push_back( new LLScrollListIcon(icon, width) ); } - - void addColumn( LLCheckBoxCtrl* check, S32 width = 0 ) - { mColumns.push_back( new LLScrollListCheck(check,width) ); } - - void setNumColumns(S32 columns); - - void setColumn( S32 column, LLScrollListCell *cell ); + // *TODO: Add callbacks to Params + typedef boost::function<void (void)> callback_t; - S32 getNumColumns() const { return mColumns.size(); } - - LLScrollListCell *getColumn(const S32 i) const { if (0 <= i && i < (S32)mColumns.size()) { return mColumns[i]; } return NULL; } - - std::string getContentsCSV() const; - - virtual void draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding); - -private: - BOOL mSelected; - BOOL mEnabled; - void* mUserdata; - LLSD mItemValue; - std::vector<LLScrollListCell *> mColumns; -}; - -/* - * A graphical control representing a scrollable table. - * Cells in the table can be simple text or more complicated things - * such as icons or even interactive elements like check boxes. - */ -class LLScrollListItemComment : public LLScrollListItem -{ -public: - LLScrollListItemComment(const std::string& comment_string, const LLColor4& color); + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + // behavioral flags + Optional<bool> multi_select, + commit_on_keyboard_movement; + + // display flags + Optional<bool> has_border, + draw_heading, + draw_stripes, + background_visible, + scroll_bar_bg_visible; + + // layout + Optional<S32> column_padding, + page_lines, + heading_height; + + // sort and search behavior + Optional<S32> search_column, + sort_column; + Optional<bool> sort_ascending; + + // colors + Optional<LLUIColor> 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> contents; + + Optional<LLViewBorder::Params> border; + + Params(); + }; - /*virtual*/ void draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding); -private: - LLColor4 mColor; -}; - -class LLScrollListItemSeparator : public LLScrollListItem -{ -public: - LLScrollListItemSeparator(); +protected: + friend class LLUICtrlFactory; - /*virtual*/ void draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding); -}; + LLScrollListCtrl(const Params&); -class LLScrollListCtrl : public LLUICtrl, public LLEditMenuHandler, - public LLCtrlListInterface, public LLCtrlScrollInterface -{ public: - LLScrollListCtrl( - const std::string& name, - const LLRect& rect, - void (*commit_callback)(LLUICtrl*, void*), - void* callback_userdata, - BOOL allow_multiple_selection, - BOOL draw_border = TRUE); - virtual ~LLScrollListCtrl(); - virtual LLXMLNodePtr getXML(bool save_children = true) const; - void setScrollListParameters(LLXMLNodePtr node); - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - S32 isEmpty() const; void deleteAllItems() { clearRows(); } // Sets an array of column descriptors - void setColumnHeadings(LLSD headings); + 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 LLSD& column, EAddPosition pos = ADD_BOTTOM); + 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& value, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); + virtual LLScrollListItem* addElement(const LLSD& element, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); + 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 @@ -421,10 +193,14 @@ public: void deleteSelectedItems(); void deselectAllItems(BOOL no_commit_on_change = FALSE); // by default, go ahead and commit on selection change - void highlightNthItem( S32 index ); - void setDoubleClickCallback( void (*cb)(void*) ) { mOnDoubleClickCallback = cb; } - void setMaximumSelectCallback( void (*cb)(void*) ) { mOnMaximumSelectCallback = cb; } - void setSortChangedCallback( void (*cb)(void*) ) { mOnSortChangedCallback = cb; } + void clearHighlightedItems(); + void mouseOverHighlightNthItem( S32 index ); + + 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<void (void* userdata)> cb, void* userdata) { mOnDoubleClickCallback = boost::bind(cb, userdata); } void swapWithNext(S32 index); void swapWithPrevious(S32 index); @@ -435,24 +211,24 @@ public: S32 getItemIndex( LLScrollListItem* item ) const; S32 getItemIndex( const LLUUID& item_id ) const; - LLScrollListItem* addCommentText( const std::string& comment_text, EAddPosition pos = ADD_BOTTOM); + 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 ); // FALSE if item not found BOOL selectItemByPrefix(const std::string& target, BOOL case_sensitive = TRUE); BOOL selectItemByPrefix(const LLWString& target, BOOL case_sensitive = TRUE); + LLScrollListItem* getItemByLabel( const std::string& item, BOOL case_sensitive = TRUE, S32 column = 0 ); const std::string getSelectedItemLabel(S32 column = 0) const; LLSD getSelectedValue(); - // DEPRECATED: Use LLSD versions of addCommentText() and getSelectedValue(). + // 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, S32 column_width = 0); + LLScrollListItem* addStringUUIDItem(const std::string& item_text, const LLUUID& id, EAddPosition pos = ADD_BOTTOM, BOOL enabled = TRUE); LLUUID getStringUUIDSelectedItem() const; LLScrollListItem* getFirstSelected() const; @@ -475,7 +251,8 @@ public: void setBgStripeColor(const LLColor4& c) { mBgStripeColor = c; } void setFgSelectedColor(const LLColor4 &c) { mFgSelectedColor = c; } void setFgUnselectedColor(const LLColor4 &c){ mFgUnselectedColor = c; } - void setHighlightedColor(const LLColor4 &c) { mHighlightedColor = 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; } @@ -500,20 +277,26 @@ public: 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; } + // 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 handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect); + /*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; @@ -527,17 +310,24 @@ public: 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. - static void onScrollChange( S32 new_pos, LLScrollbar* src, void* userdata ); + void onScrollChange( S32 new_pos, LLScrollbar* src ); static void onClickColumn(void *userdata); - void updateColumns(); + virtual void updateColumns(); void calcColumnWidths(); S32 getMaxContentWidth() { return mMaxContentWidth; } - void setDisplayHeading(BOOL display); 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); @@ -559,15 +349,16 @@ public: std::string getSortColumnName(); BOOL getSortAscending() { return mSortColumns.empty() ? TRUE : mSortColumns.back().second; } - BOOL needsSorting(); + BOOL hasSortOrder() const; - S32 selectMultiple( LLDynamicArray<LLUUID> ids ); - void sortItems(); + S32 selectMultiple( std::vector<LLUUID> 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 setSorted(BOOL sorted) { mSorted = sorted; } + void setNeedsSort(bool val = true) { mSorted = !val; } void dirtyColumns(); // some operation has potentially affected column layout or ordering protected: @@ -591,11 +382,13 @@ protected: typedef std::deque<LLScrollListItem *> item_list; item_list& getItemList() { return mItemList; } + void updateLineHeight(); + private: void selectPrevItem(BOOL extend_selection); void selectNextItem(BOOL extend_selection); void drawItems(); - void updateLineHeight(); + void updateLineHeightInsert(LLScrollListItem* item); void reportInvalidInput(); BOOL isRepeatedChars(const LLWString& string) const; @@ -603,10 +396,11 @@ private: void deselectItem(LLScrollListItem* itemp); void commitIfChanged(); BOOL setSort(S32 column, BOOL ascending); + S32 getLinesPerPage(); - - S32 mCurIndex; // For get[First/Next]Data - S32 mCurSelectedIndex; // For get[First/Next]Selected + 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 @@ -621,10 +415,10 @@ private: BOOL mSelectionChanged; BOOL mNeedsScroll; BOOL mCanSelect; - BOOL mDisplayColumnHeaders; + const BOOL mDisplayColumnHeaders; BOOL mColumnsDirty; - item_list mItemList; + mutable item_list mItemList; LLScrollListItem *mLastSelected; @@ -637,22 +431,24 @@ private: BOOL mBackgroundVisible; BOOL mDrawStripes; - LLColor4 mBgWriteableColor; - LLColor4 mBgReadOnlyColor; - LLColor4 mBgSelectedColor; - LLColor4 mBgStripeColor; - LLColor4 mFgSelectedColor; - LLColor4 mFgUnselectedColor; - LLColor4 mFgDisabledColor; - LLColor4 mHighlightedColor; + LLUIColor mBgWriteableColor; + LLUIColor mBgReadOnlyColor; + LLUIColor mBgSelectedColor; + LLUIColor mBgStripeColor; + LLUIColor mFgSelectedColor; + LLUIColor mFgUnselectedColor; + LLUIColor mFgDisabledColor; + LLUIColor mHoveredColor; + LLUIColor mHighlightedColor; S32 mBorderThickness; - void (*mOnDoubleClickCallback)(void* userdata); - void (*mOnMaximumSelectCallback)(void* userdata ); - void (*mOnSortChangedCallback)(void* userdata); + callback_t mOnDoubleClickCallback; + callback_t mOnMaximumSelectCallback; + callback_t mOnSortChangedCallback; S32 mHighlightedItem; class LLViewBorder* mBorder; + LLContextMenu *mPopupMenu; LLWString mSearchString; LLFrameTimer mSearchTimer; @@ -662,7 +458,7 @@ private: S32 mTotalStaticColumnWidth; S32 mTotalColumnPadding; - BOOL mSorted; + mutable bool mSorted; typedef std::map<std::string, LLScrollListColumn> column_map_t; column_map_t mColumns; @@ -670,15 +466,13 @@ private: BOOL mDirty; S32 mOriginalSelection; + ContextMenuType mContextMenuType; + typedef std::vector<LLScrollListColumn*> ordered_columns_t; ordered_columns_t mColumnsIndexed; typedef std::pair<S32, BOOL> sort_column_t; std::vector<sort_column_t> mSortColumns; - - // HACK: Did we draw one selected item this frame? - BOOL mDrewSelected; }; // end class LLScrollListCtrl - #endif // LL_SCROLLLISTCTRL_H diff --git a/indra/llui/llscrolllistitem.cpp b/indra/llui/llscrolllistitem.cpp new file mode 100644 index 0000000000..2c5e0d24cd --- /dev/null +++ b/indra/llui/llscrolllistitem.cpp @@ -0,0 +1,156 @@ +/** + * @file llscrolllistitem.cpp + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llscrolllistitem.h" + +#include "llrect.h" +#include "llui.h" + + +//--------------------------------------------------------------------------- +// LLScrollListItem +//--------------------------------------------------------------------------- + +LLScrollListItem::LLScrollListItem( const Params& p ) +: mSelected(FALSE), + mHighlighted(FALSE), + mEnabled(p.enabled), + mUserdata(p.userdata), + mItemValue(p.value) +{ +} + + +LLScrollListItem::~LLScrollListItem() +{ + std::for_each(mColumns.begin(), mColumns.end(), DeletePointer()); +} + +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 + { + llerrs << "LLScrollListItem::setColumn: bad column: " << column << llendl; + } +} + + +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; i<count; ++i) + { + ret += getColumn(i)->getValue().asString(); + if (i < count-1) + { + ret += ", "; + } + } + + return ret; +} + + +void LLScrollListItem::draw(const LLRect& rect, const LLColor4& fg_color, const LLColor4& bg_color, const LLColor4& highlight_color, S32 column_padding) +{ + // draw background rect + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLRect bg_rect = rect; + gl_rect_2d( bg_rect, bg_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, 0.0f); + + 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 new file mode 100644 index 0000000000..15b86cc945 --- /dev/null +++ b/indra/llui/llscrolllistitem.h @@ -0,0 +1,131 @@ +/** + * @file llscrolllistitem.h + * @brief Scroll lists are composed of rows (items), each of which + * contains columns (cells). + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LLSCROLLLISTITEM_H +#define LLSCROLLLISTITEM_H + +#include "llpointer.h" // LLPointer<> +#include "llsd.h" +#include "v4color.h" +#include "llinitparam.h" +#include "llscrolllistcell.h" + +#include <vector> + +class LLCoordGL; +class LLCheckBoxCtrl; +class LLResizeBar; +class LLScrollListCtrl; +class LLScrollColumnHeader; +class LLUIImage; + +//--------------------------------------------------------------------------- +// LLScrollListItem +//--------------------------------------------------------------------------- +class LLScrollListItem +{ + friend class LLScrollListCtrl; +public: + struct Params : public LLInitParam::Block<Params> + { + Optional<bool> enabled; + Optional<void*> userdata; + Optional<LLSD> value; + + Ignored name; // use for localization tools + Ignored type; + Ignored length; + + Multiple<LLScrollListCell::Params> columns; + + Params() + : enabled("enabled", true), + value("value"), + name("name"), + type("type"), + length("length"), + columns("columns") + { + addSynonym(columns, "column"); + addSynonym(value, "id"); + } + }; + + virtual ~LLScrollListItem(); + + void setSelected( BOOL b ) { mSelected = b; } + BOOL getSelected() const { return mSelected; } + + void setEnabled( BOOL b ) { mEnabled = b; } + BOOL getEnabled() const { return mEnabled; } + + void setHighlighted( BOOL b ) { mHighlighted = b; } + BOOL getHighlighted() const { return mHighlighted; } + + void setUserdata( void* userdata ) { mUserdata = userdata; } + void* getUserdata() const { return mUserdata; } + + LLUUID getUUID() const { return mItemValue.asUUID(); } + LLSD getValue() const { return mItemValue; } + + 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& bg_color, const LLColor4& highlight_color, S32 column_padding); + +protected: + LLScrollListItem( const Params& ); + +private: + BOOL mSelected; + BOOL mHighlighted; + BOOL mEnabled; + void* mUserdata; + LLSD mItemValue; + std::vector<LLScrollListCell *> mColumns; + LLRect mRectangle; +}; + +#endif diff --git a/indra/llui/llsdparam.cpp b/indra/llui/llsdparam.cpp new file mode 100644 index 0000000000..4bb45a3065 --- /dev/null +++ b/indra/llui/llsdparam.cpp @@ -0,0 +1,150 @@ +/** + * @file llsdparam.cpp + * @brief parameter block abstraction for creating complex objects and + * parsing construction parameters from xml and LLSD + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +// Project includes +#include "llsdparam.h" + +// +// LLParamSDParser +// +LLParamSDParser::LLParamSDParser() +{ + using boost::bind; + + registerParserFuncs<S32>(bind(&LLParamSDParser::readTypedValue<S32>, this, _1, &LLSD::asInteger), + bind(&LLParamSDParser::writeTypedValue<S32>, this, _1, _2)); + registerParserFuncs<U32>(bind(&LLParamSDParser::readTypedValue<U32>, this, _1, &LLSD::asInteger), + bind(&LLParamSDParser::writeU32Param, this, _1, _2)); + registerParserFuncs<F32>(bind(&LLParamSDParser::readTypedValue<F32>, this, _1, &LLSD::asReal), + bind(&LLParamSDParser::writeTypedValue<F32>, this, _1, _2)); + registerParserFuncs<F64>(bind(&LLParamSDParser::readTypedValue<F64>, this, _1, &LLSD::asReal), + bind(&LLParamSDParser::writeTypedValue<F64>, this, _1, _2)); + registerParserFuncs<bool>(bind(&LLParamSDParser::readTypedValue<F32>, this, _1, &LLSD::asBoolean), + bind(&LLParamSDParser::writeTypedValue<F32>, this, _1, _2)); + registerParserFuncs<std::string>(bind(&LLParamSDParser::readTypedValue<std::string>, this, _1, &LLSD::asString), + bind(&LLParamSDParser::writeTypedValue<std::string>, this, _1, _2)); + registerParserFuncs<LLUUID>(bind(&LLParamSDParser::readTypedValue<LLUUID>, this, _1, &LLSD::asUUID), + bind(&LLParamSDParser::writeTypedValue<LLUUID>, this, _1, _2)); + registerParserFuncs<LLDate>(bind(&LLParamSDParser::readTypedValue<LLDate>, this, _1, &LLSD::asDate), + bind(&LLParamSDParser::writeTypedValue<LLDate>, this, _1, _2)); + registerParserFuncs<LLURI>(bind(&LLParamSDParser::readTypedValue<LLURI>, this, _1, &LLSD::asURI), + bind(&LLParamSDParser::writeTypedValue<LLURI>, this, _1, _2)); + registerParserFuncs<LLSD>(bind(&LLParamSDParser::readSDParam, this, _1), + bind(&LLParamSDParser::writeTypedValue<LLSD>, this, _1, _2)); +} + +bool LLParamSDParser::readSDParam(void* value_ptr) +{ + if (!mCurReadSD) return false; + *((LLSD*)value_ptr) = *mCurReadSD; + return true; +} + +// special case handling of U32 due to ambiguous LLSD::assign overload +bool LLParamSDParser::writeU32Param(const void* val_ptr, const parser_t::name_stack_t& name_stack) +{ + if (!mWriteSD) return false; + + LLSD* sd_to_write = getSDWriteNode(name_stack); + if (!sd_to_write) return false; + + sd_to_write->assign((S32)*((const U32*)val_ptr)); + return true; +} + +void LLParamSDParser::readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent) +{ + mCurReadSD = NULL; + mNameStack.clear(); + setParseSilently(silent); + + readSDValues(sd, block); +} + +void LLParamSDParser::writeSD(LLSD& sd, const LLInitParam::BaseBlock& block) +{ + mWriteSD = &sd; + block.serializeBlock(*this); +} + +void LLParamSDParser::readSDValues(const LLSD& sd, LLInitParam::BaseBlock& block) +{ + if (sd.isMap()) + { + for (LLSD::map_const_iterator it = sd.beginMap(); + it != sd.endMap(); + ++it) + { + mNameStack.push_back(make_pair(it->first, newParseGeneration())); + readSDValues(it->second, block); + mNameStack.pop_back(); + } + } + else if (sd.isArray()) + { + for (LLSD::array_const_iterator it = sd.beginArray(); + it != sd.endArray(); + ++it) + { + mNameStack.back().second = newParseGeneration(); + readSDValues(*it, block); + } + } + else + { + mCurReadSD = &sd; + block.submitValue(mNameStack, *this); + } +} + +/*virtual*/ std::string LLParamSDParser::getCurrentElementName() +{ + std::string full_name = "sd"; + for (name_stack_t::iterator it = mNameStack.begin(); + it != mNameStack.end(); + ++it) + { + full_name += llformat("[%s]", it->first.c_str()); + } + + return full_name; +} + +LLSD* LLParamSDParser::getSDWriteNode(const parser_t::name_stack_t& name_stack) +{ + //TODO: implement nested LLSD writing + return mWriteSD; +} + diff --git a/indra/llui/llsdparam.h b/indra/llui/llsdparam.h new file mode 100644 index 0000000000..12f28f876f --- /dev/null +++ b/indra/llui/llsdparam.h @@ -0,0 +1,107 @@ +/** + * @file llsdparam.h + * @brief parameter block abstraction for creating complex objects and + * parsing construction parameters from xml and LLSD + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLSDPARAM_H +#define LL_LLSDPARAM_H + +#include "llinitparam.h" + +class LLParamSDParser +: public LLInitParam::Parser, + public LLSingleton<LLParamSDParser> +{ +LOG_CLASS(LLParamSDParser); + +typedef LLInitParam::Parser parser_t; + +protected: + LLParamSDParser(); + friend class LLSingleton<LLParamSDParser>; +public: + void readSD(const LLSD& sd, LLInitParam::BaseBlock& block, bool silent = false); + void writeSD(LLSD& sd, const LLInitParam::BaseBlock& block); + + /*virtual*/ std::string getCurrentElementName(); + +private: + void readSDValues(const LLSD& sd, LLInitParam::BaseBlock& block); + + template<typename T> + bool readTypedValue(void* val_ptr, boost::function<T(const LLSD&)> parser_func) + { + if (!mCurReadSD) return false; + + *((T*)val_ptr) = parser_func(*mCurReadSD); + return true; + } + + template<typename T> + bool writeTypedValue(const void* val_ptr, const parser_t::name_stack_t& name_stack) + { + if (!mWriteSD) return false; + + LLSD* sd_to_write = getSDWriteNode(name_stack); + if (!sd_to_write) return false; + + sd_to_write->assign(*((const T*)val_ptr)); + return true; + } + + LLSD* getSDWriteNode(const parser_t::name_stack_t& name_stack); + + bool readSDParam(void* value_ptr); + bool writeU32Param(const void* value_ptr, const parser_t::name_stack_t& name_stack); + + Parser::name_stack_t mNameStack; + const LLSD* mCurReadSD; + LLSD* mWriteSD; +}; + +template<typename T> +class LLSDParamAdapter : public T + { + public: + LLSDParamAdapter() {} + LLSDParamAdapter(const LLSD& sd) + { + LLParamSDParser::instance().readSD(sd, *this); + } + + LLSDParamAdapter(const T& val) + { + T::operator=(val); + } + }; + +#endif // LL_LLSDPARAM_H + diff --git a/indra/llui/llsearcheditor.cpp b/indra/llui/llsearcheditor.cpp new file mode 100644 index 0000000000..6fa99df82e --- /dev/null +++ b/indra/llui/llsearcheditor.cpp @@ -0,0 +1,165 @@ +/** + * @file llsearcheditor.cpp + * @brief LLSearchEditor implementation + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// Text editor widget to let users enter a single line. + +#include "linden_common.h" + +#include "llsearcheditor.h" + +LLSearchEditor::LLSearchEditor(const LLSearchEditor::Params& p) +: LLUICtrl(p), + mSearchButton(NULL), + mClearButton(NULL) +{ + 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 text_pad_left = p.text_pad_left; + + if (p.search_button_visible) + text_pad_left += srch_btn_rect.getWidth(); + + // Set up line editor. + LLLineEditor::Params line_editor_params(p); + line_editor_params.name("filter edit box"); + line_editor_params.rect(getLocalRect()); + line_editor_params.follows.flags(FOLLOWS_ALL); + line_editor_params.text_pad_left(text_pad_left); + 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<LLLineEditor>(line_editor_params); + 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<LLButton>(srch_btn_params); + mSearchEditor->addChild(mSearchButton); + } + + if (p.clear_button_visible) + { + // Set up clear button. + S32 clr_btn_width = getRect().getHeight(); // button is square, and as tall as search editor + LLRect clear_btn_rect(getRect().getWidth() - clr_btn_width, getRect().getHeight(), getRect().getWidth(), 0); + 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<LLButton>(clr_btn_params); + mSearchEditor->addChild(mClearButton); + } +} + +//virtual +void LLSearchEditor::draw() +{ + if (mClearButton) + mClearButton->setVisible(!mSearchEditor->getWText().empty()); + + 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); + mSearchEditor->doDelete(); // force keystroke callback +} + +void LLSearchEditor::handleKeystroke() +{ + if (mKeystrokeCallback) + { + mKeystrokeCallback(this, getValue()); + } +} diff --git a/indra/llui/llsearcheditor.h b/indra/llui/llsearcheditor.h new file mode 100644 index 0000000000..714aca9337 --- /dev/null +++ b/indra/llui/llsearcheditor.h @@ -0,0 +1,102 @@ +/** + * @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=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_SEARCHEDITOR_H +#define LL_SEARCHEDITOR_H + +#include "lllineeditor.h" +#include "llbutton.h" + +class LLSearchEditor : public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block<Params, LLLineEditor::Params> + { + Optional<LLButton::Params> search_button, + clear_button; + Optional<bool> search_button_visible, + clear_button_visible; + Optional<commit_callback_t> keystroke_callback; + + Params() + : search_button("search_button"), + search_button_visible("search_button_visible"), + clear_button("clear_button"), + clear_button_visible("clear_button_visible") + { + name = "search_editor"; + } + }; + +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; } + +protected: + void onClearButtonClick(const LLSD& data); + virtual void handleKeystroke(); + + commit_callback_t mKeystrokeCallback; + LLLineEditor* mSearchEditor; + LLButton* mSearchButton; + LLButton* mClearButton; +}; + +#endif // LL_SEARCHEDITOR_H diff --git a/indra/llui/llslider.cpp b/indra/llui/llslider.cpp index 4dfc904581..a6f729b396 100644 --- a/indra/llui/llslider.cpp +++ b/indra/llui/llslider.cpp @@ -40,53 +40,68 @@ #include "llfocusmgr.h" #include "llkeyboard.h" // for the MASK constants #include "llcontrol.h" -#include "llimagegl.h" - -static LLRegisterWidget<LLSlider> r1("slider_bar"); -static LLRegisterWidget<LLSlider> r2("volume_slider"); - - -LLSlider::LLSlider( - const std::string& name, - const LLRect& rect, - void (*on_commit_callback)(LLUICtrl* ctrl, void* userdata), - void* callback_userdata, - F32 initial_value, - F32 min_value, - F32 max_value, - F32 increment, - BOOL volume, - const std::string& control_name) - : - LLUICtrl( name, rect, TRUE, on_commit_callback, callback_userdata, - FOLLOWS_LEFT | FOLLOWS_TOP), - mValue( initial_value ), - mInitialValue( initial_value ), - mMinValue( min_value ), - mMaxValue( max_value ), - mIncrement( increment ), - mVolumeSlider( volume ), - mMouseOffset( 0 ), - mTrackColor( LLUI::sColorsGroup->getColor( "SliderTrackColor" ) ), - mThumbOutlineColor( LLUI::sColorsGroup->getColor( "SliderThumbOutlineColor" ) ), - mThumbCenterColor( LLUI::sColorsGroup->getColor( "SliderThumbCenterColor" ) ), - mMouseDownCallback( NULL ), - mMouseUpCallback( NULL ) +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register<LLSlider> 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")), + track_color("track_color"), + 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") { - mThumbImage = LLUI::sImageProvider->getUIImage("icn_slide-thumb_dark.tga"); - mTrackImage = LLUI::sImageProvider->getUIImage("icn_slide-groove_dark.tga"); - mTrackHighlightImage = LLUI::sImageProvider->getUIImage("icn_slide-highlight.tga"); - - // properly handle setting the starting thumb rect - // do it this way to handle both the operating-on-settings - // and standalone ways of using this - setControlName(control_name, NULL); - setValue(getValueF32()); + follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); +} +LLSlider::LLSlider(const LLSlider::Params& p) +: LLF32UICtrl(p), + mMouseOffset( 0 ), + mOrientation ((p.orientation() == "horizontal") ? HORIZONTAL : VERTICAL), + mTrackColor(p.track_color()), + 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) { @@ -98,38 +113,54 @@ void LLSlider::setValue(F32 value, BOOL from_event) value -= fmod(value, mIncrement); value += mMinValue; - if (!from_event && mValue != value) + if (!from_event && getValueF32() != value) { setControlValue(value); } - mValue = value; + LLF32UICtrl::setValue(value); updateThumbRect(); } void LLSlider::updateThumbRect() { - F32 t = (mValue - mMinValue) / (mMaxValue - mMinValue); - - S32 thumb_width = mThumbImage->getWidth(); - S32 thumb_height = mThumbImage->getHeight(); - 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; + 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 = mValue; + F32 old_value = getValueF32(); setValue(value); - if (mValue != old_value) + if (getValueF32() != old_value) { onCommit(); } @@ -140,18 +171,32 @@ BOOL LLSlider::handleHover(S32 x, S32 y, MASK mask) { if( hasMouseCapture() ) { - S32 thumb_half_width = mThumbImage->getWidth()/2; - S32 left_edge = thumb_half_width; - S32 right_edge = getRect().getWidth() - (thumb_half_width); + 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 ); + x += mMouseOffset; + x = llclamp( x, left_edge, right_edge ); - F32 t = F32(x - left_edge) / (right_edge - left_edge); - setValueAndCommit(t * (mMaxValue - mMinValue) + mMinValue ); + 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); - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; + lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; } else { @@ -169,10 +214,9 @@ BOOL LLSlider::handleMouseUp(S32 x, S32 y, MASK mask) { gFocusMgr.setMouseCapture( NULL ); - if( mMouseUpCallback ) - { - mMouseUpCallback( this, mCallbackUserData ); - } + if (mMouseUpSignal) + (*mMouseUpSignal)( this, getValueF32() ); + handled = TRUE; make_ui_sound("UISndClickRelease"); } @@ -191,10 +235,8 @@ BOOL LLSlider::handleMouseDown(S32 x, S32 y, MASK mask) { setFocus(TRUE); } - if( mMouseDownCallback ) - { - mMouseDownCallback( this, mCallbackUserData ); - } + if (mMouseDownSignal) + (*mMouseDownSignal)( this, getValueF32() ); if (MASK_CONTROL & mask) // if CTRL is modifying { @@ -205,7 +247,9 @@ BOOL LLSlider::handleMouseDown(S32 x, S32 y, MASK mask) // Find the offset of the actual mouse location from the center of the thumb. if (mThumbRect.pointInRect(x,y)) { - mMouseOffset = (mThumbRect.mLeft + mThumbImage->getWidth()/2) - x; + mMouseOffset = (mOrientation == HORIZONTAL) + ? (mThumbRect.mLeft + mThumbImage->getWidth()/2) - x + : (mThumbRect.mBottom + mThumbImage->getHeight()/2) - y; } else { @@ -227,15 +271,12 @@ BOOL LLSlider::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: setValueAndCommit(getValueF32() - getIncrement()); handled = TRUE; break; + case KEY_UP: case KEY_RIGHT: setValueAndCommit(getValueF32() + getIncrement()); handled = TRUE; @@ -246,8 +287,21 @@ BOOL LLSlider::handleKeyHere(KEY key, MASK mask) 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(); @@ -256,86 +310,83 @@ void LLSlider::draw() // drawing solids requires texturing be disabled gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - F32 opacity = getEnabled() ? 1.f : 0.3f; - LLColor4 center_color = (mThumbCenterColor % opacity); - LLColor4 track_color = (mTrackColor % opacity); - // Track - LLRect track_rect(mThumbImage->getWidth() / 2, - getLocalRect().getCenterY() + (mTrackImage->getHeight() / 2), - getRect().getWidth() - mThumbImage->getWidth() / 2, - getLocalRect().getCenterY() - (mTrackImage->getHeight() / 2) ); - LLRect highlight_rect(track_rect.mLeft, track_rect.mTop, mThumbRect.getCenterX(), track_rect.mBottom); - mTrackImage->draw(track_rect); - mTrackHighlightImage->draw(highlight_rect); + LLPointer<LLUIImage>& trackImage = ( mOrientation == HORIZONTAL ) + ? mTrackImageHorizontal + : mTrackImageVertical; - // Thumb - if( hasMouseCapture() ) + LLPointer<LLUIImage>& trackHighlightImage = ( mOrientation == HORIZONTAL ) + ? mTrackHighlightHorizontalImage + : mTrackHighlightVerticalImage; + + LLRect track_rect; + LLRect highlight_rect; + + if ( mOrientation == HORIZONTAL ) { - // Show ghost where thumb was before dragging began. - mThumbImage->draw(mDragStartThumbRect, mThumbCenterColor % 0.3f); + 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); } + + trackImage->draw(track_rect, LLColor4::white % alpha); + trackHighlightImage->draw(highlight_rect, LLColor4::white % alpha); + + // Thumb if (hasFocus()) { // Draw focus highlighting. - mThumbImage->drawBorder(mThumbRect, gFocusMgr.getFocusColor(), gFocusMgr.getFocusFlashWidth()); + mThumbImage->drawBorder(mThumbRect, gFocusMgr.getFocusColor() % alpha, gFocusMgr.getFocusFlashWidth()); } - // Fill in the thumb. - mThumbImage->draw(mThumbRect, hasMouseCapture() ? mThumbOutlineColor : center_color); + 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(); } -// virtual -LLXMLNodePtr LLSlider::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - node->createChild("initial_val", TRUE)->setFloatValue(getInitialValue()); - node->createChild("min_val", TRUE)->setFloatValue(getMinValue()); - node->createChild("max_val", TRUE)->setFloatValue(getMaxValue()); - node->createChild("increment", TRUE)->setFloatValue(getIncrement()); - node->createChild("volume", TRUE)->setBoolValue(mVolumeSlider); - - return node; +boost::signals2::connection LLSlider::setMouseDownCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseDownSignal) mMouseDownSignal = new commit_signal_t(); + return mMouseDownSignal->connect(cb); } - -//static -LLView* LLSlider::fromXML(LLXMLNodePtr node, LLView *parent, class LLUICtrlFactory *factory) -{ - std::string name("slider_bar"); - node->getAttributeString("name", name); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - F32 initial_value = 0.f; - node->getAttributeF32("initial_val", initial_value); - - F32 min_value = 0.f; - node->getAttributeF32("min_val", min_value); - - F32 max_value = 1.f; - node->getAttributeF32("max_val", max_value); - - F32 increment = 0.1f; - node->getAttributeF32("increment", increment); - - BOOL volume = node->hasName("volume_slider") ? TRUE : FALSE; - node->getAttributeBOOL("volume", volume); - - LLSlider* slider = new LLSlider(name, - rect, - NULL, - NULL, - initial_value, - min_value, - max_value, - increment, - volume); - - slider->initFromXML(node, parent); - - return slider; +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 154685fac1..45f8f81e40 100644 --- a/indra/llui/llslider.h +++ b/indra/llui/llslider.h @@ -33,79 +33,85 @@ #ifndef LL_LLSLIDER_H #define LL_LLSLIDER_H -#include "lluictrl.h" +#include "llf32uictrl.h" #include "v4color.h" -class LLImageGL; - -class LLSlider : public LLUICtrl +class LLSlider : public LLF32UICtrl { public: - LLSlider( - const std::string& name, - const LLRect& rect, - void (*on_commit_callback)(LLUICtrl* ctrl, void* userdata), - void* callback_userdata, - F32 initial_value, - F32 min_value, - F32 max_value, - F32 increment, - BOOL volume, //TODO: create a "volume" slider sub-class or just use image art, no? -MG - const std::string& control_name = LLStringUtil::null ); - - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, class LLUICtrlFactory *factory); + enum ORIENTATION { HORIZONTAL, VERTICAL }; - void setValue( F32 value, BOOL from_event = FALSE ); - F32 getValueF32() const { return mValue; } + struct Params : public LLInitParam::Block<Params, LLF32UICtrl::Params> + { + Optional<std::string> orientation; + + Optional<LLUIColor> track_color, + thumb_outline_color, + thumb_center_color; + + Optional<LLUIImage*> thumb_image, + thumb_image_pressed, + thumb_image_disabled, + track_image_horizontal, + track_image_vertical, + track_highlight_horizontal_image, + track_highlight_vertical_image; + + Optional<CommitCallbackParam> mouse_down_callback, + mouse_up_callback; - virtual void setValue(const LLSD& value ) { setValue((F32)value.asReal(), TRUE); } - virtual LLSD getValue() const { return LLSD(getValueF32()); } - virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } - virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } + 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(); } - F32 getInitialValue() const { return mInitialValue; } - F32 getMinValue() const { return mMinValue; } - F32 getMaxValue() const { return mMaxValue; } - F32 getIncrement() const { return mIncrement; } - void setMinValue(F32 min_value) {mMinValue = min_value; updateThumbRect(); } - void setMaxValue(F32 max_value) {mMaxValue = max_value; updateThumbRect(); } - void setIncrement(F32 increment) {mIncrement = increment;} - void setMouseDownCallback( void (*cb)(LLUICtrl* ctrl, void* userdata) ) { mMouseDownCallback = cb; } - void setMouseUpCallback( void (*cb)(LLUICtrl* ctrl, void* userdata) ) { mMouseUpCallback = cb; } + 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(); - F32 mValue; - F32 mInitialValue; - F32 mMinValue; - F32 mMaxValue; - F32 mIncrement; - BOOL mVolumeSlider; S32 mMouseOffset; LLRect mDragStartThumbRect; - LLUIImage* mThumbImage; - LLUIImage* mTrackImage; - LLUIImage* mTrackHighlightImage; + LLPointer<LLUIImage> mThumbImage; + LLPointer<LLUIImage> mThumbImagePressed; + LLPointer<LLUIImage> mThumbImageDisabled; + LLPointer<LLUIImage> mTrackImageHorizontal; + LLPointer<LLUIImage> mTrackImageVertical; + LLPointer<LLUIImage> mTrackHighlightHorizontalImage; + LLPointer<LLUIImage> mTrackHighlightVerticalImage; + + const ORIENTATION mOrientation; - LLRect mThumbRect; - LLColor4 mTrackColor; - LLColor4 mThumbOutlineColor; - LLColor4 mThumbCenterColor; + LLRect mThumbRect; + LLUIColor mTrackColor; + LLUIColor mThumbOutlineColor; + LLUIColor mThumbCenterColor; - void (*mMouseDownCallback)(LLUICtrl* ctrl, void* userdata); - void (*mMouseUpCallback)(LLUICtrl* ctrl, void* userdata); + 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 31baddd7cc..01c274bb4e 100644 --- a/indra/llui/llsliderctrl.cpp +++ b/indra/llui/llsliderctrl.cpp @@ -34,8 +34,6 @@ #include "llsliderctrl.h" -#include "audioengine.h" - #include "llmath.h" #include "llfontgl.h" #include "llgl.h" @@ -49,84 +47,104 @@ #include "llcontrol.h" #include "llfocusmgr.h" #include "llresmgr.h" +#include "lluictrlfactory.h" const U32 MAX_STRING_LENGTH = 10; -static LLRegisterWidget<LLSliderCtrl> r("slider"); - -LLSliderCtrl::LLSliderCtrl(const std::string& name, const LLRect& rect, - const std::string& label, - const LLFontGL* font, - S32 label_width, - S32 text_left, - BOOL show_text, - BOOL can_edit_text, - BOOL volume, - void (*commit_callback)(LLUICtrl*, void*), - void* callback_user_data, - F32 initial_value, F32 min_value, F32 max_value, F32 increment, - const std::string& control_which) - : LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data ), - mFont(font), - mShowText( show_text ), - mCanEditText( can_edit_text ), - mVolumeSlider( volume ), - mPrecision( 3 ), - mLabelBox( NULL ), - mLabelWidth( label_width ), - mValue( initial_value ), - mEditor( NULL ), - mTextBox( NULL ), - mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), - mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), - mSliderMouseUpCallback( NULL ), - mSliderMouseDownCallback( NULL ) +static LLDefaultChildRegistry::Register<LLSliderCtrl> 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()) { S32 top = getRect().getHeight(); S32 bottom = 0; S32 left = 0; + S32 label_width = p.label_width; + S32 text_width = p.text_width; + // Label - if( !label.empty() ) + if( !p.label().empty() ) { - if (label_width == 0) + if (!p.label_width.isProvided()) { - label_width = font->getWidth(label); + label_width = p.font()->getWidth(p.label); } LLRect label_rect( left, top, label_width, bottom ); - mLabelBox = new LLTextBox( std::string("SliderCtrl Label"), label_rect, label, font ); + LLTextBox::Params params(p.slider_label); + params.rect.setIfNotProvided(label_rect); + params.font.setIfNotProvided(p.font); + params.initial_value(p.label()); + mLabelBox = LLUICtrlFactory::create<LLTextBox> (params); addChild(mLabelBox); } + 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<S32> sliderctrl_spacing ("UISliderctrlSpacing", 0); + S32 slider_right = getRect().getWidth(); - if( show_text ) + if( p.show_text ) { - slider_right = text_left - SLIDERCTRL_SPACING; + slider_right = text_left - sliderctrl_spacing; } - S32 slider_left = label_width ? label_width + SLIDERCTRL_SPACING : 0; - LLRect slider_rect( slider_left, top, slider_right, bottom ); - mSlider = new LLSlider(std::string("slider"), - slider_rect, - LLSliderCtrl::onSliderCommit, this, - initial_value, min_value, max_value, increment, volume, - control_which ); + S32 slider_left = label_width ? label_width + sliderctrl_spacing : 0; + LLSlider::Params slider_p(p.slider_bar); + slider_p.name("slider_bar"); + slider_p.rect.setIfNotProvided(LLRect(slider_left,top,slider_right,bottom)); + slider_p.initial_value.setIfNotProvided(p.initial_value().asReal()); + slider_p.min_value.setIfNotProvided(p.min_value); + slider_p.max_value.setIfNotProvided(p.max_value); + slider_p.increment.setIfNotProvided(p.increment); + slider_p.orientation.setIfNotProvided(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<LLSlider> (slider_p); + addChild( mSlider ); - if( show_text ) + if( p.show_text() ) { LLRect text_rect( text_left, top, getRect().getWidth(), bottom ); - if( can_edit_text ) + if( p.can_edit_text() ) { - mEditor = new LLLineEditor( std::string("SliderCtrl Editor"), text_rect, - LLStringUtil::null, font, - MAX_STRING_LENGTH, - &LLSliderCtrl::onEditorCommit, NULL, NULL, this, - &LLLineEditor::prevalidateFloat ); - mEditor->setFollowsLeft(); - mEditor->setFollowsBottom(); - mEditor->setFocusReceivedCallback( &LLSliderCtrl::onEditorGainFocus, this ); - mEditor->setIgnoreTab(TRUE); + LLLineEditor::Params line_p(p.value_editor); + line_p.rect.setIfNotProvided(text_rect); + line_p.font.setIfNotProvided(p.font); + line_p.commit_callback.function(&LLSliderCtrl::onEditorCommit); + line_p.prevalidate_callback(&LLLineEditor::prevalidateFloat); + mEditor = LLUICtrlFactory::create<LLLineEditor>(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); @@ -134,9 +152,10 @@ LLSliderCtrl::LLSliderCtrl(const std::string& name, const LLRect& rect, } else { - mTextBox = new LLTextBox( std::string("SliderCtrl Text"), text_rect, LLStringUtil::null, font); - mTextBox->setFollowsLeft(); - mTextBox->setFollowsBottom(); + LLTextBox::Params text_p(p.value_text); + text_p.rect.setIfNotProvided(text_rect); + text_p.font.setIfNotProvided(p.font); + mTextBox = LLUICtrlFactory::create<LLTextBox>(text_p); addChild(mTextBox); } } @@ -144,7 +163,6 @@ LLSliderCtrl::LLSliderCtrl(const std::string& name, const LLRect& rect, updateText(); } - // static void LLSliderCtrl::onEditorGainFocus( LLFocusableElement* caller, void *userdata ) { @@ -179,7 +197,8 @@ BOOL LLSliderCtrl::setLabelArg( const std::string& key, const LLStringExplicit& S32 delta = rect.mRight - prev_right; rect = mSlider->getRect(); S32 left = rect.mLeft + delta; - left = llclamp(left, 0, rect.mRight-SLIDERCTRL_SPACING); + static LLUICachedControl<S32> sliderctrl_spacing ("UISliderctrlSpacing", 0); + left = llclamp(left, 0, rect.mRight - sliderctrl_spacing); rect.mLeft = left; mSlider->setRect(rect); } @@ -224,10 +243,11 @@ void LLSliderCtrl::updateText() } // static -void LLSliderCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) +void LLSliderCtrl::onEditorCommit( LLUICtrl* ctrl, const LLSD& userdata ) { - LLSliderCtrl* self = (LLSliderCtrl*) userdata; - llassert( caller == self->mEditor ); + LLSliderCtrl* self = dynamic_cast<LLSliderCtrl*>(ctrl->getParent()); + if (!self) + return; BOOL success = FALSE; F32 val = self->mValue; @@ -240,17 +260,9 @@ void LLSliderCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) val = (F32) atof( text.c_str() ); if( self->mSlider->getMinValue() <= val && val <= self->mSlider->getMaxValue() ) { - if( self->mValidateCallback ) + self->setValue( val ); // set the value temporarily so that the callback can retrieve it. + if( !self->mValidateSignal || (*(self->mValidateSignal))( self, val ) ) { - self->setValue( val ); // set the value temporarily so that the callback can retrieve it. - if( self->mValidateCallback( self, self->mCallbackUserData ) ) - { - success = TRUE; - } - } - else - { - self->setValue( val ); success = TRUE; } } @@ -272,26 +284,19 @@ void LLSliderCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) } // static -void LLSliderCtrl::onSliderCommit( LLUICtrl* caller, void *userdata ) +void LLSliderCtrl::onSliderCommit( LLUICtrl* ctrl, const LLSD& userdata ) { - LLSliderCtrl* self = (LLSliderCtrl*) userdata; - llassert( caller == self->mSlider ); + LLSliderCtrl* self = dynamic_cast<LLSliderCtrl*>(ctrl->getParent()); + if (!self) + return; BOOL success = FALSE; F32 saved_val = self->mValue; F32 new_val = self->mSlider->getValueF32(); - if( self->mValidateCallback ) - { - self->mValue = new_val; // set the value temporarily so that the callback can retrieve it. - if( self->mValidateCallback( self, self->mCallbackUserData ) ) - { - success = TRUE; - } - } - else + self->mValue = new_val; // set the value temporarily so that the callback can retrieve it. + if( !self->mValidateSignal || (*(self->mValidateSignal))( self, new_val ) ) { - self->mValue = new_val; success = TRUE; } @@ -316,7 +321,7 @@ void LLSliderCtrl::setEnabled(BOOL b) if( mLabelBox ) { - mLabelBox->setColor( b ? mTextEnabledColor : mTextDisabledColor ); + mLabelBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); } mSlider->setEnabled( b ); @@ -328,7 +333,7 @@ void LLSliderCtrl::setEnabled(BOOL b) if( mTextBox ) { - mTextBox->setColor( b ? mTextEnabledColor : mTextDisabledColor ); + mTextBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); } } @@ -339,7 +344,7 @@ void LLSliderCtrl::setTentative(BOOL b) { mEditor->setTentative(b); } - LLUICtrl::setTentative(b); + LLF32UICtrl::setTentative(b); } @@ -351,8 +356,9 @@ void LLSliderCtrl::onCommit() { mEditor->setTentative(FALSE); } - - LLUICtrl::onCommit(); + + setControlValue(getValueF32()); + LLF32UICtrl::onCommit(); } @@ -368,37 +374,14 @@ void LLSliderCtrl::setPrecision(S32 precision) updateText(); } -void LLSliderCtrl::setSliderMouseDownCallback( void (*slider_mousedown_callback)(LLUICtrl* caller, void* userdata) ) +boost::signals2::connection LLSliderCtrl::setSliderMouseDownCallback( const commit_signal_t::slot_type& cb ) { - mSliderMouseDownCallback = slider_mousedown_callback; - mSlider->setMouseDownCallback( LLSliderCtrl::onSliderMouseDown ); + return mSlider->setMouseDownCallback( cb ); } -// static -void LLSliderCtrl::onSliderMouseDown(LLUICtrl* caller, void* userdata) +boost::signals2::connection LLSliderCtrl::setSliderMouseUpCallback( const commit_signal_t::slot_type& cb ) { - LLSliderCtrl* self = (LLSliderCtrl*) userdata; - if( self->mSliderMouseDownCallback ) - { - self->mSliderMouseDownCallback( self, self->mCallbackUserData ); - } -} - - -void LLSliderCtrl::setSliderMouseUpCallback( void (*slider_mouseup_callback)(LLUICtrl* caller, void* userdata) ) -{ - mSliderMouseUpCallback = slider_mouseup_callback; - mSlider->setMouseUpCallback( LLSliderCtrl::onSliderMouseUp ); -} - -// static -void LLSliderCtrl::onSliderMouseUp(LLUICtrl* caller, void* userdata) -{ - LLSliderCtrl* self = (LLSliderCtrl*) userdata; - if( self->mSliderMouseUpCallback ) - { - self->mSliderMouseUpCallback( self, self->mCallbackUserData ); - } + return mSlider->setMouseUpCallback( cb ); } void LLSliderCtrl::onTabInto() @@ -414,131 +397,3 @@ void LLSliderCtrl::reportInvalidData() make_ui_sound("UISndBadKeystroke"); } -// virtual -LLXMLNodePtr LLSliderCtrl::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - node->createChild("show_text", TRUE)->setBoolValue(mShowText); - - node->createChild("can_edit_text", TRUE)->setBoolValue(mCanEditText); - - node->createChild("volume", TRUE)->setBoolValue(mVolumeSlider); - - node->createChild("decimal_digits", TRUE)->setIntValue(mPrecision); - - if (mLabelBox) - { - node->createChild("label", TRUE)->setStringValue(mLabelBox->getText()); - } - - // TomY TODO: Do we really want to export the transient state of the slider? - node->createChild("value", TRUE)->setFloatValue(mValue); - - if (mSlider) - { - node->createChild("initial_val", TRUE)->setFloatValue(mSlider->getInitialValue()); - node->createChild("min_val", TRUE)->setFloatValue(mSlider->getMinValue()); - node->createChild("max_val", TRUE)->setFloatValue(mSlider->getMaxValue()); - node->createChild("increment", TRUE)->setFloatValue(mSlider->getIncrement()); - } - addColorXML(node, mTextEnabledColor, "text_enabled_color", "LabelTextColor"); - addColorXML(node, mTextDisabledColor, "text_disabled_color", "LabelDisabledColor"); - - return node; -} - -LLView* LLSliderCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("slider"); - node->getAttributeString("name", name); - - std::string label; - node->getAttributeString("label", label); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - LLFontGL* font = LLView::selectFont(node); - - // HACK: Font might not be specified. - if (!font) - { - font = LLFontGL::getFontSansSerifSmall(); - } - - S32 label_width = 0; - node->getAttributeS32("label_width", label_width); - - BOOL show_text = TRUE; - node->getAttributeBOOL("show_text", show_text); - - BOOL can_edit_text = FALSE; - node->getAttributeBOOL("can_edit_text", can_edit_text); - - BOOL volume = FALSE; - node->getAttributeBOOL("volume", volume); - - F32 initial_value = 0.f; - node->getAttributeF32("initial_val", initial_value); - - F32 min_value = 0.f; - node->getAttributeF32("min_val", min_value); - - F32 max_value = 1.f; - node->getAttributeF32("max_val", max_value); - - F32 increment = 0.1f; - node->getAttributeF32("increment", increment); - - U32 precision = 3; - node->getAttributeU32("decimal_digits", precision); - - S32 text_left = 0; - if (show_text) - { - // calculate the size of the text box (log max_value is number of digits - 1 so plus 1) - if ( max_value ) - text_left = font->getWidth(std::string("0")) * ( static_cast < S32 > ( log10 ( max_value ) ) + precision + 1 ); - - if ( increment < 1.0f ) - text_left += font->getWidth(std::string(".")); // (mostly) take account of decimal point in value - - if ( min_value < 0.0f || max_value < 0.0f ) - text_left += font->getWidth(std::string("-")); // (mostly) take account of minus sign - - // padding to make things look nicer - text_left += 8; - } - - LLUICtrlCallback callback = NULL; - - if (label.empty()) - { - label.assign(node->getTextContents()); - } - - LLSliderCtrl* slider = new LLSliderCtrl(name, - rect, - label, - font, - label_width, - rect.getWidth() - text_left, - show_text, - can_edit_text, - volume, - callback, - NULL, - initial_value, - min_value, - max_value, - increment); - - slider->setPrecision(precision); - - slider->initFromXML(node, parent); - - slider->updateText(); - - return slider; -} diff --git a/indra/llui/llsliderctrl.h b/indra/llui/llsliderctrl.h index 272dd7ffd3..c425849782 100644 --- a/indra/llui/llsliderctrl.h +++ b/indra/llui/llsliderctrl.h @@ -38,83 +38,101 @@ #include "llslider.h" #include "lltextbox.h" #include "llrect.h" +#include "lllineeditor.h" -// -// Constants -// -const S32 SLIDERCTRL_SPACING = 4; // space between label, slider, and text -const S32 SLIDERCTRL_HEIGHT = 16; - -class LLSliderCtrl : public LLUICtrl +class LLSliderCtrl : public LLF32UICtrl { public: - LLSliderCtrl(const std::string& name, - const LLRect& rect, - const std::string& label, - const LLFontGL* font, - S32 slider_left, - S32 text_left, - BOOL show_text, - BOOL can_edit_text, - BOOL volume, //TODO: create a "volume" slider sub-class or just use image art, no? -MG - void (*commit_callback)(LLUICtrl*, void*), - void* callback_userdata, - F32 initial_value, F32 min_value, F32 max_value, F32 increment, - const std::string& control_which = LLStringUtil::null ); - + struct Params : public LLInitParam::Block<Params, LLF32UICtrl::Params> + { + Optional<std::string> orientation; + Optional<S32> label_width; + Optional<S32> text_width; + Optional<bool> show_text; + Optional<bool> can_edit_text; + Optional<bool> is_volume_slider; + Optional<S32> decimal_digits; + + Optional<LLUIColor> text_color, + text_disabled_color; + + Optional<CommitCallbackParam> mouse_down_callback, + mouse_up_callback; + + Optional<LLSlider::Params> slider_bar; + Optional<LLLineEditor::Params> value_editor; + Optional<LLTextBox::Params> value_text; + Optional<LLTextBox::Params> 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() {} // Children all cleaned up by default view destructor. - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - - F32 getValueF32() const { return mSlider->getValueF32(); } + /*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 ); - - virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } - virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } + /*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 setEnabled( BOOL b ); - virtual void clear(); virtual void setPrecision(S32 precision); - void setMinValue(F32 min_value) { mSlider->setMinValue(min_value); updateText(); } - void setMaxValue(F32 max_value) { mSlider->setMaxValue(max_value); updateText(); } - void setIncrement(F32 increment) { mSlider->setIncrement(increment);} - F32 getMinValue() { return mSlider->getMinValue(); } - F32 getMaxValue() { return mSlider->getMaxValue(); } + /*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; } - void setSliderMouseDownCallback( void (*slider_mousedown_callback)(LLUICtrl* caller, void* userdata) ); - void setSliderMouseUpCallback( void (*slider_mouseup_callback)(LLUICtrl* caller, void* userdata) ); + 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 onTabInto(); - virtual void setTentative(BOOL b); // marks value as tentative - virtual void onCommit(); // mark not tentative, then commit + /*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) + /*virtual*/ void setControlName(const std::string& control_name, LLView* context) { - LLView::setControlName(control_name, context); + LLUICtrl::setControlName(control_name, context); mSlider->setControlName(control_name, context); } - virtual std::string getControlName() const { return mSlider->getControlName(); } + static void onSliderCommit(LLUICtrl* caller, const LLSD& userdata); - static void onSliderCommit(LLUICtrl* caller, void* userdata); - static void onSliderMouseDown(LLUICtrl* caller,void* userdata); - static void onSliderMouseUp(LLUICtrl* caller,void* userdata); - - static void onEditorCommit(LLUICtrl* caller, void* 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); @@ -125,7 +143,6 @@ private: const LLFontGL* mFont; BOOL mShowText; BOOL mCanEditText; - BOOL mVolumeSlider; S32 mPrecision; LLTextBox* mLabelBox; @@ -136,11 +153,8 @@ private: class LLLineEditor* mEditor; LLTextBox* mTextBox; - LLColor4 mTextEnabledColor; - LLColor4 mTextDisabledColor; - - void (*mSliderMouseUpCallback)( LLUICtrl* ctrl, void* userdata ); - void (*mSliderMouseDownCallback)( LLUICtrl* ctrl, void* userdata ); + LLUIColor mTextEnabledColor; + LLUIColor mTextDisabledColor; }; #endif // LL_LLSLIDERCTRL_H diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index c54a2cd140..20a1ab7af3 100644 --- a/indra/llui/llspinctrl.cpp +++ b/indra/llui/llspinctrl.cpp @@ -45,101 +45,102 @@ #include "lltextbox.h" #include "llkeyboard.h" #include "llmath.h" -#include "audioengine.h" #include "llcontrol.h" #include "llfocusmgr.h" #include "llresmgr.h" +#include "lluictrlfactory.h" const U32 MAX_STRING_LENGTH = 32; -static LLRegisterWidget<LLSpinCtrl> r2("spinner"); - -LLSpinCtrl::LLSpinCtrl( const std::string& name, const LLRect& rect, const std::string& label, const LLFontGL* font, - void (*commit_callback)(LLUICtrl*, void*), - void* callback_user_data, - F32 initial_value, F32 min_value, F32 max_value, F32 increment, - const std::string& control_name, - S32 label_width) - : - LLUICtrl(name, rect, TRUE, commit_callback, callback_user_data, FOLLOWS_LEFT | FOLLOWS_TOP ), - mValue( initial_value ), - mInitialValue( initial_value ), - mMaxValue( max_value ), - mMinValue( min_value ), - mIncrement( increment ), - mPrecision( 3 ), - mLabelBox( NULL ), - mTextEnabledColor( LLUI::sColorsGroup->getColor( "LabelTextColor" ) ), - mTextDisabledColor( LLUI::sColorsGroup->getColor( "LabelDisabledColor" ) ), - mbHasBeenSet( FALSE ) +static LLDefaultChildRegistry::Register<LLSpinCtrl> r2("spinner"); + +LLSpinCtrl::Params::Params() +: label_width("label_width"), + decimal_digits("decimal_digits"), + 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") +{} + +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()) { - S32 top = getRect().getHeight(); - S32 bottom = top - 2 * SPINCTRL_BTN_HEIGHT; - S32 centered_top = top; - S32 centered_bottom = bottom; + static LLUICachedControl<S32> spinctrl_spacing ("UISpinctrlSpacing", 0); + static LLUICachedControl<S32> spinctrl_btn_width ("UISpinctrlBtnWidth", 0); + static LLUICachedControl<S32> 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( !label.empty() ) + if( !p.label().empty() ) { LLRect label_rect( 0, centered_top, label_width, centered_bottom ); - mLabelBox = new LLTextBox( std::string("SpinCtrl Label"), label_rect, label, font ); + LLTextBox::Params params; + params.name("SpinCtrl Label"); + params.rect(label_rect); + params.initial_value(p.label()); + if (p.font.isProvided()) + { + params.font(p.font); + } + mLabelBox = LLUICtrlFactory::create<LLTextBox> (params); addChild(mLabelBox); - btn_left += label_rect.mRight + SPINCTRL_SPACING; + btn_left += label_rect.mRight + spinctrl_spacing; } - S32 btn_right = btn_left + SPINCTRL_BTN_WIDTH; + S32 btn_right = btn_left + spinctrl_btn_width; // Spin buttons - LLRect up_rect( btn_left, top, btn_right, top - SPINCTRL_BTN_HEIGHT ); - std::string out_id = "UIImgBtnSpinUpOutUUID"; - std::string in_id = "UIImgBtnSpinUpInUUID"; - mUpBtn = new LLButton(std::string("SpinCtrl Up"), up_rect, - out_id, - in_id, - LLStringUtil::null, - &LLSpinCtrl::onUpBtn, this, LLFontGL::getFontSansSerif() ); - mUpBtn->setFollowsLeft(); - mUpBtn->setFollowsBottom(); - mUpBtn->setHeldDownCallback( &LLSpinCtrl::onUpBtn ); - mUpBtn->setTabStop(FALSE); + LLButton::Params up_button_params(p.up_button); + up_button_params.rect = LLRect(btn_left, getRect().getHeight(), btn_right, getRect().getHeight() - spinctrl_btn_height); + up_button_params.click_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2)); + up_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onUpBtn, this, _2)); + + mUpBtn = LLUICtrlFactory::create<LLButton>(up_button_params); addChild(mUpBtn); - LLRect down_rect( btn_left, top - SPINCTRL_BTN_HEIGHT, btn_right, bottom ); - out_id = "UIImgBtnSpinDownOutUUID"; - in_id = "UIImgBtnSpinDownInUUID"; - mDownBtn = new LLButton(std::string("SpinCtrl Down"), down_rect, - out_id, - in_id, - LLStringUtil::null, - &LLSpinCtrl::onDownBtn, this, LLFontGL::getFontSansSerif() ); - mDownBtn->setFollowsLeft(); - mDownBtn->setFollowsBottom(); - mDownBtn->setHeldDownCallback( &LLSpinCtrl::onDownBtn ); - mDownBtn->setTabStop(FALSE); + 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.click_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2)); + down_button_params.mouse_held_callback.function(boost::bind(&LLSpinCtrl::onDownBtn, this, _2)); + mDownBtn = LLUICtrlFactory::create<LLButton>(down_button_params); addChild(mDownBtn); LLRect editor_rect( btn_right + 1, centered_top, getRect().getWidth(), centered_bottom ); - mEditor = new LLLineEditor( std::string("SpinCtrl Editor"), editor_rect, LLStringUtil::null, font, - MAX_STRING_LENGTH, - &LLSpinCtrl::onEditorCommit, NULL, NULL, this, - &LLLineEditor::prevalidateFloat ); - mEditor->setFollowsLeft(); - mEditor->setFollowsBottom(); - mEditor->setFocusReceivedCallback( &LLSpinCtrl::onEditorGainFocus, this ); + 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))); + params.prevalidate_callback(&LLLineEditor::prevalidateFloat); + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + mEditor = LLUICtrlFactory::create<LLLineEditor> (params); + mEditor->setFocusReceivedCallback( boost::bind(&LLSpinCtrl::onEditorGainFocus, _1, this )); //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->setIgnoreTab(TRUE); addChild(mEditor); updateEditor(); setUseBoundingRect( TRUE ); } - F32 clamp_precision(F32 value, S32 decimal_precision) { // pow() isn't perfect @@ -157,69 +158,71 @@ F32 clamp_precision(F32 value, S32 decimal_precision) } -// static -void LLSpinCtrl::onUpBtn( void *userdata ) +void LLSpinCtrl::onUpBtn( const LLSD& data ) { - LLSpinCtrl* self = (LLSpinCtrl*) userdata; - if( self->getEnabled() ) + if( getEnabled() ) { - // use getValue()/setValue() to force reload from/to control - F32 val = (F32)self->getValue().asReal() + self->mIncrement; - val = clamp_precision(val, self->mPrecision); - val = llmin( val, self->mMaxValue ); - - if( self->mValidateCallback ) + std::string text = mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) { - F32 saved_val = (F32)self->getValue().asReal(); - self->setValue(val); - if( !self->mValidateCallback( self, self->mCallbackUserData ) ) + + 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 ) ) { - self->setValue( saved_val ); - self->reportInvalidData(); - self->updateEditor(); + setValue( saved_val ); + reportInvalidData(); + updateEditor(); return; } - } - else - { - self->setValue(val); - } - self->updateEditor(); - self->onCommit(); + updateEditor(); + onCommit(); + } } } -// static -void LLSpinCtrl::onDownBtn( void *userdata ) +void LLSpinCtrl::onDownBtn( const LLSD& data ) { - LLSpinCtrl* self = (LLSpinCtrl*) userdata; - - if( self->getEnabled() ) + if( getEnabled() ) { - F32 val = (F32)self->getValue().asReal() - self->mIncrement; - val = clamp_precision(val, self->mPrecision); - val = llmax( val, self->mMinValue ); - - if( self->mValidateCallback ) + std::string text = mEditor->getText(); + if( LLLineEditor::postvalidateFloat( text ) ) { - F32 saved_val = (F32)self->getValue().asReal(); - self->setValue(val); - if( !self->mValidateCallback( self, self->mCallbackUserData ) ) + + 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 ) ) { - self->setValue( saved_val ); - self->reportInvalidData(); - self->updateEditor(); + setValue( saved_val ); + reportInvalidData(); + updateEditor(); return; } - } - else - { - self->setValue(val); - } - self->updateEditor(); - self->onCommit(); + updateEditor(); + onCommit(); + } } } @@ -235,10 +238,10 @@ void LLSpinCtrl::onEditorGainFocus( LLFocusableElement* caller, void *userdata ) void LLSpinCtrl::setValue(const LLSD& value ) { F32 v = (F32)value.asReal(); - if (mValue != v || !mbHasBeenSet) + if (getValueF32() != v || !mbHasBeenSet) { mbHasBeenSet = TRUE; - mValue = v; + LLF32UICtrl::setValue(value); if (!mEditor->hasFocus()) { @@ -251,10 +254,10 @@ void LLSpinCtrl::setValue(const LLSD& value ) void LLSpinCtrl::forceSetValue(const LLSD& value ) { F32 v = (F32)value.asReal(); - if (mValue != v || !mbHasBeenSet) + if (getValueF32() != v || !mbHasBeenSet) { mbHasBeenSet = TRUE; - mValue = v; + LLF32UICtrl::setValue(value); updateEditor(); } @@ -286,55 +289,43 @@ void LLSpinCtrl::updateEditor() mEditor->setText( text ); } -void LLSpinCtrl::onEditorCommit( LLUICtrl* caller, void *userdata ) +void LLSpinCtrl::onEditorCommit( const LLSD& data ) { BOOL success = FALSE; - LLSpinCtrl* self = (LLSpinCtrl*) userdata; - llassert( caller == self->mEditor ); - - std::string text = self->mEditor->getText(); + std::string text = mEditor->getText(); if( LLLineEditor::postvalidateFloat( text ) ) { LLLocale locale(LLLocale::USER_LOCALE); F32 val = (F32) atof(text.c_str()); - if (val < self->mMinValue) val = self->mMinValue; - if (val > self->mMaxValue) val = self->mMaxValue; + if (val < mMinValue) val = mMinValue; + if (val > mMaxValue) val = mMaxValue; - if( self->mValidateCallback ) + F32 saved_val = getValueF32(); + setValue(val); + if( !mValidateSignal || (*mValidateSignal)( this, val ) ) { - F32 saved_val = self->mValue; - self->mValue = val; - if( self->mValidateCallback( self, self->mCallbackUserData ) ) - { - success = TRUE; - self->onCommit(); - } - else - { - self->mValue = saved_val; - } + success = TRUE; + onCommit(); } else { - self->mValue = val; - self->onCommit(); - success = TRUE; + setValue(saved_val); } } - self->updateEditor(); + updateEditor(); if( !success ) { - self->reportInvalidData(); + reportInvalidData(); } } void LLSpinCtrl::forceEditorCommit() { - onEditorCommit(mEditor, this); + onEditorCommit( LLSD() ); } @@ -348,6 +339,10 @@ void LLSpinCtrl::setEnabled(BOOL b) { LLView::setEnabled( b ); mEditor->setEnabled( b ); + if( mLabelBox ) + { + mLabelBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); + } } @@ -368,8 +363,8 @@ BOOL LLSpinCtrl::isMouseHeldDown() const void LLSpinCtrl::onCommit() { setTentative(FALSE); - setControlValue(mValue); - LLUICtrl::onCommit(); + setControlValue(getValueF32()); + LLF32UICtrl::onCommit(); } @@ -414,29 +409,19 @@ void LLSpinCtrl::reportInvalidData() make_ui_sound("UISndBadKeystroke"); } -void LLSpinCtrl::draw() -{ - if( mLabelBox ) - { - mLabelBox->setColor( getEnabled() ? mTextEnabledColor : mTextDisabledColor ); - } - LLUICtrl::draw(); -} - - BOOL LLSpinCtrl::handleScrollWheel(S32 x, S32 y, S32 clicks) { if( clicks > 0 ) { while( clicks-- ) { - LLSpinCtrl::onDownBtn(this); + onDownBtn(getValue()); } } else while( clicks++ ) { - LLSpinCtrl::onUpBtn(this); + onUpBtn(getValue()); } return TRUE; @@ -456,105 +441,15 @@ BOOL LLSpinCtrl::handleKeyHere(KEY key, MASK mask) } if(key == KEY_UP) { - LLSpinCtrl::onUpBtn(this); + onUpBtn(getValue()); return TRUE; } if(key == KEY_DOWN) { - LLSpinCtrl::onDownBtn(this); + onDownBtn(getValue()); return TRUE; } } return FALSE; } -// virtual -LLXMLNodePtr LLSpinCtrl::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - node->createChild("decimal_digits", TRUE)->setIntValue(mPrecision); - - if (mLabelBox) - { - node->createChild("label", TRUE)->setStringValue(mLabelBox->getText()); - - node->createChild("label_width", TRUE)->setIntValue(mLabelBox->getRect().getWidth()); - } - - node->createChild("initial_val", TRUE)->setFloatValue(mInitialValue); - - node->createChild("min_val", TRUE)->setFloatValue(mMinValue); - - node->createChild("max_val", TRUE)->setFloatValue(mMaxValue); - - node->createChild("increment", TRUE)->setFloatValue(mIncrement); - - addColorXML(node, mTextEnabledColor, "text_enabled_color", "LabelTextColor"); - addColorXML(node, mTextDisabledColor, "text_disabled_color", "LabelDisabledColor"); - - return node; -} - -LLView* LLSpinCtrl::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("spinner"); - node->getAttributeString("name", name); - - std::string label; - node->getAttributeString("label", label); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - LLFontGL* font = LLView::selectFont(node); - - F32 initial_value = 0.f; - node->getAttributeF32("initial_val", initial_value); - - F32 min_value = 0.f; - node->getAttributeF32("min_val", min_value); - - F32 max_value = 1.f; - node->getAttributeF32("max_val", max_value); - - F32 increment = 0.1f; - node->getAttributeF32("increment", increment); - - U32 precision = 3; - node->getAttributeU32("decimal_digits", precision); - - S32 label_width = llmin(40, rect.getWidth() - 40); - node->getAttributeS32("label_width", label_width); - - BOOL allow_text_entry = TRUE; - node->getAttributeBOOL("allow_text_entry", allow_text_entry); - - LLUICtrlCallback callback = NULL; - - if(label.empty()) - { - label.assign( node->getValue() ); - } - - LLSpinCtrl* spinner = new LLSpinCtrl(name, - rect, - label, - font, - callback, - NULL, - initial_value, - min_value, - max_value, - increment, - LLStringUtil::null, - label_width); - - spinner->setPrecision(precision); - - spinner->initFromXML(node, parent); - spinner->setAllowEdit(allow_text_entry); - - return spinner; -} - diff --git a/indra/llui/llspinctrl.h b/indra/llui/llspinctrl.h index dfd0eb3ac1..0e610b7741 100644 --- a/indra/llui/llspinctrl.h +++ b/indra/llui/llspinctrl.h @@ -35,62 +35,50 @@ #include "stdtypes.h" -#include "lluictrl.h" +#include "llbutton.h" +#include "llf32uictrl.h" #include "v4color.h" #include "llrect.h" -// -// Constants -// -const S32 SPINCTRL_BTN_HEIGHT = 8; -const S32 SPINCTRL_BTN_WIDTH = 16; -const S32 SPINCTRL_SPACING = 2; // space between label right and button left -const S32 SPINCTRL_HEIGHT = 2 * SPINCTRL_BTN_HEIGHT; -const S32 SPINCTRL_DEFAULT_LABEL_WIDTH = 10; - class LLSpinCtrl -: public LLUICtrl +: public LLF32UICtrl { public: - LLSpinCtrl(const std::string& name, const LLRect& rect, - const std::string& label, - const LLFontGL* font, - void (*commit_callback)(LLUICtrl*, void*), - void* callback_userdata, - F32 initial_value, F32 min_value, F32 max_value, F32 increment, - const std::string& control_name = std::string(), - S32 label_width = SPINCTRL_DEFAULT_LABEL_WIDTH ); - + struct Params : public LLInitParam::Block<Params, LLF32UICtrl::Params> + { + Optional<S32> label_width; + Optional<U32> decimal_digits; + Optional<bool> allow_text_entry; + + Optional<LLUIColor> text_enabled_color; + Optional<LLUIColor> text_disabled_color; + + Optional<LLButton::Params> up_button; + Optional<LLButton::Params> down_button; + + Params(); + }; +protected: + LLSpinCtrl(const Params&); + friend class LLUICtrlFactory; +public: virtual ~LLSpinCtrl() {} // Children all cleaned up by default view destructor. - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, class LLUICtrlFactory *factory); - virtual void forceSetValue(const LLSD& value ) ; virtual void setValue(const LLSD& value ); - virtual LLSD getValue() const { return mValue; } - F32 get() const { return (F32)getValue().asReal(); } + F32 get() const { return getValueF32(); } void set(F32 value) { setValue(value); mInitialValue = value; } - virtual void setMinValue(LLSD min_value) { setMinValue((F32)min_value.asReal()); } - virtual void setMaxValue(LLSD max_value) { setMaxValue((F32)max_value.asReal()); } - BOOL isMouseHeldDown() const; virtual void setEnabled( BOOL b ); virtual void setFocus( BOOL b ); virtual void clear(); - virtual BOOL isDirty() const { return( mValue != mInitialValue ); } - virtual void resetDirty() { mInitialValue = mValue; } + virtual BOOL isDirty() const { return( getValueF32() != mInitialValue ); } + virtual void resetDirty() { mInitialValue = getValueF32(); } virtual void setPrecision(S32 precision); - virtual void setMinValue(F32 min) { mMinValue = min; } - virtual void setMaxValue(F32 max) { mMaxValue = max; } - virtual void setIncrement(F32 inc) { mIncrement = inc; } - virtual F32 getMinValue() { return mMinValue ; } - virtual F32 getMaxValue() { return mMaxValue ; } - virtual F32 getIncrement() { return mIncrement ; } void setLabel(const LLStringExplicit& label); void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } @@ -107,31 +95,23 @@ public: virtual BOOL handleScrollWheel(S32 x,S32 y,S32 clicks); virtual BOOL handleKeyHere(KEY key, MASK mask); - virtual void draw(); - - static void onEditorCommit(LLUICtrl* caller, void* userdata); + void onEditorCommit(const LLSD& data); static void onEditorGainFocus(LLFocusableElement* caller, void *userdata); static void onEditorChangeFocus(LLUICtrl* caller, S32 direction, void *userdata); - static void onUpBtn(void *userdata); - static void onDownBtn(void *userdata); + void onUpBtn(const LLSD& data); + void onDownBtn(const LLSD& data); private: void updateEditor(); void reportInvalidData(); - F32 mValue; - F32 mInitialValue; - F32 mMaxValue; - F32 mMinValue; - F32 mIncrement; - S32 mPrecision; class LLTextBox* mLabelBox; class LLLineEditor* mEditor; - LLColor4 mTextEnabledColor; - LLColor4 mTextDisabledColor; + LLUIColor mTextEnabledColor; + LLUIColor mTextDisabledColor; class LLButton* mUpBtn; class LLButton* mDownBtn; diff --git a/indra/llui/llstatbar.cpp b/indra/llui/llstatbar.cpp new file mode 100644 index 0000000000..bd74b285a7 --- /dev/null +++ b/indra/llui/llstatbar.cpp @@ -0,0 +1,293 @@ +/** + * @file llstatbar.cpp + * @brief A little map of the world with network information + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +//#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "llstatbar.h" + +#include "llmath.h" +#include "llui.h" +#include "llgl.h" +#include "llfontgl.h" + +#include "llstat.h" +#include "lluictrlfactory.h" + +/////////////////////////////////////////////////////////////////////////////////// + +LLStatBar::LLStatBar(const Params& p) + : LLView(p), + mLabel(p.label), + mUnitLabel(p.unit_label), + mMinBar(p.bar_min), + mMaxBar(p.bar_max), + mStatp(LLStat::getStat(p.stat)), + mTickSpacing(p.tick_spacing), + mLabelSpacing(p.label_spacing), + mPrecision(p.precision), + mUpdatesPerSec(p.update_rate), + mPerSec(p.show_per_sec), + mDisplayBar(p.show_bar), + mDisplayHistory(p.show_history), + mDisplayMean(p.show_mean) +{ +} + +BOOL LLStatBar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (mDisplayBar) + { + if (mDisplayHistory) + { + mDisplayBar = FALSE; + mDisplayHistory = FALSE; + } + else + { + mDisplayHistory = TRUE; + } + } + else + { + mDisplayBar = TRUE; + } + + LLView* parent = getParent(); + parent->reshape(parent->getRect().getWidth(), parent->getRect().getHeight(), FALSE); + + return FALSE; +} + +void LLStatBar::draw() +{ + if (!mStatp) + { +// llinfos << "No stats for statistics bar!" << llendl; + return; + } + + // Get the values. + F32 current, min, max, mean; + if (mPerSec) + { + current = mStatp->getCurrentPerSec(); + min = mStatp->getMinPerSec(); + max = mStatp->getMaxPerSec(); + mean = mStatp->getMeanPerSec(); + } + else + { + current = mStatp->getCurrent(); + min = mStatp->getMin(); + max = mStatp->getMax(); + mean = mStatp->getMean(); + } + + + if ((mUpdatesPerSec == 0.f) || (mUpdateTimer.getElapsedTimeF32() > 1.f/mUpdatesPerSec) || (mValue == 0.f)) + { + if (mDisplayMean) + { + mValue = mean; + } + else + { + mValue = current; + } + mUpdateTimer.reset(); + } + + S32 width = getRect().getWidth() - 40; + S32 max_width = width; + S32 bar_top = getRect().getHeight() - 15; // 16 pixels from top. + S32 bar_height = bar_top - 20; + S32 tick_height = 4; + S32 tick_width = 1; + S32 left, top, right, bottom; + + F32 value_scale = max_width/(mMaxBar - mMinBar); + + LLFontGL::getFontMonospace()->renderUTF8(mLabel, 0, 0, getRect().getHeight(), LLColor4(1.f, 1.f, 1.f, 1.f), + LLFontGL::LEFT, LLFontGL::TOP); + + std::string value_format; + std::string value_str; + if (!mUnitLabel.empty()) + { + value_format = llformat( "%%.%df%%s", mPrecision); + value_str = llformat( value_format.c_str(), mValue, mUnitLabel.c_str()); + } + else + { + value_format = llformat( "%%.%df", mPrecision); + value_str = llformat( value_format.c_str(), mValue); + } + + // Draw the value. + LLFontGL::getFontMonospace()->renderUTF8(value_str, 0, width, getRect().getHeight(), + LLColor4(1.f, 1.f, 1.f, 0.5f), + LLFontGL::RIGHT, LLFontGL::TOP); + + value_format = llformat( "%%.%df", mPrecision); + if (mDisplayBar) + { + std::string tick_label; + + // Draw the tick marks. + F32 tick_value; + top = bar_top; + bottom = bar_top - bar_height - tick_height/2; + + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + for (tick_value = mMinBar; tick_value <= mMaxBar; tick_value += mTickSpacing) + { + left = llfloor((tick_value - mMinBar)*value_scale); + right = left + tick_width; + gl_rect_2d(left, top, right, bottom, LLColor4(1.f, 1.f, 1.f, 0.1f)); + } + + // Draw the tick labels (and big ticks). + bottom = bar_top - bar_height - tick_height; + for (tick_value = mMinBar; tick_value <= mMaxBar; tick_value += mLabelSpacing) + { + left = llfloor((tick_value - mMinBar)*value_scale); + right = left + tick_width; + gl_rect_2d(left, top, right, bottom, LLColor4(1.f, 1.f, 1.f, 0.25f)); + + tick_label = llformat( value_format.c_str(), tick_value); + // draw labels for the tick marks + LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, left - 1, bar_top - bar_height - tick_height, + LLColor4(1.f, 1.f, 1.f, 0.5f), + LLFontGL::LEFT, LLFontGL::TOP); + } + + // Now, draw the bars + top = bar_top; + bottom = bar_top - bar_height; + + // draw background bar. + left = 0; + right = width; + gl_rect_2d(left, top, right, bottom, LLColor4(0.f, 0.f, 0.f, 0.25f)); + + if (mStatp->getNumValues() == 0) + { + // No data, don't draw anything... + return; + } + // draw min and max + left = (S32) ((min - mMinBar) * value_scale); + + if (left < 0) + { + left = 0; + llwarns << "Min:" << min << llendl; + } + + right = (S32) ((max - mMinBar) * value_scale); + gl_rect_2d(left, top, right, bottom, LLColor4(1.f, 0.f, 0.f, 0.25f)); + + S32 num_values = mStatp->getNumValues() - 1; + if (mDisplayHistory) + { + S32 i; + for (i = 0; i < num_values; i++) + { + if (i == mStatp->getNextBin()) + { + continue; + } + if (mPerSec) + { + left = (S32)((mStatp->getPrevPerSec(i) - mMinBar) * value_scale); + right = (S32)((mStatp->getPrevPerSec(i) - mMinBar) * value_scale) + 1; + gl_rect_2d(left, bottom+i+1, right, bottom+i, LLColor4(1.f, 0.f, 0.f, 1.f)); + } + else + { + left = (S32)((mStatp->getPrev(i) - mMinBar) * value_scale); + right = (S32)((mStatp->getPrev(i) - mMinBar) * value_scale) + 1; + gl_rect_2d(left, bottom+i+1, right, bottom+i, LLColor4(1.f, 0.f, 0.f, 1.f)); + } + } + } + else + { + // draw current + left = (S32) ((current - mMinBar) * value_scale) - 1; + right = (S32) ((current - mMinBar) * value_scale) + 1; + gl_rect_2d(left, top, right, bottom, LLColor4(1.f, 0.f, 0.f, 1.f)); + } + + // draw mean bar + top = bar_top + 2; + bottom = bar_top - bar_height - 2; + left = (S32) ((mean - mMinBar) * value_scale) - 1; + right = (S32) ((mean - mMinBar) * value_scale) + 1; + gl_rect_2d(left, top, right, bottom, LLColor4(0.f, 1.f, 0.f, 1.f)); + } + + LLView::draw(); +} + +void LLStatBar::setRange(F32 bar_min, F32 bar_max, F32 tick_spacing, F32 label_spacing) +{ + mMinBar = bar_min; + mMaxBar = bar_max; + mTickSpacing = tick_spacing; + mLabelSpacing = label_spacing; +} + +LLRect LLStatBar::getRequiredRect() +{ + LLRect rect; + + if (mDisplayBar) + { + if (mDisplayHistory) + { + rect.mTop = 67; + } + else + { + rect.mTop = 40; + } + } + else + { + rect.mTop = 14; + } + return rect; +} diff --git a/indra/llui/llstatbar.h b/indra/llui/llstatbar.h new file mode 100644 index 0000000000..7de782a24f --- /dev/null +++ b/indra/llui/llstatbar.h @@ -0,0 +1,108 @@ +/** + * @file llstatbar.h + * @brief A little map of the world with network information + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLSTATBAR_H +#define LL_LLSTATBAR_H + +#include "llview.h" +#include "llframetimer.h" + +class LLStat; + +class LLStatBar : public LLView +{ +public: + struct Params : public LLInitParam::Block<Params, LLView::Params> + { + Optional<std::string> label; + Optional<std::string> unit_label; + Optional<F32> bar_min; + Optional<F32> bar_max; + Optional<F32> tick_spacing; + Optional<F32> label_spacing; + Optional<U32> precision; + Optional<F32> update_rate; + Optional<bool> show_per_sec; + Optional<bool> show_bar; + Optional<bool> show_history; + Optional<bool> show_mean; + Optional<std::string> stat; + Params() + : label("label"), + unit_label("unit_label"), + bar_min("bar_min", 0.0f), + bar_max("bar_max", 50.0f), + tick_spacing("tick_spacing", 10.0f), + label_spacing("label_spacing", 10.0f), + precision("precision", 0), + update_rate("update_rate", 5.0f), + show_per_sec("show_per_sec", TRUE), + show_bar("show_bar", TRUE), + show_history("show_history", FALSE), + show_mean("show_mean", TRUE), + stat("stat") + { + follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT); + } + }; + LLStatBar(const Params&); + + virtual void draw(); + virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); + + void setStat(LLStat* stat) { mStatp = stat; } + void setRange(F32 bar_min, F32 bar_max, F32 tick_spacing, F32 label_spacing); + void getRange(F32& bar_min, F32& bar_max) { bar_min = mMinBar; bar_max = mMaxBar; } + + /*virtual*/ LLRect getRequiredRect(); // Return the height of this object, given the set options. + +private: + F32 mMinBar; + F32 mMaxBar; + F32 mTickSpacing; + F32 mLabelSpacing; + U32 mPrecision; + F32 mUpdatesPerSec; + BOOL mPerSec; // Use the per sec stats. + BOOL mDisplayBar; // Display the bar graph. + BOOL mDisplayHistory; + BOOL mDisplayMean; // If true, display mean, if false, display current value + + LLStat* mStatp; + + LLFrameTimer mUpdateTimer; + LLUIString mLabel; + std::string mUnitLabel; + F32 mValue; +}; + +#endif diff --git a/indra/llui/llstatgraph.cpp b/indra/llui/llstatgraph.cpp new file mode 100644 index 0000000000..55d6b3159f --- /dev/null +++ b/indra/llui/llstatgraph.cpp @@ -0,0 +1,163 @@ +/** + * @file llstatgraph.cpp + * @brief Simpler compact stat graph with tooltip + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +//#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "llstatgraph.h" +#include "llrender.h" + +#include "llmath.h" +#include "llui.h" +#include "llstat.h" +#include "llgl.h" +#include "llglheaders.h" +//#include "llviewercontrol.h" + +/////////////////////////////////////////////////////////////////////////////////// + +LLStatGraph::LLStatGraph(const LLView::Params& p) +: LLView(p) +{ + mStatp = NULL; + setToolTip(p.name()); + mNumThresholds = 3; + mThresholdColors[0] = LLColor4(0.f, 1.f, 0.f, 1.f); + mThresholdColors[1] = LLColor4(1.f, 1.f, 0.f, 1.f); + mThresholdColors[2] = LLColor4(1.f, 0.f, 0.f, 1.f); + mThresholdColors[3] = LLColor4(1.f, 0.f, 0.f, 1.f); + mThresholds[0] = 50.f; + mThresholds[1] = 75.f; + mThresholds[2] = 100.f; + mMin = 0.f; + mMax = 125.f; + mPerSec = TRUE; + mValue = 0.f; + mPrecision = 0; +} + +void LLStatGraph::draw() +{ + F32 range, frac; + range = mMax - mMin; + if (mStatp) + { + if (mPerSec) + { + mValue = mStatp->getMeanPerSec(); + } + else + { + mValue = mStatp->getMean(); + } + } + 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; + + S32 i; + for (i = 0; i < mNumThresholds - 1; i++) + { + if (mThresholds[i] > mValue) + { + break; + } + } + + //gl_drop_shadow(0, getRect().getHeight(), getRect().getWidth(), 0, + // LLUIColorTable::instance().getColor("ColorDropShadow"), + // (S32) gSavedSettings.getF32("DropShadowFloater") ); + + 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 = mThresholdColors[i]; + gGL.color4fv(color.mV); + gl_rect_2d(1, llround(frac*getRect().getHeight()), getRect().getWidth() - 1, 0, TRUE); +} + +void LLStatGraph::setValue(const LLSD& value) +{ + mValue = (F32)value.asReal(); +} + +void LLStatGraph::setMin(const F32 min) +{ + mMin = min; +} + +void LLStatGraph::setMax(const F32 max) +{ + mMax = max; +} + +void LLStatGraph::setStat(LLStat *statp) +{ + mStatp = statp; +} + +void LLStatGraph::setLabel(const std::string& label) +{ + mLabel = label; +} + +void LLStatGraph::setUnits(const std::string& units) +{ + mUnits = units; +} + +void LLStatGraph::setPrecision(const S32 precision) +{ + mPrecision = precision; +} + +void LLStatGraph::setThreshold(const S32 i, F32 value) +{ + mThresholds[i] = value; +} diff --git a/indra/llui/llstatgraph.h b/indra/llui/llstatgraph.h new file mode 100644 index 0000000000..dd38050b1b --- /dev/null +++ b/indra/llui/llstatgraph.h @@ -0,0 +1,76 @@ +/** + * @file llstatgraph.h + * @brief Simpler compact stat graph with tooltip + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLSTATGRAPH_H +#define LL_LLSTATGRAPH_H + +#include "llview.h" +#include "llframetimer.h" +#include "v4color.h" + +class LLStat; + +class LLStatGraph : public LLView +{ +public: + LLStatGraph(const LLView::Params&); + + virtual void draw(); + + void setLabel(const std::string& label); + void setUnits(const std::string& units); + void setPrecision(const S32 precision); + void setStat(LLStat *statp); + void setThreshold(const S32 i, F32 value); + void setMin(const F32 min); + void setMax(const F32 max); + + /*virtual*/ void setValue(const LLSD& value); + + LLStat *mStatp; + BOOL mPerSec; +private: + F32 mValue; + + F32 mMin; + F32 mMax; + LLFrameTimer mUpdateTimer; + std::string mLabel; + std::string mUnits; + S32 mPrecision; // Num of digits of precision after dot + + S32 mNumThresholds; + F32 mThresholds[4]; + LLColor4 mThresholdColors[4]; +}; + +#endif // LL_LLSTATGRAPH_H diff --git a/indra/llui/llstatview.cpp b/indra/llui/llstatview.cpp new file mode 100644 index 0000000000..9580c76e2a --- /dev/null +++ b/indra/llui/llstatview.cpp @@ -0,0 +1,73 @@ +/** + * @file llstatview.cpp + * @brief Container for all statistics info. + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "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::sSettingGroups["config"]->getBOOL(mSetting); + } + setDisplayChildren(isopen); +} + +LLStatView::~LLStatView() +{ + // Children all cleaned up by default view destructor. + if (mSetting.length() > 0) + { + BOOL isopen = getDisplayChildren(); + LLUI::sSettingGroups["config"]->setBOOL(mSetting, isopen); + } +} + + +static StatViewRegistry::Register<LLStatBar> r1("stat_bar"); +static StatViewRegistry::Register<LLStatView> r2("stat_view"); +// stat_view can be a child of panels/etc. +static LLDefaultChildRegistry::Register<LLStatView> r3("stat_view"); + + diff --git a/indra/llui/llstatview.h b/indra/llui/llstatview.h new file mode 100644 index 0000000000..eee4e2b7e4 --- /dev/null +++ b/indra/llui/llstatview.h @@ -0,0 +1,72 @@ +/** + * @file llstatview.h + * @brief Container for all statistics info. + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLSTATVIEW_H +#define LL_LLSTATVIEW_H + +#include "llstatbar.h" +#include "llcontainerview.h" +#include <vector> + +class LLStatBar; + +// widget registrars +struct StatViewRegistry : public LLChildRegistry<StatViewRegistry> +{}; + +class LLStatView : public LLContainerView +{ +public: + struct Params : public LLInitParam::Block<Params, LLContainerView::Params> + { + Optional<std::string> setting; + Params() + : setting("setting") + { + follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT); + } + }; + + // my valid children are stored in this registry + typedef StatViewRegistry child_registry_t; + + ~LLStatView(); + +protected: + LLStatView(const Params&); + friend class LLUICtrlFactory; + +protected: + std::string mSetting; + +}; +#endif // LL_STATVIEW_ diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp index a716cbbaec..71511f69a4 100644 --- a/indra/llui/llstyle.cpp +++ b/indra/llui/llstyle.cpp @@ -33,112 +33,42 @@ #include "linden_common.h" #include "llstyle.h" + +#include "llfontgl.h" #include "llstring.h" #include "llui.h" -//#include "llviewerimagelist.h" - -LLStyle::LLStyle() -{ - init(TRUE, LLColor4(0,0,0,1),LLStringUtil::null); -} - -LLStyle::LLStyle(const LLStyle &style) -{ - if (this != &style) - { - init(style.isVisible(),style.getColor(),style.getFontString()); - if (style.isLink()) - { - setLinkHREF(style.getLinkHREF()); - } - mItalic = style.mItalic; - mBold = style.mBold; - mUnderline = style.mUnderline; - mDropShadow = style.mDropShadow; - mImageHeight = style.mImageHeight; - mImageWidth = style.mImageWidth; - mImagep = style.mImagep; - mIsEmbeddedItem = style.mIsEmbeddedItem; - } - else - { - init(TRUE, LLColor4(0,0,0,1),LLStringUtil::null); - } -} - -LLStyle::LLStyle(BOOL is_visible, const LLColor4 &color, const std::string& font_name) -{ - init(is_visible, color, font_name); -} - -void LLStyle::init(BOOL is_visible, const LLColor4 &color, const std::string& font_name) +LLStyle::Params::Params() +: visible("visible", true), + drop_shadow("drop_shadow", LLFontGL::NO_SHADOW), + color("color", LLColor4::black), + font("font", LLFontGL::getFontMonospace()), + image("image"), + link_href("href") +{} + + +LLStyle::LLStyle(const LLStyle::Params& p) +: mVisible(p.visible), + mColor(p.color()), + mReadOnlyColor(p.readonly_color()), + mFont(p.font()), + mLink(p.link_href), + mDropShadow(p.drop_shadow), + mImagep(p.image()) +{} + +void LLStyle::setFont(const LLFontGL* font) { - mVisible = is_visible; - mColor = color; - setFontName(font_name); - setLinkHREF(LLStringUtil::null); - mItalic = FALSE; - mBold = FALSE; - mUnderline = FALSE; - mDropShadow = FALSE; - mImageHeight = 0; - mImageWidth = 0; - mIsEmbeddedItem = FALSE; + mFont = font; } -// Copy assignment -LLStyle &LLStyle::operator=(const LLStyle &rhs) +const LLFontGL* LLStyle::getFont() const { - if (this != &rhs) - { - setVisible(rhs.isVisible()); - setColor(rhs.getColor()); - this->mFontName = rhs.getFontString(); - this->mLink = rhs.getLinkHREF(); - mImagep = rhs.mImagep; - mImageHeight = rhs.mImageHeight; - mImageWidth = rhs.mImageWidth; - mItalic = rhs.mItalic; - mBold = rhs.mBold; - mUnderline = rhs.mUnderline; - mDropShadow = rhs.mDropShadow; - mIsEmbeddedItem = rhs.mIsEmbeddedItem; - } - - return *this; + return mFont; } - -void LLStyle::setFontName(const std::string& fontname) -{ - mFontName = fontname; - - std::string fontname_lc = fontname; - LLStringUtil::toLower(fontname_lc); - - mFontID = LLFONT_OCRA; // default - - if ((fontname_lc == "sansserif") || (fontname_lc == "sans-serif")) - { - mFontID = LLFONT_SANSSERIF; - } - else if ((fontname_lc == "serif")) - { - mFontID = LLFONT_SMALL; - } - else if ((fontname_lc == "sansserifbig")) - { - mFontID = LLFONT_SANSSERIF_BIG; - } - else if (fontname_lc == "small") - { - mFontID = LLFONT_SANSSERIF_SMALL; - } -} - - void LLStyle::setLinkHREF(const std::string& href) { mLink = href; @@ -166,12 +96,10 @@ LLUIImagePtr LLStyle::getImage() const void LLStyle::setImage(const LLUUID& src) { - mImagep = LLUI::sImageProvider->getUIImageByID(src); + mImagep = LLUI::getUIImageByID(src); } - -void LLStyle::setImageSize(S32 width, S32 height) +void LLStyle::setImage(const std::string& name) { - mImageWidth = width; - mImageHeight = height; + mImagep = LLUI::getUIImage(name); } diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h index 3ad379cdd9..ee9ca730e9 100644 --- a/indra/llui/llstyle.h +++ b/indra/llui/llstyle.h @@ -34,60 +34,64 @@ #define LL_LLSTYLE_H #include "v4color.h" -#include "llresmgr.h" -#include "llfont.h" #include "llui.h" +#include "llinitparam.h" + +class LLFontGL; class LLStyle : public LLRefCount { public: - LLStyle(); - LLStyle(const LLStyle &style); - LLStyle(BOOL is_visible, const LLColor4 &color, const std::string& font_name); - - LLStyle &operator=(const LLStyle &rhs); - - virtual void init (BOOL is_visible, const LLColor4 &color, const std::string& font_name); + struct Params : public LLInitParam::Block<Params> + { + Optional<bool> visible; + Optional<LLFontGL::ShadowType> drop_shadow; + Optional<LLUIColor> color, + readonly_color; + Optional<const LLFontGL*> font; + Optional<LLUIImage*> image; + Optional<std::string> link_href; + Params(); + }; + LLStyle(const Params& p = Params()); +public: + const LLColor4& getColor() const { return mColor; } + void setColor(const LLColor4 &color) { mColor = color; } - virtual const LLColor4& getColor() const { return mColor; } - virtual void setColor(const LLColor4 &color) { mColor = color; } + const LLColor4& getReadOnlyColor() const { return mReadOnlyColor; } - virtual BOOL isVisible() const; - virtual void setVisible(BOOL is_visible); + BOOL isVisible() const; + void setVisible(BOOL is_visible); - virtual const std::string& getFontString() const { return mFontName; } - virtual void setFontName(const std::string& fontname); - virtual LLFONT_ID getFontID() const { return mFontID; } + LLFontGL::ShadowType getShadowType() { return mDropShadow; } - virtual const std::string& getLinkHREF() const { return mLink; } - virtual void setLinkHREF(const std::string& href); - virtual BOOL isLink() const; + void setFont(const LLFontGL* font); + const LLFontGL* getFont() const; - virtual LLUIImagePtr getImage() const; - virtual void setImage(const LLUUID& src); + const std::string& getLinkHREF() const { return mLink; } + void setLinkHREF(const std::string& href); + BOOL isLink() const; - virtual BOOL isImage() const { return ((mImageWidth != 0) && (mImageHeight != 0)); } - virtual void setImageSize(S32 width, S32 height); + LLUIImagePtr getImage() const; + void setImage(const LLUUID& src); + void setImage(const std::string& name); - BOOL getIsEmbeddedItem() const { return mIsEmbeddedItem; } - void setIsEmbeddedItem( BOOL b ) { mIsEmbeddedItem = b; } + BOOL isImage() const { return mImagep.notNull(); } // inlined here to make it easier to compare to member data below. -MG bool operator==(const LLStyle &rhs) const { return - mVisible == rhs.isVisible() - && mColor == rhs.getColor() - && mFontName == rhs.getFontString() - && mLink == rhs.getLinkHREF() + mVisible == rhs.mVisible + && mColor == rhs.mColor + && mReadOnlyColor == rhs.mReadOnlyColor + && mFont == rhs.mFont + && mLink == rhs.mLink && mImagep == rhs.mImagep - && mImageHeight == rhs.mImageHeight - && mImageWidth == rhs.mImageWidth && mItalic == rhs.mItalic && mBold == rhs.mBold && mUnderline == rhs.mUnderline - && mDropShadow == rhs.mDropShadow - && mIsEmbeddedItem == rhs.mIsEmbeddedItem; + && mDropShadow == rhs.mDropShadow; } bool operator!=(const LLStyle& rhs) const { return !(*this == rhs); } @@ -96,21 +100,19 @@ public: BOOL mItalic; BOOL mBold; BOOL mUnderline; - BOOL mDropShadow; - S32 mImageWidth; - S32 mImageHeight; + LLFontGL::ShadowType mDropShadow; protected: - virtual ~LLStyle() { } + ~LLStyle() { } private: BOOL mVisible; - LLColor4 mColor; + LLUIColor mColor; + LLUIColor mReadOnlyColor; std::string mFontName; - LLFONT_ID mFontID; + const LLFontGL* mFont; // cached for performance std::string mLink; LLUIImagePtr mImagep; - BOOL mIsEmbeddedItem; }; typedef LLPointer<LLStyle> LLStyleSP; diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index f4169488d4..43c44f2253 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -31,77 +31,159 @@ */ #include "linden_common.h" + #include "lltabcontainer.h" + #include "llfocusmgr.h" #include "llbutton.h" +#include "lllocalcliprect.h" #include "llrect.h" -#include "llresmgr.h" #include "llresizehandle.h" #include "lltextbox.h" #include "llcriticaldamp.h" #include "lluictrlfactory.h" -#include "lltabcontainervertical.h" #include "llrender.h" +#include "llfloater.h" +#include "lltrans.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; -const S32 TAB_PADDING = 15; -const S32 TABCNTR_TAB_MIN_WIDTH = 60; -const S32 TABCNTR_VERT_TAB_MIN_WIDTH = 100; -const S32 TABCNTR_TAB_MAX_WIDTH = 150; -const S32 TABCNTR_TAB_PARTIAL_WIDTH = 12; // When tabs are parially obscured, how much can you still see. -const S32 TABCNTR_TAB_HEIGHT = 16; -const S32 TABCNTR_ARROW_BTN_SIZE = 16; -const S32 TABCNTR_BUTTON_PANEL_OVERLAP = 1; // how many pixels the tab buttons and tab panels overlap. -const S32 TABCNTR_TAB_H_PAD = 4; - -const S32 TABCNTR_CLOSE_BTN_SIZE = 16; -const S32 TABCNTR_HEADER_HEIGHT = LLPANEL_BORDER_WIDTH + TABCNTR_CLOSE_BTN_SIZE; - -const S32 TABCNTRV_CLOSE_BTN_SIZE = 16; -const S32 TABCNTRV_HEADER_HEIGHT = LLPANEL_BORDER_WIDTH + TABCNTRV_CLOSE_BTN_SIZE; -//const S32 TABCNTRV_TAB_WIDTH = 100; -const S32 TABCNTRV_ARROW_BTN_SIZE = 16; -const S32 TABCNTRV_PAD = 0; - -static LLRegisterWidget<LLTabContainer> r("tab_container"); - -LLTabContainer::LLTabContainer(const std::string& name, const LLRect& rect, TabPosition pos, - BOOL bordered, BOOL is_vertical ) - : - LLPanel(name, rect, bordered), + +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) + {} + + LLTabContainer* mTabContainer; + LLPanel* mTabPanel; + LLButton* mButton; + BOOL mOldState; + LLTextBox* mPlaceholderText; + S32 mPadding; +}; + +//---------------------------------------------------------------------------- + +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<LLPlaceHolderPanel> r1("placeholder"); +static LLDefaultChildRegistry::Register<LLTabContainer> r2("tab_container"); + +LLTabContainer::TabParams::TabParams() +: tab_top_image_unselected("tab_top_image_unselected"), + tab_top_image_selected("tab_top_image_selected"), + tab_bottom_image_unselected("tab_bottom_image_unselected"), + tab_bottom_image_selected("tab_bottom_image_selected"), + tab_left_image_unselected("tab_left_image_unselected"), + tab_left_image_selected("tab_left_image_selected") +{} + +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), + tab_padding_right("tab_padding_right"), + first_tab("first_tab"), + middle_tab("middle_tab"), + last_tab("last_tab") +{ + name(std::string("tab_container")); + mouse_opaque = false; +} + +LLTabContainer::LLTabContainer(const LLTabContainer::Params& p) +: LLPanel(p), mCurrentTabIdx(-1), - mNextTabIdx(-1), - mTabsHidden(FALSE), + mTabsHidden(p.hide_tabs), mScrolled(FALSE), mScrollPos(0), mScrollPosPixels(0), mMaxScrollPos(0), - mCloseCallback( NULL ), - mCallbackUserdata( NULL ), mTitleBox(NULL), mTopBorderHeight(LLPANEL_BORDER_WIDTH), - mTabPosition(pos), mLockedTabCount(0), - mMinTabWidth(TABCNTR_TAB_MIN_WIDTH), - mMaxTabWidth(TABCNTR_TAB_MAX_WIDTH), + 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(is_vertical), + mIsVertical( p.tab_position == LEFT ), // Horizontal Specific mJumpPrevArrowBtn(NULL), mJumpNextArrowBtn(NULL), - mRightTabBtnOffset(0), - mTotalTabWidth(0) -{ - //RN: HACK to support default min width for legacy vertical tab containers - if (mIsVertical) + 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) +{ + static LLUICachedControl<S32> tabcntr_vert_tab_min_width ("UITabCntrVertTabMinWidth", 0); + + mDragAndDropDelayTimer.stop(); + + if (p.tab_width.isProvided()) + { + mMinTabWidth = p.tab_width; + } + else if (!mIsVertical) { - mMinTabWidth = TABCNTR_VERT_TAB_MIN_WIDTH; + mMinTabWidth = p.tab_min_width; } - setMouseOpaque(FALSE); + else + { + // *HACK: support default min width for legacy vertical + // tab containers + mMinTabWidth = tabcntr_vert_tab_min_width; + } + initButtons( ); - mDragAndDropDelayTimer.stop(); } LLTabContainer::~LLTabContainer() @@ -123,7 +205,7 @@ void LLTabContainer::reshape(S32 width, S32 height, BOOL called_from_parent) } //virtual -LLView* LLTabContainer::getChildView(const std::string& name, BOOL recurse, BOOL create_if_missing) const +LLView* LLTabContainer::getChildView(const std::string& name, BOOL recurse) const { tuple_list_t::const_iterator itor; for (itor = mTabList.begin(); itor != mTabList.end(); ++itor) @@ -140,26 +222,81 @@ LLView* LLTabContainer::getChildView(const std::string& name, BOOL recurse, BOOL for (itor = mTabList.begin(); itor != mTabList.end(); ++itor) { LLPanel *panel = (*itor)->mTabPanel; - LLView *child = panel->getChildView(name, recurse, FALSE); + LLView *child = panel->getChildView(name, recurse); if (child) { return child; } } } - return LLView::getChildView(name, recurse, create_if_missing); + 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<LLPanel*>(view); + + if (panelp) + { + addTabPanel(TabPanelParams().panel(panelp).label(panelp->getLabel()).is_placeholder(dynamic_cast<LLPlaceHolderPanel*>(view) != NULL)); + return true; + } + else + { + return LLUICtrl::addChild(view, tab_group); + } +} + +BOOL LLTabContainer::postBuild() +{ + selectFirstTab(); + + return TRUE; } // virtual void LLTabContainer::draw() { + static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0); + static LLUICachedControl<S32> tabcntrv_arrow_btn_size ("UITabCntrvArrowBtnSize", 0); + static LLUICachedControl<S32> tabcntr_tab_h_pad ("UITabCntrTabHPad", 0); + static LLUICachedControl<S32> tabcntr_arrow_btn_size ("UITabCntrArrowBtnSize", 0); + static LLUICachedControl<S32> tabcntr_tab_partial_width ("UITabCntrTabPartialWidth", 0); S32 target_pixel_scroll = 0; S32 cur_scroll_pos = getScrollPos(); if (cur_scroll_pos > 0) { + S32 available_width_with_arrows = getRect().getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + tabcntr_arrow_btn_size + tabcntr_arrow_btn_size + 1); if (!mIsVertical) { - 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) @@ -171,31 +308,15 @@ void LLTabContainer::draw() } // Show part of the tab to the left of what is fully visible - target_pixel_scroll -= TABCNTR_TAB_PARTIAL_WIDTH; + 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); } - else - { - S32 available_height_with_arrows = getRect().getHeight() - getTopBorderHeight() - (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; - } - target_pixel_scroll += (*iter)->mButton->getRect().getHeight(); - cur_scroll_pos--; - } - S32 total_tab_height = (BTN_HEIGHT + TABCNTRV_PAD) * getTabCount() + TABCNTRV_PAD; - // clamp so that the bottom tab never leaves bottom of panel - target_pixel_scroll = llmin(total_tab_height - available_height_with_arrows, target_pixel_scroll); - } } setScrollPosPixels((S32)lerp((F32)getScrollPosPixels(), (F32)target_pixel_scroll, LLCriticalDamp::getInterpolant(0.08f))); - BOOL has_scroll_arrows = (mMaxScrollPos > 0) || (mScrollPosPixels > 0); + BOOL has_scroll_arrows = !getTabsHidden() && ((mMaxScrollPos > 0) || (mScrollPosPixels > 0)); if (!mIsVertical) { mJumpPrevArrowBtn->setVisible( has_scroll_arrows ); @@ -207,24 +328,33 @@ void LLTabContainer::draw() S32 left = 0, top = 0; if (mIsVertical) { - top = getRect().getHeight() - getTopBorderHeight() - LLPANEL_BORDER_WIDTH - 1 - (has_scroll_arrows ? TABCNTRV_ARROW_BTN_SIZE : 0); + 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 = LLPANEL_BORDER_WIDTH + (has_scroll_arrows ? (tabcntr_arrow_btn_size * 2) : tabcntr_tab_h_pad); left -= getScrollPosPixels(); } // Hide all the buttons - for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + if (getTabsHidden()) { - LLTabTuple* tuple = *iter; - tuple->mButton->setVisible( FALSE ); + for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) + { + LLTabTuple* tuple = *iter; + tuple->mButton->setVisible( FALSE ); + } } - LLPanel::draw(); + { + 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()) @@ -236,24 +366,6 @@ void LLTabContainer::draw() tuple->mButton->setVisible( TRUE ); } - // Draw some of the buttons... - LLRect clip_rect = getLocalRect(); - if (has_scroll_arrows) - { - // ...but clip them. - if (mIsVertical) - { - clip_rect.mBottom = mNextArrowBtn->getRect().mTop + 3*TABCNTRV_PAD; - clip_rect.mTop = mPrevArrowBtn->getRect().mBottom - 3*TABCNTRV_PAD; - } - else - { - clip_rect.mLeft = mPrevArrowBtn->getRect().mRight; - clip_rect.mRight = mNextArrowBtn->getRect().mLeft; - } - } - LLLocalClipRect clip(clip_rect); - S32 max_scroll_visible = getTabCount() - getMaxScrollPos() + getScrollPos(); S32 idx = 0; for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) @@ -262,7 +374,7 @@ void LLTabContainer::draw() 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 (top) top -= BTN_HEIGHT + tabcntrv_pad; if (left) left += tuple->mButton->getRect().getWidth(); if (!mIsVertical) @@ -282,12 +394,6 @@ void LLTabContainer::draw() } } } - LLUI::pushMatrix(); - { - LLUI::translate((F32)tuple->mButton->getRect().mLeft, (F32)tuple->mButton->getRect().mBottom, 0.f); - tuple->mButton->draw(); - } - LLUI::popMatrix(); idx++; } @@ -316,8 +422,9 @@ void LLTabContainer::draw() // virtual BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) { + static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0); BOOL handled = FALSE; - BOOL has_scroll_arrows = (getMaxScrollPos() > 0); + BOOL has_scroll_arrows = (getMaxScrollPos() > 0) && !getTabsHidden(); if (has_scroll_arrows) { @@ -359,9 +466,9 @@ BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) if (mIsVertical) { tab_rect = LLRect(firsttuple->mButton->getRect().mLeft, - has_scroll_arrows ? mPrevArrowBtn->getRect().mBottom - TABCNTRV_PAD : mPrevArrowBtn->getRect().mTop, + 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 ); + has_scroll_arrows ? mNextArrowBtn->getRect().mTop + tabcntrv_pad : mNextArrowBtn->getRect().mBottom ); } else { @@ -376,7 +483,7 @@ BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) index = llclamp(index, 0, tab_count-1); LLButton* tab_button = getTab(index)->mButton; gFocusMgr.setMouseCapture(this); - gFocusMgr.setKeyboardFocus(tab_button); + tab_button->setFocus(TRUE); } } return handled; @@ -386,7 +493,7 @@ BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) BOOL LLTabContainer::handleHover( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; - BOOL has_scroll_arrows = (getMaxScrollPos() > 0); + BOOL has_scroll_arrows = (getMaxScrollPos() > 0) && !getTabsHidden(); if (has_scroll_arrows) { @@ -428,7 +535,7 @@ BOOL LLTabContainer::handleHover( S32 x, S32 y, MASK mask ) BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; - BOOL has_scroll_arrows = (getMaxScrollPos() > 0); + BOOL has_scroll_arrows = (getMaxScrollPos() > 0) && !getTabsHidden(); if (has_scroll_arrows) { @@ -481,9 +588,10 @@ BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) } // virtual -BOOL LLTabContainer::handleToolTip( S32 x, S32 y, std::string& msg, LLRect* sticky_rect ) +BOOL LLTabContainer::handleToolTip( S32 x, S32 y, MASK mask) { - BOOL handled = LLPanel::handleToolTip( x, y, msg, sticky_rect ); + static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0); + BOOL handled = LLPanel::handleToolTip( x, y, mask); if (!handled && getTabCount() > 0) { LLTabTuple* firsttuple = getTab(0); @@ -493,9 +601,9 @@ BOOL LLTabContainer::handleToolTip( S32 x, S32 y, std::string& msg, LLRect* stic if (mIsVertical) { clip = LLRect(firsttuple->mButton->getRect().mLeft, - has_scroll_arrows ? mPrevArrowBtn->getRect().mBottom - TABCNTRV_PAD : mPrevArrowBtn->getRect().mTop, + 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 ); + has_scroll_arrows ? mNextArrowBtn->getRect().mTop + tabcntrv_pad : mNextArrowBtn->getRect().mBottom ); } else { @@ -513,19 +621,13 @@ BOOL LLTabContainer::handleToolTip( S32 x, S32 y, std::string& msg, LLRect* stic tuple->mButton->setVisible( TRUE ); S32 local_x = x - tuple->mButton->getRect().mLeft; S32 local_y = y - tuple->mButton->getRect().mBottom; - handled = tuple->mButton->handleToolTip( local_x, local_y, msg, sticky_rect ); + handled = tuple->mButton->handleToolTip( local_x, local_y, mask); if( handled ) { break; } } } - - for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) - { - LLTabTuple* tuple = *iter; - tuple->mButton->setVisible( FALSE ); - } } return handled; } @@ -617,19 +719,11 @@ BOOL LLTabContainer::handleKeyHere(KEY key, MASK mask) } // virtual -LLXMLNodePtr LLTabContainer::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLPanel::getXML(); - node->createChild("tab_position", TRUE)->setStringValue((getTabPosition() == TOP ? "top" : "bottom")); - return node; -} - -// 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 = (getMaxScrollPos() > 0); - if( mDragAndDropDelayTimer.getElapsedTimeF32() > SCROLL_DELAY_TIME ) + if( mDragAndDropDelayTimer.getStarted() && mDragAndDropDelayTimer.getElapsedTimeF32() > SCROLL_DELAY_TIME ) { if (has_scroll_arrows) { @@ -676,21 +770,53 @@ BOOL LLTabContainer::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDrag return LLView::handleDragAndDrop(x, y, mask, drop, type, cargo_data, accept, tooltip); } -void LLTabContainer::addTabPanel(LLPanel* child, - const std::string& label, - BOOL select, - void (*on_tab_clicked)(void*, bool), - void* userdata, - S32 indent, - BOOL placeholder, - eInsertionPoint insertion_point) +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<LLUIImage*>(params.tab_top_image_unselected)); + tuple->mButton->setImageSelected(static_cast<LLUIImage*>(params.tab_top_image_selected)); + } + else if (pos == LLTabContainer::BOTTOM) + { + tuple->mButton->setImageUnselected(static_cast<LLUIImage*>(params.tab_bottom_image_unselected)); + tuple->mButton->setImageSelected(static_cast<LLUIImage*>(params.tab_bottom_image_selected)); + } + else if (pos == LLTabContainer::LEFT) + { + tuple->mButton->setImageUnselected(static_cast<LLUIImage*>(params.tab_left_image_unselected)); + tuple->mButton->setImageSelected(static_cast<LLUIImage*>(params.tab_left_image_selected)); + } + } +} + +void LLTabContainer::addTabPanel(const TabPanelParams& panel) { + LLPanel* child = panel.panel(); + 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<S32> tabcntrv_pad ("UITabCntrvPad", 0); + static LLUICachedControl<S32> tabcntr_button_panel_overlap ("UITabCntrButtonPanelOverlap", 0); + static LLUICachedControl<S32> tab_padding ("UITabPadding", 0); if (child->getParent() == this) { // already a child of mine return; } - const LLFontGL* font = LLResMgr::getInstance()->getRes( mIsVertical ? LLFONT_SANSSERIF : LLFONT_SANSSERIF_SMALL ); // Store the original label for possible xml export. child->setLabel(label); @@ -700,28 +826,37 @@ void LLTabContainer::addTabPanel(LLPanel* child, S32 button_width = mMinTabWidth; if (!mIsVertical) { - button_width = llclamp(font->getWidth(trimmed_label) + TAB_PADDING, mMinTabWidth, mMaxTabWidth); + button_width = llclamp(mFont->getWidth(trimmed_label) + tab_padding, mMinTabWidth, mMaxTabWidth); } // Tab panel S32 tab_panel_top; S32 tab_panel_bottom; - if( getTabPosition() == LLTabContainer::TOP ) + if (!getTabsHidden()) { - S32 tab_height = mIsVertical ? BTN_HEIGHT : TABCNTR_TAB_HEIGHT; - tab_panel_top = getRect().getHeight() - getTopBorderHeight() - (tab_height - TABCNTR_BUTTON_PANEL_OVERLAP); - tab_panel_bottom = LLPANEL_BORDER_WIDTH; + 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 { - tab_panel_top = getRect().getHeight() - getTopBorderHeight(); - tab_panel_bottom = (TABCNTR_TAB_HEIGHT - TABCNTR_BUTTON_PANEL_OVERLAP); // Run to the edge, covering up the border + //Scip tab button space if they are invisible(EXT - 576) + tab_panel_top = getRect().getHeight(); + tab_panel_bottom = LLPANEL_BORDER_WIDTH; } - + LLRect tab_panel_rect; - if (mIsVertical) + if (!getTabsHidden() && mIsVertical) { - tab_panel_rect = LLRect(mMinTabWidth + (LLPANEL_BORDER_WIDTH * 2) + TABCNTRV_PAD, + tab_panel_rect = LLRect(mMinTabWidth + (LLPANEL_BORDER_WIDTH * 2) + tabcntrv_pad, getRect().getHeight() - LLPANEL_BORDER_WIDTH, getRect().getWidth() - LLPANEL_BORDER_WIDTH, LLPANEL_BORDER_WIDTH); @@ -744,28 +879,28 @@ void LLTabContainer::addTabPanel(LLPanel* child, // Tab button LLRect btn_rect; // Note: btn_rect.mLeft is just a dummy. Will be updated in draw(). - std::string tab_img; - std::string tab_selected_img; + 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()), + 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, TABCNTR_TAB_HEIGHT ); - tab_img = "tab_top_blue.tga"; - tab_selected_img = "tab_top_selected_blue.tga"; + 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, TABCNTR_TAB_HEIGHT ); - tab_img = "tab_bottom_blue.tga"; - tab_selected_img = "tab_bottom_selected_blue.tga"; + 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; @@ -773,89 +908,132 @@ void LLTabContainer::addTabPanel(LLPanel* child, if (placeholder) { - btn_rect.translate(0, -LLBUTTON_V_PAD-2); - textbox = new LLTextBox(trimmed_label, btn_rect, trimmed_label, font); + 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<LLTextBox> (params); - btn = new LLButton(LLStringUtil::null, LLRect(0,0,0,0)); + LLButton::Params p; + p.name("placeholder"); + btn = LLUICtrlFactory::create<LLButton>(p); } else { if (mIsVertical) { - btn = new LLButton(std::string("vert tab button"), - btn_rect, - LLStringUtil::null, - LLStringUtil::null, - LLStringUtil::null, - &LLTabContainer::onTabBtn, NULL, - font, - trimmed_label, trimmed_label); - btn->setImages(std::string("tab_left.tga"), std::string("tab_left_selected.tga")); - btn->setScaleImage(TRUE); - btn->setHAlign(LLFontGL::LEFT); - btn->setFollows(FOLLOWS_TOP | FOLLOWS_LEFT); - btn->setTabStop(FALSE); + LLButton::Params p; + p.name(std::string("vert tab button")); + p.rect(btn_rect); + p.follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT); + p.click_callback.function(boost::bind(&LLTabContainer::onTabBtn, this, _2, child)); + p.font(mFont); + p.label(trimmed_label); + p.image_unselected(mMiddleTabParams.tab_left_image_unselected); + p.image_selected(mMiddleTabParams.tab_left_image_selected); + p.scale_image(true); + p.font_halign = mFontHalign; + p.pad_bottom( mLabelPadBottom ); + p.tab_stop(false); + p.label_shadow(false); if (indent) { - btn->setLeftHPad(indent); + p.pad_left(indent); } + btn = LLUICtrlFactory::create<LLButton>(p); } else { - std::string tooltip = trimmed_label; - tooltip += "\nAlt-Left arrow for previous tab"; - tooltip += "\nAlt-Right arrow for next tab"; - - btn = new LLButton(std::string(child->getName()) + " tab", - btn_rect, - LLStringUtil::null, LLStringUtil::null, LLStringUtil::null, - &LLTabContainer::onTabBtn, NULL, // set userdata below - font, - trimmed_label, trimmed_label ); - btn->setVisible( FALSE ); - btn->setToolTip( tooltip ); - btn->setScaleImage(TRUE); - btn->setImages(tab_img, tab_selected_img); - + LLButton::Params p; + p.name(std::string(child->getName()) + " tab"); + p.rect(btn_rect); + p.click_callback.function(boost::bind(&LLTabContainer::onTabBtn, this, _2, child)); + p.font(mFont); + p.label(trimmed_label); + p.visible(false); + p.scale_image(true); + p.image_unselected(tab_img); + p.image_selected(tab_selected_img); + p.tab_stop(false); + p.label_shadow(false); // Try to squeeze in a bit more text - btn->setLeftHPad( 4 ); - btn->setRightHPad( 2 ); - btn->setHAlign(LLFontGL::LEFT); - btn->setTabStop(FALSE); + p.pad_left( mLabelPadLeft ); + p.pad_right(2); + p.pad_bottom( mLabelPadBottom ); + p.font_halign = mFontHalign; + p.follows.flags = FOLLOWS_LEFT; + p.follows.flags = FOLLOWS_LEFT; + if (indent) { - btn->setLeftHPad(indent); + p.pad_left(indent); } if( getTabPosition() == TOP ) { - btn->setFollowsTop(); + p.follows.flags = p.follows.flags() | FOLLOWS_TOP; } else { - btn->setFollowsBottom(); + p.follows.flags = p.follows.flags() | FOLLOWS_BOTTOM; } + +++ btn = LLUICtrlFactory::create<LLButton>(p); } } - LLTabTuple* tuple = new LLTabTuple( this, child, btn, on_tab_clicked, userdata, textbox ); + LLTabTuple* tuple = new LLTabTuple( this, child, btn, textbox ); insertTuple( tuple, insertion_point ); - if (textbox) + // 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()) { - textbox->setSaveToXML(false); - addChild( textbox, 0 ); + update_images(tuple, mLastTabParams, getTabPosition()); + + if (mTabList.size() > 2) + { + update_images(mTabList[mTabList.size()-2], mMiddleTabParams, getTabPosition()); + } } - if (btn) + + //Don't add button and textbox if tab buttons are invisible(EXT - 576) + if (!getTabsHidden()) { - btn->setSaveToXML(false); - btn->setCallbackUserData( tuple ); - addChild( btn, 0 ); + if (textbox) + { + addChild( textbox, 0 ); + } + if (btn) + { + addChild( btn, 0 ); + } } + if (child) { - addChild(child, 1); + LLUICtrl::addChild(child, 1); } + + sendChildToFront(mPrevArrowBtn); + sendChildToFront(mNextArrowBtn); + sendChildToFront(mJumpPrevArrowBtn); + sendChildToFront(mJumpNextArrowBtn); if( select ) { @@ -867,11 +1045,12 @@ void LLTabContainer::addTabPanel(LLPanel* child, void LLTabContainer::addPlaceholder(LLPanel* child, const std::string& label) { - addTabPanel(child, label, FALSE, NULL, NULL, 0, TRUE); + addTabPanel(TabPanelParams().panel(child).label(label).is_placeholder(true)); } void LLTabContainer::removeTabPanel(LLPanel* child) { + static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0); if (mIsVertical) { // Fix-up button sizes @@ -880,8 +1059,8 @@ void LLTabContainer::removeTabPanel(LLPanel* child) { 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)), + 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) @@ -917,7 +1096,17 @@ void LLTabContainer::removeTabPanel(LLPanel* child) LLTabTuple* tuple = *iter; if( tuple->mTabPanel == child ) { - removeChild( tuple->mButton ); + // 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()); + } + + removeChild( tuple->mButton ); delete tuple->mButton; removeChild( tuple->mTabPanel ); @@ -1044,7 +1233,7 @@ S32 LLTabContainer::getPanelIndexByTitle(const std::string& title) return -1; } -LLPanel *LLTabContainer::getPanelByName(const std::string& name) +LLPanel* LLTabContainer::getPanelByName(const std::string& name) { for (S32 index = 0 ; index < (S32)mTabList.size(); index++) { @@ -1138,42 +1327,36 @@ BOOL LLTabContainer::selectTabPanel(LLPanel* child) BOOL LLTabContainer::selectTab(S32 which) { - if (which >= getTabCount()) return FALSE; - if (which < 0) return FALSE; - - //if( gFocusMgr.childHasKeyboardFocus( this ) ) - //{ - // gFocusMgr.setKeyboardFocus( NULL ); - //} + 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(); - if (!selected_tuple->mPrecommitChangeCallback) + BOOL res = FALSE; + if( !mValidateSignal || (*mValidateSignal)( this, cbdata ) ) { - return setTab(which); + res = setTab(which); + if (res && mCommitSignal) + { + (*mCommitSignal)(this, cbdata); + } } - - mNextTabIdx = which; - selected_tuple->mPrecommitChangeCallback(selected_tuple->mUserData, false); - return TRUE; + + return res; } +// private BOOL LLTabContainer::setTab(S32 which) { - if (which == -1) - { - if (mNextTabIdx == -1) - { - return FALSE; - } - which = mNextTabIdx; - mNextTabIdx = -1; - } - + static LLUICachedControl<S32> tabcntr_arrow_btn_size ("UITabCntrArrowBtnSize", 0); LLTabTuple* selected_tuple = getTab(which); if (!selected_tuple) { @@ -1196,7 +1379,7 @@ BOOL LLTabContainer::setTab(S32 which) // RN: this limits tab-stops to active button only, which would require arrow keys to switch tabs tuple->mButton->setTabStop( is_selected ); - if( is_selected && (mIsVertical || (getMaxScrollPos() > 0))) + if (is_selected) { // Make sure selected tab is within scroll region if (mIsVertical) @@ -1212,7 +1395,7 @@ BOOL LLTabContainer::setTab(S32 which) is_visible = FALSE; } } - else + else if (getMaxScrollPos() > 0) { if( i < getScrollPos() ) { @@ -1220,7 +1403,7 @@ BOOL LLTabContainer::setTab(S32 which) } else { - S32 available_width_with_arrows = getRect().getWidth() - mRightTabBtnOffset - 2 * (LLPANEL_BORDER_WIDTH + TABCNTR_ARROW_BTN_SIZE + TABCNTR_ARROW_BTN_SIZE + 1); + 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->mButton->getRect().getWidth(); S32 j = i - 1; S32 min_scroll_pos = i; @@ -1243,13 +1426,13 @@ BOOL LLTabContainer::setTab(S32 which) } is_visible = TRUE; } + else + { + is_visible = TRUE; + } } i++; } - if( selected_tuple->mOnChangeCallback ) - { - selected_tuple->mOnChangeCallback( selected_tuple->mUserData, false ); - } } if (mIsVertical && getCurrentPanelIndex() >= 0) { @@ -1295,6 +1478,7 @@ void LLTabContainer::setTabPanelFlashing(LLPanel* child, BOOL state ) void LLTabContainer::setTabImage(LLPanel* child, std::string image_name, const LLColor4& color) { + static LLUICachedControl<S32> tab_padding ("UITabPadding", 0); LLTabTuple* tuple = getTabByPanel(child); if( tuple ) { @@ -1302,7 +1486,6 @@ void LLTabContainer::setTabImage(LLPanel* child, std::string image_name, const L if (!mIsVertical) { - const LLFontGL* fontp = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ); // remove current width from total tab strip width mTotalTabWidth -= tuple->mButton->getRect().getWidth(); @@ -1313,7 +1496,38 @@ void LLTabContainer::setTabImage(LLPanel* child, std::string image_name, const L tuple->mPadding = image_overlay_width; tuple->mButton->setRightHPad(6); - tuple->mButton->reshape(llclamp(fontp->getWidth(tuple->mButton->getLabelSelected()) + TAB_PADDING + tuple->mPadding, mMinTabWidth, mMaxTabWidth), + 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::setTabImage(LLPanel* child, const LLUUID& image_id, const LLColor4& color) +{ + static LLUICachedControl<S32> tab_padding ("UITabPadding", 0); + LLTabTuple* tuple = getTabByPanel(child); + if( tuple ) + { + tuple->mButton->setImageOverlay(image_id, LLFontGL::RIGHT, color); + + if (!mIsVertical) + { + // remove current width from total tab strip width + mTotalTabWidth -= tuple->mButton->getRect().getWidth(); + + S32 image_overlay_width = tuple->mButton->getImageOverlay().notNull() ? + tuple->mButton->getImageOverlay()->getImage()->getWidth(0) : + 0; + + tuple->mPadding = image_overlay_width; + + tuple->mButton->setRightHPad(6); + 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(); @@ -1352,33 +1566,6 @@ S32 LLTabContainer::getTopBorderHeight() const return mTopBorderHeight; } -void LLTabContainer::setTabChangeCallback(LLPanel* tab, void (*on_tab_clicked)(void*, bool)) -{ - LLTabTuple* tuplep = getTabByPanel(tab); - if (tuplep) - { - tuplep->mOnChangeCallback = on_tab_clicked; - } -} - -void LLTabContainer::setTabPrecommitChangeCallback(LLPanel* tab, void (*on_precommit)(void*, bool)) -{ - LLTabTuple* tuplep = getTabByPanel(tab); - if (tuplep) - { - tuplep->mPrecommitChangeCallback = on_precommit; - } -} - -void LLTabContainer::setTabUserData(LLPanel* tab, void* userdata) -{ - LLTabTuple* tuplep = getTabByPanel(tab); - if (tuplep) - { - tuplep->mUserData = userdata; - } -} - void LLTabContainer::setRightTabBtnOffset(S32 offset) { mNextArrowBtn->translate( -offset - mRightTabBtnOffset, 0 ); @@ -1388,13 +1575,15 @@ void LLTabContainer::setRightTabBtnOffset(S32 offset) void LLTabContainer::setPanelTitle(S32 index, const std::string& title) { + static LLUICachedControl<S32> tab_padding ("UITabPadding", 0); + if (index >= 0 && index < getTabCount()) { LLTabTuple* tuple = getTab(index); LLButton* tab_button = tuple->mButton; - const LLFontGL* fontp = LLResMgr::getInstance()->getRes( LLFONT_SANSSERIF_SMALL ); + 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()); + 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); @@ -1403,182 +1592,60 @@ void LLTabContainer::setPanelTitle(S32 index, const std::string& title) } -// static -void LLTabContainer::onTabBtn( void* userdata ) +void LLTabContainer::onTabBtn( const LLSD& data, LLPanel* panel ) { - LLTabTuple* tuple = (LLTabTuple*) userdata; - LLTabContainer* self = tuple->mTabContainer; - self->selectTabPanel( tuple->mTabPanel ); + LLTabTuple* tuple = getTabByPanel(panel); + selectTabPanel( panel ); tuple->mTabPanel->setFocus(TRUE); } -// static -void LLTabContainer::onCloseBtn( void* userdata ) -{ - LLTabContainer* self = (LLTabContainer*) userdata; - if( self->mCloseCallback ) - { - self->mCloseCallback( self->mCallbackUserdata ); - } -} - -// static -void LLTabContainer::onNextBtn( void* userdata ) +void LLTabContainer::onNextBtn( const LLSD& data ) { - // Scroll tabs to the left - LLTabContainer* self = (LLTabContainer*) userdata; - if (!self->mScrolled) + if (!mScrolled) { - self->scrollNext(); + scrollNext(); } - self->mScrolled = FALSE; + mScrolled = FALSE; } -// static -void LLTabContainer::onNextBtnHeld( void* userdata ) +void LLTabContainer::onNextBtnHeld( const LLSD& data ) { - LLTabContainer* self = (LLTabContainer*) userdata; - if (self->mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME) + if (mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME) { - self->mScrollTimer.reset(); - self->scrollNext(); - self->mScrolled = TRUE; + mScrollTimer.reset(); + scrollNext(); + mScrolled = TRUE; } } -// static -void LLTabContainer::onPrevBtn( void* userdata ) +void LLTabContainer::onPrevBtn( const LLSD& data ) { - LLTabContainer* self = (LLTabContainer*) userdata; - if (!self->mScrolled) + if (!mScrolled) { - self->scrollPrev(); + scrollPrev(); } - self->mScrolled = FALSE; + mScrolled = FALSE; } -// static -void LLTabContainer::onJumpFirstBtn( void* userdata ) +void LLTabContainer::onJumpFirstBtn( const LLSD& data ) { - LLTabContainer* self = (LLTabContainer*) userdata; - self->mScrollPos = 0; + mScrollPos = 0; } -// static -void LLTabContainer::onJumpLastBtn( void* userdata ) +void LLTabContainer::onJumpLastBtn( const LLSD& data ) { - LLTabContainer* self = (LLTabContainer*) userdata; - self->mScrollPos = self->mMaxScrollPos; + mScrollPos = mMaxScrollPos; } -// static -void LLTabContainer::onPrevBtnHeld( void* userdata ) +void LLTabContainer::onPrevBtnHeld( const LLSD& data ) { - LLTabContainer* self = (LLTabContainer*) userdata; - if (self->mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME) - { - self->mScrollTimer.reset(); - self->scrollPrev(); - self->mScrolled = TRUE; - } -} - -// static -LLView* LLTabContainer::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("tab_container"); - node->getAttributeString("name", name); - - // Figure out if we are creating a vertical or horizontal tab container. - bool is_vertical = false; - LLTabContainer::TabPosition tab_position = LLTabContainer::TOP; - if (node->hasAttribute("tab_position")) - { - std::string tab_position_string; - node->getAttributeString("tab_position", tab_position_string); - LLStringUtil::toLower(tab_position_string); - - if ("top" == tab_position_string) - { - tab_position = LLTabContainer::TOP; - is_vertical = false; - } - else if ("bottom" == tab_position_string) - { - tab_position = LLTabContainer::BOTTOM; - is_vertical = false; - } - else if ("left" == tab_position_string) - { - is_vertical = true; - } - } - BOOL border = FALSE; - node->getAttributeBOOL("border", border); - - LLTabContainer* tab_container = new LLTabContainer(name, LLRect::null, tab_position, border, is_vertical); - - S32 tab_min_width = tab_container->mMinTabWidth; - if (node->hasAttribute("tab_width")) - { - node->getAttributeS32("tab_width", tab_min_width); - } - else if( node->hasAttribute("tab_min_width")) - { - node->getAttributeS32("tab_min_width", tab_min_width); - } - - S32 tab_max_width = tab_container->mMaxTabWidth; - if (node->hasAttribute("tab_max_width")) - { - node->getAttributeS32("tab_max_width", tab_max_width); - } - - tab_container->setMinTabWidth(tab_min_width); - tab_container->setMaxTabWidth(tab_max_width); - - BOOL hidden(tab_container->getTabsHidden()); - node->getAttributeBOOL("hide_tabs", hidden); - tab_container->setTabsHidden(hidden); - - tab_container->setPanelParameters(node, parent); - - if (LLFloater::getFloaterHost()) + if (mScrollTimer.getElapsedTimeF32() > SCROLL_STEP_TIME) { - LLFloater::getFloaterHost()->setTabContainer(tab_container); - } - - //parent->addChild(tab_container); - - // Add all tab panels. - LLXMLNodePtr child; - for (child = node->getFirstChild(); child.notNull(); child = child->getNextSibling()) - { - LLView *control = factory->createCtrlWidget(tab_container, child); - if (control && control->isPanel()) - { - LLPanel* panelp = (LLPanel*)control; - std::string label; - child->getAttributeString("label", label); - if (label.empty()) - { - label = panelp->getLabel(); - } - BOOL placeholder = FALSE; - child->getAttributeBOOL("placeholder", placeholder); - tab_container->addTabPanel(panelp, label, false, - NULL, NULL, 0, placeholder); - } + mScrollTimer.reset(); + scrollPrev(); + mScrolled = TRUE; } - - tab_container->selectFirstTab(); - - tab_container->postBuild(); - - tab_container->initButtons(); // now that we have the correct rect - - return tab_container; } // private @@ -1591,99 +1658,105 @@ void LLTabContainer::initButtons() return; // Don't have a rect yet or already got called } - std::string out_id; - std::string in_id; - if (mIsVertical) { + static LLUICachedControl<S32> 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; + 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 ); + 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 ); - - out_id = "UIImgBtnScrollUpOutUUID"; - in_id = "UIImgBtnScrollUpInUUID"; - mPrevArrowBtn = new LLButton(std::string("Up Arrow"), up_arrow_btn_rect, - out_id, in_id, LLStringUtil::null, - &onPrevBtn, this, NULL ); - mPrevArrowBtn->setFollowsTop(); - mPrevArrowBtn->setFollowsLeft(); - - out_id = "UIImgBtnScrollDownOutUUID"; - in_id = "UIImgBtnScrollDownInUUID"; - mNextArrowBtn = new LLButton(std::string("Down Arrow"), down_arrow_btn_rect, - out_id, in_id, LLStringUtil::null, - &onNextBtn, this, NULL ); - mNextArrowBtn->setFollowsBottom(); - mNextArrowBtn->setFollowsLeft(); + 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<LLButton>(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<LLButton>(next_btn_params); } else // Horizontal { + static LLUICachedControl<S32> tabcntr_arrow_btn_size ("UITabCntrArrowBtnSize", 0); S32 arrow_fudge = 1; // match new art better - // tabs on bottom reserve room for resize handle (just in case) - if (getTabPosition() == BOTTOM) - { - mRightTabBtnOffset = RESIZE_HANDLE_WIDTH; - } - // 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; + 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, TABCNTR_ARROW_BTN_SIZE ); + 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, TABCNTR_ARROW_BTN_SIZE ); + 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; + 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, + right_arrow_btn_rect.setLeftTopAndSize( getRect().getWidth() - mRightTabBtnOffset - right_pad - tabcntr_arrow_btn_size, btn_top + arrow_fudge, - TABCNTR_ARROW_BTN_SIZE, TABCNTR_ARROW_BTN_SIZE ); + 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, TABCNTR_ARROW_BTN_SIZE ); - - out_id = "UIImgBtnJumpLeftOutUUID"; - in_id = "UIImgBtnJumpLeftInUUID"; - mJumpPrevArrowBtn = new LLButton(std::string("Jump Left Arrow"), jump_left_arrow_btn_rect, - out_id, in_id, LLStringUtil::null, - &LLTabContainer::onJumpFirstBtn, this, LLFontGL::getFontSansSerif() ); - mJumpPrevArrowBtn->setFollowsLeft(); - - out_id = "UIImgBtnScrollLeftOutUUID"; - in_id = "UIImgBtnScrollLeftInUUID"; - mPrevArrowBtn = new LLButton(std::string("Left Arrow"), left_arrow_btn_rect, - out_id, in_id, LLStringUtil::null, - &LLTabContainer::onPrevBtn, this, LLFontGL::getFontSansSerif() ); - mPrevArrowBtn->setHeldDownCallback(onPrevBtnHeld); - mPrevArrowBtn->setFollowsLeft(); - - out_id = "UIImgBtnJumpRightOutUUID"; - in_id = "UIImgBtnJumpRightInUUID"; - mJumpNextArrowBtn = new LLButton(std::string("Jump Right Arrow"), jump_right_arrow_btn_rect, - out_id, in_id, LLStringUtil::null, - &LLTabContainer::onJumpLastBtn, this, - LLFontGL::getFontSansSerif()); - mJumpNextArrowBtn->setFollowsRight(); - - out_id = "UIImgBtnScrollRightOutUUID"; - in_id = "UIImgBtnScrollRightInUUID"; - mNextArrowBtn = new LLButton(std::string("Right Arrow"), right_arrow_btn_rect, - out_id, in_id, LLStringUtil::null, - &LLTabContainer::onNextBtn, this, - LLFontGL::getFontSansSerif()); - mNextArrowBtn->setFollowsRight(); + 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<LLButton>(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<LLButton>(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<LLButton>(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<LLButton>(p); if( getTabPosition() == TOP ) { @@ -1701,26 +1774,20 @@ void LLTabContainer::initButtons() } } - mPrevArrowBtn->setHeldDownCallback(onPrevBtnHeld); - mPrevArrowBtn->setSaveToXML(false); mPrevArrowBtn->setTabStop(FALSE); addChild(mPrevArrowBtn); - mNextArrowBtn->setHeldDownCallback(onNextBtnHeld); - mNextArrowBtn->setSaveToXML(false); mNextArrowBtn->setTabStop(FALSE); addChild(mNextArrowBtn); if (mJumpPrevArrowBtn) { - mJumpPrevArrowBtn->setSaveToXML(false); mJumpPrevArrowBtn->setTabStop(FALSE); addChild(mJumpPrevArrowBtn); } if (mJumpNextArrowBtn) { - mJumpNextArrowBtn->setSaveToXML(false); mJumpNextArrowBtn->setTabStop(FALSE); addChild(mJumpNextArrowBtn); } @@ -1729,7 +1796,17 @@ void LLTabContainer::initButtons() setDefaultTabGroup(1); } -LLTabContainer::LLTabTuple* LLTabContainer::getTabByPanel(LLPanel* child) +//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) { @@ -1775,14 +1852,16 @@ void LLTabContainer::insertTuple(LLTabTuple * tuple, eInsertionPoint insertion_p void LLTabContainer::updateMaxScrollPos() { + static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0); BOOL no_scroll = TRUE; if (mIsVertical) { - S32 tab_total_height = (BTN_HEIGHT + TABCNTRV_PAD) * getTabCount(); + S32 tab_total_height = (BTN_HEIGHT + tabcntrv_pad) * getTabCount(); S32 available_height = getRect().getHeight() - getTopBorderHeight(); if( tab_total_height > available_height ) { - S32 available_height_with_arrows = getRect().getHeight() - 2*(TABCNTRV_ARROW_BTN_SIZE + 3*TABCNTRV_PAD); + static LLUICachedControl<S32> tabcntrv_arrow_btn_size ("UITabCntrvArrowBtnSize", 0); + S32 available_height_with_arrows = getRect().getHeight() - 2*(tabcntrv_arrow_btn_size + 3*tabcntrv_pad); S32 additional_needed = tab_total_height - available_height_with_arrows; setMaxScrollPos((S32) ceil(additional_needed / float(BTN_HEIGHT) ) ); no_scroll = FALSE; @@ -1790,16 +1869,19 @@ void LLTabContainer::updateMaxScrollPos() } else { + static LLUICachedControl<S32> tabcntr_tab_h_pad ("UITabCntrTabHPad", 0); + static LLUICachedControl<S32> tabcntr_arrow_btn_size ("UITabCntrArrowBtnSize", 0); + static LLUICachedControl<S32> 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); + 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); + 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; + available_width_with_arrows -= tabcntr_tab_partial_width; S32 running_tab_width = 0; setMaxScrollPos(getTabCount()); @@ -1830,12 +1912,11 @@ void LLTabContainer::updateMaxScrollPos() void LLTabContainer::commitHoveredButton(S32 x, S32 y) { - if (hasMouseCapture()) + if (!getTabsHidden() && hasMouseCapture()) { 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()) @@ -1845,6 +1926,3 @@ void LLTabContainer::commitHoveredButton(S32 x, S32 y) } } } - - - diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h index 8117cdee9b..33c49e0d6f 100644 --- a/indra/llui/lltabcontainer.h +++ b/indra/llui/lltabcontainer.h @@ -37,7 +37,7 @@ #include "lltextbox.h" #include "llframetimer.h" -extern const S32 TABCNTR_HEADER_HEIGHT; +class LLTabTuple; class LLTabContainer : public LLPanel { @@ -56,8 +56,50 @@ public: RIGHT_OF_CURRENT } eInsertionPoint; - LLTabContainer( const std::string& name, const LLRect& rect, TabPosition pos, - BOOL bordered, BOOL is_vertical); + struct TabPositions : public LLInitParam::TypeValuesHelper<LLTabContainer::TabPosition, TabPositions> + { + static void declareValues(); + }; + + struct TabParams : public LLInitParam::Block<TabParams> + { + Optional<LLUIImage*> tab_top_image_unselected, + tab_top_image_selected, + tab_bottom_image_unselected, + tab_bottom_image_selected, + tab_left_image_unselected, + tab_left_image_selected; + TabParams(); + }; + + struct Params + : public LLInitParam::Block<Params, LLPanel::Params> + { + Optional<TabPosition, TabPositions> tab_position; + Optional<S32> tab_width, + tab_min_width, + tab_max_width, + tab_height, + label_pad_bottom, + label_pad_left; + + Optional<bool> hide_tabs; + Optional<S32> tab_padding_right; + + Optional<TabParams> first_tab, + middle_tab, + last_tab; + + 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(); @@ -69,22 +111,40 @@ public: /*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, std::string& msg, LLRect* sticky_rect ); + /*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*/ LLXMLNodePtr getXML(bool save_children = true) const; - /*virtual*/ LLView* getChildView(const std::string& name, BOOL recurse = TRUE, BOOL create_if_missing = TRUE) const; - - void addTabPanel(LLPanel* child, - const std::string& label, - BOOL select = FALSE, - void (*on_tab_clicked)(void*, bool) = NULL, - void* userdata = NULL, - S32 indent = 0, - BOOL placeholder = FALSE, - eInsertionPoint insertion_point = END); + /*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<TabPanelParams> + { + Mandatory<LLPanel*> panel; + + Optional<std::string> label; + Optional<bool> select_tab, + is_placeholder; + Optional<S32> indent; + Optional<eInsertionPoint> insert_at; + Optional<void*> 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); @@ -108,21 +168,17 @@ public: BOOL selectTabPanel( LLPanel* child ); BOOL selectTab(S32 which); BOOL selectTabByName(const std::string& title); - BOOL setTab(S32 which); 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 setTitle( const std::string& title ); const std::string getPanelTitle(S32 index); void setTopBorderHeight(S32 height); S32 getTopBorderHeight() const; - void setTabChangeCallback(LLPanel* tab, void (*on_tab_clicked)(void*,bool)); - void setTabPrecommitChangeCallback(LLPanel* tab, void (*on_precommit)(void*, bool)); - void setTabUserData(LLPanel* tab, void* userdata); - void setRightTabBtnOffset( S32 offset ); void setPanelTitle(S32 index, const std::string& title); @@ -134,51 +190,20 @@ public: void startDragAndDropDelayTimer() { mDragAndDropDelayTimer.start(); } - static void onCloseBtn(void* userdata); - static void onTabBtn(void* userdata); - static void onNextBtn(void* userdata); - static void onNextBtnHeld(void* userdata); - static void onPrevBtn(void* userdata); - static void onPrevBtnHeld(void* userdata); - static void onJumpFirstBtn( void* userdata ); - static void onJumpLastBtn( void* userdata ); - - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); + 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: - // Structure used to map tab buttons to and from tab panels - struct LLTabTuple - { - LLTabTuple( LLTabContainer* c, LLPanel* p, LLButton* b, - void (*cb)(void*,bool), void* userdata, LLTextBox* placeholder = NULL, - void (*pcb)(void*,bool) = NULL) - : - mTabContainer(c), - mTabPanel(p), - mButton(b), - mOnChangeCallback( cb ), - mPrecommitChangeCallback( pcb ), - mUserData( userdata ), - mOldState(FALSE), - mPlaceholderText(placeholder), - mPadding(0) - {} - - LLTabContainer* mTabContainer; - LLPanel* mTabPanel; - LLButton* mButton; - void (*mOnChangeCallback)(void*, bool); - void (*mPrecommitChangeCallback)(void*,bool); // Precommit callback gets called before tab is changed and - // can prevent it from being changed. onChangeCallback is called - // immediately after tab is actually changed - Nyx - void* mUserData; - BOOL mOldState; - LLTextBox* mPlaceholderText; - S32 mPadding; - }; void initButtons(); + BOOL setTab(S32 which); + LLTabTuple* getTab(S32 index) { return mTabList[index]; } LLTabTuple* getTabByPanel(LLPanel* child); void insertTuple(LLTabTuple * tuple, eInsertionPoint insertion_point); @@ -201,13 +226,15 @@ private: 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); + // Variables typedef std::vector<LLTabTuple*> tuple_list_t; tuple_list_t mTabList; S32 mCurrentTabIdx; - S32 mNextTabIdx; BOOL mTabsHidden; BOOL mScrolled; @@ -216,9 +243,6 @@ private: S32 mScrollPosPixels; S32 mMaxScrollPos; - void (*mCloseCallback)(void*); - void* mCallbackUserdata; - LLTextBox* mTitleBox; S32 mTopBorderHeight; @@ -238,9 +262,21 @@ private: 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; +}; #endif // LL_TABCONTAINER_H diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp new file mode 100644 index 0000000000..47db990a37 --- /dev/null +++ b/indra/llui/lltextbase.cpp @@ -0,0 +1,2640 @@ +/** + * @file lltextbase.cpp + * @author Martin Reddy + * @brief The base class of text box/editor, providing Url handling support + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lltextbase.h" + +#include "lllocalcliprect.h" +#include "llmenugl.h" +#include "llscrollcontainer.h" +#include "llstl.h" +#include "lltextparser.h" +#include "lltooltip.h" +#include "lluictrl.h" +#include "llurlaction.h" +#include "llurlregistry.h" +#include "llview.h" +#include "llwindow.h" +#include <boost/bind.hpp> + +const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds +const S32 CURSOR_THICKNESS = 2; + +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(); + } + return a->getEnd() < b->getEnd(); +} + + +// helper functors +struct LLTextBase::compare_bottom +{ + bool 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 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 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 +struct LLTextBase::compare_top +{ + bool 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 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 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"), + 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"), + allow_scroll("allow_scroll", true), + track_end("track_end", false), + read_only("read_only", false), + v_pad("v_pad", 0), + h_pad("h_pad", 0), + clip_partial("clip_partial", true), + line_spacing("line_spacing"), + max_text_length("max_length", 255), + font_shadow("font_shadow"), + wrap("wrap"), + use_ellipses("use_ellipses", false), + allow_html("allow_html", false), + parse_highlights("parse_highlights", false) +{ + addSynonym(track_end, "track_bottom"); + addSynonym(wrap, "word_wrap"); +} + + +LLTextBase::LLTextBase(const LLTextBase::Params &p) +: LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)), + mURLClickSignal(), + mMaxTextByteLength( p.max_text_length ), + mDefaultFont(p.font), + mFontShadow(p.font_shadow), + mPopupMenu(NULL), + mReadOnly(p.read_only), + mCursorColor(p.cursor_color), + mFgColor(p.text_color), + mBorderVisible( p.border_visible ), + mReadOnlyFgColor(p.text_readonly_color), + mWriteableBgColor(p.bg_writeable_color), + mReadOnlyBgColor(p.bg_readonly_color), + mFocusBgColor(p.bg_focus_color), + mReflowNeeded(FALSE), + mCursorPos( 0 ), + mScrollNeeded(FALSE), + mDesiredXPixel(-1), + mHPad(p.h_pad), + mVPad(p.v_pad), + mHAlign(p.font_halign), + mLineSpacingMult(p.line_spacing.multiple), + mLineSpacingPixels(p.line_spacing.pixels), + mClipPartial(p.clip_partial && !p.allow_scroll), + mTrackEnd( p.track_end ), + mScrollIndex(-1), + mSelectionStart( 0 ), + mSelectionEnd( 0 ), + mIsSelecting( FALSE ), + mWordWrap(p.wrap), + mUseEllipses( p.use_ellipses ), + mParseHTML(p.allow_html), + mParseHighlights(p.parse_highlights), + mBGVisible(p.bg_visible), + mScroller(NULL) +{ + 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<LLScrollContainer>(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<LLView>(view_params); + if (mScroller) + { + mScroller->addChild(mDocumentView); + } + else + { + addChild(mDocumentView); + } + + createDefaultSegment(); + + updateRects(); +} + +LLTextBase::~LLTextBase() +{ + delete mPopupMenu; + clearSegments(); +} + +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; + } + + // HACK: text editors always need to be enabled so that we can scroll + LLView::setEnabled(true); +} + +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 + LLWString text(getWText()); + S32 utf8_byte_size = wstring_utf8_length(text); + if ( utf8_byte_size > mMaxTextByteLength ) + { + // Truncate safely in UTF-8 + std::string temp_utf8_text = wstring_to_utf8str(text); + 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; +} + +LLStyle::Params LLTextBase::getDefaultStyle() +{ + return LLStyle::Params().color(mFgColor.get()).readonly_color(mReadOnlyFgColor.get()).font(mDefaultFont).drop_shadow(mFontShadow); +} + +void LLTextBase::onValueChange(S32 start, S32 end) +{ +} + + +// 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()) + { + LLWString text = getWText(); + std::vector<LLRect> selection_rects; + + S32 selection_left = llmin( mSelectionStart, mSelectionEnd ); + S32 selection_right = llmax( mSelectionStart, mSelectionEnd ); + LLRect selection_rect = mVisibleTextRect; + + // 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::lower_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); + + LLRect selection_rect; + selection_rect.mLeft = line_iter->mRect.mLeft; + selection_rect.mRight = line_iter->mRect.mLeft; + selection_rect.mBottom = line_iter->mRect.mBottom; + selection_rect.mTop = line_iter->mRect.mTop; + + 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); + + S32 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->getDimensions(segment_offset, num_chars, segment_width, segment_height); + selection_rect.mLeft += segment_width; + } + + // if 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->getDimensions(segment_offset, num_chars, segment_width, segment_height); + selection_rect.mRight += segment_width; + } + // else if selection ends on current segment... + else + { + S32 num_chars = selection_right - segment_line_start; + segmentp->getDimensions(segment_offset, num_chars, segment_width, segment_height); + selection_rect.mRight += segment_width; + + break; + } + } + selection_rects.push_back(selection_rect); + } + } + + // 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 = mReadOnly ? mReadOnlyFgColor.get() : mFgColor.get(); + F32 alpha = hasFocus() ? 0.7f : 0.3f; + alpha *= getDrawContext().mAlpha; + LLColor4 selection_color(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], alpha); + + for (std::vector<LLRect>::iterator rect_it = selection_rects.begin(); + rect_it != selection_rects.end(); + ++rect_it) + { + LLRect selection_rect = *rect_it; + selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom); + gl_rect_2d(selection_rect, selection_color); + } + } +} + +void LLTextBase::drawCursor() +{ + F32 alpha = getDrawContext().mAlpha; + + if( hasFocus() + && gFocusMgr.getAppHasFocus() + && !mReadOnly) + { + 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 + { + //segmentp = mSegments.back(); + 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; + if (segmentp) + { + text_color = segmentp->getColor(); + fontp = segmentp->getStyle()->getFont(); + } + else if (mReadOnly) + { + text_color = mReadOnlyFgColor.get(); + fontp = mDefaultFont; + } + else + { + text_color = mFgColor.get(); + fontp = mDefaultFont; + } + fontp->render(text, mCursorPos, cursor_rect.mLeft, cursor_rect.mTop, + LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha), + LLFontGL::LEFT, LLFontGL::TOP, + 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::sGLScaleFactor.mV[VX]); + ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]); + getWindow()->setLanguageTextInput( ime_pos ); + } + } +} + +void LLTextBase::drawText() +{ + LLWString text = getWText(); + const S32 text_len = getLength(); + if( text_len <= 0 ) + { + return; + } + 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 ); + } + + LLRect scrolled_view_rect = getVisibleDocumentRect(); + std::pair<S32, S32> 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; + } + + LLTextSegmentPtr cur_segment = *seg_iter; + + 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; + } + + LLRect text_rect(line.mRect.mLeft + mVisibleTextRect.mLeft - scrolled_view_rect.mLeft, + line.mRect.mTop - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom, + llmin(mDocumentView->getRect().getWidth(), line.mRect.mRight) - scrolled_view_rect.mLeft, + line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom); + + // 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()) + { + llwarns << "Ran off the segmentation end!" << llendl; + + return; + } + cur_segment = *seg_iter; + } + + S32 clipped_end = llmin( line_end, cur_segment->getEnd() ) - 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; + } + + text_rect.mLeft = (S32)(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 ) +{ + LLWString text(getWText()); + S32 old_len = text.length(); // length() returns character length + S32 insert_len = wstr.length(); + + pos = getEditableIndex(pos, true); + + segment_set_t::iterator seg_iter = getSegIterContaining(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 + default_segment = new LLNormalTextSegment( new LLStyle(getDefaultStyle()), 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); + } + } + + text.insert(pos, wstr); + getViewModel()->setDisplay(text); + + if ( truncate() ) + { + insert_len = getLength() - old_len; + } + + onValueChange(pos, pos + insert_len); + needsReflow(); + + return insert_len; +} + +S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) +{ + LLWString text(getWText()); + 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; + } + + text.erase(pos, length); + getViewModel()->setDisplay(text); + + // recreate default segment in case we erased everything + createDefaultSegment(); + + onValueChange(pos, pos); + needsReflow(); + + return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length +} + +S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) +{ + if (pos > (S32)getLength()) + { + return 0; + } + LLWString text(getWText()); + text[pos] = wc; + getViewModel()->setDisplay(text); + + onValueChange(pos, pos + 1); + needsReflow(); + + return 1; +} + + +void LLTextBase::createDefaultSegment() +{ + // ensures that there is always at least one segment + if (mSegments.empty()) + { + LLTextSegmentPtr default_segment = new LLNormalTextSegment( new LLStyle(getDefaultStyle()), 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()); + + if (cur_seg_iter == mSegments.end()) + { + mSegments.insert(segment_to_insert); + segment_to_insert->linkToDocument(this); + } + else + { + LLTextSegmentPtr cur_segmentp = *cur_seg_iter; + 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 + LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( cur_segmentp->getStyle(), 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(); +} + +BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleMouseDown(x, y, mask)) + { + return TRUE; + } + + return LLUICtrl::handleMouseDown(x, y, mask); +} + +BOOL LLTextBase::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleMouseUp(x, y, mask)) + { + // Did we just click on a link? + if (cur_segment->getStyle() + && cur_segment->getStyle()->isLink()) + { + // *TODO: send URL here? + mURLClickSignal(this, LLSD() ); + } + return TRUE; + } + + return LLUICtrl::handleMouseUp(x, y, mask); +} + +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); +} + +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); +} + +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); +} + +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); +} + +BOOL LLTextBase::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + LLTextSegmentPtr cur_segment = getSegmentAtLocalPos(x, y); + if (cur_segment && cur_segment->handleDoubleClick(x, y, mask)) + { + return TRUE; + } + + return LLUICtrl::handleDoubleClick(x, y, mask); +} + +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); +} + +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); +} + +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); +} + + +void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + if (width != getRect().getWidth() || height != getRect().getHeight()) + { + LLUICtrl::reshape( width, height, called_from_parent ); + + // do this first after reshape, because other things depend on + // up-to-date mVisibleTextRect + updateRects(); + + needsReflow(); + } +} + +void LLTextBase::draw() +{ + // reflow if needed, on demand + reflow(); + + // then update scroll position, as cursor may have moved + if (!mReadOnly) + { + updateScrollFromCursor(); + } + + LLRect doc_rect; + if (mScroller) + { + mScroller->localRectToOtherView(mScroller->getContentWindowRect(), &doc_rect, this); + } + else + { + doc_rect = getLocalRect(); + } + + if (mBGVisible) + { + // clip background rect against extents, if we support scrolling + LLLocalClipRect clip(doc_rect, mScroller != NULL); + + LLColor4 bg_color = mReadOnly + ? mReadOnlyBgColor.get() + : hasFocus() + ? mFocusBgColor.get() + : mWriteableBgColor.get(); + gl_rect_2d(mVisibleTextRect, bg_color, TRUE); + } + + // draw document view + LLUICtrl::draw(); + + { + // only clip if we support scrolling (mScroller != NULL) + LLLocalClipRect clip(doc_rect, mScroller != NULL); + drawSelectionBackground(); + drawText(); + drawCursor(); + } +} + + +//virtual +void LLTextBase::setColor( const LLColor4& c ) +{ + mFgColor = c; + //textsegments have own style property , + //so we have to update it also to apply changes, EXT-4433 + for(segment_set_t::iterator it = mSegments.begin(); it != mSegments.end(); it++) + { + LLTextSegment* segment = it->get(); + if(segment) + { + segment->setColor(mFgColor); + } + } +} + +//virtual +void LLTextBase::setReadOnlyColor(const LLColor4 &c) +{ + mReadOnlyFgColor = c; +} + +//virtual +void LLTextBase::setValue(const LLSD& value ) +{ + setText(value.asString()); +} + +//virtual +void LLTextBase::deselect() +{ + mSelectionStart = 0; + mSelectionEnd = 0; + mIsSelecting = FALSE; +} + + +// 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 = getLocalRectFromDocIndex(mCursorPos); + cursor_rect_doc.translate(scroller_doc_window.mLeft, scroller_doc_window.mBottom); + 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 + (mVisibleTextRect.getWidth() - width - mHPad) / 2; + case LLFontGL::RIGHT: + return mVisibleTextRect.getWidth() - width; + default: + return mHPad; + } +} + + +static LLFastTimer::DeclareTimer FTM_TEXT_REFLOW ("Text Reflow"); +void LLTextBase::reflow(S32 start_index) +{ + LLFastTimer ft(FTM_TEXT_REFLOW); + + updateSegments(); + + while(mReflowNeeded) + { + mReflowNeeded = false; + + // shrink document to minimum size (visible portion of text widget) + // to force inlined widgets with follows set to shrink + mDocumentView->reshape(mVisibleTextRect.getWidth(), mDocumentView->getRect().getHeight()); + + bool scrolled_to_bottom = mScroller ? mScroller->isAtBottom() : false; + + LLRect old_cursor_rect = getLocalRectFromDocIndex(mCursorPos); + bool follow_selection = mVisibleTextRect.overlaps(old_cursor_rect); // cursor is visible + old_cursor_rect.translate(-mVisibleTextRect.mLeft, -mVisibleTextRect.mBottom); + + S32 first_line = getFirstVisibleLine(); + + // if scroll anchor not on first line, update it to first character of first line + if (!mLineInfoList.empty() + && (mScrollIndex < mLineInfoList[first_line].mDocIndexStart + || mScrollIndex >= mLineInfoList[first_line].mDocIndexEnd)) + { + mScrollIndex = mLineInfoList[first_line].mDocIndexStart; + } + LLRect first_char_rect = getLocalRectFromDocIndex(mScrollIndex); + // subtract off effect of horizontal scrollbar from local position of first char + first_char_rect.translate(-mVisibleTextRect.mLeft, -mVisibleTextRect.mBottom); + + S32 cur_top = 0; + + segment_set_t::iterator seg_iter = mSegments.begin(); + S32 seg_offset = 0; + S32 line_start_index = 0; + const S32 text_available_width = mVisibleTextRect.getWidth() - mHPad; // reserve room for margin + S32 remaining_pixels = text_available_width; + LLWString text(getWText()); + 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()); + line_start_index = iter->mDocIndexStart; + line_count = iter->mLineNum; + getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset); + mLineInfoList.erase(iter, mLineInfoList.end()); + } + + S32 line_height = 0; + + 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, remaining_pixels) : S32_MAX, + seg_offset, + cur_index - line_start_index, + S32_MAX); + + S32 segment_width, segment_height; + bool force_newline = segment->getDimensions(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; + if (remaining_pixels < 0) + { + // getNumChars() and getDimensions() should return consistent results + remaining_pixels = 0; + } + + seg_offset += character_count; + + S32 last_segment_char_on_line = segment->getStart() + seg_offset; + + S32 text_actual_width = 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 -= llround((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 -= llround((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 -= llround((F32)line_height * mLineSpacingMult) + mLineSpacingPixels; + line_height = 0; + remaining_pixels = text_available_width; + } + ++seg_iter; + seg_offset = 0; + } + 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); + mScroller->scrollToShowRect(new_cursor_rect_doc, old_cursor_rect); + } + else + { + // keep first line of text visible + LLRect new_first_char_rect = getDocRectFromDocIndex(mScrollIndex); + mScroller->scrollToShowRect(new_first_char_rect, 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<S32, S32> LLTextBase::getVisibleLines(bool 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 (fully_visible) + { + first_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mTop, compare_top()); + last_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), visible_region.mBottom, compare_bottom()); + } + else + { + first_iter = std::lower_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<S32, S32>(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); +} + + +static LLFastTimer::DeclareTimer FTM_UPDATE_TEXT_SEGMENTS("Update Text Segments"); +void LLTextBase::updateSegments() +{ + LLFastTimer ft(FTM_UPDATE_TEXT_SEGMENTS); + 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::getSegIterContaining(S32 index) +{ + segment_set_t::iterator it = mSegments.upper_bound(new LLIndexSegment(index)); + return it; +} + +LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 index) const +{ + LLTextBase::segment_set_t::const_iterator it = mSegments.upper_bound(new LLIndexSegment(index)); + return it; +} + +// Finds the text segment (if any) at the give local screen position +LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y ) +{ + // Find the cursor position at the requested local screen position + S32 offset = getDocIndexFromLocalCoord( x, y, FALSE ); + 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)); + registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, 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 + delete mPopupMenu; + mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(xui_file, LLMenuGL::sMenuContainer, + LLMenuHolderGL::child_registry_t::instance()); + if (mPopupMenu) + { + mPopupMenu->show(x, y); + LLMenuGL::showPopup(this, mPopupMenu, 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 + startOfDoc(); + + onValueChange(0, getLength()); +} + +//virtual +std::string LLTextBase::getText() const +{ + return getViewModel()->getValue().asString(); +} + +void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) +{ + LLStyle::Params style_params(input_params); + style_params.fillFrom(getDefaultStyle()); + + if (!style_params.font.isProvided()) + { + style_params.font = mDefaultFont; + } + if (!style_params.drop_shadow.isProvided()) + { + style_params.drop_shadow = mFontShadow; + } + + S32 part = (S32)LLTextParser::WHOLE; + if(mParseHTML) + { + S32 start=0,end=0; + LLUrlMatch match; + std::string text = new_text; + while ( LLUrlRegistry::instance().findUrl(text, match, + boost::bind(&LLTextBase::replaceUrlLabel, this, _1, _2)) ) + { + start = match.getStart(); + end = match.getEnd()+1; + + LLStyle::Params link_params = style_params; + link_params.color = match.getColor(); + link_params.readonly_color = match.getColor(); + // apply font name from requested style_params + std::string font_name = LLFontGL::nameFromFont(style_params.font()); + std::string font_size = LLFontGL::sizeFromFont(style_params.font()); + link_params.font.name(font_name); + link_params.font.size(font_size); + link_params.font.style("UNDERLINE"); + + link_params.link_href = match.getUrl(); + + // 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, prepend_newline, part, style_params); + prepend_newline = false; + } + + // output an optional icon before the Url + if (! match.getIcon().empty()) + { + LLUIImagePtr image = LLUI::getUIImage(match.getIcon()); + if (image) + { + LLStyle::Params icon; + icon.image = image; + // HACK: fix spacing of images and remove the fixed char spacing + appendAndHighlightText(" ", prepend_newline, part, icon); + prepend_newline = false; + } + } + // output the styled Url + appendAndHighlightText(match.getLabel(), prepend_newline, part, link_params); + prepend_newline = false; + + // set the tooltip for the Url label + if (! match.getTooltip().empty()) + { + segment_set_t::iterator it = getSegIterContaining(getLength()-1); + if (it != mSegments.end()) + { + LLTextSegmentPtr segment = *it; + segment->setToolTip(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, prepend_newline, part, style_params); + } + else + { + appendAndHighlightText(new_text, prepend_newline, part, style_params); + } +} + +void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepend_newline, S32 highlight_part, const LLStyle::Params& stylep) +{ + if (new_text.empty()) return; + + // 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); + + LLTextParser* highlight = LLTextParser::getInstance(); + + if (mParseHighlights && highlight) + { + LLStyle::Params highlight_params = stylep; + + LLSD pieces = highlight->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; + if (prepend_newline && (i == 0 || pieces.size() <= 1 )) + { + wide_text = utf8str_to_wstring(std::string("\n") + pieces[i]["text"].asString()); + } + else + { + wide_text = utf8str_to_wstring(pieces[i]["text"].asString()); + } + S32 cur_length = getLength(); + LLTextSegmentPtr segmentp = new LLNormalTextSegment(new LLStyle(highlight_params), 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; + + // Add carriage return if not first line + if (getLength() != 0 + && prepend_newline) + { + wide_text = utf8str_to_wstring(std::string("\n") + new_text); + } + else + { + wide_text = utf8str_to_wstring(new_text); + } + + segment_vec_t segments; + S32 segment_start = old_length; + S32 segment_end = old_length + wide_text.size(); + segments.push_back(new LLNormalTextSegment(new LLStyle(stylep), 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); + } + + //if( !allow_undo ) + //{ + // blockUndo(); + //} +} + + +void LLTextBase::replaceUrlLabel(const std::string &url, + const std::string &label) +{ + // 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; + const LLStyleSP 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->isLink() && 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; + } + + // 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)); +} + +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 ) const +{ + // Figure out which line we're nearest to. + LLRect visible_region = getVisibleDocumentRect(); + + // binary search for line that starts before local_y + line_list_t::const_iterator line_iter = std::lower_bound(mLineInfoList.begin(), mLineInfoList.end(), local_y - mVisibleTextRect.mBottom + visible_region.mBottom, compare_bottom()); + + if (line_iter == mLineInfoList.end()) + { + return getLength(); // past the end + } + + S32 pos = getLength(); + S32 start_x = mVisibleTextRect.mLeft + line_iter->mRect.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 - 1) - segment_line_start; + S32 text_width, text_height; + segmentp->getDimensions(line_seg_offset, segment_line_length, text_width, text_height); + if (local_x < start_x + text_width // cursor to left of right edge of text + || segmentp->getEnd() >= line_iter->mDocIndexEnd - 1) // or this segment wraps to next line + { + // Figure out which character we're nearest to. + S32 offset; + if (!segmentp->canEdit()) + { + S32 segment_width, segment_height; + segmentp->getDimensions(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; + } + 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(); + } + + LLRect doc_rect; + + // clamp pos to valid values + pos = llclamp(pos, 0, mLineInfoList.back().mDocIndexEnd - 1); + + // find line that contains cursor + line_list_t::const_iterator line_iter = std::upper_bound(mLineInfoList.begin(), mLineInfoList.end(), pos, line_end_compare()); + + doc_rect.mLeft = line_iter->mRect.mLeft; + doc_rect.mBottom = line_iter->mRect.mBottom; + doc_rect.mTop = line_iter->mRect.mTop; + + 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); + + 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 + S32 segment_width, segment_height; + segmentp->getDimensions(line_seg_offset, cursor_seg_offset - line_seg_offset, segment_width, segment_height); + doc_rect.mLeft += segment_width; + + break; + } + else + { + // add remainder of current text segment to cursor position + S32 segment_width, segment_height; + segmentp->getDimensions(line_seg_offset, (segmentp->getEnd() - segmentp->getStart()) - line_seg_offset, segment_width, segment_height); + doc_rect.mLeft += 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; + } + } + + // set rect to 0 width + doc_rect.mRight = doc_rect.mLeft; + + return doc_rect; +} + +LLRect LLTextBase::getLocalRectFromDocIndex(S32 pos) const +{ + LLRect local_rect; + if (mLineInfoList.empty()) + { + // return default height rect in upper left + local_rect = mVisibleTextRect; + local_rect.mBottom = local_rect.mTop - (S32)(mDefaultFont->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(mVisibleTextRect.mLeft - scrolled_view_rect.mLeft, + mVisibleTextRect.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 new_line = line; + if( (delta < 0) && (line > 0 ) ) + { + new_line = line - 1; + } + else if( (delta > 0) && (line < (getLineCount() - 1)) ) + { + new_line = line + 1; + } + + LLRect visible_region = getVisibleDocumentRect(); + + S32 new_cursor_pos = getDocIndexFromLocalCoord(mDesiredXPixel, mLineInfoList[new_line].mRect.mBottom + mVisibleTextRect.mBottom - visible_region.mBottom, TRUE); + 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 (0 <= row && row < (S32)mLineInfoList.size()) + { + S32 doc_pos = mLineInfoList[row].mDocIndexStart; + column = llclamp(column, 0, mLineInfoList[row].mDocIndexEnd - mLineInfoList[row].mDocIndexStart - 1); + doc_pos += column; + updateCursorXPos(); + + return setCursorPos(doc_pos); + } + return false; +} + + +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() +{ + 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; + // subtract a pixel off the bottom to deal with rounding errors in measuring font height + mTextBoundingRect.mBottom -= 1; + + S32 delta_pos = -mTextBoundingRect.mBottom; + // 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 = mTextBoundingRect; + // use old mVisibleTextRect constraint document to width of viewable region + doc_rect.mLeft = 0; + + // allow horizontal scrolling? + // if so, use entire width of text contents + // otherwise, stop at width of mVisibleTextRect + doc_rect.mRight = mScroller + ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) + : mVisibleTextRect.getWidth(); + + 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 + LLRect old_text_rect = mVisibleTextRect; + mVisibleTextRect = mScroller ? mScroller->getContentWindowRect() : getLocalRect(); + //FIXME: replace border with image? + if (mBorderVisible) + { + mVisibleTextRect.stretch(-1); + } + if (mVisibleTextRect != old_text_rect) + { + needsReflow(); + } + + // update document container again, using new mVisibleTextRect (that has scrollbars enabled as needed) + doc_rect.mRight = mScroller + ? llmax(mVisibleTextRect.getWidth(), mTextBoundingRect.mRight) + : mVisibleTextRect.getWidth(); + 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 + { + // entire document rect is visible when not scrolling + // 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; + } +} + +// +// LLTextSegment +// + +LLTextSegment::~LLTextSegment() +{} + +bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { width = 0; height = 0; return false;} +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) const { return 0; } +void LLTextSegment::updateLayout(const LLTextBase& editor) {} +F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& 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) {} +const LLStyleSP LLTextSegment::getStyle() const {static LLStyleSP sp(new LLStyle()); return sp; } +void LLTextSegment::setStyle(const LLStyleSP &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::handleToolTip(S32 x, S32 y, MASK mask) { return FALSE; } +std::string LLTextSegment::getName() const { return ""; } +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( const LLStyleSP& style, S32 start, S32 end, LLTextBase& editor ) +: LLTextSegment(start, end), + mStyle( style ), + mToken(NULL), + mEditor(editor) +{ + mFontHeight = llceil(mStyle->getFont()->getLineHeight()); + + LLUIImagePtr image = mStyle->getImage(); + if (image.notNull()) + { + mImageLoadedConnection = image->addLoadedCallback(boost::bind(&LLTextBase::needsReflow, &mEditor)); + } +} + +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 = llceil(mStyle->getFont()->getLineHeight()); +} + +LLNormalTextSegment::~LLNormalTextSegment() +{ + mImageLoadedConnection.disconnect(); +} + + +F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) +{ + if( end - start > 0 ) + { + if ( mStyle->isImage() && (start >= 0) && (end <= mEnd - mStart)) + { + LLColor4 color = LLColor4::white % mEditor.getDrawContext().mAlpha; + LLUIImagePtr image = mStyle->getImage(); + S32 style_image_height = image->getHeight(); + S32 style_image_width = image->getWidth(); + // Center the image vertically + S32 image_bottom = draw_rect.getCenterY() - (style_image_height/2); + image->draw(draw_rect.mLeft, image_bottom, + style_image_width, style_image_height, color); + } + + 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, LLRect rect) +{ + F32 alpha = LLViewDrawContext::getCurrentContext().mAlpha; + + const LLWString &text = mEditor.getWText(); + + if ( text[seg_end-1] == '\n' ) + { + --seg_end; + } + + 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; + + font = mStyle->getFont(); + + 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.mLeft, rect.mTop, + color, + LLFontGL::LEFT, LLFontGL::TOP, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, rect.getWidth(), + &right_x, + mEditor.getUseEllipses()); + } + rect.mLeft = (S32)ceil(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.mLeft, rect.mTop, + LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ), + LLFontGL::LEFT, LLFontGL::TOP, + LLFontGL::NORMAL, + LLFontGL::NO_SHADOW, + length, rect.getWidth(), + &right_x, + mEditor.getUseEllipses()); + } + rect.mLeft = (S32)ceil(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.mLeft, rect.mTop, + color, + LLFontGL::LEFT, LLFontGL::TOP, + LLFontGL::NORMAL, + mStyle->getShadowType(), + length, rect.getWidth(), + &right_x, + mEditor.getUseEllipses()); + } + return right_x; +} + +BOOL LLNormalTextSegment::handleHover(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + LLUI::getWindow()->setCursor(UI_CURSOR_HAND); + return TRUE; + } + return FALSE; +} + +BOOL LLNormalTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + mEditor.createUrlContextMenu(x, y, getStyle()->getLinkHREF()); + return TRUE; + } + return FALSE; +} + +BOOL LLNormalTextSegment::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (getStyle() && getStyle()->isLink()) + { + // 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()) + { + LLUrlAction::clickAction(getStyle()->getLinkHREF()); + 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)); + 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) + { + llwarns << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << llendl; + return; + } + mTooltip = tooltip; +} + +bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +{ + height = 0; + width = 0; + bool force_newline = false; + if (num_chars > 0) + { + height = mFontHeight; + LLWString text = mEditor.getWText(); + // if last character is a newline, then return true, forcing line break + llwchar last_char = text[mStart + first_char + num_chars - 1]; + if (last_char == '\n') + { + force_newline = true; + // don't count newline in font width + width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars - 1); + } + else + { + width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars); + } + } + + LLUIImagePtr image = mStyle->getImage(); + if( image.notNull()) + { + width += image->getWidth(); + height = llmax(height, image->getHeight()); + } + + return force_newline; +} + +S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const +{ + LLWString text = mEditor.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) const +{ + LLWString text = mEditor.getWText(); + + LLUIImagePtr image = mStyle->getImage(); + if( image.notNull()) + { + num_pixels -= image->getWidth(); + } + + // search for newline and if found, truncate there + S32 last_char = mStart + segment_offset; + for (; last_char != mEnd; ++last_char) + { + if (text[last_char] == '\n') + { + break; + } + } + + // 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 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 >= mEditor.getLength() + || text[last_char_in_run] == '\n')) + { + num_chars++; + } + return num_chars; +} + +void LLNormalTextSegment::dump() const +{ + llinfos << "Segment [" << +// mColor.mV[VX] << ", " << +// mColor.mV[VY] << ", " << +// mColor.mV[VZ] << "]\t[" << + mStart << ", " << + getEnd() << "]" << + llendl; +} + + +// +// 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::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const +{ + if (first_char == 0 && num_chars == 0) + { + // we didn't fit on a line, the widget will fall on the next line + // so dimensions here are 0 + width = 0; + 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) 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 (line_offset != 0 + && (mForceNewLine || 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 LLRect& 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); +} diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h new file mode 100644 index 0000000000..038b9eaa62 --- /dev/null +++ b/indra/llui/lltextbase.h @@ -0,0 +1,505 @@ +/** + * @file lltextbase.h + * @author Martin Reddy + * @brief The base class of text box/editor, providing Url handling support + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLTEXTBASE_H +#define LL_LLTEXTBASE_H + +#include "v4color.h" +#include "lleditmenuhandler.h" +#include "llstyle.h" +#include "llkeywords.h" +#include "llpanel.h" + +#include <string> +#include <set> + +#include <boost/signals2.hpp> + +class LLContextMenu; +class LLTextSegment; + +typedef LLPointer<LLTextSegment> 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: + struct LineSpacingParams : public LLInitParam::Choice<LineSpacingParams> + { + Alternative<F32> multiple; + Alternative<S32> pixels; + LineSpacingParams(); + }; + + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<LLUIColor> cursor_color, + text_color, + text_readonly_color, + bg_readonly_color, + bg_writeable_color, + bg_focus_color; + + Optional<bool> bg_visible, + border_visible, + track_end, + read_only, + allow_scroll, + wrap, + use_ellipses, + allow_html, + parse_highlights, + clip_partial; + + Optional<S32> v_pad, + h_pad; + + + Optional<LineSpacingParams> + line_spacing; + + Optional<S32> max_text_length; + + Optional<LLFontGL::ShadowType> font_shadow; + + Params(); + }; + + // 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 handleToolTip(S32 x, S32 y, MASK mask); + + // LLView interface + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + /*virtual*/ void draw(); + + // LLUICtrl interface + /*virtual*/ BOOL acceptsTextInput() const { return !mReadOnly; } + /*virtual*/ void setColor( const LLColor4& c ); + virtual void setReadOnlyColor(const LLColor4 &c); + + /*virtual*/ void setValue(const LLSD& value ); + /*virtual*/ LLTextViewModel* getViewModel() const; + + // LLEditMenuHandler interface + /*virtual*/ void deselect(); + + // used by LLTextSegment layout code + bool getWordWrap() { return mWordWrap; } + bool getUseEllipses() { return mUseEllipses; } + bool truncate(); // returns true of truncation occurred + + // 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 std::string getText() const; + void setMaxTextLength(S32 length) { mMaxTextByteLength = length; } + + // wide-char versions + void setWText(const LLWString& text); + LLWString getWText() const; + + void appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params = LLStyle::Params()); + // force reflow of text + void needsReflow() { mReflowNeeded = TRUE; } + + S32 getLength() const { return getWText().length(); } + S32 getLineCount() const { return mLineInfoList.size(); } + + void addDocumentChild(LLView* view); + void removeDocumentChild(LLView* view); + const LLView* getDocumentView() const { return mDocumentView; } + LLRect getVisibleTextRect() { return mVisibleTextRect; } + LLRect getTextBoundingRect(); + LLRect getVisibleDocumentRect() const; + + S32 getVPad() { return mVPad; } + S32 getHPad() { return mHPad; } + + S32 getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const; + LLRect getLocalRectFromDocIndex(S32 pos) const; + LLRect getDocRectFromDocIndex(S32 pos) const; + + void setReadOnly(bool read_only) { mReadOnly = read_only; } + bool getReadOnly() { return mReadOnly; } + + // 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* getDefaultFont() const { return mDefaultFont; } + LLStyle::Params getDefaultStyle(); + +public: + // Fired when a URL link is clicked + commit_signal_t mURLClickSignal; + +protected: + // helper structs + struct compare_bottom; + struct compare_top; + struct line_end_compare; + typedef std::vector<LLTextSegmentPtr> 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<LLTextSegmentPtr, compare_segment_end> segment_set_t; + + // 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_info> line_list_t; + + // member functions + LLTextBase(const Params &p); + virtual ~LLTextBase(); + void initFromParams(const Params& p); + virtual void onValueChange(S32 start, S32 end); + + // draw methods + 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, bool prepend_newline, S32 highlight_part, const LLStyle::Params& stylep); + + + // 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 ); + 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); + + // 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<S32, S32> getVisibleLines(bool fully_visible = false); + S32 getLeftOffset(S32 width); + void reflow(S32 start_index = 0); + + // 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; } + void replaceUrlLabel(const std::string &url, const std::string &label); + +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; + + // colors + LLUIColor mCursorColor; + LLUIColor mFgColor; + LLUIColor mReadOnlyFgColor; + LLUIColor mWriteableBgColor; + LLUIColor mReadOnlyBgColor; + LLUIColor mFocusBgColor; + + // 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; + + BOOL mIsSelecting; // Are we in the middle of a drag-select? + + // configuration + S32 mHPad; // padding on left of text + S32 mVPad; // padding above text + LLFontGL::HAlign mHAlign; + 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 + const LLFontGL* mDefaultFont; // font that is used when none specified + LLFontGL::ShadowType mFontShadow; + bool mBorderVisible; + bool mParseHTML; // make URLs interactive + bool mParseHighlights; // highlight user-defined keywords + bool mWordWrap; + bool mUseEllipses; + bool mTrackEnd; // if true, keeps scroll position at end of document during resize + bool mReadOnly; + bool mBGVisible; // render background? + bool mClipPartial; // false if we show lines that are partially inside bounding rect + S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes + + // support widgets + LLContextMenu* mPopupMenu; + LLView* mDocumentView; + class LLScrollContainer* mScroller; + + // transient state + bool mReflowNeeded; // need to reflow text because of change to text contents or display region + 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 + +}; + +/// +/// 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(); + + virtual bool getDimensions(S32 first_char, S32 num_chars, S32& 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) const; + virtual void updateLayout(const class LLTextBase& editor); + virtual F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& 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 const LLStyleSP getStyle() const; + virtual void setStyle(const LLStyleSP &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 handleToolTip(S32 x, S32 y, MASK mask); + /*virtual*/ 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( const LLStyleSP& style, S32 start, S32 end, LLTextBase& editor ); + LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible = TRUE); + ~LLNormalTextSegment(); + + /*virtual*/ bool getDimensions(S32 first_char, S32 num_chars, S32& 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) const; + /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect); + /*virtual*/ bool canEdit() const { return true; } + /*virtual*/ const LLColor4& getColor() const { return mStyle->getColor(); } + /*virtual*/ void setColor(const LLColor4 &color) { mStyle->setColor(color); } + /*virtual*/ const LLStyleSP getStyle() const { return mStyle; } + /*virtual*/ void setStyle(const LLStyleSP &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, LLRect rect); + +protected: + class LLTextBase& mEditor; + LLStyleSP mStyle; + S32 mFontHeight; + LLKeywordToken* mToken; + std::string mTooltip; + boost::signals2::connection mImageLoadedConnection; +}; + +class LLIndexSegment : public LLTextSegment +{ +public: + LLIndexSegment(S32 pos) : LLTextSegment(pos, pos) {} +}; + +class LLInlineViewSegment : public LLTextSegment +{ +public: + struct Params : public LLInitParam::Block<Params> + { + Mandatory<LLView*> view; + Optional<bool> force_newline; + Optional<S32> left_pad, + right_pad, + bottom_pad, + top_pad; + }; + + LLInlineViewSegment(const Params& p, S32 start, S32 end); + ~LLInlineViewSegment(); + /*virtual*/ bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const; + /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const; + /*virtual*/ void updateLayout(const class LLTextBase& editor); + /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& 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; +}; + + +#endif diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp index 89893bcf8d..a1f5b5726b 100644 --- a/indra/llui/lltextbox.cpp +++ b/indra/llui/lltextbox.cpp @@ -31,435 +31,144 @@ */ #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 LLRegisterWidget<LLTextBox> r("text"); +static LLDefaultChildRegistry::Register<LLTextBox> r("text"); -LLTextBox::LLTextBox(const std::string& name, const LLRect& rect, const std::string& text, - const LLFontGL* font, BOOL mouse_opaque) -: LLUICtrl(name, rect, mouse_opaque, NULL, NULL, FOLLOWS_LEFT | FOLLOWS_TOP ), - mFontGL(font ? font : LLFontGL::getFontSansSerifSmall()) -{ - initDefaults(); - setText( text ); - setTabStop(FALSE); -} +// Compiler optimization, generate extern template +template class LLTextBox* LLView::getChild<class LLTextBox>( + const std::string& name, BOOL recurse) const; -LLTextBox::LLTextBox(const std::string& name, const std::string& text, F32 max_width, - const LLFontGL* font, BOOL mouse_opaque) : - LLUICtrl(name, LLRect(0, 0, 1, 1), mouse_opaque, NULL, NULL, FOLLOWS_LEFT | FOLLOWS_TOP), - mFontGL(font ? font : LLFontGL::getFontSansSerifSmall()) -{ - initDefaults(); - setWrappedText(text, max_width); - reshapeToFitText(); - setTabStop(FALSE); -} +LLTextBox::LLTextBox(const LLTextBox::Params& p) +: LLTextBase(p), + mClickedCallback(NULL) +{} -LLTextBox::LLTextBox(const std::string& name_and_label, const LLRect& rect) : - LLUICtrl(name_and_label, rect, TRUE, NULL, NULL, FOLLOWS_LEFT | FOLLOWS_TOP), - mFontGL(LLFontGL::getFontSansSerifSmall()) -{ - initDefaults(); - setText( name_and_label ); - setTabStop(FALSE); -} - -void LLTextBox::initDefaults() -{ - mTextColor = LLUI::sColorsGroup->getColor("LabelTextColor"); - mDisabledColor = LLUI::sColorsGroup->getColor("LabelDisabledColor"); - mBackgroundColor = LLUI::sColorsGroup->getColor("DefaultBackgroundColor"); - mBorderColor = LLUI::sColorsGroup->getColor("DefaultHighlightLight"); - mHoverColor = LLUI::sColorsGroup->getColor( "LabelSelectedColor" ); - mHoverActive = FALSE; - mHasHover = FALSE; - mBackgroundVisible = FALSE; - mBorderVisible = FALSE; - mFontStyle = LLFontGL::DROP_SHADOW_SOFT; - mBorderDropShadowVisible = FALSE; - mUseEllipses = FALSE; - mLineSpacing = 0; - mHPad = 0; - mVPad = 0; - mHAlign = LLFontGL::LEFT; - mVAlign = LLFontGL::TOP; - mClickedCallback = NULL; - mCallbackUserData = NULL; -} +LLTextBox::~LLTextBox() +{} BOOL LLTextBox::handleMouseDown(S32 x, S32 y, MASK mask) { - BOOL handled = FALSE; + BOOL handled = LLTextBase::handleMouseDown(x, y, mask); - // HACK: Only do this if there actually is a click callback, so that - // overly large text boxes in the older UI won't start eating clicks. - if (mClickedCallback) + if (getSoundFlags() & MOUSE_DOWN) { - handled = TRUE; + make_ui_sound("UISndClick"); + } + if (!handled && mClickedCallback) + { // Route future Mouse messages here preemptively. (Release on mouse up.) gFocusMgr.setMouseCapture( this ); - - if (getSoundFlags() & MOUSE_DOWN) - { - make_ui_sound("UISndClick"); - } + + handled = TRUE; } return handled; } - BOOL LLTextBox::handleMouseUp(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; - // We only handle the click if the click both started and ended within us - - // HACK: Only do this if there actually is a click callback, so that - // overly large text boxes in the older UI won't start eating clicks. - if (mClickedCallback - && hasMouseCapture()) + if (getSoundFlags() & MOUSE_UP) { - handled = TRUE; + 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 ); - if (getSoundFlags() & MOUSE_UP) + // 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) { - make_ui_sound("UISndClickRelease"); - } - - // 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) - { - (*mClickedCallback)( mCallbackUserData ); + mClickedCallback(); + handled = TRUE; } } + else + { + handled = LLTextBase::handleMouseUp(x, y, mask); + } return handled; } BOOL LLTextBox::handleHover(S32 x, S32 y, MASK mask) { - BOOL handled = LLView::handleHover(x,y,mask); - if(mHoverActive) + BOOL handled = LLTextBase::handleHover(x, y, mask); + if (!handled && mClickedCallback) { - mHasHover = TRUE; // This should be set every frame during a hover. - getWindow()->setCursor(UI_CURSOR_ARROW); + // Clickable text boxes change the cursor to a hand + LLUI::getWindow()->setCursor(UI_CURSOR_HAND); + return TRUE; } - - return (handled || mHasHover); + return handled; } -void LLTextBox::setText(const LLStringExplicit& text) +void LLTextBox::setText(const LLStringExplicit& text , const LLStyle::Params& input_params ) { + // does string argument insertion mText.assign(text); - setLineLengths(); -} - -void LLTextBox::setLineLengths() -{ - mLineLengthList.clear(); - std::string::size_type cur = 0; - std::string::size_type len = mText.getWString().size(); - - while (cur < len) - { - std::string::size_type end = mText.getWString().find('\n', cur); - std::string::size_type runLen; - - if (end == std::string::npos) - { - runLen = len - cur; - cur = len; - } - else - { - runLen = end - cur; - cur = end + 1; // skip the new line character - } - - mLineLengthList.push_back( (S32)runLen ); - } + LLTextBase::setText(mText.getString(), input_params ); } -void LLTextBox::setWrappedText(const LLStringExplicit& in_text, F32 max_width) +void LLTextBox::setClickedCallback( boost::function<void (void*)> cb, void* userdata /*= NULL */ ) { - if (max_width < 0.0) - { - max_width = (F32)getRect().getWidth(); - } - - LLWString wtext = utf8str_to_wstring(in_text); - LLWString final_wtext; - - LLWString::size_type cur = 0;; - LLWString::size_type len = wtext.size(); - - while (cur < len) - { - LLWString::size_type end = wtext.find('\n', cur); - if (end == LLWString::npos) - { - end = len; - } - - LLWString::size_type runLen = end - cur; - if (runLen > 0) - { - LLWString run(wtext, cur, runLen); - LLWString::size_type useLen = - mFontGL->maxDrawableChars(run.c_str(), max_width, runLen, TRUE); - - final_wtext.append(wtext, cur, useLen); - cur += useLen; - } - - if (cur < len) - { - if (wtext[cur] == '\n') - { - cur += 1; - } - final_wtext += '\n'; - } - } - - std::string final_text = wstring_to_utf8str(final_wtext); - setText(final_text); + mClickedCallback = boost::bind(cb, userdata); } S32 LLTextBox::getTextPixelWidth() { - S32 max_line_width = 0; - if( mLineLengthList.size() > 0 ) - { - S32 cur_pos = 0; - for (std::vector<S32>::iterator iter = mLineLengthList.begin(); - iter != mLineLengthList.end(); ++iter) - { - S32 line_length = *iter; - S32 line_width = mFontGL->getWidth( mText.getWString().c_str(), cur_pos, line_length ); - if( line_width > max_line_width ) - { - max_line_width = line_width; - } - cur_pos += line_length+1; - } - } - else - { - max_line_width = mFontGL->getWidth(mText.getWString().c_str()); - } - return max_line_width; + return getTextBoundingRect().getWidth(); } S32 LLTextBox::getTextPixelHeight() { - S32 num_lines = mLineLengthList.size(); - if( num_lines < 1 ) - { - num_lines = 1; - } - return (S32)(num_lines * mFontGL->getLineHeight()); + return getTextBoundingRect().getHeight(); } -BOOL LLTextBox::setTextArg( const std::string& key, const LLStringExplicit& text ) +LLSD LLTextBox::getValue() const { - mText.setArg(key, text); - setLineLengths(); - return TRUE; + return LLSD(getText()); } -void LLTextBox::draw() +BOOL LLTextBox::setTextArg( const std::string& key, const LLStringExplicit& text ) { - if (mBorderVisible) - { - gl_rect_2d_offset_local(getLocalRect(), 2, FALSE); - } - - if( mBorderDropShadowVisible ) - { - static LLColor4 color_drop_shadow = LLUI::sColorsGroup->getColor("ColorDropShadow"); - static S32 drop_shadow_tooltip = LLUI::sConfigGroup->getS32("DropShadowTooltip"); - gl_drop_shadow(0, getRect().getHeight(), getRect().getWidth(), 0, - color_drop_shadow, drop_shadow_tooltip); - } - - if (mBackgroundVisible) - { - LLRect r( 0, getRect().getHeight(), getRect().getWidth(), 0 ); - gl_rect_2d( r, mBackgroundColor ); - } - - S32 text_x = 0; - switch( mHAlign ) - { - case LLFontGL::LEFT: - text_x = mHPad; - break; - case LLFontGL::HCENTER: - text_x = getRect().getWidth() / 2; - break; - case LLFontGL::RIGHT: - text_x = getRect().getWidth() - mHPad; - break; - } - - S32 text_y = getRect().getHeight() - mVPad; - - if ( getEnabled() ) - { - if(mHasHover) - { - drawText( text_x, text_y, mHoverColor ); - } - else - { - drawText( text_x, text_y, mTextColor ); - } - } - else - { - drawText( text_x, text_y, mDisabledColor ); - } - - if (sDebugRects) - { - drawDebugRect(); - } - - mHasHover = FALSE; // This is reset every frame. -} + mText.setArg(key, text); + LLTextBase::setText(mText.getString()); -void LLTextBox::reshape(S32 width, S32 height, BOOL called_from_parent) -{ - // reparse line lengths - setLineLengths(); - LLView::reshape(width, height, called_from_parent); + return TRUE; } -void LLTextBox::drawText( S32 x, S32 y, const LLColor4& color ) -{ - if( mLineLengthList.empty() ) - { - mFontGL->render(mText.getWString(), 0, (F32)x, (F32)y, color, - mHAlign, mVAlign, - mFontStyle, - S32_MAX, getRect().getWidth(), NULL, TRUE, mUseEllipses); - } - else - { - S32 cur_pos = 0; - for (std::vector<S32>::iterator iter = mLineLengthList.begin(); - iter != mLineLengthList.end(); ++iter) - { - S32 line_length = *iter; - mFontGL->render(mText.getWString(), cur_pos, (F32)x, (F32)y, color, - mHAlign, mVAlign, - mFontStyle, - line_length, getRect().getWidth(), NULL, TRUE, mUseEllipses ); - cur_pos += line_length + 1; - y -= llfloor(mFontGL->getLineHeight()) + mLineSpacing; - } - } -} void LLTextBox::reshapeToFitText() { + reflow(); + S32 width = getTextPixelWidth(); S32 height = getTextPixelHeight(); - reshape( width + 2 * mHPad, height + 2 * mVPad ); + reshape( width + 2 * mHPad, height + 2 * mVPad, FALSE ); } -// virtual -LLXMLNodePtr LLTextBox::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - // Attributes - node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mFontGL)); - node->createChild("halign", TRUE)->setStringValue(LLFontGL::nameFromHAlign(mHAlign)); - addColorXML(node, mTextColor, "text_color", "LabelTextColor"); - addColorXML(node, mDisabledColor, "disabled_color", "LabelDisabledColor"); - addColorXML(node, mBackgroundColor, "bg_color", "DefaultBackgroundColor"); - addColorXML(node, mBorderColor, "border_color", "DefaultHighlightLight"); - node->createChild("bg_visible", TRUE)->setBoolValue(mBackgroundVisible); - node->createChild("border_visible", TRUE)->setBoolValue(mBorderVisible); - node->createChild("border_drop_shadow_visible", TRUE)->setBoolValue(mBorderDropShadowVisible); - node->createChild("h_pad", TRUE)->setIntValue(mHPad); - node->createChild("v_pad", TRUE)->setIntValue(mVPad); - - // Contents - node->setStringValue(mText); - - return node; -} -// static -LLView* LLTextBox::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) +void LLTextBox::onUrlLabelUpdated(const std::string &url, const std::string &label) { - std::string name("text_box"); - node->getAttributeString("name", name); - LLFontGL* font = LLView::selectFont(node); - - std::string text = node->getTextContents(); - - LLTextBox* text_box = new LLTextBox(name, - LLRect(), - text, - font, - FALSE); - - - LLFontGL::HAlign halign = LLView::selectFontHAlign(node); - text_box->setHAlign(halign); - - text_box->initFromXML(node, parent); - - node->getAttributeS32("line_spacing", text_box->mLineSpacing); - - std::string font_style; - if (node->getAttributeString("font-style", font_style)) - { - text_box->mFontStyle = LLFontGL::getStyleFromString(font_style); - } - - BOOL mouse_opaque = text_box->getMouseOpaque(); - if (node->getAttributeBOOL("mouse_opaque", mouse_opaque)) - { - text_box->setMouseOpaque(mouse_opaque); - } - - if(node->hasAttribute("text_color")) - { - LLColor4 color; - LLUICtrlFactory::getAttributeColor(node, "text_color", color); - text_box->setColor(color); - } - - if(node->hasAttribute("hover_color")) - { - LLColor4 color; - LLUICtrlFactory::getAttributeColor(node, "hover_color", color); - text_box->setHoverColor(color); - text_box->setHoverActive(true); - } - - BOOL hover_active = FALSE; - if(node->getAttributeBOOL("hover", hover_active)) - { - text_box->setHoverActive(hover_active); - } - - return text_box; + needsReflow(); } + diff --git a/indra/llui/lltextbox.h b/indra/llui/lltextbox.h index 07a6aa3622..3a045534d3 100644 --- a/indra/llui/lltextbox.h +++ b/indra/llui/lltextbox.h @@ -33,110 +33,56 @@ #ifndef LL_LLTEXTBOX_H #define LL_LLTEXTBOX_H -#include "lluictrl.h" -#include "v4color.h" -#include "llstring.h" #include "lluistring.h" +#include "lltextbase.h" - -class LLTextBox -: public LLUICtrl +class LLTextBox : + public LLTextBase { public: - // By default, follows top and left and is mouse-opaque. - // If no text, text = name. - // If no font, uses default system font. - LLTextBox(const std::string& name, const LLRect& rect, const std::string& text, - const LLFontGL* font = NULL, BOOL mouse_opaque = TRUE ); - - // Construct a textbox which handles word wrapping for us. - LLTextBox(const std::string& name, const std::string& text, F32 max_width = 200, - const LLFontGL* font = NULL, BOOL mouse_opaque = TRUE ); - - // "Simple" constructors for text boxes that have the same name and label *TO BE DEPRECATED* - LLTextBox(const std::string& name_and_label, const LLRect& rect); - - // Consolidate common member initialization - // 20+ initializers times 3+ constructors is unmaintainable. - void initDefaults(); - - virtual ~LLTextBox() {} - - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, class LLUICtrlFactory *factory); - - virtual void draw(); - virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + // *TODO: Add callback to Params + typedef boost::function<void (void)> callback_t; + + struct Params : public LLInitParam::Block<Params, LLTextBase::Params> + {}; - 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); +protected: + LLTextBox(const Params&); + friend class LLUICtrlFactory; - void setColor( const LLColor4& c ) { mTextColor = c; } - void setDisabledColor( const LLColor4& c) { mDisabledColor = c; } - void setBackgroundColor( const LLColor4& c) { mBackgroundColor = c; } - void setBorderColor( const LLColor4& c) { mBorderColor = c; } +public: + virtual ~LLTextBox(); - void setHoverColor( const LLColor4& c ) { mHoverColor = c; } - void setHoverActive( BOOL active ) { mHoverActive = active; } + /*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); - void setText( const LLStringExplicit& text ); - void setWrappedText(const LLStringExplicit& text, F32 max_width = -1.0); // -1 means use existing control width - void setUseEllipses( BOOL use_ellipses ) { mUseEllipses = use_ellipses; } + /*virtual*/ void setText( const LLStringExplicit& text, const LLStyle::Params& input_params = LLStyle::Params() ); - void setBackgroundVisible(BOOL visible) { mBackgroundVisible = visible; } - void setBorderVisible(BOOL visible) { mBorderVisible = visible; } - void setFontStyle(U8 style) { mFontStyle = style; } - void setBorderDropshadowVisible(BOOL visible){ mBorderDropShadowVisible = visible; } - void setHPad(S32 pixels) { mHPad = pixels; } - void setVPad(S32 pixels) { mVPad = pixels; } void setRightAlign() { mHAlign = LLFontGL::RIGHT; } void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; } - void setClickedCallback( void (*cb)(void *data), void* data = NULL ){ mClickedCallback = cb; mCallbackUserData = data; } // mouse down and up within button - - const LLFontGL* getFont() const { return mFontGL; } + void setClickedCallback( boost::function<void (void*)> cb, void* userdata = NULL ); void reshapeToFitText(); - const std::string& getText() const { return mText.getString(); } S32 getTextPixelWidth(); S32 getTextPixelHeight(); - virtual void setValue(const LLSD& value ) { setText(value.asString()); } - virtual LLSD getValue() const { return LLSD(getText()); } - virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text ); - -private: - void setLineLengths(); - void drawText(S32 x, S32 y, const LLColor4& color ); + /*virtual*/ LLSD getValue() const; + /*virtual*/ BOOL setTextArg( const std::string& key, const LLStringExplicit& text ); - LLUIString mText; - const LLFontGL* mFontGL; - LLColor4 mTextColor; - LLColor4 mDisabledColor; - LLColor4 mBackgroundColor; - LLColor4 mBorderColor; - LLColor4 mHoverColor; - - BOOL mHoverActive; - BOOL mHasHover; - BOOL mBackgroundVisible; - BOOL mBorderVisible; - - U8 mFontStyle; // style bit flags for font - BOOL mBorderDropShadowVisible; - BOOL mUseEllipses; +protected: + void onUrlLabelUpdated(const std::string &url, const std::string &label); - S32 mLineSpacing; - - S32 mHPad; - S32 mVPad; - LLFontGL::HAlign mHAlign; - LLFontGL::VAlign mVAlign; - - std::vector<S32> mLineLengthList; - void (*mClickedCallback)(void* data ); - void* mCallbackUserData; + LLUIString mText; + callback_t mClickedCallback; }; +// Build time optimization, generate once in .cpp file +#ifndef LLTEXTBOX_CPP +extern template class LLTextBox* LLView::getChild<class LLTextBox>( + const std::string& name, BOOL recurse) const; +#endif + #endif diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 5e54c7a307..f2c3879a6c 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -1,6 +1,5 @@ /** * @file lltexteditor.cpp - * @brief LLTextEditor base class * * $LicenseInfo:firstyear=2001&license=viewergpl$ * @@ -34,9 +33,13 @@ #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" @@ -45,7 +48,6 @@ #include "lltimer.h" #include "llmath.h" -#include "audioengine.h" #include "llclipboard.h" #include "llscrollbar.h" #include "llstl.h" @@ -55,70 +57,56 @@ #include "llundo.h" #include "llviewborder.h" #include "llcontrol.h" -#include "llimagegl.h" #include "llwindow.h" #include "lltextparser.h" +#include "llscrollcontainer.h" +#include "llpanel.h" +#include "llurlregistry.h" +#include "lltooltip.h" +#include "llmenugl.h" + #include <queue> +#include "llcombobox.h" // // Globals // -static LLRegisterWidget<LLTextEditor> r("simple_text_editor"); +static LLDefaultChildRegistry::Register<LLTextEditor> r("simple_text_editor"); -BOOL gDebugTextEditorTips = FALSE; +// Compiler optimization, generate extern template +template class LLTextEditor* LLView::getChild<class LLTextEditor>( + const std::string& name, BOOL recurse) const; // // Constants // -const S32 UI_TEXTEDITOR_BUFFER_BLOCK_SIZE = 512; -const S32 UI_TEXTEDITOR_BORDER = 1; -const S32 UI_TEXTEDITOR_H_PAD = 4; -const S32 UI_TEXTEDITOR_V_PAD_TOP = 4; const S32 UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32; const S32 UI_TEXTEDITOR_LINE_NUMBER_DIGITS = 4; -const F32 CURSOR_FLASH_DELAY = 1.0f; // in seconds -const S32 CURSOR_THICKNESS = 2; const S32 SPACES_PER_TAB = 4; -const F32 PREEDIT_MARKER_BRIGHTNESS = 0.4f; -const S32 PREEDIT_MARKER_GAP = 1; -const S32 PREEDIT_MARKER_POSITION = 2; -const S32 PREEDIT_MARKER_THICKNESS = 1; -const F32 PREEDIT_STANDOUT_BRIGHTNESS = 0.6f; -const S32 PREEDIT_STANDOUT_GAP = 1; -const S32 PREEDIT_STANDOUT_POSITION = 2; -const S32 PREEDIT_STANDOUT_THICKNESS = 2; - - -LLColor4 LLTextEditor::mLinkColor = LLColor4::blue; -void (* LLTextEditor::mURLcallback)(const std::string&) = NULL; -bool (* LLTextEditor::mSecondlifeURLcallback)(const std::string&) = NULL; -bool (* LLTextEditor::mSecondlifeURLcallbackRightClick)(const std::string&) = NULL; - - /////////////////////////////////////////////////////////////////// -class LLTextEditor::LLTextCmdInsert : public LLTextEditor::LLTextCmd +class LLTextEditor::TextCmdInsert : public LLTextBase::TextCmd { public: - LLTextCmdInsert(S32 pos, BOOL group_with_next, const LLWString &ws) - : LLTextCmd(pos, group_with_next), mWString(ws) + TextCmdInsert(S32 pos, BOOL group_with_next, const LLWString &ws, LLTextSegmentPtr segment) + : TextCmd(pos, group_with_next, segment), mWString(ws) { } - virtual ~LLTextCmdInsert() {} - virtual BOOL execute( LLTextEditor* editor, S32* delta ) + 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( LLTextEditor* editor ) + virtual S32 undo( LLTextBase* editor ) { remove(editor, getPosition(), mWString.length() ); return getPosition(); } - virtual S32 redo( LLTextEditor* editor ) + virtual S32 redo( LLTextBase* editor ) { insert(editor, getPosition(), mWString ); return getPosition() + mWString.length(); @@ -129,11 +117,11 @@ private: }; /////////////////////////////////////////////////////////////////// -class LLTextEditor::LLTextCmdAddChar : public LLTextEditor::LLTextCmd +class LLTextEditor::TextCmdAddChar : public LLTextBase::TextCmd { public: - LLTextCmdAddChar( S32 pos, BOOL group_with_next, llwchar wc) - : LLTextCmd(pos, group_with_next), mWString(1, wc), mBlockExtensions(FALSE) + 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() @@ -142,16 +130,19 @@ public: } 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( LLTextEditor* editor, S32* delta ) + 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( LLTextEditor* editor, S32 pos, llwchar wc, S32* delta ) + virtual BOOL extendAndExecute( LLTextBase* editor, S32 pos, llwchar wc, S32* delta ) { LLWString ws; ws += wc; @@ -163,12 +154,12 @@ public: } return (*delta != 0); } - virtual S32 undo( LLTextEditor* editor ) + virtual S32 undo( LLTextBase* editor ) { remove(editor, getPosition(), mWString.length() ); return getPosition(); } - virtual S32 redo( LLTextEditor* editor ) + virtual S32 redo( LLTextBase* editor ) { insert(editor, getPosition(), mWString ); return getPosition() + mWString.length(); @@ -182,25 +173,25 @@ private: /////////////////////////////////////////////////////////////////// -class LLTextEditor::LLTextCmdOverwriteChar : public LLTextEditor::LLTextCmd +class LLTextEditor::TextCmdOverwriteChar : public LLTextBase::TextCmd { public: - LLTextCmdOverwriteChar( S32 pos, BOOL group_with_next, llwchar wc) - : LLTextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {} + TextCmdOverwriteChar( S32 pos, BOOL group_with_next, llwchar wc) + : TextCmd(pos, group_with_next), mChar(wc), mOldChar(0) {} - virtual BOOL execute( LLTextEditor* editor, S32* delta ) + virtual BOOL execute( LLTextBase* editor, S32* delta ) { - mOldChar = editor->getWChar(getPosition()); + mOldChar = editor->getWText()[getPosition()]; overwrite(editor, getPosition(), mChar); *delta = 0; return TRUE; } - virtual S32 undo( LLTextEditor* editor ) + virtual S32 undo( LLTextBase* editor ) { overwrite(editor, getPosition(), mOldChar); return getPosition(); } - virtual S32 redo( LLTextEditor* editor ) + virtual S32 redo( LLTextBase* editor ) { overwrite(editor, getPosition(), mChar); return getPosition()+1; @@ -213,25 +204,26 @@ private: /////////////////////////////////////////////////////////////////// -class LLTextEditor::LLTextCmdRemove : public LLTextEditor::LLTextCmd +class LLTextEditor::TextCmdRemove : public LLTextBase::TextCmd { public: - LLTextCmdRemove( S32 pos, BOOL group_with_next, S32 len ) : - LLTextCmd(pos, group_with_next), mLen(len) + 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( LLTextEditor* editor, S32* delta ) + virtual BOOL execute( LLTextBase* editor, S32* delta ) { - mWString = editor->getWSubString(getPosition(), mLen); + mWString = editor->getWText().substr(getPosition(), mLen); *delta = remove(editor, getPosition(), mLen ); return (*delta != 0); } - virtual S32 undo( LLTextEditor* editor ) + virtual S32 undo( LLTextBase* editor ) { - insert(editor, getPosition(), mWString ); + insert(editor, getPosition(), mWString); return getPosition() + mWString.length(); } - virtual S32 redo( LLTextEditor* editor ) + virtual S32 redo( LLTextBase* editor ) { remove(editor, getPosition(), mLen ); return getPosition(); @@ -243,109 +235,69 @@ private: /////////////////////////////////////////////////////////////////// - -LLTextEditor::LLTextEditor( - const std::string& name, - const LLRect& rect, - S32 max_length, // In bytes - const std::string &default_text, - const LLFontGL* font, - BOOL allow_embedded_items) - : - LLUICtrl( name, rect, TRUE, NULL, NULL, FOLLOWS_TOP | FOLLOWS_LEFT ), - mTextIsUpToDate(TRUE), - mMaxTextByteLength( max_length ), +LLTextEditor::Params::Params() +: default_text("default_text"), + embedded_items("embedded_items", false), + ignore_tab("ignore_tab", true), + handle_edit_keys_directly("handle_edit_keys_directly", false), + show_line_numbers("show_line_numbers", false), + default_color("default_color"), + commit_on_focus_lost("commit_on_focus_lost", false) +{} + +LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : + LLTextBase(p), mBaseDocIsPristine(TRUE), mPristineCmd( NULL ), mLastCmd( NULL ), - mCursorPos( 0 ), - mIsSelecting( FALSE ), - mSelectionStart( 0 ), - mSelectionEnd( 0 ), - mScrolledToBottom( TRUE ), - mOnScrollEndCallback( NULL ), - mOnScrollEndData( NULL ), - mCursorColor( LLUI::sColorsGroup->getColor( "TextCursorColor" ) ), - mFgColor( LLUI::sColorsGroup->getColor( "TextFgColor" ) ), - mDefaultColor( LLUI::sColorsGroup->getColor( "TextDefaultColor" ) ), - mReadOnlyFgColor( LLUI::sColorsGroup->getColor( "TextFgReadOnlyColor" ) ), - mWriteableBgColor( LLUI::sColorsGroup->getColor( "TextBgWriteableColor" ) ), - mReadOnlyBgColor( LLUI::sColorsGroup->getColor( "TextBgReadOnlyColor" ) ), - mFocusBgColor( LLUI::sColorsGroup->getColor( "TextBgFocusColor" ) ), - mReadOnly(FALSE), - mWordWrap( FALSE ), - mShowLineNumbers ( FALSE ), - mTabsToNextField( TRUE ), - mCommitOnFocusLost( FALSE ), - mHideScrollbarForShortDocs( FALSE ), - mTakesNonScrollClicks( TRUE ), - mTrackBottom( FALSE ), - mAllowEmbeddedItems( allow_embedded_items ), - mAcceptCallingCardNames(FALSE), - mHandleEditKeysDirectly( FALSE ), + mDefaultColor( p.default_color() ), + mShowLineNumbers ( p.show_line_numbers ), + mCommitOnFocusLost( p.commit_on_focus_lost), + mAllowEmbeddedItems( p.embedded_items ), + mHandleEditKeysDirectly( p.handle_edit_keys_directly ), mMouseDownX(0), mMouseDownY(0), - mLastSelectionX(-1), - mLastSelectionY(-1), - mReflowNeeded(FALSE), - mScrollNeeded(FALSE) + mTabsToNextField(p.ignore_tab), + mContextMenu(NULL) { + mDefaultFont = p.font; + mSourceID.generate(); - // reset desired x cursor position - mDesiredXPixel = -1; + //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<LLViewBorder> (params); + addChild( mBorder ); + + setText(p.default_text()); - if (font) + if (mShowLineNumbers) { - mGLFont = font; + mHPad += UI_TEXTEDITOR_LINE_NUMBER_MARGIN; + updateRects(); } - else - { - mGLFont = LLFontGL::getFontSansSerif(); - } - - updateTextRect(); - - S32 line_height = llround( mGLFont->getLineHeight() ); - S32 page_size = mTextRect.getHeight() / line_height; - - // Init the scrollbar - LLRect scroll_rect; - scroll_rect.setOriginAndSize( - getRect().getWidth() - SCROLLBAR_SIZE, - 1, - SCROLLBAR_SIZE, - getRect().getHeight() - 1); - S32 lines_in_doc = getLineCount(); - mScrollbar = new LLScrollbar( std::string("Scrollbar"), scroll_rect, - LLScrollbar::VERTICAL, - lines_in_doc, - 0, - page_size, - NULL, this ); - mScrollbar->setFollowsRight(); - mScrollbar->setFollowsTop(); - mScrollbar->setFollowsBottom(); - mScrollbar->setEnabled( TRUE ); - mScrollbar->setVisible( TRUE ); - mScrollbar->setOnScrollEndCallback(mOnScrollEndCallback, mOnScrollEndData); - addChild(mScrollbar); - - mBorder = new LLViewBorder( std::string("text ed border"), LLRect(0, getRect().getHeight(), getRect().getWidth(), 0), LLViewBorder::BEVEL_IN, LLViewBorder::STYLE_LINE, UI_TEXTEDITOR_BORDER ); - addChild( mBorder ); +} - appendText(default_text, FALSE, FALSE); - - resetDirty(); // Update saved text state +void LLTextEditor::initFromParams( const LLTextEditor::Params& p) +{ + LLTextBase::initFromParams(p); - mParseHTML=FALSE; - mHTML.clear(); + if (p.commit_on_focus_lost.isProvided()) + { + mCommitOnFocusLost = p.commit_on_focus_lost; + } + + updateAllowingLanguageInput(); } - LLTextEditor::~LLTextEditor() { - gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() while LLTextEditor still valid // Route menu back to the default if( gEditMenuHandler == this ) @@ -354,254 +306,26 @@ LLTextEditor::~LLTextEditor() } // Scrollbar is deleted by LLView - mHoverSegment = NULL; - std::for_each(mSegments.begin(), mSegments.end(), DeletePointer()); - std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); -} - -void LLTextEditor::setTrackColor( const LLColor4& color ) -{ - mScrollbar->setTrackColor(color); -} -void LLTextEditor::setThumbColor( const LLColor4& color ) -{ - mScrollbar->setThumbColor(color); -} - -void LLTextEditor::setHighlightColor( const LLColor4& color ) -{ - mScrollbar->setHighlightColor(color); -} - -void LLTextEditor::setShadowColor( const LLColor4& color ) -{ - mScrollbar->setShadowColor(color); -} - -void LLTextEditor::updateLineStartList(S32 startpos) -{ - updateSegments(); - - bindEmbeddedChars(mGLFont); - - S32 seg_num = mSegments.size(); - S32 seg_idx = 0; - S32 seg_offset = 0; - - if (!mLineStartList.empty()) - { - getSegmentAndOffset(startpos, &seg_idx, &seg_offset); - line_info t(seg_idx, seg_offset); - line_list_t::iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), t, line_info_compare()); - if (iter != mLineStartList.begin()) --iter; - seg_idx = iter->mSegment; - seg_offset = iter->mOffset; - mLineStartList.erase(iter, mLineStartList.end()); - } - - while( seg_idx < seg_num ) - { - mLineStartList.push_back(line_info(seg_idx,seg_offset)); - BOOL line_ended = FALSE; - S32 start_x = mShowLineNumbers ? UI_TEXTEDITOR_LINE_NUMBER_MARGIN : 0; - S32 line_width = start_x; - while(!line_ended && seg_idx < seg_num) - { - LLTextSegment* segment = mSegments[seg_idx]; - S32 start_idx = segment->getStart() + seg_offset; - S32 end_idx = start_idx; - while (end_idx < segment->getEnd() && mWText[end_idx] != '\n') - { - end_idx++; - } - if (start_idx == end_idx) - { - if (end_idx >= segment->getEnd()) - { - // empty segment - seg_idx++; - seg_offset = 0; - } - else - { - // empty line - line_ended = TRUE; - seg_offset++; - } - } - else - { - const llwchar* str = mWText.c_str() + start_idx; - S32 drawn = mGLFont->maxDrawableChars(str, (F32)abs(mTextRect.getWidth()) - line_width, - end_idx - start_idx, mWordWrap, mAllowEmbeddedItems ); - if( 0 == drawn && line_width == start_x) - { - // If at the beginning of a line, draw at least one character, even if it doesn't all fit. - drawn = 1; - } - seg_offset += drawn; - line_width += mGLFont->getWidth(str, 0, drawn, mAllowEmbeddedItems); - end_idx = segment->getStart() + seg_offset; - if (end_idx < segment->getEnd()) - { - line_ended = TRUE; - if (mWText[end_idx] == '\n') - { - seg_offset++; // skip newline - } - } - else - { - // finished with segment - seg_idx++; - seg_offset = 0; - } - } - } - } - - unbindEmbeddedChars(mGLFont); - - mScrollbar->setDocSize( getLineCount() ); - - if (mHideScrollbarForShortDocs) - { - BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize()); - mScrollbar->setVisible(!short_doc); - } - - // if scrolled to bottom, stay at bottom - // unless user is selecting text - // do this after updating page size - if (mScrolledToBottom && mTrackBottom && !hasMouseCapture()) - { - endOfDoc(); - } + // context menu is owned by menu holder, not us + //delete mContextMenu; } //////////////////////////////////////////////////////////// // LLTextEditor // Public methods -BOOL LLTextEditor::truncate() -{ - BOOL did_truncate = FALSE; - - // First rough check - if we're less than 1/4th the size, we're OK - if (mWText.size() >= (size_t) (mMaxTextByteLength / 4)) - { - // Have to check actual byte size - S32 utf8_byte_size = wstring_utf8_length( mWText ); - if ( utf8_byte_size > mMaxTextByteLength ) - { - // Truncate safely in UTF-8 - std::string temp_utf8_text = wstring_to_utf8str( mWText ); - temp_utf8_text = utf8str_truncate( temp_utf8_text, mMaxTextByteLength ); - mWText = utf8str_to_wstring( temp_utf8_text ); - mTextIsUpToDate = FALSE; - did_truncate = TRUE; - } - } - - return did_truncate; -} - -void LLTextEditor::setText(const LLStringExplicit &utf8str) +void LLTextEditor::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) { - // LLStringUtil::removeCRLF(utf8str); - mUTF8Text = utf8str_removeCRLF(utf8str); - // mUTF8Text = utf8str; - mWText = utf8str_to_wstring(mUTF8Text); - mTextIsUpToDate = TRUE; - - truncate(); blockUndo(); - - setCursorPos(0); deselect(); - needsReflow(); + LLTextBase::setText(utf8str, input_params); resetDirty(); } -void LLTextEditor::setWText(const LLWString &wtext) -{ - mWText = wtext; - mUTF8Text.clear(); - mTextIsUpToDate = FALSE; - - truncate(); - blockUndo(); - - setCursorPos(0); - deselect(); - - needsReflow(); - - resetDirty(); -} - -// virtual -void LLTextEditor::setValue(const LLSD& value) -{ - setText(value.asString()); -} - -const std::string& LLTextEditor::getText() const -{ - if (!mTextIsUpToDate) - { - if (mAllowEmbeddedItems) - { - llwarns << "getText() called on text with embedded items (not supported)" << llendl; - } - mUTF8Text = wstring_to_utf8str(mWText); - mTextIsUpToDate = TRUE; - } - return mUTF8Text; -} - -// virtual -LLSD LLTextEditor::getValue() const -{ - return LLSD(getText()); -} - -void LLTextEditor::setWordWrap(BOOL b) -{ - mWordWrap = b; - - setCursorPos(0); - deselect(); - - needsReflow(); -} - - -void LLTextEditor::setBorderVisible(BOOL b) -{ - mBorder->setVisible(b); -} - -BOOL LLTextEditor::isBorderVisible() const -{ - return mBorder->getVisible(); -} - -void LLTextEditor::setHideScrollbarForShortDocs(BOOL b) -{ - mHideScrollbarForShortDocs = b; - - if (mHideScrollbarForShortDocs) - { - BOOL short_doc = (mScrollbar->getDocSize() <= mScrollbar->getPageSize()); - mScrollbar->setVisible(!short_doc); - } -} - void LLTextEditor::selectNext(const std::string& search_text_in, BOOL case_insensitive, BOOL wrap) { if (search_text_in.empty()) @@ -624,7 +348,7 @@ void LLTextEditor::selectNext(const std::string& search_text_in, BOOL case_insen if (selected_text == search_text) { // We already have this word selected, we are searching for the next. - mCursorPos += search_text.size(); + setCursorPos(mCursorPos + search_text.size()); } } @@ -687,9 +411,7 @@ BOOL LLTextEditor::replaceText(const std::string& search_text_in, const std::str void LLTextEditor::replaceTextAll(const std::string& search_text, const std::string& replace_text, BOOL case_insensitive) { - S32 cur_pos = mScrollbar->getDocPos(); - - setCursorPos(0); + startOfDoc(); selectNext(search_text, case_insensitive, FALSE); BOOL replaced = TRUE; @@ -697,24 +419,16 @@ void LLTextEditor::replaceTextAll(const std::string& search_text, const std::str { replaced = replaceText(search_text,replace_text, case_insensitive, FALSE); } - - mScrollbar->setDocPos(cur_pos); -} - -// Picks a new cursor position based on the screen size of text being drawn. -void LLTextEditor::setCursorAtLocalPos( S32 local_x, S32 local_y, BOOL round ) -{ - setCursorPos(getCursorPosFromLocalCoord(local_x, local_y, round)); } S32 LLTextEditor::prevWordPos(S32 cursorPos) const { - const LLWString& wtext = mWText; + LLWString wtext(getWText()); while( (cursorPos > 0) && (wtext[cursorPos-1] == ' ') ) { cursorPos--; } - while( (cursorPos > 0) && isPartOfWord( wtext[cursorPos-1] ) ) + while( (cursorPos > 0) && LLWStringUtil::isPartOfWord( wtext[cursorPos-1] ) ) { cursorPos--; } @@ -723,8 +437,8 @@ S32 LLTextEditor::prevWordPos(S32 cursorPos) const S32 LLTextEditor::nextWordPos(S32 cursorPos) const { - const LLWString& wtext = mWText; - while( (cursorPos < getLength()) && isPartOfWord( wtext[cursorPos] ) ) + LLWString wtext(getWText()); + while( (cursorPos < getLength()) && LLWStringUtil::isPartOfWord( wtext[cursorPos] ) ) { cursorPos++; } @@ -735,163 +449,45 @@ S32 LLTextEditor::nextWordPos(S32 cursorPos) const return cursorPos; } -S32 LLTextEditor::getLineStart( S32 line ) const +const LLTextSegmentPtr LLTextEditor::getPreviousSegment() const { - S32 num_lines = getLineCount(); - if (num_lines == 0) - { - return 0; - } - - line = llclamp(line, 0, num_lines-1); - S32 segidx = mLineStartList[line].mSegment; - S32 segoffset = mLineStartList[line].mOffset; - LLTextSegment* seg = mSegments[segidx]; - S32 res = seg->getStart() + segoffset; - if (res > seg->getEnd()) llerrs << "wtf" << llendl; - return res; -} + // find segment index at character to left of cursor (or rightmost edge of selection) + segment_set_t::const_iterator it = mSegments.lower_bound(new LLIndexSegment(mCursorPos)); -// Given an offset into text (pos), find the corresponding line (from the start of the doc) and an offset into the line. -void LLTextEditor::getLineAndOffset( S32 startpos, S32* linep, S32* offsetp ) const -{ - if (mLineStartList.empty()) + if (it != mSegments.end()) { - *linep = 0; - *offsetp = startpos; + return *it; } else { - S32 seg_idx, seg_offset; - getSegmentAndOffset( startpos, &seg_idx, &seg_offset ); - - line_info tline(seg_idx, seg_offset); - line_list_t::const_iterator iter = std::upper_bound(mLineStartList.begin(), mLineStartList.end(), tline, line_info_compare()); - if (iter != mLineStartList.begin()) --iter; - *linep = iter - mLineStartList.begin(); - S32 line_start = mSegments[iter->mSegment]->getStart() + iter->mOffset; - *offsetp = startpos - line_start; + return LLTextSegmentPtr(); } } -void LLTextEditor::getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp ) const -{ - if (mSegments.empty()) - { - *segidxp = -1; - *offsetp = startpos; - } - - LLTextSegment tseg(startpos); - segment_list_t::const_iterator seg_iter; - seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &tseg, LLTextSegment::compare()); - if (seg_iter != mSegments.begin()) --seg_iter; - *segidxp = seg_iter - mSegments.begin(); - *offsetp = startpos - (*seg_iter)->getStart(); -} - -const LLTextSegment* LLTextEditor::getPreviousSegment() const -{ - // find segment index at character to left of cursor (or rightmost edge of selection) - S32 idx = llmax(0, getSegmentIdxAtOffset(mCursorPos) - 1); - return idx >= 0 ? mSegments[idx] : NULL; -} - -void LLTextEditor::getSelectedSegments(std::vector<const LLTextSegment*>& segments) const +void LLTextEditor::getSelectedSegments(LLTextEditor::segment_vec_t& segments) const { S32 left = hasSelection() ? llmin(mSelectionStart, mSelectionEnd) : mCursorPos; S32 right = hasSelection() ? llmax(mSelectionStart, mSelectionEnd) : mCursorPos; - S32 first_idx = llmax(0, getSegmentIdxAtOffset(left)); - S32 last_idx = llmax(0, first_idx, getSegmentIdxAtOffset(right)); - for (S32 idx = first_idx; idx <= last_idx; ++idx) - { - segments.push_back(mSegments[idx]); - } + return getSegmentsInRange(segments, left, right, true); } -S32 LLTextEditor::getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const +void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out, S32 start, S32 end, bool include_partial) const { - if(mShowLineNumbers) - { - local_x -= UI_TEXTEDITOR_LINE_NUMBER_MARGIN; - } - - // 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. + 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; - // Figure out which line we're nearest to. - S32 total_lines = getLineCount(); - S32 line_height = llround( mGLFont->getLineHeight() ); - S32 max_visible_lines = mTextRect.getHeight() / line_height; - S32 scroll_lines = mScrollbar->getDocPos(); - S32 visible_lines = llmin( total_lines - scroll_lines, max_visible_lines ); // Lines currently visible - - //S32 line = S32( 0.5f + ((mTextRect.mTop - local_y) / mGLFont->getLineHeight()) ); - S32 line = (mTextRect.mTop - 1 - local_y) / line_height; - if (line >= total_lines) + for (segment_set_t::const_iterator it = first_it; it != end_it; ++it) { - return getLength(); // past the end - } - - line = llclamp( line, 0, visible_lines ) + scroll_lines; - - S32 line_start = getLineStart(line); - S32 next_start = getLineStart(line+1); - S32 line_end = (next_start != line_start) ? next_start - 1 : getLength(); - - if(line_start == -1) - { - return 0; - } - else - { - S32 line_len = line_end - line_start; - S32 pos; - - if (mAllowEmbeddedItems) - { - // Figure out which character we're nearest to. - bindEmbeddedChars(mGLFont); - pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start, - (F32)(local_x - mTextRect.mLeft), - (F32)(mTextRect.getWidth()), - line_len, - round, TRUE); - unbindEmbeddedChars(mGLFont); - } - else + LLTextSegmentPtr segment = *it; + if (include_partial + || (segment->getStart() >= start + && segment->getEnd() <= end)) { - pos = mGLFont->charFromPixelOffset(mWText.c_str(), line_start, - (F32)(local_x - mTextRect.mLeft), - (F32)mTextRect.getWidth(), - line_len, - round); + segments_out.push_back(segment); } - - return line_start + pos; - } -} - -void LLTextEditor::setCursor(S32 row, S32 column) -{ - const llwchar* doc = mWText.c_str(); - const char CR = 10; - while(row--) - { - while (CR != *doc++); } - doc += column; - setCursorPos(doc - mWText.c_str()); -} - -void LLTextEditor::setCursorPos(S32 offset) -{ - mCursorPos = llclamp(offset, 0, (S32)getLength()); - needsScroll(); - // reset desired x cursor position - mDesiredXPixel = -1; } // virtual @@ -909,25 +505,6 @@ void LLTextEditor::deselect() } -void LLTextEditor::startSelection() -{ - if( !mIsSelecting ) - { - mIsSelecting = TRUE; - mSelectionStart = mCursorPos; - mSelectionEnd = mCursorPos; - } -} - -void LLTextEditor::endSelection() -{ - if( mIsSelecting ) - { - mIsSelecting = FALSE; - mSelectionEnd = mCursorPos; - } -} - BOOL LLTextEditor::selectionContainsLineBreaks() { if (hasSelection()) @@ -935,7 +512,7 @@ BOOL LLTextEditor::selectionContainsLineBreaks() S32 left = llmin(mSelectionStart, mSelectionEnd); S32 right = left + llabs(mSelectionStart - mSelectionEnd); - const LLWString &wtext = mWText; + LLWString wtext = getWText(); for( S32 i = left; i < right; i++ ) { if (wtext[i] == '\n') @@ -972,7 +549,7 @@ S32 LLTextEditor::indentLine( S32 pos, S32 spaces ) // Unindent for(S32 i=0; i < -spaces; i++) { - const LLWString &wtext = mWText; + LLWString wtext = getWText(); if (wtext[pos] == ' ') { delta_spaces += remove( pos, 1, FALSE ); @@ -987,7 +564,7 @@ void LLTextEditor::indentSelectedLines( S32 spaces ) { if( hasSelection() ) { - const LLWString &text = mWText; + LLWString text = getWText(); S32 left = llmin( mSelectionStart, mSelectionEnd ); S32 right = left + llabs( mSelectionStart - mSelectionEnd ); BOOL cursor_on_right = (mSelectionEnd > mSelectionStart); @@ -1032,7 +609,7 @@ void LLTextEditor::indentSelectedLines( S32 spaces ) } right += delta_spaces; - //text = mWText; + text = getWText(); // Find the next new line while( (cur < right) && (text[cur] != '\n') ) @@ -1058,7 +635,7 @@ void LLTextEditor::indentSelectedLines( S32 spaces ) mSelectionStart = right; mSelectionEnd = left; } - mCursorPos = mSelectionEnd; + setCursorPos(mSelectionEnd); } } @@ -1073,64 +650,24 @@ void LLTextEditor::selectAll() { mSelectionStart = getLength(); mSelectionEnd = 0; - mCursorPos = mSelectionEnd; + setCursorPos(mSelectionEnd); } - -BOOL LLTextEditor::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen) +BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) { - 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->handleToolTip(local_x, local_y, msg, sticky_rect_screen ) ) - { - return TRUE; - } - } - - if( mSegments.empty() ) - { - return TRUE; - } + BOOL handled = FALSE; - const LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); - if( cur_segment ) + // set focus first, in case click callbacks want to change it + // RN: do we really need to have a tab stop? + if (hasTabStop()) { - BOOL has_tool_tip = FALSE; - has_tool_tip = cur_segment->getToolTip( msg ); - - if( has_tool_tip ) - { - // Just use a slop area around the cursor - // Convert rect local to screen coordinates - S32 SLOP = 8; - localPointToScreen( - x - SLOP, y - SLOP, - &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); - sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP; - sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP; - } + setFocus( TRUE ); } - return TRUE; -} - -BOOL LLTextEditor::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - // Pretend the mouse is over the scrollbar - return mScrollbar->handleScrollWheel( 0, 0, clicks ); -} - -BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) -{ - BOOL handled = FALSE; // Let scrollbar have first dibs - handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + handled = LLTextBase::handleMouseDown(x, y, mask); - if( !handled && mTakesNonScrollClicks) + if( !handled ) { if (!(mask & MASK_SHIFT)) { @@ -1144,31 +681,10 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) if (mask & MASK_SHIFT) { S32 old_cursor_pos = mCursorPos; - setCursorAtLocalPos( x, y, TRUE ); + setCursorAtLocalPos( x, y, true ); if (hasSelection()) { - /* Mac-like behavior - extend selection towards the cursor - if (mCursorPos < mSelectionStart - && mCursorPos < mSelectionEnd) - { - // ...left of selection - mSelectionStart = llmax(mSelectionStart, mSelectionEnd); - mSelectionEnd = mCursorPos; - } - else if (mCursorPos > mSelectionStart - && mCursorPos > mSelectionEnd) - { - // ...right of selection - mSelectionStart = llmin(mSelectionStart, mSelectionEnd); - mSelectionEnd = mCursorPos; - } - else - { - mSelectionEnd = mCursorPos; - } - */ - // Windows behavior mSelectionEnd = mCursorPos; } else @@ -1181,7 +697,7 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) } else { - setCursorAtLocalPos( x, y, TRUE ); + setCursorAtLocalPos( x, y, true ); startSelection(); } gFocusMgr.setMouseCapture( this ); @@ -1190,26 +706,42 @@ BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) handled = TRUE; } - if (hasTabStop()) - { - setFocus( TRUE ); - handled = TRUE; - } - // Delay cursor flashing - resetKeystrokeTimer(); + resetCursorBlink(); return handled; } +BOOL LLTextEditor::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (hasTabStop()) + { + setFocus(TRUE); + } + if (!LLTextBase::handleRightMouseDown(x, y, mask)) + { + showContextMenu(x, y); + } + return TRUE; +} + + BOOL LLTextEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { - setFocus( TRUE ); - if( canPastePrimary() ) + if (hasTabStop()) + { + setFocus(TRUE); + } + + if (!LLTextBase::handleMouseDown(x, y, mask)) { - setCursorAtLocalPos( x, y, TRUE ); - pastePrimary(); + if( canPastePrimary() ) + { + setCursorAtLocalPos( x, y, true ); + // does not rely on focus being set + pastePrimary(); + } } return TRUE; } @@ -1219,31 +751,19 @@ BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; - mHoverSegment = NULL; if(hasMouseCapture() ) { if( mIsSelecting ) { - if (x != mLastSelectionX || y != mLastSelectionY) - { - mLastSelectionX = x; - mLastSelectionY = y; - } - - if( y > mTextRect.mTop ) - { - mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 ); - } - else - if( y < mTextRect.mBottom ) - { - mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 ); + if(mScroller) + { + mScroller->autoScroll(x, y); } - - setCursorAtLocalPos( x, y, TRUE ); + 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; } - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (active)" << llendl; getWindow()->setCursor(UI_CURSOR_IBEAM); handled = TRUE; @@ -1252,61 +772,21 @@ BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask) if( !handled ) { // Pass to children - handled = LLView::childrenHandleHover(x, y, mask) != NULL; + handled = LLTextBase::handleHover(x, y, mask); } if( handled ) { // Delay cursor flashing - resetKeystrokeTimer(); + resetCursorBlink(); } - // Opaque - if( !handled && mTakesNonScrollClicks) + if( !handled ) { - // Check to see if we're over an HTML-style link - if( !mSegments.empty() ) - { - const LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y ); - if( cur_segment ) - { - if(cur_segment->getStyle()->isLink()) - { - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over link, inactive)" << llendl; - getWindow()->setCursor(UI_CURSOR_HAND); - handled = TRUE; - } - else - if(cur_segment->getStyle()->getIsEmbeddedItem()) - { - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over embedded item, inactive)" << llendl; - getWindow()->setCursor(UI_CURSOR_HAND); - //getWindow()->setCursor(UI_CURSOR_ARROW); - handled = TRUE; - } - mHoverSegment = cur_segment; - } - } - - if( !handled ) - { - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (inactive)" << llendl; - if (!mScrollbar->getVisible() || x < getRect().getWidth() - SCROLLBAR_SIZE) - { - getWindow()->setCursor(UI_CURSOR_IBEAM); - } - else - { - getWindow()->setCursor(UI_CURSOR_ARROW); - } - handled = TRUE; - } + getWindow()->setCursor(UI_CURSOR_IBEAM); + handled = TRUE; } - if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) - { - mOnScrollEndCallback(mOnScrollEndData); - } return handled; } @@ -1315,33 +795,27 @@ BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; - // let scrollbar have first dibs - handled = LLView::childrenHandleMouseUp(x, y, mask) != NULL; + // if I'm not currently selecting text + if (!(hasSelection() && hasMouseCapture())) + { + // let text segments handle mouse event + handled = LLTextBase::handleMouseUp(x, y, mask); + } - if( !handled && mTakesNonScrollClicks) + if( !handled ) { if( mIsSelecting ) { - // Finish selection - if( y > mTextRect.mTop ) + if(mScroller) { - mScrollbar->setDocPos( mScrollbar->getDocPos() - 1 ); + mScroller->autoScroll(x, y); } - else - if( y < mTextRect.mBottom ) - { - mScrollbar->setDocPos( mScrollbar->getDocPos() + 1 ); - } - - setCursorAtLocalPos( x, y, TRUE ); + 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(); } - if( !hasSelection() ) - { - handleMouseUpOverSegment( x, y, mask ); - } - // take selection to 'primary' clipboard updatePrimary(); @@ -1349,7 +823,7 @@ BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) } // Delay cursor flashing - resetKeystrokeTimer(); + resetCursorBlink(); if( hasMouseCapture() ) { @@ -1366,28 +840,28 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; - // let scrollbar have first dibs - handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL; + // let scrollbar and text segments have first dibs + handled = LLTextBase::handleDoubleClick(x, y, mask); - if( !handled && mTakesNonScrollClicks) + if( !handled ) { - setCursorAtLocalPos( x, y, FALSE ); + setCursorAtLocalPos( x, y, false ); deselect(); - const LLWString &text = mWText; + LLWString text = getWText(); - if( isPartOfWord( text[mCursorPos] ) ) + if( LLWStringUtil::isPartOfWord( text[mCursorPos] ) ) { // Select word the cursor is over - while ((mCursorPos > 0) && isPartOfWord(text[mCursorPos-1])) + while ((mCursorPos > 0) && LLWStringUtil::isPartOfWord(text[mCursorPos-1])) { - mCursorPos--; + if (!setCursorPos(mCursorPos - 1)) break; } startSelection(); - while ((mCursorPos < (S32)text.length()) && isPartOfWord( text[mCursorPos] ) ) + while ((mCursorPos < (S32)text.length()) && LLWStringUtil::isPartOfWord( text[mCursorPos] ) ) { - mCursorPos++; + if (!setCursorPos(mCursorPos + 1)) break; } mSelectionEnd = mCursorPos; @@ -1396,7 +870,7 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) { // Select the character the cursor is over startSelection(); - mCursorPos++; + setCursorPos(mCursorPos + 1); mSelectionEnd = mCursorPos; } @@ -1405,7 +879,7 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) mIsSelecting = FALSE; // delay cursor flashing - resetKeystrokeTimer(); + resetCursorBlink(); // take selection to 'primary' clipboard updatePrimary(); @@ -1417,35 +891,18 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) } -// Allow calling cards to be dropped onto text fields. Append the name and -// a carriage return. -// virtual -BOOL LLTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask, - BOOL drop, EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, - std::string& tooltip_msg) -{ - *accept = ACCEPT_NO; - - return TRUE; -} - //---------------------------------------------------------------------------- // Returns change in number of characters in mText -S32 LLTextEditor::execute( LLTextCmd* cmd ) +S32 LLTextEditor::execute( TextCmd* cmd ) { 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); - if (enditer != mUndoStack.begin()) - { - --enditer; - std::for_each(mUndoStack.begin(), enditer, DeletePointer()); - mUndoStack.erase(mUndoStack.begin(), enditer); - } + 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; @@ -1459,30 +916,31 @@ S32 LLTextEditor::execute( LLTextCmd* cmd ) return delta; } -S32 LLTextEditor::insert(const S32 pos, const LLWString &wstr, const BOOL group_with_next_op) +S32 LLTextEditor::insert(S32 pos, const LLWString &wstr, bool group_with_next_op, LLTextSegmentPtr segment) { - return execute( new LLTextCmdInsert( pos, group_with_next_op, wstr ) ); + return execute( new TextCmdInsert( pos, group_with_next_op, wstr, segment ) ); } -S32 LLTextEditor::remove(const S32 pos, const S32 length, const BOOL group_with_next_op) +S32 LLTextEditor::remove(S32 pos, S32 length, bool group_with_next_op) { - return execute( new LLTextCmdRemove( pos, group_with_next_op, length ) ); -} + S32 end_pos = getEditableIndex(pos + length, true); -S32 LLTextEditor::append(const LLWString &wstr, const BOOL group_with_next_op) -{ - return insert(mWText.length(), wstr, group_with_next_op); + segment_vec_t segments_to_remove; + // store text segments + getSegmentsInRange(segments_to_remove, pos, pos + length, false); + + return execute( new TextCmdRemove( pos, group_with_next_op, end_pos - pos, segments_to_remove ) ); } S32 LLTextEditor::overwriteChar(S32 pos, llwchar wc) { - if ((S32)mWText.length() == pos) + if ((S32)getLength() == pos) { return addChar(pos, wc); } else { - return execute(new LLTextCmdOverwriteChar(pos, FALSE, wc)); + return execute(new TextCmdOverwriteChar(pos, FALSE, wc)); } } @@ -1498,12 +956,11 @@ void LLTextEditor::removeCharOrTab() { S32 chars_to_remove = 1; - const LLWString &text = mWText; + LLWString text = getWText(); if (text[mCursorPos - 1] == ' ') { // Try to remove a "tab" - S32 line, offset; - getLineAndOffset(mCursorPos, &line, &offset); + S32 offset = getLineOffsetFromDocIndex(mCursorPos); if (offset > 0) { chars_to_remove = offset % SPACES_PER_TAB; @@ -1563,7 +1020,7 @@ void LLTextEditor::removeChar() // Add a single character to the text S32 LLTextEditor::addChar(S32 pos, llwchar wc) { - if ( (wstring_utf8_length( mWText ) + wchar_utf8_length( wc )) >= mMaxTextByteLength) + if ( (wstring_utf8_length( getWText() ) + wchar_utf8_length( wc )) > mMaxTextByteLength) { make_ui_sound("UISndBadKeystroke"); return 0; @@ -1577,7 +1034,7 @@ S32 LLTextEditor::addChar(S32 pos, llwchar wc) } else { - return execute(new LLTextCmdAddChar(pos, FALSE, wc)); + return execute(new TextCmdAddChar(pos, FALSE, wc, LLTextSegmentPtr())); } } @@ -1614,10 +1071,10 @@ BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) if( 0 < mCursorPos ) { startSelection(); - mCursorPos--; + setCursorPos(mCursorPos - 1); if( mask & MASK_CONTROL ) { - mCursorPos = prevWordPos(mCursorPos); + setCursorPos(prevWordPos(mCursorPos)); } mSelectionEnd = mCursorPos; } @@ -1627,10 +1084,10 @@ BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) if( mCursorPos < getLength() ) { startSelection(); - mCursorPos++; + setCursorPos(mCursorPos + 1); if( mask & MASK_CONTROL ) { - mCursorPos = nextWordPos(mCursorPos); + setCursorPos(nextWordPos(mCursorPos)); } mSelectionEnd = mCursorPos; } @@ -1652,7 +1109,7 @@ BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) startSelection(); if( mask & MASK_CONTROL ) { - mCursorPos = 0; + setCursorPos(0); } else { @@ -1677,7 +1134,7 @@ BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) startSelection(); if( mask & MASK_CONTROL ) { - mCursorPos = getLength(); + setCursorPos(getLength()); } else { @@ -1728,14 +1185,7 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) switch( key ) { case KEY_UP: - if (mReadOnly) - { - mScrollbar->setDocPos(mScrollbar->getDocPos() - 1); - } - else - { - changeLine( -1 ); - } + changeLine( -1 ); break; case KEY_PAGE_UP: @@ -1743,25 +1193,11 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) break; case KEY_HOME: - if (mReadOnly) - { - mScrollbar->setDocPos(0); - } - else - { - startOfLine(); - } + startOfLine(); break; case KEY_DOWN: - if (mReadOnly) - { - mScrollbar->setDocPos(mScrollbar->getDocPos() + 1); - } - else - { - changeLine( 1 ); - } + changeLine( 1 ); break; case KEY_PAGE_DOWN: @@ -1769,21 +1205,10 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) break; case KEY_END: - if (mReadOnly) - { - mScrollbar->setDocPos(mScrollbar->getDocPosMax()); - } - else - { - endOfLine(); - } + endOfLine(); break; case KEY_LEFT: - if (mReadOnly) - { - break; - } if( hasSelection() ) { setCursorPos(llmin( mCursorPos - 1, mSelectionStart, mSelectionEnd )); @@ -1802,10 +1227,6 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) break; case KEY_RIGHT: - if (mReadOnly) - { - break; - } if( hasSelection() ) { setCursorPos(llmax( mCursorPos + 1, mSelectionStart, mSelectionEnd )); @@ -1829,10 +1250,6 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) } } - if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) - { - mOnScrollEndCallback(mOnScrollEndData); - } return handled; } @@ -1865,10 +1282,12 @@ void LLTextEditor::cut() } S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); S32 length = llabs( mSelectionStart - mSelectionEnd ); - gClipboard.copyFromSubstring( mWText, left_pos, length, mSourceID ); + gClipboard.copyFromSubstring( getWText(), left_pos, length, mSourceID ); deleteSelection( FALSE ); needsReflow(); + + onKeyStroke(); } BOOL LLTextEditor::canCopy() const @@ -1885,7 +1304,7 @@ void LLTextEditor::copy() } S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); S32 length = llabs( mSelectionStart - mSelectionEnd ); - gClipboard.copyFromSubstring(mWText, left_pos, length, mSourceID); + gClipboard.copyFromSubstring(getWText(), left_pos, length, mSourceID); } BOOL LLTextEditor::canPaste() const @@ -1957,7 +1376,7 @@ void LLTextEditor::pasteHelper(bool is_primary) for( S32 i = 0; i < len; i++ ) { llwchar wc = clean_string[i]; - if( (wc < LLFont::FIRST_CHAR) && (wc != LF) ) + if( (wc < LLFontFreetype::FIRST_CHAR) && (wc != LF) ) { clean_string[i] = LL_UNKNOWN_CHAR; } @@ -1969,10 +1388,12 @@ void LLTextEditor::pasteHelper(bool is_primary) } // Insert the new text into the existing text. - setCursorPos(mCursorPos + insert(mCursorPos, clean_string, FALSE)); + setCursorPos(mCursorPos + insert(mCursorPos, clean_string, FALSE, LLTextSegmentPtr())); deselect(); needsReflow(); + + onKeyStroke(); } @@ -1986,7 +1407,7 @@ void LLTextEditor::copyPrimary() } S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); S32 length = llabs( mSelectionStart - mSelectionEnd ); - gClipboard.copyFromPrimarySubstring(mWText, left_pos, length, mSourceID); + gClipboard.copyFromPrimarySubstring(getWText(), left_pos, length, mSourceID); } BOOL LLTextEditor::canPastePrimary() const @@ -2016,7 +1437,7 @@ BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask) if( mask & MASK_SHIFT ) { startSelection(); - mCursorPos = 0; + setCursorPos(0); mSelectionEnd = mCursorPos; } else @@ -2024,7 +1445,7 @@ BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask) // Ctrl-Home, Ctrl-Left, Ctrl-Right, Ctrl-Down // all move the cursor as if clicking, so should deselect. deselect(); - setCursorPos(0); + startOfDoc(); } break; @@ -2213,8 +1634,7 @@ BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return deleteSelection(FALSE); } - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); + S32 offset = getLineOffsetFromDocIndex(mCursorPos); S32 spaces_needed = SPACES_PER_TAB - (offset % SPACES_PER_TAB); for( S32 i=0; i < spaces_needed; i++ ) @@ -2237,7 +1657,7 @@ void LLTextEditor::unindentLineBeforeCloseBrace() { if( mCursorPos >= 1 ) { - const LLWString &text = mWText; + LLWString text = getWText(); if( ' ' == text[ mCursorPos - 1 ] ) { removeCharOrTab(); @@ -2253,42 +1673,95 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) BOOL return_key_hit = FALSE; BOOL text_may_have_changed = TRUE; - if ( gFocusMgr.getKeyboardFocus() == this ) + // 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 (KEY_F10 == key) + { + LLComboBox::Params cp; + cp.name = "combo box"; + cp.label = "my combo"; + cp.rect.width = 100; + cp.rect.height = 20; + cp.items.add().label = "item 1"; + cp.items.add().label = "item 2"; + cp.items.add().label = "item 3"; + + appendWidget(LLUICtrlFactory::create<LLComboBox>(cp), "combo", true, false); + } + if (KEY_F11 == key) { - // 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) + LLButton::Params bp; + bp.name = "text button"; + bp.label = "Click me"; + bp.rect.width = 100; + bp.rect.height = 20; + + appendWidget(LLUICtrlFactory::create<LLButton>(bp), "button", true, false); + } + */ + if (mReadOnly) + { + if(mScroller) { - return FALSE; + handled = mScroller->handleKeyHere( key, mask ); + } + else + { + handled = handleNavigationKey( key, mask ); } + } + else + { + // handle navigation keys ourself handled = handleNavigationKey( key, mask ); + } + + + if( handled ) + { + text_may_have_changed = FALSE; + } + + if( !handled ) + { + handled = handleSelectionKey( key, mask ); if( handled ) { - text_may_have_changed = FALSE; + selection_modified = TRUE; } - - if( !handled ) + } + + if( !handled ) + { + handled = handleControlKey( key, mask ); + if( handled ) { - handled = handleSelectionKey( key, mask ); - if( handled ) - { - selection_modified = TRUE; - } + selection_modified = TRUE; } - - if( !handled ) + } + + if( !handled && mHandleEditKeysDirectly ) + { + handled = handleEditKey( key, mask ); + if( handled ) { - handled = handleControlKey( key, mask ); - if( handled ) - { - selection_modified = TRUE; - } + selection_modified = TRUE; + text_may_have_changed = TRUE; } + } - if( !handled && mHandleEditKeysDirectly ) + // Handle most keys only if the text editor is writeable. + if( !mReadOnly ) + { + if( !handled ) { - handled = handleEditKey( key, mask ); + handled = handleSpecialKey( key, mask, &return_key_hit ); if( handled ) { selection_modified = TRUE; @@ -2296,41 +1769,29 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) } } - // Handle most keys only if the text editor is writeable. - if( !mReadOnly ) - { - if( !handled ) - { - handled = handleSpecialKey( key, mask, &return_key_hit ); - if( handled ) - { - selection_modified = TRUE; - text_may_have_changed = TRUE; - } - } + } + if( handled ) + { + resetCursorBlink(); + + // 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 ) + { + deselect(); } - if( handled ) + if(text_may_have_changed) { - resetKeystrokeTimer(); - - // 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 ) - { - deselect(); - } + needsReflow(); - if(text_may_have_changed) - { - needsReflow(); - } - needsScroll(); + onKeyStroke(); } + needsScroll(); } return handled; @@ -2346,34 +1807,33 @@ BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char) BOOL handled = FALSE; - if ( gFocusMgr.getKeyboardFocus() == this ) + // Handle most keys only if the text editor is writeable. + if( !mReadOnly ) { - // Handle most keys only if the text editor is writeable. - if( !mReadOnly ) + if( '}' == uni_char ) { - if( '}' == uni_char ) - { - unindentLineBeforeCloseBrace(); - } + unindentLineBeforeCloseBrace(); + } - // TODO: KLW Add auto show of tool tip on ( - addChar( uni_char ); + // TODO: KLW Add auto show of tool tip on ( + addChar( uni_char ); - // Keys that add characters temporarily hide the cursor - getWindow()->hideCursorUntilMouseMove(); + // Keys that add characters temporarily hide the cursor + getWindow()->hideCursorUntilMouseMove(); - handled = TRUE; - } + handled = TRUE; + } - if( handled ) - { - resetKeystrokeTimer(); + if( handled ) + { + resetCursorBlink(); - // Most keystrokes will make the selection box go away, but not all will. - deselect(); + // Most keystrokes will make the selection box go away, but not all will. + deselect(); - needsReflow(); - } + needsReflow(); + + onKeyStroke(); } return handled; @@ -2401,12 +1861,11 @@ void LLTextEditor::doDelete() { S32 i; S32 chars_to_remove = 1; - const LLWString &text = mWText; + LLWString text = getWText(); if( (text[ mCursorPos ] == ' ') && (mCursorPos + SPACES_PER_TAB < getLength()) ) { // Try to remove a full tab's worth of spaces - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); + S32 offset = getLineOffsetFromDocIndex(mCursorPos); chars_to_remove = SPACES_PER_TAB - (offset % SPACES_PER_TAB); if( chars_to_remove == 0 ) { @@ -2428,8 +1887,11 @@ void LLTextEditor::doDelete() setCursorPos(mCursorPos + 1); removeChar(); } + } + onKeyStroke(); + needsReflow(); } @@ -2474,6 +1936,8 @@ void LLTextEditor::undo() setCursorPos(pos); needsReflow(); + + onKeyStroke(); } BOOL LLTextEditor::canRedo() const @@ -2516,11 +1980,13 @@ void LLTextEditor::redo() setCursorPos(pos); needsReflow(); + + onKeyStroke(); } void LLTextEditor::onFocusReceived() { - LLUICtrl::onFocusReceived(); + LLTextBase::onFocusReceived(); updateAllowingLanguageInput(); } @@ -2543,344 +2009,75 @@ void LLTextEditor::onFocusLost() // Make sure cursor is shown again getWindow()->showCursorFromMouseMove(); - LLUICtrl::onFocusLost(); + 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; + bool read_only = !enabled; if (read_only != mReadOnly) { - mReadOnly = read_only; + //mReadOnly = read_only; + LLTextBase::setReadOnly(read_only); updateSegments(); updateAllowingLanguageInput(); } } -void LLTextEditor::drawBackground() +void LLTextEditor::showContextMenu(S32 x, S32 y) { - S32 left = 0; - S32 top = getRect().getHeight(); - S32 right = getRect().getWidth(); - S32 bottom = 0; - - LLColor4 bg_color = mReadOnly ? mReadOnlyBgColor - : gFocusMgr.getKeyboardFocus() == this ? mFocusBgColor : mWriteableBgColor; - if( mShowLineNumbers ) { - gl_rect_2d(left, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN, bottom, mReadOnlyBgColor ); // line number area always read-only - gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, right, bottom, bg_color); // body text area to the right of line numbers - gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN-1, bottom, LLColor4::grey3); // separator - } else { - gl_rect_2d(left, top, right, bottom, bg_color); // body text area - } - - LLView::draw(); -} - -// Draws the black box behind the selected text -void LLTextEditor::drawSelectionBackground() -{ - // Draw selection even if we don't have keyboard focus for search/replace - if( hasSelection() ) + if (!mContextMenu) { - const LLWString &text = mWText; - const S32 text_len = getLength(); - std::queue<S32> line_endings; - - S32 line_height = llround( mGLFont->getLineHeight() ); - - S32 selection_left = llmin( mSelectionStart, mSelectionEnd ); - S32 selection_right = llmax( mSelectionStart, mSelectionEnd ); - S32 selection_left_x = mTextRect.mLeft; - S32 selection_left_y = mTextRect.mTop - line_height; - S32 selection_right_x = mTextRect.mRight; - S32 selection_right_y = mTextRect.mBottom; - - BOOL selection_left_visible = FALSE; - BOOL selection_right_visible = FALSE; - - // Skip through the lines we aren't drawing. - S32 cur_line = mScrollbar->getDocPos(); - - S32 left_line_num = cur_line; - S32 num_lines = getLineCount(); - S32 right_line_num = num_lines - 1; - - S32 line_start = -1; - if (cur_line >= num_lines) - { - return; - } - - line_start = getLineStart(cur_line); - - S32 left_visible_pos = line_start; - S32 right_visible_pos = line_start; - - S32 text_y = mTextRect.mTop - line_height; - - // Find the coordinates of the selected area - while((cur_line < num_lines)) - { - S32 next_line = -1; - S32 line_end = text_len; - - if ((cur_line + 1) < num_lines) - { - next_line = getLineStart(cur_line + 1); - line_end = next_line; - - line_end = ( (line_end - line_start)==0 || text[next_line-1] == '\n' || text[next_line-1] == '\0' || text[next_line-1] == ' ' || text[next_line-1] == '\t' ) ? next_line-1 : next_line; - } - - const llwchar* line = text.c_str() + line_start; - - if( line_start <= selection_left && selection_left <= line_end ) - { - left_line_num = cur_line; - selection_left_visible = TRUE; - selection_left_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_left - line_start, mAllowEmbeddedItems); - selection_left_y = text_y; - } - if( line_start <= selection_right && selection_right <= line_end ) - { - right_line_num = cur_line; - selection_right_visible = TRUE; - selection_right_x = mTextRect.mLeft + mGLFont->getWidth(line, 0, selection_right - line_start, mAllowEmbeddedItems); - if (selection_right == line_end) - { - // add empty space for "newline" - //selection_right_x += mGLFont->getWidth("n"); - } - selection_right_y = text_y; - } - - // if selection spans end of current line... - if (selection_left <= line_end && line_end < selection_right && selection_left != selection_right) - { - // extend selection slightly beyond end of line - // to indicate selection of newline character (use "n" character to determine width) - const LLWString nstr(utf8str_to_wstring(std::string("n"))); - line_endings.push(mTextRect.mLeft + mGLFont->getWidth(line, 0, line_end - line_start, mAllowEmbeddedItems) + mGLFont->getWidth(nstr.c_str())); - } - - // move down one line - text_y -= line_height; - - right_visible_pos = line_end; - line_start = next_line; - cur_line++; - - if (selection_right_visible) - { - break; - } - } - - // Draw the selection box (we're using a box instead of reversing the colors on the selected text). - BOOL selection_visible = (left_visible_pos <= selection_right) && (selection_left <= right_visible_pos); - if( selection_visible ) - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - const LLColor4& color = mReadOnly ? mReadOnlyBgColor : mWriteableBgColor; - F32 alpha = hasFocus() ? 1.f : 0.5f; - gGL.color4f( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], alpha ); - S32 margin_offset = mShowLineNumbers ? UI_TEXTEDITOR_LINE_NUMBER_MARGIN : 0; - - if( selection_left_y == selection_right_y ) - { - // Draw from selection start to selection end - gl_rect_2d( selection_left_x + margin_offset, selection_left_y + line_height + 1, - selection_right_x + margin_offset, selection_right_y); - } - else - { - // Draw from selection start to the end of the first line - if( mTextRect.mRight == selection_left_x ) - { - selection_left_x -= CURSOR_THICKNESS; - } - - S32 line_end = line_endings.front(); - line_endings.pop(); - gl_rect_2d( selection_left_x + margin_offset, selection_left_y + line_height + 1, - line_end + margin_offset, selection_left_y ); - - S32 line_num = left_line_num + 1; - while(line_endings.size()) - { - S32 vert_offset = -(line_num - left_line_num) * line_height; - // Draw the block between the two lines - gl_rect_2d( mTextRect.mLeft + margin_offset, selection_left_y + vert_offset + line_height + 1, - line_endings.front() + margin_offset, selection_left_y + vert_offset); - line_endings.pop(); - line_num++; - } - - // Draw from the start of the last line to selection end - if( mTextRect.mLeft == selection_right_x ) - { - selection_right_x += CURSOR_THICKNESS; - } - gl_rect_2d( mTextRect.mLeft + margin_offset, selection_right_y + line_height + 1, - selection_right_x + margin_offset, selection_right_y ); - } - } + mContextMenu = LLUICtrlFactory::instance().createFromFile<LLContextMenu>("menu_text_editor.xml", + LLMenuGL::sMenuContainer, + LLMenuHolderGL::child_registry_t::instance()); } -} - -void LLTextEditor::drawCursor() -{ - if( gFocusMgr.getKeyboardFocus() == this - && gShowTextEditCursor && !mReadOnly) - { - const LLWString &text = mWText; - const S32 text_len = getLength(); - - // Skip through the lines we aren't drawing. - S32 cur_pos = mScrollbar->getDocPos(); - - S32 num_lines = getLineCount(); - if (cur_pos >= num_lines) - { - return; - } - S32 line_start = getLineStart(cur_pos); - - F32 line_height = mGLFont->getLineHeight(); - F32 text_y = (F32)(mTextRect.mTop) - line_height; - - F32 cursor_left = 0.f; - F32 next_char_left = 0.f; - F32 cursor_bottom = 0.f; - BOOL cursor_visible = FALSE; - - S32 line_end = 0; - // Determine if the cursor is visible and if so what its coordinates are. - while( (mTextRect.mBottom <= llround(text_y)) && (cur_pos < num_lines)) - { - line_end = text_len + 1; - S32 next_line = -1; - - if ((cur_pos + 1) < num_lines) - { - next_line = getLineStart(cur_pos + 1); - line_end = next_line - 1; - } - - const llwchar* line = text.c_str() + line_start; - - // Find the cursor and selection bounds - if( line_start <= mCursorPos && mCursorPos <= line_end ) - { - cursor_visible = TRUE; - next_char_left = (F32)mTextRect.mLeft + mGLFont->getWidthF32(line, 0, mCursorPos - line_start, mAllowEmbeddedItems ); - cursor_left = next_char_left - 1.f; - cursor_bottom = text_y; - break; - } - - // move down one line - text_y -= line_height; - line_start = next_line; - cur_pos++; - } - - if(mShowLineNumbers) - { - cursor_left += UI_TEXTEDITOR_LINE_NUMBER_MARGIN; - } - - // Draw the cursor - if( cursor_visible ) - { - // (Flash the cursor every half second starting a fixed time after the last keystroke) - F32 elapsed = mKeystrokeTimer.getElapsedTimeF32(); - if( (elapsed < CURSOR_FLASH_DELAY ) || (S32(elapsed * 2) & 1) ) - { - F32 cursor_top = cursor_bottom + line_height + 1.f; - F32 cursor_right = cursor_left + (F32)CURSOR_THICKNESS; - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) - { - cursor_left += CURSOR_THICKNESS; - const LLWString space(utf8str_to_wstring(std::string(" "))); - F32 spacew = mGLFont->getWidthF32(space.c_str()); - if (mCursorPos == line_end) - { - cursor_right = cursor_left + spacew; - } - else - { - F32 width = mGLFont->getWidthF32(text.c_str(), mCursorPos, 1, mAllowEmbeddedItems); - cursor_right = cursor_left + llmax(spacew, width); - } - } - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - gGL.color4fv( mCursorColor.mV ); - - gl_rect_2d(llfloor(cursor_left), llfloor(cursor_top), - llfloor(cursor_right), llfloor(cursor_bottom)); - - if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection() && text[mCursorPos] != '\n') - { - const LLTextSegment* segmentp = getSegmentAtOffset(mCursorPos); - LLColor4 text_color; - if (segmentp) - { - text_color = segmentp->getColor(); - } - else if (mReadOnly) - { - text_color = mReadOnlyFgColor; - } - else - { - text_color = mFgColor; - } - mGLFont->render(text, mCursorPos, next_char_left, cursor_bottom + line_height, - LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], 1.f), - LLFontGL::LEFT, LLFontGL::TOP, - LLFontGL::NORMAL, - 1); - } - - // Make sure the IME is in the right place - LLRect screen_pos = getScreenRect(); - LLCoordGL ime_pos( screen_pos.mLeft + llfloor(cursor_left), screen_pos.mBottom + llfloor(cursor_top) ); - ime_pos.mX = (S32) (ime_pos.mX * LLUI::sGLScaleFactor.mV[VX]); - ime_pos.mY = (S32) (ime_pos.mY * LLUI::sGLScaleFactor.mV[VY]); - getWindow()->setLanguageTextInput( ime_pos ); - } - } - } + S32 screen_x, screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + mContextMenu->show(screen_x, screen_y); } + void LLTextEditor::drawPreeditMarker() { + static LLUICachedControl<F32> preedit_marker_brightness ("UIPreeditMarkerBrightness", 0); + static LLUICachedControl<S32> preedit_marker_gap ("UIPreeditMarkerGap", 0); + static LLUICachedControl<S32> preedit_marker_position ("UIPreeditMarkerPosition", 0); + static LLUICachedControl<S32> preedit_marker_thickness ("UIPreeditMarkerThickness", 0); + static LLUICachedControl<F32> preedit_standout_brightness ("UIPreeditStandoutBrightness", 0); + static LLUICachedControl<S32> preedit_standout_gap ("UIPreeditStandoutGap", 0); + static LLUICachedControl<S32> preedit_standout_position ("UIPreeditStandoutPosition", 0); + static LLUICachedControl<S32> preedit_standout_thickness ("UIPreeditStandoutThickness", 0); + if (!hasPreeditString()) { return; } - const llwchar *text = mWText.c_str(); + const LLWString textString(getWText()); + const llwchar *text = textString.c_str(); const S32 text_len = getLength(); const S32 num_lines = getLineCount(); - S32 cur_line = mScrollbar->getDocPos(); + S32 cur_line = getFirstVisibleLine(); if (cur_line >= num_lines) { return; } - const S32 line_height = llround( mGLFont->getLineHeight() ); + const S32 line_height = llround( mDefaultFont->getLineHeight() ); S32 line_start = getLineStart(cur_line); - S32 line_y = mTextRect.mTop - line_height; - while((mTextRect.mBottom <= line_y) && (num_lines > 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; @@ -2912,36 +2109,36 @@ void LLTextEditor::drawPreeditMarker() continue; } - S32 preedit_left = mTextRect.mLeft; + S32 preedit_left = mVisibleTextRect.mLeft; if (left > line_start) { - preedit_left += mGLFont->getWidth(text, line_start, left - line_start, mAllowEmbeddedItems); + preedit_left += mDefaultFont->getWidth(text, line_start, left - line_start); } - S32 preedit_right = mTextRect.mLeft; + S32 preedit_right = mVisibleTextRect.mLeft; if (right < line_end) { - preedit_right += mGLFont->getWidth(text, line_start, right - line_start, mAllowEmbeddedItems); + preedit_right += mDefaultFont->getWidth(text, line_start, right - line_start); } else { - preedit_right += mGLFont->getWidth(text, line_start, line_end - line_start, mAllowEmbeddedItems); + preedit_right += mDefaultFont->getWidth(text, line_start, line_end - line_start); } if (mPreeditStandouts[i]) { - gl_rect_2d(preedit_left + PREEDIT_STANDOUT_GAP, - line_y + PREEDIT_STANDOUT_POSITION, - preedit_right - PREEDIT_STANDOUT_GAP - 1, - line_y + PREEDIT_STANDOUT_POSITION - PREEDIT_STANDOUT_THICKNESS, - (mCursorColor * PREEDIT_STANDOUT_BRIGHTNESS + mWriteableBgColor * (1 - PREEDIT_STANDOUT_BRIGHTNESS)).setAlpha(1.0f)); + gl_rect_2d(preedit_left + preedit_standout_gap, + line_y + preedit_standout_position, + preedit_right - preedit_standout_gap - 1, + line_y + preedit_standout_position - 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, - line_y + PREEDIT_MARKER_POSITION, - preedit_right - PREEDIT_MARKER_GAP - 1, - line_y + PREEDIT_MARKER_POSITION - PREEDIT_MARKER_THICKNESS, - (mCursorColor * PREEDIT_MARKER_BRIGHTNESS + mWriteableBgColor * (1 - PREEDIT_MARKER_BRIGHTNESS)).setAlpha(1.0f)); + gl_rect_2d(preedit_left + preedit_marker_gap, + line_y + preedit_marker_position, + preedit_right - preedit_marker_gap - 1, + line_y + preedit_marker_position - preedit_marker_thickness, + (mCursorColor.get() * preedit_marker_brightness + mWriteableBgColor.get() * (1 - preedit_marker_brightness)).setAlpha(1.0f)); } } } @@ -2954,292 +2151,85 @@ void LLTextEditor::drawPreeditMarker() } -void LLTextEditor::drawText() +void LLTextEditor::drawLineNumbers() { - const LLWString &text = mWText; - const S32 text_len = getLength(); - if( text_len <= 0 ) return; - 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 ); - } - LLGLSUIDefault gls_ui; - - // There are several concepts that are important for understanding the following drawing code. - // The document is logically a sequence of characters (stored in a LLWString). - // Variables below with "start" or "end" in their names refer to positions or offsets into it. - // Next there are two kinds of "line" variables to understand. Newline characters in the - // character sequence represent logical lines. These are what get numbered and so variables - // representing this kind of line have "num" in their names. - // The others represent line fragments or displayed lines which the scrollbar deals with. - // When the "show line numbers" property is turned on, we draw line numbers to the left of the - // beginning of each logical line and not in front of wrapped "continuation" display lines. -MG - - S32 cur_line = mScrollbar->getDocPos(); // scrollbar counts each wrap as a new line. + LLRect scrolled_view_rect = getVisibleDocumentRect(); + LLRect content_rect = getVisibleTextRect(); + LLLocalClipRect clip(content_rect); + S32 first_line = getFirstVisibleLine(); S32 num_lines = getLineCount(); - if (cur_line >= num_lines) return; - S32 line_start = getLineStart(cur_line); - S32 prev_start = getLineStart(cur_line-1); - S32 cur_line_num = getLineForPosition(line_start); // doesn't count wraps. i.e. only counts newlines. - S32 prev_line_num = getLineForPosition(prev_start); - BOOL cur_line_is_continuation = cur_line_num > 0 && cur_line_num == prev_line_num; - BOOL line_wraps = FALSE; - - LLTextSegment t(line_start); - segment_list_t::iterator seg_iter; - seg_iter = std::upper_bound(mSegments.begin(), mSegments.end(), &t, LLTextSegment::compare()); - if (seg_iter == mSegments.end() || (*seg_iter)->getStart() > line_start) --seg_iter; - LLTextSegment* cur_segment = *seg_iter; + if (first_line >= num_lines) + { + return; + } - S32 line_height = llround( mGLFont->getLineHeight() ); - F32 text_y = (F32)(mTextRect.mTop - line_height); - while((mTextRect.mBottom <= text_y) && (cur_line < num_lines)) + S32 cursor_line = mLineInfoList[getLineNumFromDocIndex(mCursorPos)].mLineNum; + + if (mShowLineNumbers) { - S32 next_start = -1; - S32 line_end = text_len; + S32 left = 0; + S32 top = getRect().getHeight(); + S32 bottom = 0; - if ((cur_line + 1) < num_lines) - { - next_start = getLineStart(cur_line + 1); - line_end = next_start; - } - line_wraps = text[line_end-1] != '\n'; - if ( ! line_wraps ) - { - --line_end; // don't attempt to draw the newline char. - } - - F32 text_start = (F32)mTextRect.mLeft; - F32 text_x = text_start + (mShowLineNumbers ? UI_TEXTEDITOR_LINE_NUMBER_MARGIN : 0); - - // draw the line numbers - if( mShowLineNumbers && !cur_line_is_continuation) - { - const LLFontGL *num_font = LLFontGL::getFontMonospace(); - F32 y_top = text_y + ((F32)llround(num_font->getLineHeight()) / 2); - const LLWString ltext = utf8str_to_wstring(llformat("%*d", UI_TEXTEDITOR_LINE_NUMBER_DIGITS, cur_line_num )); - BOOL is_cur_line = getCurrentLine() == cur_line_num; - const U8 style = is_cur_line ? LLFontGL::BOLD : LLFontGL::NORMAL; - const LLColor4 fg_color = is_cur_line ? mCursorColor : mReadOnlyFgColor; - num_font->render( - ltext, // string to draw - 0, // begin offset - 3., // x - y_top, // y - fg_color, - LLFontGL::LEFT, // horizontal alignment - LLFontGL::VCENTER, // vertical alignment - style, - S32_MAX, // max chars - UI_TEXTEDITOR_LINE_NUMBER_MARGIN); // max pixels - } + gl_rect_2d(left, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN, bottom, mReadOnlyBgColor.get() ); // line number area always read-only + gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN-1, bottom, LLColor4::grey3); // separator - S32 seg_start = line_start; - while( seg_start < line_end ) + S32 last_line_num = -1; + + for (S32 cur_line = first_line; cur_line < num_lines; cur_line++) { - while( cur_segment->getEnd() <= seg_start ) + line_info& line = mLineInfoList[cur_line]; + + if ((line.mRect.mTop - scrolled_view_rect.mBottom) < mVisibleTextRect.mBottom) { - seg_iter++; - if (seg_iter == mSegments.end()) - { - llwarns << "Ran off the segmentation end!" << llendl; - return; - } - cur_segment = *seg_iter; + break; } - - // Draw a segment within the line - S32 clipped_end = llmin( line_end, cur_segment->getEnd() ); - S32 clipped_len = clipped_end - seg_start; - if( clipped_len > 0 ) - { - LLStyleSP style = cur_segment->getStyle(); - if ( style->isImage() && (cur_segment->getStart() >= seg_start) && (cur_segment->getStart() <= clipped_end)) - { - S32 style_image_height = style->mImageHeight; - S32 style_image_width = style->mImageWidth; - LLUIImagePtr image = style->getImage(); - image->draw(llround(text_x), llround(text_y)+line_height-style_image_height, - style_image_width, style_image_height); - } - if (cur_segment == mHoverSegment && style->getIsEmbeddedItem()) - { - style->mUnderline = TRUE; - } - - S32 left_pos = llmin( mSelectionStart, mSelectionEnd ); - - if ( (mParseHTML) && (left_pos > seg_start) && (left_pos < clipped_end) && mIsSelecting && (mSelectionStart == mSelectionEnd) ) - { - mHTML = style->getLinkHREF(); - } - - drawClippedSegment( text, seg_start, clipped_end, text_x, text_y, selection_left, selection_right, style, &text_x ); - - if( text_x == text_start && mShowLineNumbers ) - { - text_x += UI_TEXTEDITOR_LINE_NUMBER_MARGIN; - } - - // Note: text_x is incremented by drawClippedSegment() - seg_start += clipped_len; + S32 line_bottom = line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom; + // draw the line numbers + if(line.mLineNum != last_line_num && line.mRect.mTop <= scrolled_view_rect.mTop) + { + const LLFontGL *num_font = LLFontGL::getFontMonospace(); + const LLWString ltext = utf8str_to_wstring(llformat("%d", line.mLineNum )); + BOOL is_cur_line = cursor_line == line.mLineNum; + const U8 style = is_cur_line ? LLFontGL::BOLD : LLFontGL::NORMAL; + const LLColor4 fg_color = is_cur_line ? mCursorColor : mReadOnlyFgColor; + num_font->render( + ltext, // string to draw + 0, // begin offset + UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 2, // x + line_bottom, // y + fg_color, + LLFontGL::RIGHT, // horizontal alignment + LLFontGL::BOTTOM, // vertical alignment + style, + LLFontGL::NO_SHADOW, + S32_MAX, // max chars + UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 2); // max pixels + last_line_num = line.mLineNum; } } - - // move down one line - text_y -= (F32)line_height; - - if( line_wraps ) - { - cur_line_num--; - } - cur_line_is_continuation = line_wraps; // so as to not not number the continuation lines - - line_start = next_start; - cur_line++; - cur_line_num++; } } -// Draws a single text segment, reversing the color for selection if needed. -void LLTextEditor::drawClippedSegment(const LLWString &text, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyleSP& style, F32* right_x ) -{ - if (!style->isVisible()) - { - return; - } - - const LLFontGL* font = mGLFont; - - LLColor4 color = style->getColor(); - - if ( style->getFontString()[0] ) - { - font = LLResMgr::getInstance()->getRes(style->getFontID()); - } - - U8 font_flags = LLFontGL::NORMAL; - - if (style->mBold) - { - font_flags |= LLFontGL::BOLD; - } - if (style->mItalic) - { - font_flags |= LLFontGL::ITALIC; - } - if (style->mUnderline) - { - font_flags |= LLFontGL::UNDERLINE; - } - - if (style->getIsEmbeddedItem()) - { - if (mReadOnly) - { - color = LLUI::sColorsGroup->getColor("TextEmbeddedItemReadOnlyColor"); - } - else - { - color = LLUI::sColorsGroup->getColor("TextEmbeddedItemColor"); - } - } - - F32 y_top = y + (F32)llround(font->getLineHeight()); - - if( selection_left > seg_start ) - { - // Draw normally - S32 start = seg_start; - S32 end = llmin( selection_left, seg_end ); - S32 length = end - start; - font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems); - } - x = *right_x; - - if( (selection_left < seg_end) && (selection_right > seg_start) ) - { - // Draw reversed - S32 start = llmax( selection_left, seg_start ); - S32 end = llmin( selection_right, seg_end ); - S32 length = end - start; - - font->render(text, start, x, y_top, - LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ), - LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems); - } - x = *right_x; - if( selection_right < seg_end ) - { - // Draw normally - S32 start = llmax( selection_right, seg_start ); - S32 end = seg_end; - S32 length = end - start; - font->render(text, start, x, y_top, color, LLFontGL::LEFT, LLFontGL::TOP, font_flags, length, S32_MAX, right_x, mAllowEmbeddedItems); - } - } - - void LLTextEditor::draw() { - // do on-demand reflow - if (mReflowNeeded) - { - updateLineStartList(); - mReflowNeeded = FALSE; - } - - // then update scroll position, as cursor may have moved - if (mScrollNeeded) - { - updateScrollFromCursor(); - mScrollNeeded = FALSE; - } - { - LLLocalClipRect clip(LLRect(0, getRect().getHeight(), getRect().getWidth() - (mScrollbar->getVisible() ? SCROLLBAR_SIZE : 0), 0)); - - bindEmbeddedChars(mGLFont); - - drawBackground(); - drawSelectionBackground(); - drawPreeditMarker(); - drawText(); - drawCursor(); - - unbindEmbeddedChars(mGLFont); - - //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( gFocusMgr.getKeyboardFocus() == this);// && !mReadOnly); + // 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); + drawPreeditMarker(); } - - LLView::draw(); // Draw children (scrollbar and border) - - // remember if we are supposed to be at the bottom of the buffer - mScrolledToBottom = isScrolledToBottom(); -} + LLTextBase::draw(); + drawLineNumbers(); -void LLTextEditor::onTabInto() -{ - // selecting all on tabInto causes users to hit tab twice and replace their text with a tab character - // theoretically, one could selectAll if mTabsToNextField is true, but we couldn't think of a use case - // where you'd want to select all anyway - // preserve insertion point when returning to the editor - //selectAll(); -} - -// virtual -void LLTextEditor::clear() -{ - setText(LLStringUtil::null); + //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 @@ -3257,7 +2247,7 @@ void LLTextEditor::setFocus( BOOL new_state ) getWindow()->allowLanguageTextInput(this, FALSE); } - LLUICtrl::setFocus( new_state ); + LLTextBase::setFocus( new_state ); if( new_state ) { @@ -3265,7 +2255,7 @@ void LLTextEditor::setFocus( BOOL new_state ) gEditMenuHandler = this; // Don't start the cursor flashing right away - resetKeystrokeTimer(); + resetCursorBlink(); } else { @@ -3279,289 +2269,28 @@ void LLTextEditor::setFocus( BOOL new_state ) } } -// virtual -BOOL LLTextEditor::acceptsTextInput() const -{ - return !mReadOnly; -} - -// Given a line (from the start of the doc) and an offset into the line, find the offset (pos) into text. -S32 LLTextEditor::getPos( S32 line, S32 offset ) -{ - S32 line_start = getLineStart(line); - S32 next_start = getLineStart(line+1); - if (next_start == line_start) - { - next_start = getLength() + 1; - } - S32 line_length = next_start - line_start - 1; - line_length = llmax(line_length, 0); - return line_start + llmin( offset, line_length ); -} - - -void LLTextEditor::changePage( S32 delta ) -{ - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); - - // get desired x position to remember previous position - S32 desired_x_pixel = mDesiredXPixel; - - // allow one line overlap - S32 page_size = mScrollbar->getPageSize() - 1; - if( delta == -1 ) - { - line = llmax( line - page_size, 0); - setCursorPos(getPos( line, offset )); - mScrollbar->setDocPos( mScrollbar->getDocPos() - page_size ); - } - else - if( delta == 1 ) - { - setCursorPos(getPos( line + page_size, offset )); - mScrollbar->setDocPos( mScrollbar->getDocPos() + page_size ); - } - - // put desired position into remember-buffer after setCursorPos() - mDesiredXPixel = desired_x_pixel; - - if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) - { - mOnScrollEndCallback(mOnScrollEndData); - } -} - -void LLTextEditor::changeLine( S32 delta ) -{ - bindEmbeddedChars(mGLFont); - - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); - - S32 line_start = getLineStart(line); - - // set desired x position to remembered previous position - S32 desired_x_pixel = mDesiredXPixel; - // if remembered position was reset (thus -1), calculate new one here - if( desired_x_pixel == -1 ) - { - desired_x_pixel = mGLFont->getWidth(mWText.c_str(), line_start, offset, mAllowEmbeddedItems ); - } - - S32 new_line = 0; - if( (delta < 0) && (line > 0 ) ) - { - new_line = line - 1; - } - else - if( (delta > 0) && (line < (getLineCount() - 1)) ) - { - new_line = line + 1; - } - else - { - unbindEmbeddedChars(mGLFont); - return; - } - - S32 num_lines = getLineCount(); - S32 new_line_start = getLineStart(new_line); - S32 new_line_end = getLength(); - if (new_line + 1 < num_lines) - { - new_line_end = getLineStart(new_line + 1) - 1; - } - - S32 new_line_len = new_line_end - new_line_start; - - S32 new_offset; - new_offset = mGLFont->charFromPixelOffset(mWText.c_str(), new_line_start, - (F32)desired_x_pixel, - (F32)mTextRect.getWidth(), - new_line_len, - mAllowEmbeddedItems); - - setCursorPos (getPos( new_line, new_offset )); - - // put desired position into remember-buffer after setCursorPos() - mDesiredXPixel = desired_x_pixel; - unbindEmbeddedChars(mGLFont); -} - -BOOL LLTextEditor::isScrolledToTop() -{ - return mScrollbar->isAtBeginning(); -} - -BOOL LLTextEditor::isScrolledToBottom() -{ - return mScrollbar->isAtEnd(); -} - - -void LLTextEditor::startOfLine() -{ - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); - setCursorPos(mCursorPos - offset); -} - - // public void LLTextEditor::setCursorAndScrollToEnd() { deselect(); endOfDoc(); - needsScroll(); -} - -void LLTextEditor::getLineAndColumnForPosition( S32 position, S32* line, S32* col, BOOL include_wordwrap ) -{ - if( include_wordwrap ) - { - getLineAndOffset( mCursorPos, line, col ); - } - else - { - const LLWString &text = mWText; - S32 line_count = 0; - S32 line_start = 0; - S32 i; - for( i = 0; text[i] && (i < position); i++ ) - { - if( '\n' == text[i] ) - { - line_start = i + 1; - line_count++; - } - } - *line = line_count; - *col = i - line_start; - } } void LLTextEditor::getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wordwrap ) { - getLineAndColumnForPosition(mCursorPos, line, col, include_wordwrap); -} - -S32 LLTextEditor::getCurrentLine() -{ - return getLineForPosition(mCursorPos); -} - -S32 LLTextEditor::getLineForPosition(S32 position) -{ - S32 line, col; - getLineAndColumnForPosition(position, &line, &col, FALSE); - return line; -} - - -void LLTextEditor::endOfLine() -{ - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); - S32 num_lines = getLineCount(); - if (line + 1 >= num_lines) - { - setCursorPos(getLength()); - } - else - { - setCursorPos( getLineStart(line + 1) - 1 ); - } -} - -void LLTextEditor::endOfDoc() -{ - mScrollbar->setDocPos(mScrollbar->getDocPosMax()); - mScrolledToBottom = true; - - S32 len = getLength(); - if( len ) - { - setCursorPos(len); - } - if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) - { - mOnScrollEndCallback(mOnScrollEndData); - } -} - -// Sets the scrollbar from the cursor position -void LLTextEditor::updateScrollFromCursor() -{ - mScrollbar->setDocSize( getLineCount() ); - - if (mReadOnly) - { - // no cursor in read only mode - return; - } - - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); - - S32 page_size = mScrollbar->getPageSize(); - - if( line < mScrollbar->getDocPos() ) - { - // scroll so that the cursor is at the top of the page - mScrollbar->setDocPos( line ); - } - else if( line >= mScrollbar->getDocPos() + page_size - 1 ) - { - S32 new_pos = 0; - if( line < mScrollbar->getDocSize() - 1 ) - { - // scroll so that the cursor is one line above the bottom of the page, - new_pos = line - page_size + 1; - } - else - { - // if there is less than a page of text remaining, scroll so that the cursor is at the bottom - new_pos = mScrollbar->getDocPosMax(); - } - mScrollbar->setDocPos( new_pos ); - } - - // Check if we've scrolled to bottom for callback if asked for callback - if (mOnScrollEndCallback && mOnScrollEndData && (mScrollbar->getDocPos() == mScrollbar->getDocPosMax())) - { - mOnScrollEndCallback(mOnScrollEndData); - } -} - -void LLTextEditor::reshape(S32 width, S32 height, BOOL called_from_parent) -{ - LLView::reshape( width, height, called_from_parent ); - - // do this first after reshape, because other things depend on - // up-to-date mTextRect - updateTextRect(); - - needsReflow(); - - // propagate shape information to scrollbar - mScrollbar->setDocSize( getLineCount() ); - - S32 line_height = llround( mGLFont->getLineHeight() ); - S32 page_lines = mTextRect.getHeight() / line_height; - mScrollbar->setPageSize( page_lines ); + *line = getLineNumFromDocIndex(mCursorPos, include_wordwrap); + *col = getLineOffsetFromDocIndex(mCursorPos, include_wordwrap); } void LLTextEditor::autoIndent() { // Count the number of spaces in the current line - S32 line, offset; - getLineAndOffset( mCursorPos, &line, &offset ); + S32 line = getLineNumFromDocIndex(mCursorPos); S32 line_start = getLineStart(line); S32 space_count = 0; S32 i; - const LLWString &text = mWText; + LLWString text = getWText(); while( ' ' == text[line_start] ) { space_count++; @@ -3594,129 +2323,16 @@ void LLTextEditor::insertText(const std::string &new_text) deleteSelection(TRUE); } - setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE )); + setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE, LLTextSegmentPtr() )); needsReflow(); setEnabled( enabled ); } - -void LLTextEditor::appendColoredText(const std::string &new_text, - bool allow_undo, - bool prepend_newline, - const LLColor4 &color, - const std::string& font_name) -{ - LLColor4 lcolor=color; - if (mParseHighlights) - { - LLTextParser* highlight = LLTextParser::getInstance(); - highlight->parseFullLineHighlights(new_text, &lcolor); - } - - LLStyleSP style(new LLStyle); - style->setVisible(true); - style->setColor(lcolor); - style->setFontName(font_name); - appendStyledText(new_text, allow_undo, prepend_newline, style); -} - -void LLTextEditor::appendStyledText(const std::string &new_text, - bool allow_undo, - bool prepend_newline, - LLStyleSP stylep) -{ - S32 part = (S32)LLTextParser::WHOLE; - if(mParseHTML) - { - - S32 start=0,end=0; - std::string text = new_text; - while ( findHTML(text, &start, &end) ) - { - LLStyleSP html(new LLStyle); - html->setVisible(true); - html->setColor(mLinkColor); - if (stylep) - { - html->setFontName(stylep->getFontString()); - } - html->mUnderline = TRUE; - - 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); - appendHighlightedText(subtext,allow_undo, prepend_newline, part, stylep); - } - - html->setLinkHREF(text.substr(start,end-start)); - appendText(text.substr(start, end-start),allow_undo, prepend_newline, html); - 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()) appendHighlightedText(text,allow_undo, prepend_newline, part, stylep); - } - else - { - appendHighlightedText(new_text, allow_undo, prepend_newline, part, stylep); - } -} - -void LLTextEditor::appendHighlightedText(const std::string &new_text, - bool allow_undo, - bool prepend_newline, - S32 highlight_part, - LLStyleSP stylep) -{ - if (mParseHighlights) - { - LLTextParser* highlight = LLTextParser::getInstance(); - - if (highlight && stylep) - { - LLSD pieces = highlight->parsePartialLineHighlights(new_text, stylep->getColor(), highlight_part); - bool lprepend=prepend_newline; - for (S32 i=0;i<pieces.size();i++) - { - LLSD color_llsd = pieces[i]["color"]; - LLColor4 lcolor; - lcolor.setValue(color_llsd); - LLStyleSP lstylep(new LLStyle(*stylep)); - lstylep->setColor(lcolor); - if (i != 0 && (pieces.size() > 1) ) lprepend=FALSE; - appendText((std::string)pieces[i]["text"], allow_undo, lprepend, lstylep); - } - return; - } - } - appendText(new_text, allow_undo, prepend_newline, stylep); -} - -// Appends new text to end of document -void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool prepend_newline, - const LLStyleSP stylep) +void LLTextEditor::appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo) { // Save old state - BOOL was_scrolled_to_bottom = (mScrollbar->getDocPos() == mScrollbar->getDocPosMax()); S32 selection_start = mSelectionStart; S32 selection_end = mSelectionEnd; BOOL was_selecting = mIsSelecting; @@ -3728,50 +2344,19 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool setCursorPos(old_length); - // Add carriage return if not first line - if (getLength() != 0 - && prepend_newline) - { - std::string final_text = "\n"; - final_text += new_text; - append(utf8str_to_wstring(final_text), TRUE); - } - else - { - append(utf8str_to_wstring(new_text), TRUE ); - } + 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); - if (stylep) - { - S32 segment_start = old_length; - S32 segment_end = getLength(); - LLTextSegment* segment = new LLTextSegment(stylep, segment_start, segment_end ); - mSegments.push_back(segment); - } - needsReflow(); // Set the cursor and scroll position - // Maintain the scroll position unless the scroll was at the end of the doc (in which - // case, move it to the new end of the doc) or unless the user was doing actively selecting - if( was_scrolled_to_bottom && !was_selecting ) - { - if( selection_start != selection_end ) - { - // maintain an existing non-active selection - mSelectionStart = selection_start; - mSelectionEnd = selection_end; - } - endOfDoc(); - } - else if( selection_start != selection_end ) + if( selection_start != selection_end ) { mSelectionStart = selection_start; mSelectionEnd = selection_end; - - - mIsSelecting = was_selecting; setCursorPos(cursor_pos); } @@ -3784,67 +2369,71 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool setCursorPos(cursor_pos); } - if( !allow_undo ) + 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(); - mCursorPos = llclamp(mCursorPos, 0, len); - mSelectionStart = llclamp(mSelectionStart, 0, len); - mSelectionEnd = llclamp(mSelectionEnd, 0, len); +void LLTextEditor::replaceUrlLabel(const std::string &url, + const std::string &label) +{ + // 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; - pruneSegments(); - - // pruneSegments will invalidate mLineStartList. - updateLineStartList(); - needsScroll(); -} + // 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; + const LLStyleSP style = seg->getStyle(); -/////////////////////////////////////////////////////////////////// -// Returns change in number of characters in mWText + // 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); -S32 LLTextEditor::insertStringNoUndo(const S32 pos, const LLWString &wstr) -{ - S32 old_len = mWText.length(); // length() returns character length - S32 insert_len = wstr.length(); + // if we find a link with our Url, then replace the label + if (style->isLink() && 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; + } - mWText.insert(pos, wstr); - mTextIsUpToDate = FALSE; + // work out the character offset for the next segment + seg_start = seg->getEnd(); + } - if ( truncate() ) + // update the editor with the new (wide) text string + if (modified) { - // The user's not getting everything he's hoping for - make_ui_sound("UISndBadKeystroke"); - insert_len = mWText.length() - old_len; + getViewModel()->setDisplay(text); + deselect(); + setCursorPos(mCursorPos); + needsReflow(); } - - return insert_len; } -S32 LLTextEditor::removeStringNoUndo(S32 pos, S32 length) +void LLTextEditor::removeTextFromEnd(S32 num_chars) { - mWText.erase(pos, length); - mTextIsUpToDate = FALSE; - return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length -} + if (num_chars <= 0) return; -S32 LLTextEditor::overwriteCharNoUndo(S32 pos, llwchar wc) -{ - if (pos > (S32)mWText.length()) - { - return 0; - } - mWText[pos] = wc; - mTextIsUpToDate = FALSE; - return 1; + 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); + + needsReflow(); + needsScroll(); } //---------------------------------------------------------------------------- @@ -3910,20 +2499,13 @@ BOOL LLTextEditor::tryToRevertToPristineState() } -void LLTextEditor::updateTextRect() -{ - mTextRect.setOriginAndSize( - UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD, - UI_TEXTEDITOR_BORDER, - getRect().getWidth() - SCROLLBAR_SIZE - 2 * (UI_TEXTEDITOR_BORDER + UI_TEXTEDITOR_H_PAD), - getRect().getHeight() - 2 * UI_TEXTEDITOR_BORDER - UI_TEXTEDITOR_V_PAD_TOP ); -} - +static LLFastTimer::DeclareTimer FTM_SYNTAX_HIGHLIGHTING("Syntax Highlighting"); void LLTextEditor::loadKeywords(const std::string& filename, const std::vector<std::string>& funcs, const std::vector<std::string>& tooltips, const LLColor3& color) { + LLFastTimer ft(FTM_SYNTAX_HIGHLIGHTING); if(mKeywords.loadFromFile(filename)) { S32 count = llmin(funcs.size(), tooltips.size()); @@ -3932,199 +2514,70 @@ void LLTextEditor::loadKeywords(const std::string& filename, std::string name = utf8str_trim(funcs[i]); mKeywords.addToken(LLKeywordToken::WORD, name, color, tooltips[i] ); } + segment_vec_t segment_list; + mKeywords.findSegments(&segment_list, getWText(), mDefaultColor.get(), *this); - mKeywords.findSegments( &mSegments, mWText, mDefaultColor ); - - llassert( mSegments.front()->getStart() == 0 ); - llassert( mSegments.back()->getEnd() == getLength() ); + mSegments.clear(); + segment_set_t::iterator insert_it = mSegments.begin(); + for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it) + { + insert_it = mSegments.insert(insert_it, *list_it); + } } } void LLTextEditor::updateSegments() { + LLFastTimer ft(FTM_SYNTAX_HIGHLIGHTING); if (mKeywords.isLoaded()) { // HACK: No non-ascii keywords for now - mKeywords.findSegments(&mSegments, mWText, mDefaultColor); - } - else if (mAllowEmbeddedItems) - { - findEmbeddedItemSegments(); - } + segment_vec_t segment_list; + mKeywords.findSegments(&segment_list, getWText(), mDefaultColor.get(), *this); - // Make sure we have at least one segment - if (mSegments.size() == 1 && mSegments[0]->getIsDefault()) - { - delete mSegments[0]; - mSegments.clear(); // create default segment - } - if (mSegments.empty()) - { - LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor ); - LLTextSegment* default_segment = new LLTextSegment( text_color, 0, mWText.length() ); - default_segment->setIsDefault(TRUE); - mSegments.push_back(default_segment); - } -} - -// Only effective if text was removed from the end of the editor -// *NOTE: Using this will invalidate references to mSegments from mLineStartList. -void LLTextEditor::pruneSegments() -{ - S32 len = mWText.length(); - // Find and update the first valid segment - segment_list_t::iterator iter = mSegments.end(); - while(iter != mSegments.begin()) - { - --iter; - LLTextSegment* seg = *iter; - if (seg->getStart() < len) + clearSegments(); + segment_set_t::iterator insert_it = mSegments.begin(); + for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it) { - // valid segment - if (seg->getEnd() > len) - { - seg->setEnd(len); - } - break; // done - } - } - if (iter != mSegments.end()) - { - // erase invalid segments - ++iter; - std::for_each(iter, mSegments.end(), DeletePointer()); - mSegments.erase(iter, mSegments.end()); - } - else - { - llwarns << "Tried to erase end of empty LLTextEditor" << llendl; - } -} - -void LLTextEditor::findEmbeddedItemSegments() -{ - mHoverSegment = NULL; - std::for_each(mSegments.begin(), mSegments.end(), DeletePointer()); - mSegments.clear(); - - BOOL found_embedded_items = FALSE; - const LLWString &text = mWText; - S32 idx = 0; - while( text[idx] ) - { - if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR ) - { - found_embedded_items = TRUE; - break; + insertSegment(*list_it); } - ++idx; - } - - if( !found_embedded_items ) - { - return; - } - - S32 text_len = text.length(); - - BOOL in_text = FALSE; - - LLColor4& text_color = ( mReadOnly ? mReadOnlyFgColor : mFgColor ); - - if( idx > 0 ) - { - mSegments.push_back( new LLTextSegment( text_color, 0, text_len ) ); // text - in_text = TRUE; } - LLStyleSP embedded_style(new LLStyle); - embedded_style->setIsEmbeddedItem( TRUE ); - - // Start with i just after the first embedded item - while ( text[idx] ) - { - if( text[idx] >= FIRST_EMBEDDED_CHAR && text[idx] <= LAST_EMBEDDED_CHAR ) - { - if( in_text ) - { - mSegments.back()->setEnd( idx ); - } - mSegments.push_back( new LLTextSegment( embedded_style, idx, idx + 1 ) ); // item - in_text = FALSE; - } - else - if( !in_text ) - { - mSegments.push_back( new LLTextSegment( text_color, idx, text_len ) ); // text - in_text = TRUE; - } - ++idx; - } + LLTextBase::updateSegments(); } -BOOL LLTextEditor::handleMouseUpOverSegment(S32 x, S32 y, MASK mask) +void LLTextEditor::updateLinkSegments() { - if ( hasMouseCapture() ) + LLWString wtext = getWText(); + + // update any segments that contain a link + for (segment_set_t::iterator it = mSegments.begin(); it != mSegments.end(); ++it) { - // This mouse up was part of a click. - // Regardless of where the cursor is, see if we recently touched a link - // and launch it if we did. - if (mParseHTML && mHTML.length() > 0) + LLTextSegment *segment = *it; + if (segment && segment->getStyle() && segment->getStyle()->isLink()) { - //Special handling for slurls - if ( (mSecondlifeURLcallback!=NULL) && !(*mSecondlifeURLcallback)(mHTML) ) + // 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. + LLStyleSP style = static_cast<LLStyleSP>(segment->getStyle()); + LLWString url_label = wtext.substr(segment->getStart(), segment->getEnd()-segment->getStart()); + if (LLUrlRegistry::instance().hasUrl(url_label)) { - if (mURLcallback!=NULL) (*mURLcallback)(mHTML); + std::string new_url = wstring_to_utf8str(url_label); + LLStringUtil::trim(new_url); + style->setLinkHREF(new_url); } - mHTML.clear(); } } - - return FALSE; } -// Finds the text segment (if any) at the give local screen position -const LLTextSegment* LLTextEditor::getSegmentAtLocalPos( S32 x, S32 y ) const -{ - // Find the cursor position at the requested local screen position - S32 offset = getCursorPosFromLocalCoord( x, y, FALSE ); - S32 idx = getSegmentIdxAtOffset(offset); - return idx >= 0 ? mSegments[idx] : NULL; -} - -const LLTextSegment* LLTextEditor::getSegmentAtOffset(S32 offset) const -{ - S32 idx = getSegmentIdxAtOffset(offset); - return idx >= 0 ? mSegments[idx] : NULL; -} - -S32 LLTextEditor::getSegmentIdxAtOffset(S32 offset) const -{ - if (mSegments.empty() || offset < 0 || offset >= getLength()) - { - return -1; - } - else - { - S32 segidx, segoff; - getSegmentAndOffset(offset, &segidx, &segoff); - return segidx; - } -} void LLTextEditor::onMouseCaptureLost() { endSelection(); } -void LLTextEditor::setOnScrollEndCallback(void (*callback)(void*), void* userdata) -{ - mOnScrollEndCallback = callback; - mOnScrollEndData = userdata; - mScrollbar->setOnScrollEndCallback(callback, userdata); -} - /////////////////////////////////////////////////////////////////// // Hack for Notecards @@ -4208,7 +2661,7 @@ BOOL LLTextEditor::importBuffer(const char* buffer, S32 length ) delete[] text; - setCursorPos(0); + startOfDoc(); deselect(); needsReflow(); @@ -4222,350 +2675,28 @@ BOOL LLTextEditor::exportBuffer(std::string &buffer ) outstream << "Linden text version 1\n"; outstream << "{\n"; - outstream << llformat("Text length %d\n", mWText.length() ); + outstream << llformat("Text length %d\n", getLength() ); outstream << getText(); outstream << "}\n"; return TRUE; } -////////////////////////////////////////////////////////////////////////// -// LLTextSegment - -LLTextSegment::LLTextSegment(S32 start) : - mStart(start), - mEnd(0), - mToken(NULL), - mIsDefault(FALSE) -{ -} -LLTextSegment::LLTextSegment( const LLStyleSP& style, S32 start, S32 end ) : - mStyle( style ), - mStart( start), - mEnd( end ), - mToken(NULL), - mIsDefault(FALSE) -{ -} -LLTextSegment::LLTextSegment( const LLColor4& color, S32 start, S32 end, BOOL is_visible) : - mStyle(new LLStyle(is_visible,color,LLStringUtil::null)), - mStart( start), - mEnd( end ), - mToken(NULL), - mIsDefault(FALSE) -{ -} -LLTextSegment::LLTextSegment( const LLColor4& color, S32 start, S32 end ) : - mStyle(new LLStyle(TRUE, color,LLStringUtil::null )), - mStart( start), - mEnd( end ), - mToken(NULL), - mIsDefault(FALSE) -{ -} -LLTextSegment::LLTextSegment( const LLColor3& color, S32 start, S32 end ) : - mStyle(new LLStyle(TRUE, color,LLStringUtil::null )), - mStart( start), - mEnd( end ), - mToken(NULL), - mIsDefault(FALSE) -{ -} - -BOOL LLTextSegment::getToolTip(std::string& msg) const -{ - if (mToken && !mToken->getToolTip().empty()) - { - const LLWString& wmsg = mToken->getToolTip(); - msg = wstring_to_utf8str(wmsg); - return TRUE; - } - return FALSE; -} - - - -void LLTextSegment::dump() const -{ - llinfos << "Segment [" << -// mColor.mV[VX] << ", " << -// mColor.mV[VY] << ", " << -// mColor.mV[VZ] << "]\t[" << - mStart << ", " << - getEnd() << "]" << - llendl; - -} - -// virtual -LLXMLNodePtr LLTextEditor::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLUICtrl::getXML(); - - // Attributes - - node->createChild("max_length", TRUE)->setIntValue(getMaxLength()); - node->createChild("embedded_items", TRUE)->setBoolValue(mAllowEmbeddedItems); - node->createChild("font", TRUE)->setStringValue(LLFontGL::nameFromFont(mGLFont)); - node->createChild("word_wrap", TRUE)->setBoolValue(mWordWrap); - node->createChild("hide_scrollbar", TRUE)->setBoolValue(mHideScrollbarForShortDocs); - - addColorXML(node, mCursorColor, "cursor_color", "TextCursorColor"); - addColorXML(node, mFgColor, "text_color", "TextFgColor"); - addColorXML(node, mDefaultColor, "text_default_color", "TextDefaultColor"); - addColorXML(node, mReadOnlyFgColor, "text_readonly_color", "TextFgReadOnlyColor"); - addColorXML(node, mReadOnlyBgColor, "bg_readonly_color", "TextBgReadOnlyColor"); - addColorXML(node, mWriteableBgColor, "bg_writeable_color", "TextBgWriteableColor"); - addColorXML(node, mFocusBgColor, "bg_focus_color", "TextBgFocusColor"); - - // Contents - node->setStringValue(getText()); - - return node; -} - -// static -LLView* LLTextEditor::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("text_editor"); - node->getAttributeString("name", name); - - LLRect rect; - createRect(node, rect, parent, LLRect()); - - U32 max_text_length = 255; - node->getAttributeU32("max_length", max_text_length); - - BOOL allow_embedded_items; - node->getAttributeBOOL("embedded_items", allow_embedded_items); - - LLFontGL* font = LLView::selectFont(node); - - std::string text = node->getTextContents().substr(0, max_text_length - 1); - - LLTextEditor* text_editor = new LLTextEditor(name, - rect, - max_text_length, - text, - font, - allow_embedded_items); - - text_editor->setTextEditorParameters(node); - - BOOL hide_scrollbar = FALSE; - node->getAttributeBOOL("hide_scrollbar",hide_scrollbar); - text_editor->setHideScrollbarForShortDocs(hide_scrollbar); - - text_editor->initFromXML(node, parent); - - return text_editor; -} - -void LLTextEditor::setTextEditorParameters(LLXMLNodePtr node) -{ - BOOL word_wrap = FALSE; - node->getAttributeBOOL("word_wrap", word_wrap); - setWordWrap(word_wrap); - - node->getAttributeBOOL("show_line_numbers", mShowLineNumbers); - - node->getAttributeBOOL("track_bottom", mTrackBottom); - - LLColor4 color; - if (LLUICtrlFactory::getAttributeColor(node,"cursor_color", color)) - { - setCursorColor(color); - } - if(LLUICtrlFactory::getAttributeColor(node,"text_color", color)) - { - setFgColor(color); - } - if(LLUICtrlFactory::getAttributeColor(node,"text_readonly_color", color)) - { - setReadOnlyFgColor(color); - } - if(LLUICtrlFactory::getAttributeColor(node,"bg_readonly_color", color)) - { - setReadOnlyBgColor(color); - } - if(LLUICtrlFactory::getAttributeColor(node,"bg_writeable_color", color)) - { - setWriteableBgColor(color); - } -} - -/////////////////////////////////////////////////////////////////// -// Refactoring note: We may eventually want to replace this with boost::regex or -// boost::tokenizer capabilities since we've already fixed at least two JIRAs -// concerning logic issues associated with this function. -S32 LLTextEditor::findHTMLToken(const std::string &line, S32 pos, BOOL reverse) const -{ - std::string openers=" \t\n('\"[{<>"; - std::string closers=" \t\n)'\"]}><;"; - - if (reverse) - { - for (int index=pos; index >= 0; index--) - { - char c = line[index]; - S32 m2 = openers.find(c); - if (m2 >= 0) - { - return index+1; - } - } - return 0; // index is -1, don't want to return that. - } - else - { - // adjust the search slightly, to allow matching parenthesis inside the URL - S32 paren_count = 0; - for (int index=pos; index<(S32)line.length(); index++) - { - char c = line[index]; - - if (c == '(') - { - paren_count++; - } - else if (c == ')') - { - if (paren_count <= 0) - { - return index; - } - else - { - paren_count--; - } - } - else - { - S32 m2 = closers.find(c); - if (m2 >= 0) - { - return index; - } - } - } - return line.length(); - } -} - -BOOL LLTextEditor::findHTML(const std::string &line, S32 *begin, S32 *end) const +void LLTextEditor::updateAllowingLanguageInput() { - - S32 m1,m2,m3; - BOOL matched = FALSE; - - m1=line.find("://",*end); - - if (m1 >= 0) //Easy match. - { - *begin = findHTMLToken(line, m1, TRUE); - *end = findHTMLToken(line, m1, FALSE); - - //Load_url only handles http and https so don't hilite ftp, smb, etc. - m2 = line.substr(*begin,(m1 - *begin)).find("http"); - m3 = line.substr(*begin,(m1 - *begin)).find("secondlife"); - - std::string badneighbors=".,<>?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\"; - - if (m2 >= 0 || m3>=0) - { - S32 bn = badneighbors.find(line.substr(m1+3,1)); - - if (bn < 0) - { - matched = TRUE; - } - } - } -/* matches things like secondlife.com (no http://) needs a whitelist to really be effective. - else //Harder match. - { - m1 = line.find(".",*end); - - if (m1 >= 0) - { - *end = findHTMLToken(line, m1, FALSE); - *begin = findHTMLToken(line, m1, TRUE); - - m1 = line.rfind(".",*end); - - if ( ( *end - m1 ) > 2 && m1 > *begin) - { - std::string badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`"; - m2 = badneighbors.find(line.substr(m1+1,1)); - m3 = badneighbors.find(line.substr(m1-1,1)); - if (m3<0 && m2<0) - { - matched = TRUE; - } - } - } - } - */ - - if (matched) - { - S32 strpos, strpos2; - - std::string url = line.substr(*begin,*end - *begin); - std::string slurlID = "slurl.com/secondlife/"; - strpos = url.find(slurlID); - - if (strpos < 0) - { - slurlID="secondlife://"; - strpos = url.find(slurlID); - } - - if (strpos < 0) - { - slurlID="sl://"; - strpos = url.find(slurlID); - } - - if (strpos >= 0) - { - strpos+=slurlID.length(); - - while ( ( strpos2=url.find("/",strpos) ) == -1 ) - { - if ((*end+2) >= (S32)line.length() || line.substr(*end,1) != " " ) - { - matched=FALSE; - break; - } - - strpos = (*end + 1) - *begin; - - *end = findHTMLToken(line,(*begin + strpos),FALSE); - url = line.substr(*begin,*end - *begin); - } - } - - } - - if (!matched) + LLWindow* window = getWindow(); + if (!window) { - *begin=*end=0; + // test app, no window available + return; } - return matched; -} - - - -void LLTextEditor::updateAllowingLanguageInput() -{ if (hasFocus() && !mReadOnly) { - getWindow()->allowLanguageTextInput(this, TRUE); + window->allowLanguageTextInput(this, TRUE); } else { - getWindow()->allowLanguageTextInput(this, FALSE); + window->allowLanguageTextInput(this, FALSE); } } @@ -4586,7 +2717,7 @@ void LLTextEditor::resetPreedit() deselect(); } - mCursorPos = mPreeditPositions.front(); + setCursorPos(mPreeditPositions.front()); removeStringNoUndo(mCursorPos, mPreeditPositions.back() - mCursorPos); insertStringNoUndo(mCursorPos, mPreeditOverwrittenWString); @@ -4626,7 +2757,7 @@ void LLTextEditor::updatePreedit(const LLWString &preedit_string, if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode()) { - mPreeditOverwrittenWString = getWSubString(insert_preedit_at, mPreeditWString.length()); + mPreeditOverwrittenWString = getWText().substr(insert_preedit_at, mPreeditWString.length()); removeStringNoUndo(insert_preedit_at, mPreeditWString.length()); } else @@ -4641,7 +2772,9 @@ void LLTextEditor::updatePreedit(const LLWString &preedit_string, setCursorPos(insert_preedit_at + caret_position); // Update of the preedit should be caused by some key strokes. - mKeystrokeTimer.reset(); + resetCursorBlink(); + + onKeyStroke(); } BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect *bounds, LLRect *control) const @@ -4649,7 +2782,7 @@ BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect if (control) { LLRect control_rect_screen; - localRectToScreen(mTextRect, &control_rect_screen); + localRectToScreen(mVisibleTextRect, &control_rect_screen); LLUI::screenRectToGL(control_rect_screen, control); } @@ -4670,7 +2803,7 @@ BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect return FALSE; } - const S32 first_visible_line = mScrollbar->getDocPos(); + const S32 first_visible_line = getFirstVisibleLine(); if (query < getLineStart(first_visible_line)) { return FALSE; @@ -4694,13 +2827,14 @@ BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect current_line++; } - const llwchar * const text = mWText.c_str(); - const S32 line_height = llround(mGLFont->getLineHeight()); + const LLWString textString(getWText()); + const llwchar * const text = textString.c_str(); + const S32 line_height = llround(mDefaultFont->getLineHeight()); if (coord) { - const S32 query_x = mTextRect.mLeft + mGLFont->getWidth(text, current_line_start, query - current_line_start, mAllowEmbeddedItems); - const S32 query_y = mTextRect.mTop - (current_line - first_visible_line) * line_height - line_height / 2; + const S32 query_x = mVisibleTextRect.mLeft + mDefaultFont->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::screenPointToGL(query_screen_x, query_screen_y, &coord->mX, &coord->mY); @@ -4708,23 +2842,23 @@ BOOL LLTextEditor::getPreeditLocation(S32 query_offset, LLCoordGL *coord, LLRect if (bounds) { - S32 preedit_left = mTextRect.mLeft; + S32 preedit_left = mVisibleTextRect.mLeft; if (preedit_left_position > current_line_start) { - preedit_left += mGLFont->getWidth(text, current_line_start, preedit_left_position - current_line_start, mAllowEmbeddedItems); + preedit_left += mDefaultFont->getWidth(text, current_line_start, preedit_left_position - current_line_start); } - S32 preedit_right = mTextRect.mLeft; + S32 preedit_right = mVisibleTextRect.mLeft; if (preedit_right_position < current_line_end) { - preedit_right += mGLFont->getWidth(text, current_line_start, preedit_right_position - current_line_start, mAllowEmbeddedItems); + preedit_right += mDefaultFont->getWidth(text, current_line_start, preedit_right_position - current_line_start); } else { - preedit_right += mGLFont->getWidth(text, current_line_start, current_line_end - current_line_start, mAllowEmbeddedItems); + preedit_right += mDefaultFont->getWidth(text, current_line_start, current_line_end - current_line_start); } - const S32 preedit_top = mTextRect.mTop - (current_line - first_visible_line) * line_height; + 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); @@ -4772,7 +2906,7 @@ void LLTextEditor::markAsPreedit(S32 position, S32 length) { llwarns << "markAsPreedit invoked when hasPreeditString is true." << llendl; } - mPreeditWString = LLWString( mWText, position, length ); + mPreeditWString = LLWString( getWText(), position, length ); if (length > 0) { mPreeditPositions.resize(2); @@ -4798,5 +2932,39 @@ void LLTextEditor::markAsPreedit(S32 position, S32 length) S32 LLTextEditor::getPreeditFontSize() const { - return llround(mGLFont->getLineHeight() * LLUI::sGLScaleFactor.mV[VY]); + return llround(mDefaultFont->getLineHeight() * LLUI::sGLScaleFactor.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); +} + +//virtual +void LLTextEditor::clear() +{ + getViewModel()->setDisplay(LLWStringUtil::null); + clearSegments(); } diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 9291e1c436..043dda8fa6 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -37,25 +37,50 @@ #include "llrect.h" #include "llkeywords.h" -#include "lluictrl.h" #include "llframetimer.h" #include "lldarray.h" #include "llstyle.h" #include "lleditmenuhandler.h" #include "lldarray.h" +#include "llviewborder.h" // for params +#include "lltextbase.h" #include "llpreeditor.h" +#include "llcontrol.h" class LLFontGL; class LLScrollbar; -class LLViewBorder; class LLKeywordToken; -class LLTextCmd; +class TextCmd; class LLUICtrlFactory; +class LLScrollContainer; -class LLTextEditor : public LLUICtrl, LLEditMenuHandler, protected LLPreeditor +class LLTextEditor : + public LLTextBase, + protected LLPreeditor { public: + struct Params : public LLInitParam::Block<Params, LLTextBase::Params> + { + Optional<std::string> default_text; + + Optional<bool> embedded_items, + ignore_tab, + handle_edit_keys_directly, + show_line_numbers, + commit_on_focus_lost; + + //colors + Optional<LLUIColor> default_color; + + Params(); + }; + + void initFromParams(const Params&); +protected: + LLTextEditor(const Params&); + friend class LLUICtrlFactory; +public: // // Constants // @@ -63,51 +88,38 @@ public: static const llwchar LAST_EMBEDDED_CHAR = 0x10ffff; static const S32 MAX_EMBEDDED_ITEMS = LAST_EMBEDDED_CHAR - FIRST_EMBEDDED_CHAR + 1; - LLTextEditor(const std::string& name, - const LLRect& rect, - S32 max_length, - const std::string &default_text, - const LLFontGL* glfont = NULL, - BOOL allow_embedded_items = FALSE); - virtual ~LLTextEditor(); - virtual LLXMLNodePtr getXML(bool save_children = true) const; - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, class LLUICtrlFactory *factory); - void setTextEditorParameters(LLXMLNodePtr node); - void setParseHTML(BOOL parsing) {mParseHTML=parsing;} + typedef boost::signals2::signal<void (LLTextEditor* caller)> keystroke_signal_t; + + void setKeystrokeCallback(const keystroke_signal_t::slot_type& callback); + void setParseHighlights(BOOL parsing) {mParseHighlights=parsing;} // 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 handleScrollWheel(S32 x, S32 y, S32 clicks); 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 BOOL handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect); - 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 onMouseCaptureLost(); // view overrides - virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); virtual void draw(); virtual void onFocusReceived(); virtual void onFocusLost(); + virtual void onCommit(); virtual void setEnabled(BOOL enabled); // uictrl overrides - virtual void onTabInto(); virtual void clear(); virtual void setFocus( BOOL b ); - virtual BOOL acceptsTextInput() const; - virtual BOOL isDirty() const { return( mLastCmd != NULL || (mPristineCmd && (mPristineCmd != mLastCmd)) ); } + virtual BOOL isDirty() const; // LLEditMenuHandler interface virtual void undo(); @@ -137,6 +149,7 @@ public: 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); + void replaceUrlLabel(const std::string &url, const std::string &label); // Undo/redo stack void blockUndo(); @@ -146,38 +159,27 @@ public: BOOL isPristine() const; BOOL allowsEmbeddedItems() const { return mAllowEmbeddedItems; } + // + // Text manipulation + // + // inserts text at cursor void insertText(const std::string &text); - // appends text at end - void appendText(const std::string &wtext, bool allow_undo, bool prepend_newline, - const LLStyleSP stylep = NULL); - - void appendColoredText(const std::string &wtext, bool allow_undo, - bool prepend_newline, - const LLColor4 &color, - const std::string& font_name = LLStringUtil::null); - // if styled text starts a line, you need to prepend a newline. - void appendStyledText(const std::string &new_text, bool allow_undo, - bool prepend_newline, - LLStyleSP stylep = NULL); - void appendHighlightedText(const std::string &new_text, bool allow_undo, - bool prepend_newline, S32 highlight_part, - LLStyleSP stylep); - + + 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 setCursor(S32 row, S32 column); - void setCursorPos(S32 offset); void setCursorAndScrollToEnd(); - void getLineAndColumnForPosition( S32 position, S32* line, S32* col, BOOL include_wordwrap ); void getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wordwrap ); - S32 getLineForPosition(S32 position); - S32 getCurrentLine(); void loadKeywords(const std::string& filename, const std::vector<std::string>& funcs, @@ -186,114 +188,31 @@ public: LLKeywords::keyword_iterator_t keywordsBegin() { return mKeywords.begin(); } LLKeywords::keyword_iterator_t keywordsEnd() { return mKeywords.end(); } - // Color support - void setCursorColor(const LLColor4& c) { mCursorColor = c; } - void setFgColor( const LLColor4& c ) { mFgColor = c; } - void setTextDefaultColor( const LLColor4& c ) { mDefaultColor = c; } - void setReadOnlyFgColor( const LLColor4& c ) { mReadOnlyFgColor = c; } - void setWriteableBgColor( const LLColor4& c ) { mWriteableBgColor = c; } - void setReadOnlyBgColor( const LLColor4& c ) { mReadOnlyBgColor = c; } - void setTrackColor( const LLColor4& color ); - void setThumbColor( const LLColor4& color ); - void setHighlightColor( const LLColor4& color ); - void setShadowColor( const LLColor4& color ); - // Hacky methods to make it into a word-wrapping, potentially scrolling, // read-only text box. - void setBorderVisible(BOOL b); - BOOL isBorderVisible() const; - void setTakesNonScrollClicks(BOOL b) { mTakesNonScrollClicks = b; } - void setHideScrollbarForShortDocs(BOOL b); - - void setWordWrap( BOOL b ); - void setTabsToNextField(BOOL b) { mTabsToNextField = b; } - BOOL tabsToNextField() const { return mTabsToNextField; } void setCommitOnFocusLost(BOOL b) { mCommitOnFocusLost = b; } // Hack to handle Notecards virtual BOOL importBuffer(const char* buffer, S32 length ); virtual BOOL exportBuffer(std::string& buffer ); - // If takes focus, will take keyboard focus on click. - void setTakesFocus(BOOL b) { mTakesFocus = b; } - - void setSourceID(const LLUUID& id) { mSourceID = id; } const LLUUID& getSourceID() const { return mSourceID; } - void setAcceptCallingCardNames(BOOL enable) { mAcceptCallingCardNames = enable; } - BOOL acceptsCallingCardNames() const { return mAcceptCallingCardNames; } - - void setHandleEditKeysDirectly( BOOL b ) { mHandleEditKeysDirectly = b; } - - // Callbacks - static void setLinkColor(LLColor4 color) { mLinkColor = color; } - static void setURLCallbacks(void (*callback1) (const std::string& url), - bool (*callback2) (const std::string& url), - bool (*callback3) (const std::string& url) ) - { mURLcallback = callback1; mSecondlifeURLcallback = callback2; mSecondlifeURLcallbackRightClick = callback3;} - - void setOnScrollEndCallback(void (*callback)(void*), void* userdata); - - // new methods - void setValue(const LLSD& value); - LLSD getValue() const; - - const std::string& getText() const; - - // Non-undoable - void setText(const LLStringExplicit &utf8str); - void setWText(const LLWString &wtext); - - // Returns byte length limit - S32 getMaxLength() const { return mMaxTextByteLength; } - - // Change cursor - void startOfLine(); - void endOfLine(); - void endOfDoc(); - BOOL isScrolledToTop(); - BOOL isScrolledToBottom(); - - // Getters - const LLWString& getWText() const { return mWText; } - llwchar getWChar(S32 pos) const { return mWText[pos]; } - LLWString getWSubString(S32 pos, S32 len) const { return mWText.substr(pos, len); } - - const LLTextSegment* getCurrentSegment() const { return getSegmentAtOffset(mCursorPos); } - const LLTextSegment* getPreviousSegment() const; - void getSelectedSegments(std::vector<const LLTextSegment*>& segments) const; - - static bool isPartOfWord(llwchar c) { return (c == '_') || LLStringOps::isAlnum((char)c); } + const LLTextSegmentPtr getPreviousSegment() const; + void getSelectedSegments(segment_vec_t& segments) const; protected: - // - // Methods - // - - S32 getLength() const { return mWText.length(); } - void getSegmentAndOffset( S32 startpos, S32* segidxp, S32* offsetp ) const; + void showContextMenu(S32 x, S32 y); void drawPreeditMarker(); - void updateLineStartList(S32 startpos = 0); - void updateScrollFromCursor(); - void updateTextRect(); - const LLRect& getTextRect() const { return mTextRect; } - void assignEmbedded(const std::string &s); - BOOL truncate(); // Returns true if truncation occurs void removeCharOrTab(); - void setCursorAtLocalPos(S32 x, S32 y, BOOL round); - S32 getCursorPosFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const; void indentSelectedLines( S32 spaces ); S32 indentLine( S32 pos, S32 spaces ); void unindentLineBeforeCloseBrace(); - S32 getSegmentIdxAtOffset(S32 offset) const; - const LLTextSegment* getSegmentAtLocalPos(S32 x, S32 y) const; - const LLTextSegment* getSegmentAtOffset(S32 offset) const; - void reportBadKeystroke() { make_ui_sound("UISndBadKeystroke"); } BOOL handleNavigationKey(const KEY key, const MASK mask); @@ -302,66 +221,22 @@ protected: BOOL handleControlKey(const KEY key, const MASK mask); BOOL handleEditKey(const KEY key, const MASK mask); - BOOL hasSelection() const { return (mSelectionStart !=mSelectionEnd); } BOOL selectionContainsLineBreaks(); - void startSelection(); - void endSelection(); void deleteSelection(BOOL transient_operation); S32 prevWordPos(S32 cursorPos) const; S32 nextWordPos(S32 cursorPos) const; - S32 getLineCount() const { return mLineStartList.size(); } - S32 getLineStart( S32 line ) const; - void getLineAndOffset(S32 pos, S32* linep, S32* offsetp) const; - S32 getPos(S32 line, S32 offset); - - void changePage(S32 delta); - void changeLine(S32 delta); - void autoIndent(); - void findEmbeddedItemSegments(); - - virtual BOOL handleMouseUpOverSegment(S32 x, S32 y, MASK mask); + 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; } - virtual void bindEmbeddedChars(const LLFontGL* font) const {} - virtual void unbindEmbeddedChars(const LLFontGL* font) const {} - S32 findHTMLToken(const std::string &line, S32 pos, BOOL reverse) const; - BOOL findHTML(const std::string &line, S32 *begin, S32 *end) const; - // 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 LLTextCmd - { - public: - LLTextCmd( S32 pos, BOOL group_with_next ) : mPos(pos), mGroupWithNext(group_with_next) {} - virtual ~LLTextCmd() {} - virtual BOOL execute(LLTextEditor* editor, S32* delta) = 0; - virtual S32 undo(LLTextEditor* editor) = 0; - virtual S32 redo(LLTextEditor* editor) = 0; - virtual BOOL canExtend(S32 pos) const { return FALSE; } - virtual void blockExtensions() {} - virtual BOOL extendAndExecute( LLTextEditor* 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(LLTextEditor* editor, S32 pos, const LLWString &wstr) { return editor->insertStringNoUndo( pos, wstr ); } - S32 remove(LLTextEditor* editor, S32 pos, S32 length) { return editor->removeStringNoUndo( pos, length ); } - S32 overwrite(LLTextEditor* editor, S32 pos, llwchar wc) { return editor->overwriteCharNoUndo(pos, wc); } - - S32 getPosition() const { return mPos; } - BOOL groupWithNext() const { return mGroupWithNext; } - - private: - const S32 mPos; - BOOL mGroupWithNext; - }; // Here's the method that takes and applies text commands. - S32 execute(LLTextCmd* cmd); + S32 execute(TextCmd* cmd); // Undoable operations void addChar(llwchar c); // at mCursorPos @@ -369,17 +244,9 @@ protected: S32 overwriteChar(S32 pos, llwchar wc); void removeChar(); S32 removeChar(S32 pos); - S32 insert(const S32 pos, const LLWString &wstr, const BOOL group_with_next_op); - S32 remove(const S32 pos, const S32 length, const BOOL group_with_next_op); - S32 append(const LLWString &wstr, const BOOL group_with_next_op); + 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); - // Direct operations - S32 insertStringNoUndo(S32 pos, const LLWString &wstr); // returns num of chars actually inserted - S32 removeStringNoUndo(S32 pos, S32 length); - S32 overwriteCharNoUndo(S32 pos, llwchar wc); - - void resetKeystrokeTimer() { mKeystrokeTimer.reset(); } - void updateAllowingLanguageInput(); BOOL hasPreeditString() const; @@ -392,6 +259,7 @@ protected: 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 // @@ -399,149 +267,60 @@ protected: // as possible behind protected accessor methods. // - // I-beam is just after the mCursorPos-th character. - S32 mCursorPos; - // Use these to determine if a click on an embedded item is a drag or not. S32 mMouseDownX; S32 mMouseDownY; - // Are we in the middle of a drag-select? To figure out if there is a current - // selection, call hasSelection(). - BOOL mIsSelecting; - S32 mSelectionStart; - S32 mSelectionEnd; - S32 mLastSelectionX; - S32 mLastSelectionY; - - BOOL mParseHTML; - BOOL mParseHighlights; - std::string mHTML; - - typedef std::vector<LLTextSegment *> segment_list_t; - segment_list_t mSegments; - const LLTextSegment* mHoverSegment; - - // Scrollbar data - class LLScrollbar* mScrollbar; - BOOL mHideScrollbarForShortDocs; - BOOL mTakesNonScrollClicks; - void (*mOnScrollEndCallback)(void*); - void *mOnScrollEndData; - LLWString mPreeditWString; LLWString mPreeditOverwrittenWString; std::vector<S32> mPreeditPositions; std::vector<BOOL> mPreeditStandouts; - -private: +protected: + LLUIColor mDefaultColor; + + BOOL mShowLineNumbers; + + /*virtual*/ void updateSegments(); + void updateLinkSegments(); + +private: // // Methods // - void pasteHelper(bool is_primary); + void pasteHelper(bool is_primary); - void updateSegments(); - void pruneSegments(); + void drawLineNumbers(); - void drawBackground(); - void drawSelectionBackground(); - void drawCursor(); - void drawText(); - void drawClippedSegment(const LLWString &wtext, S32 seg_start, S32 seg_end, F32 x, F32 y, S32 selection_left, S32 selection_right, const LLStyleSP& color, F32* right_x); - - void needsReflow() - { - mReflowNeeded = TRUE; - // cursor might have moved, need to scroll - mScrollNeeded = TRUE; - } - void needsScroll() { mScrollNeeded = TRUE; } + void onKeyStroke(); // // Data // LLKeywords mKeywords; - static LLColor4 mLinkColor; - static void (*mURLcallback) (const std::string& url); - static bool (*mSecondlifeURLcallback) (const std::string& url); - static bool (*mSecondlifeURLcallbackRightClick) (const std::string& url); - - // Concrete LLTextCmd sub-classes used by the LLTextEditor base class - class LLTextCmdInsert; - class LLTextCmdAddChar; - class LLTextCmdOverwriteChar; - class LLTextCmdRemove; - - LLWString mWText; - mutable std::string mUTF8Text; - mutable BOOL mTextIsUpToDate; - - S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes - const LLFontGL* mGLFont; + // Concrete TextCmd sub-classes used by the LLTextEditor base class + class TextCmdInsert; + class TextCmdAddChar; + class TextCmdOverwriteChar; + class TextCmdRemove; class LLViewBorder* mBorder; BOOL mBaseDocIsPristine; - LLTextCmd* mPristineCmd; + TextCmd* mPristineCmd; - LLTextCmd* mLastCmd; + TextCmd* mLastCmd; - typedef std::deque<LLTextCmd*> undo_stack_t; + typedef std::deque<TextCmd*> undo_stack_t; undo_stack_t mUndoStack; - S32 mDesiredXPixel; // X pixel position where the user wants the cursor to be - LLRect mTextRect; // The rect in which text is drawn. Excludes borders. - // 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 segment, S32 offset) : mSegment(segment), mOffset(offset) {} - S32 mSegment; - S32 mOffset; - }; - struct line_info_compare - { - bool operator()(const line_info& a, const line_info& b) const - { - if (a.mSegment < b.mSegment) - return true; - else if (a.mSegment > b.mSegment) - return false; - else - return a.mOffset < b.mOffset; - } - }; - typedef std::vector<line_info> line_list_t; - line_list_t mLineStartList; - BOOL mReflowNeeded; - BOOL mScrollNeeded; - - LLFrameTimer mKeystrokeTimer; - - LLColor4 mCursorColor; - - LLColor4 mFgColor; - LLColor4 mDefaultColor; - LLColor4 mReadOnlyFgColor; - LLColor4 mWriteableBgColor; - LLColor4 mReadOnlyBgColor; - LLColor4 mFocusBgColor; - - BOOL mReadOnly; - BOOL mWordWrap; - BOOL mShowLineNumbers; - BOOL mTabsToNextField; // if true, tab moves focus to next field, else inserts spaces BOOL mCommitOnFocusLost; BOOL mTakesFocus; - BOOL mTrackBottom; // if true, keeps scroll position at bottom during resize - BOOL mScrolledToBottom; BOOL mAllowEmbeddedItems; - BOOL mAcceptCallingCardNames; - LLUUID mSourceID; // If true, the standard edit keys (Ctrl-X, Delete, etc,) are handled here @@ -549,50 +328,16 @@ private: BOOL mHandleEditKeysDirectly; LLCoordGL mLastIMEPosition; // Last position of the IME editor -}; // end class LLTextEditor - + keystroke_signal_t mKeystrokeSignal; -class LLTextSegment -{ -public: - // for creating a compare value - LLTextSegment(S32 start); - LLTextSegment( const LLStyleSP& style, S32 start, S32 end ); - LLTextSegment( const LLColor4& color, S32 start, S32 end, BOOL is_visible); - LLTextSegment( const LLColor4& color, S32 start, S32 end ); - LLTextSegment( const LLColor3& color, S32 start, S32 end ); - - S32 getStart() const { return mStart; } - S32 getEnd() const { return mEnd; } - void setEnd( S32 end ) { mEnd = end; } - const LLColor4& getColor() const { return mStyle->getColor(); } - void setColor(const LLColor4 &color) { mStyle->setColor(color); } - const LLStyleSP& getStyle() const { return mStyle; } - void setStyle(const LLStyleSP &style) { mStyle = style; } - void setIsDefault(BOOL b) { mIsDefault = b; } - BOOL getIsDefault() const { return mIsDefault; } - void setToken( LLKeywordToken* token ) { mToken = token; } - LLKeywordToken* getToken() const { return mToken; } - BOOL getToolTip( std::string& msg ) const; - - void dump() const; - - struct compare - { - bool operator()(const LLTextSegment* a, const LLTextSegment* b) const - { - return a->mStart < b->mStart; - } - }; - -private: - LLStyleSP mStyle; - S32 mStart; - S32 mEnd; - LLKeywordToken* mToken; - BOOL mIsDefault; -}; + LLContextMenu* mContextMenu; +}; // end class LLTextEditor +// Build time optimization, generate once in .cpp file +#ifndef LLTEXTEDITOR_CPP +extern template class LLTextEditor* LLView::getChild<class LLTextEditor>( + const std::string& name, BOOL recurse) const; +#endif #endif // LL_TEXTEDITOR_ diff --git a/indra/llui/lltextparser.cpp b/indra/llui/lltextparser.cpp index 227d24a865..76a39e3094 100644 --- a/indra/llui/lltextparser.cpp +++ b/indra/llui/lltextparser.cpp @@ -1,10 +1,9 @@ /** - * @file lltexteditor.cpp - * @brief LLTextEditor base class + * @file lltextparser.cpp * * $LicenseInfo:firstyear=2001&license=viewergpl$ * - * Copyright (c) 2001-2007, Linden Research, Inc. + * Copyright (c) 2001-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -12,12 +11,13 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlife.com/developers/opensource/gplv2 + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at http://secondlife.com/developers/opensource/flossexception + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, @@ -31,6 +31,8 @@ #include "linden_common.h" +#include "lltextparser.h" + #include "llsd.h" #include "llsdserialize.h" #include "llerror.h" @@ -39,23 +41,13 @@ #include "message.h" #include "llmath.h" #include "v4color.h" -#include "audioengine.h" -#include "llwindow.h" #include "lldir.h" -#include "lltextparser.h" -//#include "lltexttospeech.h" - // Routines used for parsing text for TextParsers and html LLTextParser* LLTextParser::sInstance = NULL; // -// Constants -// -const F32 SOUND_GAIN = 1.0f; - -// // Member Functions // @@ -75,38 +67,7 @@ LLTextParser* LLTextParser::getInstance() return sInstance; } -void LLTextParser::triggerAlerts(LLUUID agent_id, LLVector3d position, std::string text, LLWindow* viewer_window) -{ -// bool spoken=FALSE; - for (S32 i=0;i<mHighlights.size();i++) - { - if (findPattern(text,mHighlights[i]) >= 0 ) - { - if(gAudiop) - { - if ((std::string)mHighlights[i]["sound_lluuid"] != LLUUID::null.asString()) - { - gAudiop->triggerSound(mHighlights[i]["sound_lluuid"].asUUID(), agent_id, SOUND_GAIN, LLAudioEngine::AUDIO_TYPE_UI, position); - } -/* - if (!spoken) - { - LLTextToSpeech* text_to_speech = NULL; - text_to_speech = LLTextToSpeech::getInstance(); - spoken = text_to_speech->speak((LLString)mHighlights[i]["voice"],text); - } - */ - } - if (mHighlights[i]["flash"]) - { - if (viewer_window && viewer_window->getMinimized()) - { - viewer_window->flashIcon(5.f); - } - } - } - } -} +// Moved triggerAlerts() to llfloaterchat.cpp to break llui/llaudio library dependency. S32 LLTextParser::findPattern(const std::string &text, LLSD highlight) { @@ -142,7 +103,7 @@ S32 LLTextParser::findPattern(const std::string &text, LLSD highlight) return found; } -LLSD LLTextParser::parsePartialLineHighlights(const std::string &text, const LLColor4 &color, S32 part, S32 index) +LLSD LLTextParser::parsePartialLineHighlights(const std::string &text, const LLColor4 &color, EHighlightPosition part, S32 index) { //evil recursive string atomizer. LLSD ret_llsd, start_llsd, middle_llsd, end_llsd; @@ -161,7 +122,7 @@ LLSD LLTextParser::parsePartialLineHighlights(const std::string &text, const LLC { S32 end = std::string(mHighlights[i]["pattern"]).length(); S32 len = text.length(); - S32 newpart; + EHighlightPosition newpart; if (start==0) { start_llsd[0]["text"] =text.substr(0,end); diff --git a/indra/llui/lltextparser.h b/indra/llui/lltextparser.h index 5c5c3f3301..072ac0f300 100644 --- a/indra/llui/lltextparser.h +++ b/indra/llui/lltextparser.h @@ -3,6 +3,30 @@ * @brief GUI for user-defined highlights * * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ * */ @@ -10,12 +34,8 @@ #ifndef LL_LLTEXTPARSER_H #define LL_LLTEXTPARSER_H -#include <vector> -#include "linden_common.h" +#include "llsd.h" -#include "lltextparser.h" - -class LLSD; class LLUUID; class LLVector3d; class LLColor4; @@ -23,25 +43,25 @@ class LLColor4; class LLTextParser { public: - enum ConditionType { CONTAINS, MATCHES, STARTS_WITH, ENDS_WITH }; - enum HighlightType { PART, ALL }; - enum HighlightPosition { WHOLE, START, MIDDLE, END }; - enum DialogAction { ACTION_NONE, ACTION_CLOSE, ACTION_ADD, ACTION_COPY, ACTION_UPDATE }; + typedef enum e_condition_type { CONTAINS, MATCHES, STARTS_WITH, ENDS_WITH } EConditionType; + typedef enum e_highlight_type { PART, ALL } EHighlightType; + typedef enum e_highlight_position { WHOLE, START, MIDDLE, END } EHighlightPosition; + typedef enum e_dialog_action { ACTION_NONE, ACTION_CLOSE, ACTION_ADD, ACTION_COPY, ACTION_UPDATE } EDialogAction; static LLTextParser* getInstance(); LLTextParser(){}; ~LLTextParser(); S32 findPattern(const std::string &text, LLSD highlight); - LLSD parsePartialLineHighlights(const std::string &text,const LLColor4 &color,S32 part=WHOLE, S32 index=0); + LLSD parsePartialLineHighlights(const std::string &text,const LLColor4 &color, EHighlightPosition part=WHOLE, S32 index=0); bool parseFullLineHighlights(const std::string &text, LLColor4 *color); - void triggerAlerts(LLUUID agent_id, LLVector3d position, std::string text, LLWindow* viewer_window); std::string getFileName(); LLSD loadFromDisk(); bool saveToDisk(LLSD highlights); + public: - LLSD mHighlights; + LLSD mHighlights; private: static LLTextParser* sInstance; }; diff --git a/indra/llui/lltoggleablemenu.cpp b/indra/llui/lltoggleablemenu.cpp new file mode 100644 index 0000000000..5df1d35383 --- /dev/null +++ b/indra/llui/lltoggleablemenu.cpp @@ -0,0 +1,89 @@ +/** + * @file lltoggleablemenu.cpp + * @brief Menu toggled by a button press + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + + +#include "linden_common.h" + +#include "lltoggleablemenu.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register<LLToggleableMenu> r("toggleable_menu"); + +LLToggleableMenu::LLToggleableMenu(const LLToggleableMenu::Params& p) +: LLMenuGL(p), + mButtonRect(), + mClosedByButtonClick(false) +{ +} + +// virtual +void LLToggleableMenu::handleVisibilityChange (BOOL curVisibilityIn) +{ + S32 x,y; + LLUI::getMousePositionLocal(LLUI::getRootView(), &x, &y); + + if (!curVisibilityIn && mButtonRect.pointInRect(x, y)) + { + mClosedByButtonClick = true; + } +} + +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; +} diff --git a/indra/llui/lltoggleablemenu.h b/indra/llui/lltoggleablemenu.h new file mode 100644 index 0000000000..9d8c5261b9 --- /dev/null +++ b/indra/llui/lltoggleablemenu.h @@ -0,0 +1,68 @@ +/** + * @file lltoggleablemenu.h + * @brief Menu toggled by a button press + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#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<Params, LLMenuGL::Params> + {}; +protected: + LLToggleableMenu(const Params&); + friend class LLUICtrlFactory; +public: + virtual void handleVisibilityChange (BOOL curVisibilityIn); + + 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(); + +protected: + bool mClosedByButtonClick; + LLRect mButtonRect; +}; + +#endif // LL_LLTOGGLEABLEMENU_H diff --git a/indra/llui/lltooltip.cpp b/indra/llui/lltooltip.cpp new file mode 100644 index 0000000000..01c7a81309 --- /dev/null +++ b/indra/llui/lltooltip.cpp @@ -0,0 +1,600 @@ +/** + * @file lltooltip.cpp + * @brief LLToolTipMgr class implementation and related classes + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +// 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<LLToolTipView> register_tooltip_view("tooltip_view"); + +LLToolTipView::Params::Params() +{ + 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) + { + // allow new tooltips because mouse moved + 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::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLToolTipMgr::instance().blockToolTips(); +} + + +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<LLToolTip> 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::sSettingGroups["config"]->getF32( "ToolTipDelay" )), + visible_time_over("visible_time_over", LLUI::sSettingGroups["config"]->getF32( "ToolTipVisibleTimeOver" )), + visible_time_near("visible_time_near", LLUI::sSettingGroups["config"]->getF32( "ToolTipVisibleTimeNear" )), + visible_time_far("visible_time_far", LLUI::sSettingGroups["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) +{ + chrome = true; +} + +LLToolTip::LLToolTip(const LLToolTip::Params& p) +: LLPanel(p), + mHasClickCallback(p.click_callback.isProvided()), + mPadding(p.padding), + mTextBox(NULL), + mInfoButton(NULL), + mPlayMediaButton(NULL), + mHomePageButton(NULL) +{ + 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.allow_html = false; // disallow hyperlinks in tooltips, as they want to spawn their own explanatory tooltips + mTextBox = LLUICtrlFactory::create<LLTextBox> (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"; + icon_params.label(""); // provid label but set to empty so name does not overwrite it -angela + 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(icon_params.highlight_color()); + mInfoButton = LLUICtrlFactory::create<LLButton>(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<LLButton>(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<LLButton>(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(p.max_width, REALLY_LARGE_HEIGHT); + + if (p.styled_message.isProvided()) + { + for (LLInitParam::ParamIterator<LLToolTip::StyledText>::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()); + } + + S32 text_width = llmin(p.max_width(), mTextBox->getTextPixelWidth()); + S32 text_height = mTextBox->getTextPixelHeight(); + mTextBox->reshape(text_width, text_height); + + // 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; + + 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()) + { + F32 tool_tip_fade_time = LLUI::sSettingGroups["config"]->getF32("ToolTipFadeTime"); + 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; +} + + +// +// LLToolTipMgr +// + +LLToolTipMgr::LLToolTipMgr() +: 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()) + { + // 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.setIfNotProvided(true); + } + tooltip_params.rect = LLRect (0, 1, 1, 0); + + mToolTip = LLUICtrlFactory::create<LLToolTip> (tooltip_params); + + gToolTipView->addChild(mToolTip); + + if (params.pos.isProvided()) + { + LLCoordGL pos = params.pos; + // try to spawn at requested position + LLUI::positionViewNearMouse(mToolTip, pos.mX, pos.mY); + } + else + { + // just spawn at mouse location + LLUI::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::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) +{ + show(LLToolTip::Params().message(msg)); +} + +void LLToolTipMgr::show(const LLToolTip::Params& params) +{ + // fill in default tooltip params from tool_tip.xml + LLToolTip::Params params_with_defaults(params); + params_with_defaults.fillFrom(LLUICtrlFactory::instance().getDefaultParams<LLToolTip>()); + if (!params_with_defaults.validateBlock()) + { + llwarns << "Could not display tooltip!" << llendl; + return; + } + + S32 mouse_x; + S32 mouse_y; + LLUI::getMousePositionLocal(gToolTipView, &mouse_x, &mouse_y); + + // are we ready to show the tooltip? + if (!mToolTipsBlocked // we haven't hit a key, moved the mouse, etc. + && LLUI::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::getWindow()->isCursorHidden()) + { + blockToolTips(); + return; + } + + // hide existing tooltips if they have timed out + S32 mouse_x, mouse_y; + LLUI::getMousePositionLocal(gToolTipView, &mouse_x, &mouse_y); + + F32 tooltip_timeout = 0.f; + if (toolTipVisible()) + { + // 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(); + } + } +} + + + +// EOF diff --git a/indra/llui/lltooltip.h b/indra/llui/lltooltip.h new file mode 100644 index 0000000000..7978b6a583 --- /dev/null +++ b/indra/llui/lltooltip.h @@ -0,0 +1,173 @@ +/** + * @file lltooltip.h + * @brief LLToolTipMgr class definition and related classes + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#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, LLView::Params> + { + 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 ); + + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + + void drawStickyRect(); + + /*virtual*/ void draw(); +}; + +class LLToolTip : public LLPanel +{ +public: + + struct StyledText : public LLInitParam::Block<StyledText> + { + Mandatory<std::string> text; + Optional<LLStyle::Params> style; + }; + + struct Params : public LLInitParam::Block<Params, LLPanel::Params> + { + typedef boost::function<void(void)> click_callback_t; + + Optional<std::string> message; + Multiple<StyledText> styled_message; + + Optional<LLCoordGL> pos; + Optional<F32> 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<LLRect> sticky_rect; + Optional<const LLFontGL*> font; + Optional<LLUIImage*> image; + Optional<LLUIColor> text_color; + Optional<bool> time_based_media, + web_based_media, + media_playing; + Optional<click_callback_t> click_callback, + click_playmedia_callback, + click_homepage_callback; + Optional<S32> max_width, + padding; + Optional<bool> wrap; + + 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); + void initFromParams(const LLToolTip::Params& params); + +private: + class LLTextBox* mTextBox; + class LLButton* mInfoButton; + class LLButton* mPlayMediaButton; + class LLButton* mHomePageButton; + + LLFrameTimer mFadeTimer; + LLFrameTimer mVisibleTimer; + bool mHasClickCallback; + S32 mPadding; // pixels +}; + +// used for the inspector tooltips which need different background images etc. +class LLInspector : public LLToolTip +{ +public: + struct Params : public LLInitParam::Block<Params, LLToolTip::Params> {}; +}; + +class LLToolTipMgr : public LLSingleton<LLToolTipMgr> +{ + LOG_CLASS(LLToolTipMgr); +public: + LLToolTipMgr(); + void show(const LLToolTip::Params& params); + void show(const std::string& message); + + void unblockToolTips(); + void blockToolTips(); + + void hideToolTips(); + bool toolTipVisible(); + LLRect getToolTipRect(); + LLRect getMouseNearRect(); + void updateToolTipVisibility(); + +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 new file mode 100644 index 0000000000..eaee260c7a --- /dev/null +++ b/indra/llui/lltransutil.cpp @@ -0,0 +1,67 @@ +/** + * @file lltrans.cpp + * @brief LLTrans implementation + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lltrans.h" +#include "lluictrlfactory.h" + +#include "lltransutil.h" + + +bool LLTransUtil::parseStrings(const std::string& xml_filename, const std::set<std::string>& default_args) +{ + LLXMLNodePtr root; + BOOL success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); + if (!success) + { + llerrs << "Couldn't load string table" << llendl; + 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) + { + llerrs << "Couldn't load string table " << xml_filename << llendl; + return false; + } + + return LLTrans::parseLanguageStrings(root); +} diff --git a/indra/llui/lltransutil.h b/indra/llui/lltransutil.h new file mode 100644 index 0000000000..2ddfd81361 --- /dev/null +++ b/indra/llui/lltransutil.h @@ -0,0 +1,51 @@ +/** + * @file lltransutil.h + * @brief LLTrans helper + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_TRANSUTIL_H +#define LL_TRANSUTIL_H + +#include "lltrans.h" + +namespace LLTransUtil +{ + /** + * @brief Parses the xml file that holds the strings. Used once on startup + * @param xml_filename Filename to parse + * @param default_args Set of strings (expected to be in the file) to use as default replacement args, e.g. "SECOND_LIFE" + * @returns true if the file was parsed successfully, true if something went wrong + */ + bool parseStrings(const std::string& xml_filename, const std::set<std::string>& default_args); + + bool parseLanguageStrings(const std::string& xml_filename); +}; + +#endif diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index 57ce13c9c6..d0ed3b6fca 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -38,45 +38,60 @@ #include <map> // Linden library includes -#include "audioengine.h" #include "v2math.h" #include "v4color.h" #include "llrender.h" #include "llrect.h" -#include "llimagegl.h" #include "lldir.h" -#include "llfontgl.h" +#include "llgl.h" // Project includes #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 "llwindow.h" +// for registration +#include "llfiltereditor.h" +#include "llflyoutbutton.h" +#include "llsearcheditor.h" + +// for XUIParse +#include "llquaternion.h" +#include <boost/tokenizer.hpp> + // // Globals // const LLColor4 UI_VERTEX_COLOR(1.f, 1.f, 1.f, 1.f); -// Used to hide the flashing text cursor when window doesn't have focus. -BOOL gShowTextEditCursor = TRUE; - // Language for UI construction std::map<std::string, std::string> gTranslation; std::list<std::string> gUntranslated; +/*static*/ LLUI::settings_map_t LLUI::sSettingGroups; +/*static*/ LLImageProviderInterface* LLUI::sImageProvider = NULL; +/*static*/ LLUIAudioCallback LLUI::sAudioCallback = NULL; +/*static*/ LLVector2 LLUI::sGLScaleFactor(1.f, 1.f); +/*static*/ LLWindow* LLUI::sWindow = NULL; +/*static*/ LLView* LLUI::sRootView = NULL; +/*static*/ BOOL LLUI::sDirty = FALSE; +/*static*/ LLRect LLUI::sDirtyRect; +/*static*/ LLHelp* LLUI::sHelpImpl = NULL; +/*static*/ std::vector<std::string> LLUI::sXUIPaths; +/*static*/ LLFrameTimer LLUI::sMouseIdleTimer; + +// register filtereditor here +static LLDefaultChildRegistry::Register<LLFilterEditor> register_filter_editor("filter_editor"); +static LLDefaultChildRegistry::Register<LLFlyoutButton> register_flyout_button("flyout_button"); +static LLDefaultChildRegistry::Register<LLSearchEditor> register_search_editor("search_editor"); +static LLDefaultChildRegistry::Register<LLMenuButton> register_menu_button("menu_button"); -LLControlGroup* LLUI::sConfigGroup = NULL; -LLControlGroup* LLUI::sIgnoresGroup = NULL; -LLControlGroup* LLUI::sColorsGroup = NULL; -LLImageProviderInterface* LLUI::sImageProvider = NULL; -LLUIAudioCallback LLUI::sAudioCallback = NULL; -LLVector2 LLUI::sGLScaleFactor(1.f, 1.f); -LLWindow* LLUI::sWindow = NULL; -LLHtmlHelp* LLUI::sHtmlHelp = NULL; -BOOL LLUI::sShowXUINames = FALSE; -std::stack<LLRect> LLScreenClipRect::sClipRectStack; -BOOL LLUI::sQAMode = FALSE; // // Functions @@ -84,18 +99,18 @@ BOOL LLUI::sQAMode = FALSE; void make_ui_sound(const char* namep) { std::string name = ll_safe_string(namep); - if (!LLUI::sConfigGroup->controlExists(name)) + if (!LLUI::sSettingGroups["config"]->controlExists(name)) { llwarns << "tried to make ui sound for unknown sound name: " << name << llendl; } else { - LLUUID uuid(LLUI::sConfigGroup->getString(name)); + LLUUID uuid(LLUI::sSettingGroups["config"]->getString(name)); if (uuid.isNull()) { - if (LLUI::sConfigGroup->getString(name) == LLUUID::null.asString()) + if (LLUI::sSettingGroups["config"]->getString(name) == LLUUID::null.asString()) { - if (LLUI::sConfigGroup->getBOOL("UISndDebugSpamToggle")) + if (LLUI::sSettingGroups["config"]->getBOOL("UISndDebugSpamToggle")) { llinfos << "ui sound name: " << name << " triggered but silent (null uuid)" << llendl; } @@ -108,7 +123,7 @@ void make_ui_sound(const char* namep) } else if (LLUI::sAudioCallback != NULL) { - if (LLUI::sConfigGroup->getBOOL("UISndDebugSpamToggle")) + if (LLUI::sSettingGroups["config"]->getBOOL("UISndDebugSpamToggle")) { llinfos << "ui sound name: " << name << llendl; } @@ -408,7 +423,7 @@ void gl_corners_2d(S32 left, S32 top, S32 right, S32 bottom, S32 length, F32 max } -void gl_draw_image( S32 x, S32 y, LLImageGL* image, const LLColor4& color, const LLRectf& uv_rect ) +void gl_draw_image( S32 x, S32 y, LLTexture* image, const LLColor4& color, const LLRectf& uv_rect ) { if (NULL == image) { @@ -418,7 +433,7 @@ void gl_draw_image( S32 x, S32 y, LLImageGL* image, const LLColor4& color, const gl_draw_scaled_rotated_image( x, y, image->getWidth(0), image->getHeight(0), 0.f, image, color, uv_rect ); } -void gl_draw_scaled_image(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color, const LLRectf& uv_rect) +void gl_draw_scaled_image(S32 x, S32 y, S32 width, S32 height, LLTexture* image, const LLColor4& color, const LLRectf& uv_rect) { if (NULL == image) { @@ -428,7 +443,7 @@ void gl_draw_scaled_image(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, gl_draw_scaled_rotated_image( x, y, width, height, 0.f, image, color, uv_rect ); } -void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 border_width, S32 border_height, S32 width, S32 height, LLImageGL* image, const LLColor4& color, BOOL solid_color, const LLRectf& uv_rect) +void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 border_width, S32 border_height, S32 width, S32 height, LLTexture* image, const LLColor4& color, BOOL solid_color, const LLRectf& uv_rect) { if (NULL == image) { @@ -444,7 +459,7 @@ void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 border_width, S32 border gl_draw_scaled_image_with_border(x, y, width, height, image, color, solid_color, uv_rect, scale_rect); } -void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color, BOOL solid_color, const LLRectf& uv_rect, const LLRectf& scale_rect) +void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLTexture* image, const LLColor4& color, BOOL solid_color, const LLRectf& uv_rect, const LLRectf& scale_rect) { stop_glerror(); @@ -630,12 +645,12 @@ void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLIma } } -void gl_draw_rotated_image(S32 x, S32 y, F32 degrees, LLImageGL* image, const LLColor4& color, const LLRectf& uv_rect) +void gl_draw_rotated_image(S32 x, S32 y, F32 degrees, LLTexture* image, const LLColor4& color, const LLRectf& uv_rect) { gl_draw_scaled_rotated_image( x, y, image->getWidth(0), image->getHeight(0), degrees, image, color, uv_rect ); } -void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degrees, LLImageGL* image, const LLColor4& color, const LLRectf& uv_rect) +void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degrees, LLTexture* image, const LLColor4& color, const LLRectf& uv_rect) { if (NULL == image) { @@ -681,44 +696,6 @@ void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degre } -void gl_draw_scaled_image_inverted(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color, const LLRectf& uv_rect) -{ - if (NULL == image) - { - llwarns << "image == NULL; aborting function" << llendl; - return; - } - - LLGLSUIDefault gls_ui; - - gGL.pushMatrix(); - { - gGL.translatef((F32)x, (F32)y, 0.f); - - gGL.getTexUnit(0)->bind(image); - - gGL.color4fv(color.mV); - - gGL.begin(LLRender::QUADS); - { - gGL.texCoord2f(uv_rect.mRight, uv_rect.mBottom); - gGL.vertex2i(width, height ); - - gGL.texCoord2f(uv_rect.mLeft, uv_rect.mBottom); - gGL.vertex2i(0, height ); - - gGL.texCoord2f(uv_rect.mLeft, uv_rect.mTop); - gGL.vertex2i(0, 0); - - gGL.texCoord2f(uv_rect.mRight, uv_rect.mTop); - gGL.vertex2i(width, 0); - } - gGL.end(); - } - gGL.popMatrix(); -} - - void gl_stippled_line_3d( const LLVector3& start, const LLVector3& end, const LLColor4& color, F32 phase ) { phase = fmod(phase, 1.f); @@ -1548,27 +1525,17 @@ void gl_segmented_rect_3d_tex_top(const LLVector2& border_scale, const LLVector3 gl_segmented_rect_3d_tex(border_scale, border_width, border_height, width_vec, height_vec, ROUNDED_RECT_TOP); } -bool handleShowXUINamesChanged(const LLSD& newvalue) -{ - LLUI::sShowXUINames = newvalue.asBoolean(); - return true; -} - -void LLUI::initClass(LLControlGroup* config, - LLControlGroup* ignores, - LLControlGroup* colors, +void LLUI::initClass(const settings_map_t& settings, LLImageProviderInterface* image_provider, LLUIAudioCallback audio_callback, const LLVector2* scale_factor, const std::string& language) { - sConfigGroup = config; - sIgnoresGroup = ignores; - sColorsGroup = colors; + sSettingGroups = settings; - if (sConfigGroup == NULL - || sIgnoresGroup == NULL - || sColorsGroup == NULL) + if ((get_ptr_in_map(sSettingGroups, std::string("config")) == NULL) || + (get_ptr_in_map(sSettingGroups, std::string("floater")) == NULL) || + (get_ptr_in_map(sSettingGroups, std::string("ignores")) == NULL)) { llerrs << "Failure to initialize configuration groups" << llendl; } @@ -1577,18 +1544,49 @@ void LLUI::initClass(LLControlGroup* config, sAudioCallback = audio_callback; sGLScaleFactor = (scale_factor == NULL) ? LLVector2(1.f, 1.f) : *scale_factor; sWindow = NULL; // set later in startup - LLFontGL::sShadowColor = colors->getColor("ColorDropShadow"); + LLFontGL::sShadowColor = LLUIColorTable::instance().getColor("ColorDropShadow"); + + // Callbacks for associating controls with floater visibilty: + LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Floater.Toggle", boost::bind(&LLFloaterReg::toggleFloaterInstance, _2)); + LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Floater.Show", boost::bind(&LLFloaterReg::showFloaterInstance, _2)); + LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Floater.Hide", boost::bind(&LLFloaterReg::hideFloaterInstance, _2)); + LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Floater.InitToVisibilityControl", boost::bind(&LLFloaterReg::initUICtrlToFloaterVisibilityControl, _1, _2)); + + // Button initialization callback for toggle buttons + LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Button.SetFloaterToggle", boost::bind(&LLButton::setFloaterToggle, _1, _2)); + + // Button initialization callback for toggle buttons on dockale floaters + LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Button.SetDockableFloaterToggle", boost::bind(&LLButton::setDockableFloaterToggle, _1, _2)); + + // Display the help topic for the current context + LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Button.ShowHelp", boost::bind(&LLButton::showHelp, _1, _2)); - LLUI::sShowXUINames = LLUI::sConfigGroup->getBOOL("ShowXUINames"); - LLUI::sConfigGroup->getControl("ShowXUINames")->getSignal()->connect(&handleShowXUINamesChanged); + // Currently unused, but kept for reference: + LLUICtrl::CommitCallbackRegistry::defaultRegistrar().add("Button.ToggleFloater", boost::bind(&LLButton::toggleFloaterAndSetToggleState, _1, _2)); + + // Used by menus along with Floater.Toggle to display visibility as a checkmark + LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.Visible", boost::bind(&LLFloaterReg::floaterInstanceVisible, _2)); } void LLUI::cleanupClass() { sImageProvider->cleanUp(); - LLLineEditor::cleanupLineEditor(); } +//static +void LLUI::dirtyRect(LLRect rect) +{ + if (!sDirty) + { + sDirtyRect = rect; + sDirty = TRUE; + } + else + { + sDirtyRect.unionWith(rect); + } +} + //static void LLUI::translate(F32 x, F32 y, F32 z) @@ -1637,7 +1635,7 @@ void LLUI::setLineWidth(F32 width) } //static -void LLUI::setCursorPositionScreen(S32 x, S32 y) +void LLUI::setMousePositionScreen(S32 x, S32 y) { S32 screen_x, screen_y; screen_x = llround((F32)x * sGLScaleFactor.mV[VX]); @@ -1650,26 +1648,34 @@ void LLUI::setCursorPositionScreen(S32 x, S32 y) } //static -void LLUI::setCursorPositionLocal(const LLView* viewp, S32 x, S32 y) +void LLUI::getMousePositionScreen(S32 *x, S32 *y) +{ + LLCoordWindow cursor_pos_window; + getWindow()->getCursorPosition(&cursor_pos_window); + LLCoordGL cursor_pos_gl; + getWindow()->convertCoords(cursor_pos_window, &cursor_pos_gl); + *x = llround((F32)cursor_pos_gl.mX / sGLScaleFactor.mV[VX]); + *y = llround((F32)cursor_pos_gl.mY / sGLScaleFactor.mV[VX]); +} + +//static +void LLUI::setMousePositionLocal(const LLView* viewp, S32 x, S32 y) { S32 screen_x, screen_y; viewp->localPointToScreen(x, y, &screen_x, &screen_y); - setCursorPositionScreen(screen_x, screen_y); + setMousePositionScreen(screen_x, screen_y); } //static -void LLUI::getCursorPositionLocal(const LLView* viewp, S32 *x, S32 *y) +void LLUI::getMousePositionLocal(const LLView* viewp, S32 *x, S32 *y) { - LLCoordWindow cursor_pos_window; - LLView::getWindow()->getCursorPosition(&cursor_pos_window); - LLCoordGL cursor_pos_gl; - LLView::getWindow()->convertCoords(cursor_pos_window, &cursor_pos_gl); - cursor_pos_gl.mX = llround((F32)cursor_pos_gl.mX / LLUI::sGLScaleFactor.mV[VX]); - cursor_pos_gl.mY = llround((F32)cursor_pos_gl.mY / LLUI::sGLScaleFactor.mV[VY]); - viewp->screenPointToLocal(cursor_pos_gl.mX, cursor_pos_gl.mY, x, 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 @@ -1678,27 +1684,61 @@ void LLUI::getCursorPositionLocal(const LLView* viewp, S32 *x, S32 *y) // static std::string LLUI::getLanguage() { - std::string language = "en-us"; - if (sConfigGroup) + std::string language = "en"; + if (sSettingGroups["config"]) { - language = sConfigGroup->getString("Language"); + language = sSettingGroups["config"]->getString("Language"); if (language.empty() || language == "default") { - language = sConfigGroup->getString("InstallLanguage"); + language = sSettingGroups["config"]->getString("InstallLanguage"); } if (language.empty() || language == "default") { - language = sConfigGroup->getString("SystemLanguage"); + language = sSettingGroups["config"]->getString("SystemLanguage"); } if (language.empty() || language == "default") { - language = "en-us"; + language = "en"; } } return language; } //static +void LLUI::setupPaths() +{ + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "paths.xml"); + + LLXMLNodePtr root; + BOOL success = LLXMLNode::parseFile(filename, root, NULL); + sXUIPaths.clear(); + + if (success) + { + LLStringUtil::format_map_t path_args; + path_args["[LANGUAGE]"] = LLUI::getLanguage(); + + for (LLXMLNodePtr path = root->getFirstChild(); path.notNull(); path = path->getNextSibling()) + { + std::string path_val_ui(path->getValue()); + LLStringUtil::format(path_val_ui, path_args); + if (std::find(sXUIPaths.begin(), sXUIPaths.end(), path_val_ui) == sXUIPaths.end()) + { + sXUIPaths.push_back(path_val_ui); + } + } + } + else // parsing failed + { + std::string slash = gDirUtilp->getDirDelimiter(); + std::string dir = "xui" + slash + "en"; + llwarns << "XUI::config file unable to open: " << filename << llendl; + sXUIPaths.push_back(dir); + } +} + + +//static std::string LLUI::locateSkin(const std::string& filename) { std::string slash = gDirUtilp->getDirDelimiter(); @@ -1707,7 +1747,7 @@ std::string LLUI::locateSkin(const std::string& filename) { found_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename); // Should be CUSTOM_SKINS? } - if (sConfigGroup && sConfigGroup->controlExists("Language")) + if (sSettingGroups["config"] && sSettingGroups["config"]->controlExists("Language")) { if (!gDirUtilp->fileExists(found_file)) { @@ -1718,7 +1758,7 @@ std::string LLUI::locateSkin(const std::string& filename) } if (!gDirUtilp->fileExists(found_file)) { - std::string local_skin = "xui" + slash + "en-us" + slash + filename; + std::string local_skin = "xui" + slash + "en" + slash + filename; found_file = gDirUtilp->findSkinnedFilename(local_skin); } if (!gDirUtilp->fileExists(found_file)) @@ -1765,186 +1805,299 @@ void LLUI::glRectToScreen(const LLRect& gl, LLRect *screen) glPointToScreen(gl.mRight, gl.mBottom, &screen->mRight, &screen->mBottom); } -//static -LLUIImage* LLUI::getUIImage(const std::string& name) +//static +LLPointer<LLUIImage> LLUI::getUIImageByID(const LLUUID& image_id, S32 priority) { - if (!name.empty()) - return sImageProvider->getUIImage(name); + if (sImageProvider) + { + return sImageProvider->getUIImageByID(image_id, priority); + } else + { return NULL; -} - -// static -void LLUI::setHtmlHelp(LLHtmlHelp* html_help) -{ - LLUI::sHtmlHelp = html_help; + } } //static -void LLUI::setQAMode(BOOL b) +LLPointer<LLUIImage> LLUI::getUIImage(const std::string& name, S32 priority) { - LLUI::sQAMode = b; + if (!name.empty() && sImageProvider) + return sImageProvider->getUIImage(name, priority); + else + return NULL; } -LLScreenClipRect::LLScreenClipRect(const LLRect& rect, BOOL enabled) : mScissorState(GL_SCISSOR_TEST), mEnabled(enabled) +LLControlGroup& LLUI::getControlControlGroup (const std::string& controlname) { - if (mEnabled) + for (settings_map_t::iterator itor = sSettingGroups.begin(); + itor != sSettingGroups.end(); ++itor) { - pushClipRect(rect); + LLControlGroup* control_group = itor->second; + if(control_group != NULL) + { + if (control_group->controlExists(controlname)) + return *control_group; + } } - mScissorState.setEnabled(!sClipRectStack.empty()); - updateScissorRegion(); -} -LLScreenClipRect::~LLScreenClipRect() -{ - if (mEnabled) - { - popClipRect(); - } - updateScissorRegion(); + return *sSettingGroups["config"]; // default group } -//static -void LLScreenClipRect::pushClipRect(const LLRect& rect) +//static +// 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) { - LLRect combined_clip_rect = rect; - if (!sClipRectStack.empty()) + const S32 CURSOR_HEIGHT = 16; // Approximate "normal" cursor size + const S32 CURSOR_WIDTH = 8; + + LLView* parent = view->getParent(); + + S32 mouse_x; + S32 mouse_y; + LLUI::getMousePositionScreen(&mouse_x, &mouse_y); + + // If no spawn location provided, use mouse position + if (spawn_x == S32_MAX || spawn_y == S32_MAX) { - LLRect top = sClipRectStack.top(); - combined_clip_rect.intersectWith(top); + spawn_x = mouse_x + CURSOR_WIDTH; + spawn_y = mouse_y - CURSOR_HEIGHT; } - sClipRectStack.push(combined_clip_rect); -} -//static -void LLScreenClipRect::popClipRect() -{ - sClipRectStack.pop(); -} + LLRect virtual_window_rect = parent->getLocalRect(); -//static -void LLScreenClipRect::updateScissorRegion() -{ - if (sClipRectStack.empty()) return; + 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); - LLRect rect = sClipRectStack.top(); - stop_glerror(); - S32 x,y,w,h; - x = llfloor(rect.mLeft * LLUI::sGLScaleFactor.mV[VX]); - y = llfloor(rect.mBottom * LLUI::sGLScaleFactor.mV[VY]); - w = llmax(0, llceil(rect.getWidth() * LLUI::sGLScaleFactor.mV[VX])) + 1; - h = llmax(0, llceil(rect.getHeight() * LLUI::sGLScaleFactor.mV[VY])) + 1; - glScissor( x,y,w,h ); - stop_glerror(); + S32 local_x, local_y; + // convert screen coordinates to tooltipview-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 onscreen and not overlapping the mouse + view->translateIntoRectWithExclusion( virtual_window_rect, mouse_rect, FALSE ); } -LLLocalClipRect::LLLocalClipRect(const LLRect &rect, BOOL enabled) -: LLScreenClipRect(LLRect(rect.mLeft + LLFontGL::sCurOrigin.mX, - rect.mTop + LLFontGL::sCurOrigin.mY, - rect.mRight + LLFontGL::sCurOrigin.mX, - rect.mBottom + LLFontGL::sCurOrigin.mY), - enabled) +// LLLocalClipRect and LLScreenClipRect moved to lllocalcliprect.h/cpp + +namespace LLInitParam { -} + TypedParam<LLUIColor >::TypedParam(BlockDescriptor& descriptor, const char* name, const LLUIColor& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) + : super_t(descriptor, name, value, func, min_count, max_count), + red("red"), + green("green"), + blue("blue"), + alpha("alpha"), + control("") + {} + void TypedParam<LLUIColor>::setValueFromBlock() const + { + if (control.isProvided()) + { + mData.mValue = LLUIColorTable::instance().getColor(control); + } + else + { + mData.mValue = LLColor4(red, green, blue, alpha); + } + } + + void TypedParam<LLUIColor>::setBlockFromValue() + { + LLColor4 color = mData.mValue.get(); + red = color.mV[VRED]; + green = color.mV[VGREEN]; + blue = color.mV[VBLUE]; + alpha = color.mV[VALPHA]; + control.set("", false); + } -// -// LLUIImage -// + void TypeValues<LLUIColor>::declareValues() + { + declare("white", LLColor4::white); + declare("black", LLColor4::black); + declare("red", LLColor4::red); + declare("green", LLColor4::green); + declare("blue", LLColor4::blue); + } -LLUIImage::LLUIImage(const std::string& name, LLPointer<LLImageGL> image) : - mName(name), - mImage(image), - mScaleRegion(0.f, 1.f, 1.f, 0.f), - mClipRegion(0.f, 1.f, 1.f, 0.f), - mUniformScaling(TRUE), - mNoClip(TRUE) -{ -} + bool ParamCompare<const LLFontGL*, false>::equals(const LLFontGL* a, const LLFontGL* b) + { + return !(a->getFontDesc() < b->getFontDesc()) + && !(b->getFontDesc() < a->getFontDesc()); + } -void LLUIImage::setClipRegion(const LLRectf& region) -{ - mClipRegion = region; - mNoClip = mClipRegion.mLeft == 0.f - && mClipRegion.mRight == 1.f - && mClipRegion.mBottom == 0.f - && mClipRegion.mTop == 1.f; -} + TypedParam<const LLFontGL*>::TypedParam(BlockDescriptor& descriptor, const char* _name, const LLFontGL*const value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) + : super_t(descriptor, _name, value, func, min_count, max_count), + name("name"), + size("size"), + style("style") + { + addSynonym(name, ""); + } -void LLUIImage::setScaleRegion(const LLRectf& region) -{ - mScaleRegion = region; - mUniformScaling = mScaleRegion.mLeft == 0.f - && mScaleRegion.mRight == 1.f - && mScaleRegion.mBottom == 0.f - && mScaleRegion.mTop == 1.f; -} + void TypedParam<const LLFontGL*>::setValueFromBlock() const + { + const LLFontGL* res_fontp = LLFontGL::getFontByName(name); + if (res_fontp) + { + mData.mValue = res_fontp; + return; + } -//TODO: move drawing implementation inside class -void LLUIImage::draw(S32 x, S32 y, const LLColor4& color) const -{ - gl_draw_image(x, y, mImage, color, mClipRegion); -} + U8 fontstyle = 0; + fontstyle = LLFontGL::getStyleFromString(style()); + LLFontDescriptor desc(name(), size(), fontstyle); + const LLFontGL* fontp = LLFontGL::getFont(desc); + if (fontp) + { + mData.mValue = fontp; + } + } + + void TypedParam<const LLFontGL*>::setBlockFromValue() + { + if (mData.mValue) + { + name = LLFontGL::nameFromFont(mData.mValue); + size = LLFontGL::sizeFromFont(mData.mValue); + style = LLFontGL::getStringFromStyle(mData.mValue->getFontDesc().getStyle()); + } + } -void LLUIImage::draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color) const -{ - if (mUniformScaling) + TypedParam<LLRect>::TypedParam(BlockDescriptor& descriptor, const char* name, const LLRect& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) + : super_t(descriptor, name, value, func, min_count, max_count), + left("left"), + top("top"), + right("right"), + bottom("bottom"), + width("width"), + height("height") + {} + + void TypedParam<LLRect>::setValueFromBlock() const + { + 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; + } + mData.mValue = rect; + } + + void TypedParam<LLRect>::setBlockFromValue() { - gl_draw_scaled_image(x, y, width, height, mImage, color, mClipRegion); + // because of the ambiguity in specifying a rect by position and/or dimensions + // we clear the "provided" flag so that values from xui/etc have priority + // over those calculated from the rect object + + left.set(mData.mValue.mLeft, false); + right.set(mData.mValue.mRight, false); + bottom.set(mData.mValue.mBottom, false); + top.set(mData.mValue.mTop, false); + width.set(mData.mValue.getWidth(), false); + height.set(mData.mValue.getHeight(), false); } - else + + TypedParam<LLCoordGL>::TypedParam(BlockDescriptor& descriptor, const char* name, LLCoordGL value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) + : super_t(descriptor, name, value, func, min_count, max_count), + x("x"), + y("y") { - gl_draw_scaled_image_with_border( - x, y, - width, height, - mImage, - color, - FALSE, - mClipRegion, - mScaleRegion); } -} -void LLUIImage::drawSolid(S32 x, S32 y, S32 width, S32 height, const LLColor4& color) const -{ - gl_draw_scaled_image_with_border( - x, y, - width, height, - mImage, - color, - TRUE, - mClipRegion, - mScaleRegion); -} + void TypedParam<LLCoordGL>::setValueFromBlock() const + { + mData.mValue.set(x, y); + } + + void TypedParam<LLCoordGL>::setBlockFromValue() + { + x = mData.mValue.mX; + y = mData.mValue.mY; + } -void LLUIImage::drawBorder(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, S32 border_width) const -{ - LLRect border_rect; - border_rect.setOriginAndSize(x, y, width, height); - border_rect.stretch(border_width, border_width); - drawSolid(border_rect, color); -} -S32 LLUIImage::getWidth() const -{ - // return clipped dimensions of actual image area - return llround((F32)mImage->getWidth(0) * mClipRegion.getWidth()); -} + void TypeValues<LLFontGL::HAlign>::declareValues() + { + declare("left", LLFontGL::LEFT); + declare("right", LLFontGL::RIGHT); + declare("center", LLFontGL::HCENTER); + } -S32 LLUIImage::getHeight() const -{ - // return clipped dimensions of actual image area - return llround((F32)mImage->getHeight(0) * mClipRegion.getHeight()); -} + void TypeValues<LLFontGL::VAlign>::declareValues() + { + declare("top", LLFontGL::TOP); + declare("center", LLFontGL::VCENTER); + declare("baseline", LLFontGL::BASELINE); + declare("bottom", LLFontGL::BOTTOM); + } -S32 LLUIImage::getTextureWidth() const -{ - return mImage->getWidth(0); + void TypeValues<LLFontGL::ShadowType>::declareValues() + { + declare("none", LLFontGL::NO_SHADOW); + declare("hard", LLFontGL::DROP_SHADOW); + declare("soft", LLFontGL::DROP_SHADOW_SOFT); + } } -S32 LLUIImage::getTextureHeight() const -{ - return mImage->getHeight(0); -} diff --git a/indra/llui/llui.h b/indra/llui/llui.h index ebcc7304b1..5840e76f5c 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -35,28 +35,35 @@ #ifndef LL_LLUI_H #define LL_LLUI_H +#include "llpointer.h" // LLPointer<> #include "llrect.h" #include "llcontrol.h" -#include "llrect.h" #include "llcoord.h" -//#include "llhtmlhelp.h" -#include "llgl.h" // *TODO: break this dependency -#include <stack> -//#include "llimagegl.h" -#include <boost/signal.hpp> +#include "lluiimage.h" // *TODO: break this dependency, need to add #include "lluiimage.h" to all widgets that hold an Optional<LLUIImage*> in their paramblocks +#include "llinitparam.h" +#include "llregistry.h" +#include "lluicolor.h" +#include "lluicolortable.h" +#include <boost/signals2.hpp> +#include "lllazyvalue.h" +#include "llhandle.h" // *TODO: remove this dependency, added as a + // convenience when LLHandle moved to llhandle.h +#include "llframetimer.h" // LLUIFactory #include "llsd.h" +// for initparam specialization +#include "llfontgl.h" + class LLColor4; -class LLHtmlHelp; -class LLImageGL; class LLVector3; class LLVector2; +class LLUIImage; class LLUUID; class LLWindow; class LLView; -class LLUIImage; +class LLHelp; // UI colors extern const LLColor4 UI_VERTEX_COLOR; @@ -91,14 +98,12 @@ void gl_washer_2d(F32 outer_radius, F32 inner_radius, S32 steps, const LLColor4& void gl_washer_segment_2d(F32 outer_radius, F32 inner_radius, F32 start_radians, F32 end_radians, S32 steps, const LLColor4& inner_color, const LLColor4& outer_color); void gl_washer_spokes_2d(F32 outer_radius, F32 inner_radius, S32 count, const LLColor4& inner_color, const LLColor4& outer_color); -void gl_draw_image(S32 x, S32 y, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); -void gl_draw_scaled_image(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); -void gl_draw_rotated_image(S32 x, S32 y, F32 degrees, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); -void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degrees,LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); -void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 border_width, S32 border_height, S32 width, S32 height, LLImageGL* image, const LLColor4 &color, BOOL solid_color = FALSE, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); -void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4 &color, BOOL solid_color = FALSE, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f), const LLRectf& scale_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); -// Flip vertical, used for LLFloaterHTML -void gl_draw_scaled_image_inverted(S32 x, S32 y, S32 width, S32 height, LLImageGL* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); +void gl_draw_image(S32 x, S32 y, LLTexture* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); +void gl_draw_scaled_image(S32 x, S32 y, S32 width, S32 height, LLTexture* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); +void gl_draw_rotated_image(S32 x, S32 y, F32 degrees, LLTexture* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); +void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degrees,LLTexture* image, const LLColor4& color = UI_VERTEX_COLOR, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); +void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 border_width, S32 border_height, S32 width, S32 height, LLTexture* image, const LLColor4 &color, BOOL solid_color = FALSE, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); +void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLTexture* image, const LLColor4 &color, BOOL solid_color = FALSE, const LLRectf& uv_rect = LLRectf(0.f, 1.f, 1.f, 0.f), const LLRectf& scale_rect = LLRectf(0.f, 1.f, 1.f, 0.f)); void gl_rect_2d_xor(S32 left, S32 top, S32 right, S32 bottom); void gl_stippled_line_3d( const LLVector3& start, const LLVector3& end, const LLColor4& color, F32 phase = 0.f ); @@ -143,9 +148,6 @@ inline void gl_rect_2d_offset_local( const LLRect& rect, S32 pixel_offset, BOOL gl_rect_2d_offset_local( rect.mLeft, rect.mTop, rect.mRight, rect.mBottom, pixel_offset, filled ); } -// Used to hide the flashing text cursor when window doesn't have focus. -extern BOOL gShowTextEditCursor; - class LLImageProviderInterface; typedef void (*LLUIAudioCallback)(const LLUUID& uuid); @@ -157,9 +159,8 @@ public: // // Methods // - static void initClass(LLControlGroup* config, - LLControlGroup* ignores, - LLControlGroup* colors, + typedef std::map<std::string, LLControlGroup*> settings_map_t; + static void initClass(const settings_map_t& settings, LLImageProviderInterface* image_provider, LLUIAudioCallback audio_callback = NULL, const LLVector2 *scale_factor = NULL, @@ -171,497 +172,84 @@ public: static void loadIdentity(); static void translate(F32 x, F32 y, F32 z = 0.0f); + static LLRect sDirtyRect; + static BOOL sDirty; + static void dirtyRect(LLRect rect); + // Return the ISO639 language name ("en", "ko", etc.) for the viewer UI. // http://www.loc.gov/standards/iso639-2/php/code_list.php static std::string getLanguage(); + + static void setupPaths(); + static const std::vector<std::string>& getXUIPaths() { return sXUIPaths; } + static std::string getSkinPath() { return sXUIPaths.front(); } + static std::string getLocalizedSkinPath() { return sXUIPaths.back(); } //all files may not exist at the localized path //helper functions (should probably move free standing rendering helper functions here) + static LLView* getRootView() { return sRootView; } + static void setRootView(LLView* view) { sRootView = view; } static std::string locateSkin(const std::string& filename); - static void setCursorPositionScreen(S32 x, S32 y); - static void setCursorPositionLocal(const LLView* viewp, S32 x, S32 y); - static void getCursorPositionLocal(const LLView* viewp, S32 *x, S32 *y); + static void setMousePositionScreen(S32 x, S32 y); + static void getMousePositionScreen(S32 *x, S32 *y); + static void setMousePositionLocal(const LLView* viewp, S32 x, S32 y); + static void getMousePositionLocal(const LLView* viewp, S32 *x, S32 *y); static void setScaleFactor(const LLVector2& scale_factor); static void setLineWidth(F32 width); - static LLUIImage* getUIImage(const std::string& name); + static LLPointer<LLUIImage> getUIImageByID(const LLUUID& image_id, S32 priority = 0); + static LLPointer<LLUIImage> getUIImage(const std::string& name, S32 priority = 0); static LLVector2 getWindowSize(); static void screenPointToGL(S32 screen_x, S32 screen_y, S32 *gl_x, S32 *gl_y); static void glPointToScreen(S32 gl_x, S32 gl_y, S32 *screen_x, S32 *screen_y); static void screenRectToGL(const LLRect& screen, LLRect *gl); static void glRectToScreen(const LLRect& gl, LLRect *screen); - static void setHtmlHelp(LLHtmlHelp* html_help); + // Returns the control group containing the control name, or the default group + static LLControlGroup& getControlControlGroup (const std::string& controlname); + static F32 getMouseIdleTime() { return sMouseIdleTimer.getElapsedTimeF32(); } + static void resetMouseIdleTimer() { sMouseIdleTimer.reset(); } + static LLWindow* getWindow() { return sWindow; } + + // Ensures view does not overlap mouse cursor, but is inside + // the view's parent rectangle. Used for tooltips, inspectors. + // Optionally override the view's default X/Y, which are relative to the + // view's parent. + static void positionViewNearMouse(LLView* view, S32 spawn_x = S32_MAX, S32 spawn_y = S32_MAX); // // Data // - static LLControlGroup* sConfigGroup; - static LLControlGroup* sIgnoresGroup; - static LLControlGroup* sColorsGroup; - static LLImageProviderInterface* sImageProvider; + static settings_map_t sSettingGroups; static LLUIAudioCallback sAudioCallback; static LLVector2 sGLScaleFactor; static LLWindow* sWindow; - static BOOL sShowXUINames; - static LLHtmlHelp* sHtmlHelp; - - // *TODO: remove the following when QAR-369 settings clean-up work is in. - // Also remove the call to this method which will then be obsolete. - // Search for QAR-369 below to enable the proper accessing of this feature. -MG - static void setQAMode(BOOL b); - static BOOL sQAMode; - -}; - -// FactoryPolicy is a static class that controls the creation and lookup of UI elements, -// such as floaters. -// The key parameter is used to provide a unique identifier and/or associated construction -// parameters for a given UI instance -// -// Specialize this traits for different types, or provide a class with an identical interface -// in the place of the traits parameter -// -// For example: -// -// template <> -// class FactoryPolicy<MyClass> /* FactoryPolicy specialized for MyClass */ -// { -// public: -// static MyClass* findInstance(const LLSD& key = LLSD()) -// { -// /* return instance of MyClass associated with key */ -// } -// -// static MyClass* createInstance(const LLSD& key = LLSD()) -// { -// /* create new instance of MyClass using key for construction parameters */ -// } -// } -// -// class MyClass : public LLUIFactory<MyClass> -// { -// /* uses FactoryPolicy<MyClass> by default */ -// } - -template <class T> -class FactoryPolicy -{ -public: - // basic factory methods - static T* findInstance(const LLSD& key); // unimplemented, provide specialiation - static T* createInstance(const LLSD& key); // unimplemented, provide specialiation -}; - -// VisibilityPolicy controls the visibility of UI elements, such as floaters. -// The key parameter is used to store the unique identifier of a given UI instance -// -// Specialize this traits for different types, or duplicate this interface for specific instances -// (see above) - -template <class T> -class VisibilityPolicy -{ -public: - // visibility methods - static bool visible(T* instance, const LLSD& key); // unimplemented, provide specialiation - static void show(T* instance, const LLSD& key); // unimplemented, provide specialiation - static void hide(T* instance, const LLSD& key); // unimplemented, provide specialiation -}; - -// Manages generation of UI elements by LLSD, such that (generally) there is -// a unique instance per distinct LLSD parameter -// Class T is the instance type being managed, and the FACTORY_POLICY and VISIBILITY_POLICY -// classes provide static methods for creating, accessing, showing and hiding the associated -// element T -template <class T, class FACTORY_POLICY = FactoryPolicy<T>, class VISIBILITY_POLICY = VisibilityPolicy<T> > -class LLUIFactory -{ -public: - // give names to the template parameters so derived classes can refer to them - // except this doesn't work in gcc - typedef FACTORY_POLICY factory_policy_t; - typedef VISIBILITY_POLICY visibility_policy_t; - - LLUIFactory() - { - } - - virtual ~LLUIFactory() - { - } - - // default show and hide methods - static T* showInstance(const LLSD& key = LLSD()) - { - T* instance = getInstance(key); - if (instance != NULL) - { - VISIBILITY_POLICY::show(instance, key); - } - return instance; - } - - static void hideInstance(const LLSD& key = LLSD()) - { - T* instance = getInstance(key); - if (instance != NULL) - { - VISIBILITY_POLICY::hide(instance, key); - } - } - - static void toggleInstance(const LLSD& key = LLSD()) - { - if (instanceVisible(key)) - { - hideInstance(key); - } - else - { - showInstance(key); - } - } - - static bool instanceVisible(const LLSD& key = LLSD()) - { - T* instance = FACTORY_POLICY::findInstance(key); - return instance != NULL && VISIBILITY_POLICY::visible(instance, key); - } - - static T* getInstance(const LLSD& key = LLSD()) - { - T* instance = FACTORY_POLICY::findInstance(key); - if (instance == NULL) - { - instance = FACTORY_POLICY::createInstance(key); - } - return instance; - } - -}; - - -// Creates a UI singleton by ignoring the identifying parameter -// and always generating the same instance via the LLUIFactory interface. -// Note that since UI elements can be destroyed by their hierarchy, this singleton -// pattern uses a static pointer to an instance that will be re-created as needed. -// -// Usage Pattern: -// -// class LLFloaterFoo : public LLFloater, public LLUISingleton<LLFloaterFoo> -// { -// friend class LLUISingleton<LLFloaterFoo>; -// private: -// LLFloaterFoo(const LLSD& key); -// }; -// -// Note that LLUISingleton takes an option VisibilityPolicy parameter that defines -// how showInstance(), hideInstance(), etc. work. -// -// https://wiki.lindenlab.com/mediawiki/index.php?title=LLUISingleton&oldid=79352 - -template <class T, class VISIBILITY_POLICY = VisibilityPolicy<T> > -class LLUISingleton: public LLUIFactory<T, LLUISingleton<T, VISIBILITY_POLICY>, VISIBILITY_POLICY> -{ -protected: - - // T must derive from LLUISingleton<T> - LLUISingleton() { sInstance = static_cast<T*>(this); } - - ~LLUISingleton() { sInstance = NULL; } - -public: - static T* findInstance(const LLSD& key = LLSD()) - { - return sInstance; - } - - static T* createInstance(const LLSD& key = LLSD()) - { - if (sInstance == NULL) - { - sInstance = new T(key); - } - return sInstance; - } - -private: - static T* sInstance; -}; - -template <class T, class U> T* LLUISingleton<T,U>::sInstance = NULL; - -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<LLRect> sClipRectStack; -}; - -class LLLocalClipRect : public LLScreenClipRect -{ -public: - LLLocalClipRect(const LLRect& rect, BOOL enabled = TRUE); -}; - -class LLUIImage : public LLRefCount -{ -public: - LLUIImage(const std::string& name, LLPointer<LLImageGL> image); - - void setClipRegion(const LLRectf& region); - void setScaleRegion(const LLRectf& region); - - LLPointer<LLImageGL> getImage() { return mImage; } - const LLPointer<LLImageGL>& getImage() const { return mImage; } - - void draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color = UI_VERTEX_COLOR) const; - void draw(S32 x, S32 y, const LLColor4& color = UI_VERTEX_COLOR) const; - void draw(const LLRect& rect, const LLColor4& color = UI_VERTEX_COLOR) const { draw(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), color); } - - void drawSolid(S32 x, S32 y, S32 width, S32 height, const LLColor4& color) const; - void drawSolid(const LLRect& rect, const LLColor4& color) const { drawSolid(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), color); } - void drawSolid(S32 x, S32 y, const LLColor4& color) const { drawSolid(x, y, mImage->getWidth(0), mImage->getHeight(0), color); } - - void drawBorder(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, S32 border_width) const; - void drawBorder(const LLRect& rect, const LLColor4& color, S32 border_width) const { drawBorder(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), color, border_width); } - void drawBorder(S32 x, S32 y, const LLColor4& color, S32 border_width) const { drawBorder(x, y, mImage->getWidth(0), mImage->getHeight(0), color, border_width); } - - const std::string& getName() const { return mName; } - - S32 getWidth() const; - S32 getHeight() const; - - // returns dimensions of underlying textures, which might not be equal to ui image portion - S32 getTextureWidth() const; - S32 getTextureHeight() const; - -protected: - std::string mName; - LLRectf mScaleRegion; - LLRectf mClipRegion; - LLPointer<LLImageGL> mImage; - BOOL mUniformScaling; - BOOL mNoClip; -}; - -typedef LLPointer<LLUIImage> LLUIImagePtr; - -template <typename T> -class LLTombStone : public LLRefCount -{ -public: - LLTombStone(T* target = NULL) : mTarget(target) {} - - void setTarget(T* target) { mTarget = target; } - T* getTarget() const { return mTarget; } -private: - T* mTarget; -}; - -// LLHandles are used to refer to objects whose lifetime you do not control or influence. -// Calling get() on a handle will return a pointer to the referenced object or NULL, -// if the object no longer exists. Note that during the lifetime of the returned pointer, -// you are assuming that the object will not be deleted by any action you perform, -// or any other thread, as normal when using pointers, so avoid using that pointer outside of -// the local code block. -// -// https://wiki.lindenlab.com/mediawiki/index.php?title=LLHandle&oldid=79669 - -template <typename T> -class LLHandle -{ -public: - LLHandle() : mTombStone(sDefaultTombStone) {} - const LLHandle<T>& operator =(const LLHandle<T>& other) - { - mTombStone = other.mTombStone; - return *this; - } - - bool isDead() const - { - return mTombStone->getTarget() == NULL; - } - - void markDead() - { - mTombStone = sDefaultTombStone; - } - - T* get() const - { - return mTombStone->getTarget(); - } - - friend bool operator== (const LLHandle<T>& lhs, const LLHandle<T>& rhs) - { - return lhs.mTombStone == rhs.mTombStone; - } - friend bool operator!= (const LLHandle<T>& lhs, const LLHandle<T>& rhs) - { - return !(lhs == rhs); - } - friend bool operator< (const LLHandle<T>& lhs, const LLHandle<T>& rhs) - { - return lhs.mTombStone < rhs.mTombStone; - } - friend bool operator> (const LLHandle<T>& lhs, const LLHandle<T>& rhs) - { - return lhs.mTombStone > rhs.mTombStone; - } -protected: - -protected: - LLPointer<LLTombStone<T> > mTombStone; - + static LLView* sRootView; + static LLHelp* sHelpImpl; private: - static LLPointer<LLTombStone<T> > sDefaultTombStone; -}; - -// initialize static "empty" tombstone pointer -template <typename T> LLPointer<LLTombStone<T> > LLHandle<T>::sDefaultTombStone = new LLTombStone<T>(); - - -template <typename T> -class LLRootHandle : public LLHandle<T> -{ -public: - LLRootHandle(T* object) { bind(object); } - LLRootHandle() {}; - ~LLRootHandle() { unbind(); } - - // this is redundant, since a LLRootHandle *is* an LLHandle - LLHandle<T> getHandle() { return LLHandle<T>(*this); } - - void bind(T* object) - { - // unbind existing tombstone - if (LLHandle<T>::mTombStone.notNull()) - { - if (LLHandle<T>::mTombStone->getTarget() == object) return; - LLHandle<T>::mTombStone->setTarget(NULL); - } - // tombstone reference counted, so no paired delete - LLHandle<T>::mTombStone = new LLTombStone<T>(object); - } - - void unbind() - { - LLHandle<T>::mTombStone->setTarget(NULL); - } - - //don't allow copying of root handles, since there should only be one -private: - LLRootHandle(const LLRootHandle& other) {}; + static LLImageProviderInterface* sImageProvider; + static std::vector<std::string> sXUIPaths; + static LLFrameTimer sMouseIdleTimer; }; -// Use this as a mixin for simple classes that need handles and when you don't -// want handles at multiple points of the inheritance hierarchy -template <typename T> -class LLHandleProvider -{ -protected: - typedef LLHandle<T> handle_type_t; - LLHandleProvider() - { - // provided here to enforce T deriving from LLHandleProvider<T> - } - - LLHandle<T> getHandle() - { - // perform lazy binding to avoid small tombstone allocations for handle - // providers whose handles are never referenced - mHandle.bind(static_cast<T*>(this)); - return mHandle; - } -private: - LLRootHandle<T> mHandle; -}; +// Moved LLLocalClipRect to lllocalcliprect.h +// Moved all LLHandle-related code to llhandle.h //RN: maybe this needs to moved elsewhere? class LLImageProviderInterface { -public: +protected: LLImageProviderInterface() {}; virtual ~LLImageProviderInterface() {}; - - virtual LLUIImagePtr getUIImage(const std::string& name) = 0; - virtual LLUIImagePtr getUIImageByID(const LLUUID& id) = 0; - virtual void cleanUp() = 0; -}; - -// This mix-in class adds support for tracking all instances of the specificed class parameter T -// The (optional) key associates a value of type KEY with a given instance of T, for quick lookup -// If KEY is not provided, then instances are stored in a simple list -template<typename T, typename KEY = T*> -class LLInstanceTracker : boost::noncopyable -{ public: - typedef typename std::map<KEY, T*>::iterator instance_iter; - typedef typename std::map<KEY, T*>::const_iterator instance_const_iter; - - static T* getInstance(KEY k) { instance_iter found = sInstances.find(k); return (found == sInstances.end()) ? NULL : found->second; } - - static instance_iter beginInstances() { return sInstances.begin(); } - static instance_iter endInstances() { return sInstances.end(); } - static S32 instanceCount() { return sInstances.size(); } -protected: - LLInstanceTracker(KEY key) { add(key); } - virtual ~LLInstanceTracker() { remove(); } - virtual void setKey(KEY key) { remove(); add(key); } - virtual const KEY& getKey() const { return mKey; } - -private: - void add(KEY key) - { - mKey = key; - sInstances[key] = static_cast<T*>(this); - } - void remove() { sInstances.erase(mKey); } - -private: - - KEY mKey; - static std::map<KEY, T*> sInstances; -}; - -template<typename T> -class LLInstanceTracker<T, T*> : boost::noncopyable -{ -public: - typedef typename std::set<T*>::iterator instance_iter; - typedef typename std::set<T*>::const_iterator instance_const_iter; - - static instance_iter instancesBegin() { return sInstances.begin(); } - static instance_iter instancesEnd() { return sInstances.end(); } - static S32 instanceCount() { return sInstances.size(); } - -protected: - LLInstanceTracker() { sInstances.insert(static_cast<T*>(this)); } - virtual ~LLInstanceTracker() { sInstances.erase(static_cast<T*>(this)); } - - static std::set<T*> sInstances; + virtual LLPointer<LLUIImage> getUIImage(const std::string& name, S32 priority) = 0; + virtual LLPointer<LLUIImage> getUIImageByID(const LLUUID& id, S32 priority) = 0; + virtual void cleanUp() = 0; }; -template <typename T, typename KEY> std::map<KEY, T*> LLInstanceTracker<T, KEY>::sInstances; -template <typename T> std::set<T*> LLInstanceTracker<T, T*>::sInstances; - class LLCallbackRegistry { public: - typedef boost::signal<void()> callback_signal_t; + typedef boost::signals2::signal<void()> callback_signal_t; void registerCallback(const callback_signal_t::slot_type& slot) { @@ -746,93 +334,145 @@ private: template <typename T> LLRegisterWith<LLInitClassList> LLInitClass<T>::sRegister(&T::initClass); template <typename T> LLRegisterWith<LLDestroyClassList> LLDestroyClass<T>::sRegister(&T::destroyClass); - -template <typename DERIVED> -class LLParamBlock +// useful parameter blocks +struct TimeIntervalParam : public LLInitParam::Choice<TimeIntervalParam> { -protected: - LLParamBlock() { sBlock = (DERIVED*)this; } + Alternative<F32> seconds; + Alternative<S32> frames; + TimeIntervalParam() + : seconds("seconds"), + frames("frames") + {} +}; - typedef typename boost::add_const<DERIVED>::type Tconst; +template <class T> +class LLUICachedControl : public LLCachedControl<T> +{ +public: + // This constructor will declare a control if it doesn't exist in the contol group + LLUICachedControl(const std::string& name, + const T& default_value, + const std::string& comment = "Declared In Code") + : LLCachedControl<T>(LLUI::getControlControlGroup(name), name, default_value, comment) + {} + + // This constructor will signal an error if the control doesn't exist in the control group + LLUICachedControl(const std::string& name) + : LLCachedControl<T>(LLUI::getControlControlGroup(name), name) + {} +}; - template <typename T> - class LLMandatoryParam +namespace LLInitParam +{ + template<> + class TypedParam<LLRect> + : public BlockValue<LLRect> { + typedef BlockValue<LLRect> super_t; public: - typedef typename boost::add_const<T>::type T_const; + Optional<S32> left, + top, + right, + bottom, + width, + height; - LLMandatoryParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {} - LLMandatoryParam(const LLMandatoryParam<T>& other) : mVal(other.mVal) {} + TypedParam(BlockDescriptor& descriptor, const char* name, const LLRect& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count); - DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; } - operator T() const { return mVal; } - T operator=(T_const set_value) { mVal = set_value; return mVal; } + void setValueFromBlock() const; + void setBlockFromValue(); + }; - private: - T mVal; - DERIVED* mBlock; + template<> + struct TypeValues<LLUIColor> : public TypeValuesHelper<LLUIColor> + { + static void declareValues(); }; - template <typename T> - class LLOptionalParam + template<> + class TypedParam<LLUIColor> + : public BlockValue<LLUIColor> { + typedef BlockValue<LLUIColor> super_t; public: - typedef typename boost::add_const<T>::type T_const; - - LLOptionalParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {} - LLOptionalParam() : mBlock(sBlock) {} - LLOptionalParam(const LLOptionalParam<T>& other) : mVal(other.mVal) {} - - DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; } - operator T() const { return mVal; } - T operator=(T_const set_value) { mVal = set_value; return mVal; } + Optional<F32> red, + green, + blue, + alpha; + Optional<std::string> control; + + TypedParam(BlockDescriptor& descriptor, const char* name, const LLUIColor& value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count); + void setValueFromBlock() const; + void setBlockFromValue(); + }; - private: - T mVal; - DERIVED* mBlock; + // provide a better default for Optional<const LLFontGL*> than NULL + template <> + struct DefaultInitializer<const LLFontGL*> + { + // return reference to a single default instance of T + // built-in types will be initialized to zero, default constructor otherwise + static const LLFontGL* get() + { + static const LLFontGL* sDefaultFont = LLFontGL::getFontDefault(); + return sDefaultFont; + } }; - // specialization that requires initialization for reference types - template <typename T> - class LLOptionalParam <T&> + + template<> + class TypedParam<const LLFontGL*> + : public BlockValue<const LLFontGL*> { + typedef BlockValue<const LLFontGL*> super_t; public: - typedef typename boost::add_const<T&>::type T_const; + Mandatory<std::string> name; + Optional<std::string> size, + style; + + TypedParam(BlockDescriptor& descriptor, const char* name, const LLFontGL* const value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count); + void setValueFromBlock() const; + void setBlockFromValue(); + }; - LLOptionalParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {} - LLOptionalParam(const LLOptionalParam<T&>& other) : mVal(other.mVal) {} + template<> + struct TypeValues<LLFontGL::HAlign> : public TypeValuesHelper<LLFontGL::HAlign> + { + static void declareValues(); + }; - DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; } - operator T&() const { return mVal; } - T& operator=(T_const set_value) { mVal = set_value; return mVal; } + template<> + struct TypeValues<LLFontGL::VAlign> : public TypeValuesHelper<LLFontGL::VAlign> + { + static void declareValues(); + }; - private: - T& mVal; - DERIVED* mBlock; + template<> + struct TypeValues<LLFontGL::ShadowType> : public TypeValuesHelper<LLFontGL::ShadowType> + { + static void declareValues(); }; - // specialization that initializes pointer params to NULL - template<typename T> - class LLOptionalParam<T*> + template<> + struct ParamCompare<const LLFontGL*, false> { - public: - typedef typename boost::add_const<T*>::type T_const; - - LLOptionalParam(T_const initial_val) : mVal(initial_val), mBlock(sBlock) {} - LLOptionalParam() : mVal((T*)NULL), mBlock(sBlock) {} - LLOptionalParam(const LLOptionalParam<T*>& other) : mVal(other.mVal) {} - - DERIVED& operator ()(T_const set_value) { mVal = set_value; return *mBlock; } - operator T*() const { return mVal; } - T* operator=(T_const set_value) { mVal = set_value; return mVal; } - private: - T* mVal; - DERIVED* mBlock; + static bool equals(const LLFontGL* a, const LLFontGL* b); }; - static DERIVED* sBlock; -}; -template <typename T> T* LLParamBlock<T>::sBlock = NULL; + template<> + class TypedParam<LLCoordGL> + : public BlockValue<LLCoordGL> + { + typedef BlockValue<LLCoordGL> super_t; + public: + Optional<S32> x, + y; + + TypedParam(BlockDescriptor& descriptor, const char* name, LLCoordGL value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count); + void setValueFromBlock() const; + void setBlockFromValue(); + }; +} #endif diff --git a/indra/llui/lluicolortable.cpp b/indra/llui/lluicolortable.cpp new file mode 100644 index 0000000000..9be33483d0 --- /dev/null +++ b/indra/llui/lluicolortable.cpp @@ -0,0 +1,332 @@ +/** + * @file lluicolortable.cpp + * @brief brief LLUIColorTable class implementation file + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include <queue> + +#include "lldir.h" +#include "llui.h" +#include "lluicolortable.h" +#include "lluictrlfactory.h" + +LLUIColorTable::ColorParams::ColorParams() +: value("value"), + reference("reference") +{ +} + +LLUIColorTable::ColorEntryParams::ColorEntryParams() +: name("name"), + color("") +{ +} + +LLUIColorTable::Params::Params() +: color_entries("color") +{ +} + +void LLUIColorTable::insertFromParams(const Params& p) +{ + // this map will contain all color references after the following loop + typedef std::map<std::string, std::string> string_string_map_t; + string_string_map_t unresolved_refs; + + for(LLInitParam::ParamIterator<ColorEntryParams>::const_iterator it = p.color_entries().begin(); + it != p.color_entries().end(); + ++it) + { + ColorEntryParams color_entry = *it; + if(color_entry.color.value.isChosen()) + { + if(mUserSetColors.find(color_entry.name)!=mUserSetColors.end()) + { + setColor(color_entry.name, color_entry.color.value); + } + else + { + setColor(color_entry.name, color_entry.color.value, mLoadedColors); + } + } + else + { + unresolved_refs.insert(string_string_map_t::value_type(color_entry.name, color_entry.color.reference)); + } + } + + // maintain an in order queue of visited references for better debugging of cycles + typedef std::queue<std::string> string_queue_t; + string_queue_t ref_chain; + + // maintain a map of the previously visited references in the reference chain for detecting cycles + typedef std::map<std::string, string_string_map_t::iterator> string_color_ref_iter_map_t; + string_color_ref_iter_map_t visited_refs; + + // loop through the unresolved color references until there are none left + while(!unresolved_refs.empty()) + { + // we haven't visited any references yet + visited_refs.clear(); + + string_string_map_t::iterator current = unresolved_refs.begin(); + string_string_map_t::iterator previous; + + while(true) + { + if(current != unresolved_refs.end()) + { + // locate the current reference in the previously visited references... + string_color_ref_iter_map_t::iterator visited = visited_refs.lower_bound(current->first); + if(visited != visited_refs.end() + && !(visited_refs.key_comp()(current->first, visited->first))) + { + // ...if we find the current reference in the previously visited references + // we know that there is a cycle + std::string ending_ref = current->first; + std::string warning("The following colors form a cycle: "); + + // warn about the references in the chain and remove them from + // the unresolved references map because they cannot be resolved + for(string_color_ref_iter_map_t::iterator iter = visited_refs.begin(); + iter != visited_refs.end(); + ++iter) + { + if(!ref_chain.empty()) + { + warning += ref_chain.front() + "->"; + ref_chain.pop(); + } + unresolved_refs.erase(iter->second); + } + + llwarns << warning + ending_ref << llendl; + + break; + } + else + { + // ...continue along the reference chain + ref_chain.push(current->first); + visited_refs.insert(visited, string_color_ref_iter_map_t::value_type(current->first, current)); + } + } + else + { + // since this reference does not refer to another reference it must refer to an + // actual color, lets find it... + string_color_map_t::iterator color_value = mLoadedColors.find(previous->second); + + if(color_value != mLoadedColors.end()) + { + // ...we found the color, and we now add every reference in the reference chain + // to the color map + for(string_color_ref_iter_map_t::iterator iter = visited_refs.begin(); + iter != visited_refs.end(); + ++iter) + { + setColor(iter->first, color_value->second, mLoadedColors); + unresolved_refs.erase(iter->second); + } + + break; + } + else + { + // ... we did not find the color which imples that the current reference + // references a non-existant color + for(string_color_ref_iter_map_t::iterator iter = visited_refs.begin(); + iter != visited_refs.end(); + ++iter) + { + llwarns << iter->first << " references a non-existent color" << llendl; + unresolved_refs.erase(iter->second); + } + + break; + } + } + + // find the next color reference in the reference chain + previous = current; + current = unresolved_refs.find(current->second); + } + } +} + +void LLUIColorTable::clear() +{ + clearTable(mLoadedColors); + clearTable(mUserSetColors); +} + +LLUIColor LLUIColorTable::getColor(const std::string& name, const LLColor4& default_color) const +{ + string_color_map_t::const_iterator iter = mUserSetColors.find(name); + + if(iter != mUserSetColors.end()) + { + return LLUIColor(&iter->second); + } + + iter = mLoadedColors.find(name); + + if(iter != mLoadedColors.end()) + { + return LLUIColor(&iter->second); + } + + return LLUIColor(default_color); +} + +// update user color, loaded colors are parsed on initialization +void LLUIColorTable::setColor(const std::string& name, const LLColor4& color) +{ + setColor(name, color, mUserSetColors); + setColor(name, color, mLoadedColors); +} + +bool LLUIColorTable::loadFromSettings() +{ + bool result = false; + + std::string default_filename = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "colors.xml"); + result |= loadFromFilename(default_filename); + + std::string current_filename = gDirUtilp->getExpandedFilename(LL_PATH_TOP_SKIN, "colors.xml"); + if(current_filename != default_filename) + { + result |= loadFromFilename(current_filename); + } + + std::string user_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "colors.xml"); + loadFromFilename(user_filename); + + return result; +} + +void LLUIColorTable::saveUserSettings() const +{ + Params params; + + for(string_color_map_t::const_iterator it = mUserSetColors.begin(); + it != mUserSetColors.end(); + ++it) + { + ColorEntryParams color_entry; + color_entry.name = it->first; + color_entry.color.value = it->second; + + params.color_entries.add(color_entry); + } + + LLXMLNodePtr output_node = new LLXMLNode("colors", false); + LLXUIParser::instance().writeXUI(output_node, params); + + if(!output_node->isNull()) + { + const std::string& filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "colors.xml"); + LLFILE *fp = LLFile::fopen(filename, "w"); + + if(fp != NULL) + { + LLXMLNode::writeHeaderToFile(fp); + output_node->writeToFile(fp); + + fclose(fp); + } + } +} + +bool LLUIColorTable::colorExists(const std::string& color_name) const +{ + return ((mLoadedColors.find(color_name) != mLoadedColors.end()) + || (mUserSetColors.find(color_name) != mUserSetColors.end())); +} + +void LLUIColorTable::clearTable(string_color_map_t& table) +{ + for(string_color_map_t::iterator it = table.begin(); + it != table.end(); + ++it) + { + it->second = LLColor4::magenta; + } +} + +// this method inserts a color into the table if it does not exist +// if the color already exists it changes the color +void LLUIColorTable::setColor(const std::string& name, const LLColor4& color, string_color_map_t& table) +{ + string_color_map_t::iterator it = table.lower_bound(name); + if(it != table.end() + && !(table.key_comp()(name, it->first))) + { + it->second = color; + } + else + { + table.insert(it, string_color_map_t::value_type(name, color)); + } +} + +bool LLUIColorTable::loadFromFilename(const std::string& filename) +{ + LLXMLNodePtr root; + + if(!LLXMLNode::parseFile(filename, root, NULL)) + { + llwarns << "Unable to parse color file " << filename << llendl; + return false; + } + + if(!root->hasName("colors")) + { + llwarns << filename << " is not a valid color definition file" << llendl; + return false; + } + + Params params; + LLXUIParser::instance().readXUI(root, params, filename); + + if(params.validateBlock()) + { + insertFromParams(params); + } + else + { + llwarns << filename << " failed to load" << llendl; + return false; + } + + return true; +} diff --git a/indra/llui/lluicolortable.h b/indra/llui/lluicolortable.h new file mode 100644 index 0000000000..59be0c4f9a --- /dev/null +++ b/indra/llui/lluicolortable.h @@ -0,0 +1,106 @@ +/** + * @file lluicolortable.h + * @brief brief LLUIColorTable class header file + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLUICOLORTABLE_H_ +#define LL_LLUICOLORTABLE_H_ + +#include <map> + +#include "llinitparam.h" +#include "llsingleton.h" + +#include "v4color.h" + +class LLUIColor; + +class LLUIColorTable : public LLSingleton<LLUIColorTable> +{ +LOG_CLASS(LLUIColorTable); +public: + struct ColorParams : LLInitParam::Choice<ColorParams> + { + Alternative<LLColor4> value; + Alternative<std::string> reference; + + ColorParams(); + }; + + struct ColorEntryParams : LLInitParam::Block<ColorEntryParams> + { + Mandatory<std::string> name; + Mandatory<ColorParams> color; + + ColorEntryParams(); + }; + + struct Params : LLInitParam::Block<Params> + { + Multiple<ColorEntryParams> color_entries; + + Params(); + }; + + // define colors by passing in a param block that can be generated via XUI file or manually + void insertFromParams(const Params& p); + + // reset all colors to default magenta color + void clear(); + + // color lookup + LLUIColor getColor(const std::string& name, const LLColor4& default_color = LLColor4::magenta) const; + + // if the color is in the table, it's value is changed, otherwise it is added + void setColor(const std::string& name, const LLColor4& color); + + // returns true if color_name exists in the table + bool colorExists(const std::string& color_name) const; + + // loads colors from settings files + bool loadFromSettings(); + + // saves colors specified by the user to the users skin directory + void saveUserSettings() const; + +private: + bool loadFromFilename(const std::string& filename); + + // consider using sorted vector, can be much faster + typedef std::map<std::string, LLColor4> string_color_map_t; + + void clearTable(string_color_map_t& table); + void setColor(const std::string& name, const LLColor4& color, string_color_map_t& table); + + string_color_map_t mLoadedColors; + string_color_map_t mUserSetColors; +}; + +#endif // LL_LLUICOLORTABLE_H diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 9d97312ab0..3ade46d367 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -31,108 +31,343 @@ * $/LicenseInfo$ */ -//#include "llviewerprecompiledheaders.h" #include "linden_common.h" + +#define LLUICTRL_CPP #include "lluictrl.h" + #include "llfocusmgr.h" #include "llpanel.h" +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register<LLUICtrl> r("ui_ctrl"); -static LLRegisterWidget<LLUICtrl> r("ui_ctrl"); +// Compiler optimization, generate extern template +template class LLUICtrl* LLView::getChild<class LLUICtrl>( + const std::string& name, BOOL recurse) const; -LLFocusableElement::LLFocusableElement() -: mFocusLostCallback(NULL), - mFocusReceivedCallback(NULL), - mFocusChangedCallback(NULL), - mFocusCallbackUserData(NULL) +LLUICtrl::CallbackParam::CallbackParam() +: name("name"), + function_name("function"), + parameter("parameter"), + control_name("control") // Shortcut to control -> "control_name" for backwards compatability { + addSynonym(parameter, "userdata"); } -//virtual -LLFocusableElement::~LLFocusableElement() +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"); } -void LLFocusableElement::onFocusReceived() +LLUICtrl::Params::Params() +: tab_stop("tab_stop", true), + chrome("chrome", 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::getFontSansSerif()), + 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() { - if( mFocusReceivedCallback ) + return LLUICtrlFactory::getDefaultParams<LLUICtrl>(); +} + + +LLUICtrl::LLUICtrl(const LLUICtrl::Params& p, const LLViewModelPtr& viewmodel) +: LLView(p), + mTentative(FALSE), + mIsChrome(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) +{ + mUICtrlHandle.bind(this); +} + +void LLUICtrl::initFromParams(const Params& p) +{ + LLView::initFromParams(p); + + 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 if(p.enabled_controls.disabled.isChosen()) + { + LLControlVariable* control = findControl(p.enabled_controls.disabled); + if (control) + setDisabledControlVariable(control); + } + } + if(p.controls_visibility.isProvided()) + { + if (p.controls_visibility.visible.isChosen()) + { + LLControlVariable* control = findControl(p.controls_visibility.visible); + if (control) + setMakeVisibleControlVariable(control); + } + else if (p.controls_visibility.invisible.isChosen()) + { + LLControlVariable* control = findControl(p.controls_visibility.invisible); + if (control) + setMakeInvisibleControlVariable(control); + } + } + + 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()) { - mFocusReceivedCallback( this, mFocusCallbackUserData ); + setMouseEnterCallback(initCommitCallback(p.mouseenter_callback)); } - if( mFocusChangedCallback ) + + if(p.mouseleave_callback.isProvided()) { - mFocusChangedCallback( this, mFocusCallbackUserData ); + setMouseLeaveCallback(initCommitCallback(p.mouseleave_callback)); } } -void LLFocusableElement::onFocusLost() + +LLUICtrl::~LLUICtrl() { - if( mFocusLostCallback ) + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() + + if( gFocusMgr.getTopCtrl() == this ) { - mFocusLostCallback( this, mFocusCallbackUserData ); + llwarns << "UI Control holding top ctrl deleted: " << getName() << ". Top view removed." << llendl; + gFocusMgr.removeTopCtrlWithoutCallback( this ); } - if( mFocusChangedCallback ) + 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()) { - mFocusChangedCallback( this, mFocusCallbackUserData ); + if (cb.parameter.isProvided()) + return boost::bind(cb.function(), _1, cb.parameter); + else + return cb.function(); } + else + { + std::string function_name = cb.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()) + { + llwarns << "No callback found for: '" << function_name << "' in control: " << getName() << llendl; + } + } + return default_commit_handler; } -BOOL LLFocusableElement::hasFocus() const +LLUICtrl::enable_signal_t::slot_type LLUICtrl::initEnableCallback(const EnableCallbackParam& cb) { - return FALSE; + // 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; } -void LLFocusableElement::setFocus(BOOL b) +// 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()); + } +} - -LLUICtrl::LLUICtrl() : - mCommitCallback(NULL), - mLostTopCallback(NULL), - mValidateCallback(NULL), - mCallbackUserData(NULL), - mTentative(FALSE), - mTabStop(TRUE), - mIsChrome(FALSE) +//virtual +BOOL LLUICtrl::handleMouseDown(S32 x, S32 y, MASK mask) { + BOOL handled = LLView::handleMouseDown(x,y,mask); + if (mMouseDownSignal) + { + (*mMouseDownSignal)(this,x,y,mask); + } + return handled; } -LLUICtrl::LLUICtrl(const std::string& name, const LLRect& rect, BOOL mouse_opaque, - void (*on_commit_callback)(LLUICtrl*, void*), - void* callback_userdata, - U32 reshape) -: // can't make this automatically follow top and left, breaks lots - // of buttons in the UI. JC 7/20/2002 - LLView( name, rect, mouse_opaque, reshape ), - mCommitCallback( on_commit_callback) , - mLostTopCallback( NULL ), - mValidateCallback( NULL ), - mCallbackUserData( callback_userdata ), - mTentative( FALSE ), - mTabStop( TRUE ), - mIsChrome(FALSE) +//virtual +BOOL LLUICtrl::handleMouseUp(S32 x, S32 y, MASK mask) { + BOOL handled = LLView::handleMouseUp(x,y,mask); + if (mMouseUpSignal) + { + (*mMouseUpSignal)(this,x,y,mask); + } + return handled; } -LLUICtrl::~LLUICtrl() +//virtual +BOOL LLUICtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) { - gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() + BOOL handled = LLView::handleRightMouseDown(x,y,mask); + if (mRightMouseDownSignal) + { + (*mRightMouseDownSignal)(this,x,y,mask); + } + return handled; +} - if( gFocusMgr.getTopCtrl() == this ) +//virtual +BOOL LLUICtrl::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + BOOL handled = LLView::handleRightMouseUp(x,y,mask); + if(mRightMouseUpSignal) { - llwarns << "UI Control holding top ctrl deleted: " << getName() << ". Top view removed." << llendl; - gFocusMgr.removeTopCtrlWithoutCallback( this ); + (*mRightMouseUpSignal)(this,x,y,mask); } + return handled; } -void LLUICtrl::onCommit() +BOOL LLUICtrl::handleDoubleClick(S32 x, S32 y, MASK mask) { - if( mCommitCallback ) + BOOL handled = LLView::handleDoubleClick(x, y, mask); + if (mDoubleClickSignal) { - mCommitCallback( this, mCallbackUserData ); + (*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) + (*mCommitSignal)(this, getValue()); } //virtual @@ -141,10 +376,170 @@ BOOL LLUICtrl::isCtrl() const return TRUE; } +//virtual +void LLUICtrl::setValue(const LLSD& value) +{ + mViewModel->setValue(value); +} + //virtual LLSD LLUICtrl::getValue() const { - return LLSD(); + 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; +} + +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? + //llwarns << "setControlName called twice on same control!" << llendl; + mControlConnection.disconnect(); // disconnect current signal + mControlVariable = NULL; + } + + if (control) + { + mControlVariable = control; + mControlConnection = mControlVariable->getSignal()->connect(boost::bind(&controlListener, _2, getUICtrlHandle(), std::string("value"))); + setValue(mControlVariable->getValue()); + } +} + +//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); + 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, getUICtrlHandle(), 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, getUICtrlHandle(), 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, getUICtrlHandle(), 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, getUICtrlHandle(), std::string("invisible"))); + setVisible(!(mMakeInvisibleControlVariable->getValue().asBoolean())); + } +} +// static +bool LLUICtrl::controlListener(const LLSD& newvalue, LLHandle<LLUICtrl> 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 @@ -205,58 +600,6 @@ void LLUICtrl::setFocus(BOOL b) } } -void LLUICtrl::onFocusReceived() -{ - // trigger callbacks - LLFocusableElement::onFocusReceived(); - - // find first view in hierarchy above new focus that is a LLUICtrl - LLView* viewp = getParent(); - LLUICtrl* last_focus = gFocusMgr.getLastKeyboardFocus(); - - while (viewp && !viewp->isCtrl()) - { - viewp = viewp->getParent(); - } - - // and if it has newly gained focus, call onFocusReceived() - LLUICtrl* ctrlp = static_cast<LLUICtrl*>(viewp); - if (ctrlp && (!last_focus || !last_focus->hasAncestor(ctrlp))) - { - ctrlp->onFocusReceived(); - } -} - -void LLUICtrl::onFocusLost() -{ - // trigger callbacks - LLFocusableElement::onFocusLost(); - - // find first view in hierarchy above old focus that is a LLUICtrl - LLView* viewp = getParent(); - while (viewp && !viewp->isCtrl()) - { - viewp = viewp->getParent(); - } - - // and if it has just lost focus, call onFocusReceived() - LLUICtrl* ctrlp = static_cast<LLUICtrl*>(viewp); - // hasFocus() includes any descendants - if (ctrlp && !ctrlp->hasFocus()) - { - ctrlp->onFocusLost(); - } -} - -void LLUICtrl::onLostTop() -{ - if (mLostTopCallback) - { - mLostTopCallback(this, mCallbackUserData); - } -} - - // virtual void LLUICtrl::setTabStop( BOOL b ) { @@ -278,12 +621,13 @@ BOOL LLUICtrl::acceptsTextInput() const //virtual BOOL LLUICtrl::isDirty() const { - return FALSE; + return mViewModel->isDirty(); }; //virtual void LLUICtrl::resetDirty() { + mViewModel->resetDirty(); } // virtual @@ -305,7 +649,6 @@ void LLUICtrl::setIsChrome(BOOL is_chrome) // virtual BOOL LLUICtrl::getIsChrome() const { - LLView* parent_ctrl = getParent(); while(parent_ctrl) { @@ -456,7 +799,8 @@ BOOL LLUICtrl::focusNextItem(BOOL text_fields_only) { // this assumes that this method is called on the focus root. LLCtrlQuery query = getTabOrderQuery(); - if(text_fields_only || LLUI::sConfigGroup->getBOOL("TabToTextFieldsOnly")) + static LLUICachedControl<bool> tab_to_text_fields_only ("TabToTextFieldsOnly", false); + if(text_fields_only || tab_to_text_fields_only) { query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); } @@ -468,7 +812,8 @@ BOOL LLUICtrl::focusPrevItem(BOOL text_fields_only) { // this assumes that this method is called on the focus root. LLCtrlQuery query = getTabOrderQuery(); - if(text_fields_only || LLUI::sConfigGroup->getBOOL("TabToTextFieldsOnly")) + static LLUICachedControl<bool> tab_to_text_fields_only ("TabToTextFieldsOnly", false); + if(text_fields_only || tab_to_text_fields_only) { query.addPreFilter(LLUICtrl::LLTextInputFilter::getInstance()); } @@ -492,65 +837,6 @@ LLUICtrl* LLUICtrl::findRootMostFocusRoot() return focus_root; } - -/* -// Don't let the children handle the tool tip. Handle it here instead. -BOOL LLUICtrl::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen) -{ - BOOL handled = FALSE; - if (getVisible() && pointInView( x, y ) ) - { - if( !mToolTipMsg.empty() ) - { - msg = mToolTipMsg; - - // Convert rect local to screen coordinates - localPointToScreen( - 0, 0, - &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); - localPointToScreen( - getRect().getWidth(), getRect().getHeight(), - &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) ); - - handled = TRUE; - } - } - - if (!handled) - { - return LLView::handleToolTip(x, y, msg, sticky_rect_screen); - } - - return handled; -}*/ - -void LLUICtrl::initFromXML(LLXMLNodePtr node, LLView* parent) -{ - BOOL has_tab_stop = hasTabStop(); - node->getAttributeBOOL("tab_stop", has_tab_stop); - - setTabStop(has_tab_stop); - - LLView::initFromXML(node, parent); -} - -LLXMLNodePtr LLUICtrl::getXML(bool save_children) const -{ - LLXMLNodePtr node = LLView::getXML(save_children); - node->createChild("tab_stop", TRUE)->setBoolValue(hasTabStop()); - - return node; -} - -//static -LLView* LLUICtrl::fromXML(LLXMLNodePtr node, LLView* parent, class LLUICtrlFactory* factory) -{ - LLUICtrl* ctrl = new LLUICtrl(); - ctrl->initFromXML(node, parent); - return ctrl; -} - - // Skip over any parents that are not LLUICtrl's // Used in focus logic since only LLUICtrl elements can have focus LLUICtrl* LLUICtrl::getParentUICtrl() const @@ -570,6 +856,59 @@ LLUICtrl* LLUICtrl::getParentUICtrl() const 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<LLPanel *>(ctrl); + + if (panel) + { + // does the panel have a sub-panel with a help topic? + LLPanel *subpanel = panel->childGetVisiblePanelWithHelp(); + 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->childGetVisibleTabWithHelp(); + if (tab) + { + help_topic_out = tab->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<void (LLUICtrl*,void*)> cb, void* data) +{ + return setCommitCallback( boost::bind(cb, _1, data)); +} +boost::signals2::connection LLUICtrl::setValidateBeforeCommit( boost::function<bool (const LLSD& data)> cb ) +{ + if (!mValidateSignal) mValidateSignal = new enable_signal_t(); + return mValidateSignal->connect(boost::bind(cb, _2)); +} + // virtual void LLUICtrl::setTentative(BOOL b) { @@ -583,18 +922,59 @@ BOOL LLUICtrl::getTentative() const } // virtual -void LLUICtrl::setDoubleClickCallback( void (*cb)(void*) ) +void LLUICtrl::setColor(const LLColor4& color) +{ } + +boost::signals2::connection LLUICtrl::setCommitCallback( const commit_signal_t::slot_type& cb ) { + if (!mCommitSignal) mCommitSignal = new commit_signal_t(); + return mCommitSignal->connect(cb); } -// virtual -void LLUICtrl::setColor(const LLColor4& color) -{ } +boost::signals2::connection LLUICtrl::setValidateCallback( const enable_signal_t::slot_type& cb ) +{ + if (!mValidateSignal) mValidateSignal = new enable_signal_t(); + return mValidateSignal->connect(cb); +} -// virtual -void LLUICtrl::setMinValue(LLSD min_value) -{ } +boost::signals2::connection LLUICtrl::setMouseEnterCallback( const commit_signal_t::slot_type& cb ) +{ + if (!mMouseEnterSignal) mMouseEnterSignal = new commit_signal_t(); + return mMouseEnterSignal->connect(cb); +} -// virtual -void LLUICtrl::setMaxValue(LLSD max_value) -{ } +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); +} diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index db41af8470..b9a4f61e15 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -34,57 +34,133 @@ #ifndef LL_LLUICTRL_H #define LL_LLUICTRL_H -#include "llview.h" +//#include "llboost.h" #include "llrect.h" #include "llsd.h" +#include <boost/function.hpp> +#include <boost/signals2.hpp> + +#include "llinitparam.h" +#include "llview.h" +#include "llviewmodel.h" // *TODO move dependency to .cpp file + +const BOOL TAKE_FOCUS_YES = TRUE; +const BOOL TAKE_FOCUS_NO = FALSE; +// NOTE: the LLFocusableElement class declaration has been moved from here to llfocusmgr.h. -class LLFocusableElement +class LLUICtrl + : public LLView, public boost::signals2::trackable { - friend class LLFocusMgr; // allow access to focus change handlers public: - LLFocusableElement(); - virtual ~LLFocusableElement(); - virtual void setFocus( BOOL b ); - virtual BOOL hasFocus() const; - void setFocusLostCallback(void (*cb)(LLFocusableElement* caller, void*), void* user_data = NULL) { mFocusLostCallback = cb; mFocusCallbackUserData = user_data; } - void setFocusReceivedCallback( void (*cb)(LLFocusableElement*, void*), void* user_data = NULL) { mFocusReceivedCallback = cb; mFocusCallbackUserData = user_data; } - void setFocusChangedCallback( void (*cb)(LLFocusableElement*, void*), void* user_data = NULL ) { mFocusChangedCallback = cb; mFocusCallbackUserData = user_data; } + typedef boost::function<void (LLUICtrl* ctrl, const LLSD& param)> commit_callback_t; + typedef boost::signals2::signal<void (LLUICtrl* ctrl, const LLSD& param)> commit_signal_t; + // *TODO: add xml support for this type of signal in the future + typedef boost::signals2::signal<void (LLUICtrl* ctrl, S32 x, S32 y, MASK mask)> mouse_signal_t; + + typedef boost::function<bool (LLUICtrl* ctrl, const LLSD& param)> enable_callback_t; + typedef boost::signals2::signal<bool (LLUICtrl* ctrl, const LLSD& param), boost_boolean_combiner> enable_signal_t; + + struct CallbackParam : public LLInitParam::Block<CallbackParam> + { + Ignored name; -protected: - virtual void onFocusReceived(); - virtual void onFocusLost(); - void (*mFocusLostCallback)( LLFocusableElement* caller, void* userdata ); - void (*mFocusReceivedCallback)( LLFocusableElement* ctrl, void* userdata ); - void (*mFocusChangedCallback)( LLFocusableElement* ctrl, void* userdata ); - void* mFocusCallbackUserData; -}; + Optional<std::string> function_name; + Optional<LLSD> parameter; + + Optional<std::string> control_name; + + CallbackParam(); + }; + + struct CommitCallbackParam : public LLInitParam::Block<CommitCallbackParam, CallbackParam > + { + Optional<commit_callback_t> function; + }; + + // also used for visible callbacks + struct EnableCallbackParam : public LLInitParam::Block<EnableCallbackParam, CallbackParam > + { + Optional<enable_callback_t> function; + }; + + struct EnableControls : public LLInitParam::Choice<EnableControls> + { + Alternative<std::string> enabled; + Alternative<std::string> disabled; + + EnableControls(); + }; + struct ControlVisibility : public LLInitParam::Choice<ControlVisibility> + { + Alternative<std::string> visible; + Alternative<std::string> invisible; + + ControlVisibility(); + }; + struct Params : public LLInitParam::Block<Params, LLView::Params> + { + Optional<std::string> label; + Optional<bool> tab_stop, + chrome; + Optional<LLSD> initial_value; + + Optional<CommitCallbackParam> init_callback, + commit_callback; + Optional<EnableCallbackParam> validate_callback; + + Optional<CommitCallbackParam> mouseenter_callback; + Optional<CommitCallbackParam> mouseleave_callback; + + Optional<std::string> control_name; + Optional<EnableControls> enabled_controls; + Optional<ControlVisibility> controls_visibility; + + // font params + Optional<const LLFontGL*> font; + Optional<LLFontGL::HAlign> font_halign; + Optional<LLFontGL::VAlign> font_valign; + + // cruft from LLXMLNode implementation + Ignored type, + length; + + Params(); + }; -class LLUICtrl -: public LLView, public LLFocusableElement -{ -public: - typedef void (*LLUICtrlCallback)(LLUICtrl* ctrl, void* userdata); - typedef BOOL (*LLUICtrlValidate)(LLUICtrl* ctrl, void* userdata); - - LLUICtrl(); - LLUICtrl( const std::string& name, const LLRect& rect, BOOL mouse_opaque, - LLUICtrlCallback callback, - void* callback_userdata, - U32 reshape=FOLLOWS_NONE); /*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&); + +public: // LLView interface - /*virtual*/ void initFromXML(LLXMLNodePtr node, LLView* parent); - /*virtual*/ LLXMLNodePtr getXML(bool save_children = true) const; /*virtual*/ BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); - /*virtual*/ void onFocusReceived(); - /*virtual*/ void onFocusLost(); /*virtual*/ BOOL isCtrl() const; /*virtual*/ void setTentative(BOOL b); /*virtual*/ BOOL getTentative() const; + /*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask); + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL canFocusChildren() const; + /*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 handleRightMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); // From LLFocusableElement /*virtual*/ void setFocus( BOOL b ); @@ -97,7 +173,23 @@ public: 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); + + LLControlVariable* getControlVariable() { return mControlVariable; } + + void setEnabledControlVariable(LLControlVariable* control); + void setDisabledControlVariable(LLControlVariable* control); + void setMakeVisibleControlVariable(LLControlVariable* control); + void setMakeInvisibleControlVariable(LLControlVariable* control); + + 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); @@ -108,17 +200,17 @@ public: virtual BOOL isDirty() const; // Defauls to false virtual void resetDirty(); //Defaults to no-op - // Call appropriate callbacks - virtual void onLostTop(); // called when registered as top ctrl and user clicks elsewhere + // 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 setDoubleClickCallback( void (*cb)(void*) ); + virtual void setColor(const LLColor4& color); - virtual void setMinValue(LLSD min_value); - virtual void setMaxValue(LLSD max_value); BOOL focusNextItem(BOOL text_entry_only); BOOL focusPrevItem(BOOL text_entry_only); @@ -126,6 +218,7 @@ public: BOOL focusLastItem(BOOL prefer_text_fields = FALSE); // Non Virtuals + LLHandle<LLUICtrl> getUICtrlHandle() const { return mUICtrlHandle; } BOOL getIsChrome() const; void setTabStop( BOOL b ); @@ -133,16 +226,28 @@ public: LLUICtrl* getParentUICtrl() const; - void* getCallbackUserData() const { return mCallbackUserData; } - void setCallbackUserData( void* data ) { mCallbackUserData = data; } + // 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 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 ); - void setCommitCallback( void (*cb)(LLUICtrl*, void*) ) { mCommitCallback = cb; } - void setValidateBeforeCommit( BOOL(*cb)(LLUICtrl*, void*) ) { mValidateCallback = cb; } - void setLostTopCallback( void (*cb)(LLUICtrl*, void*) ) { mLostTopCallback = 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 ); - static LLView* fromXML(LLXMLNodePtr node, LLView* parent, class LLUICtrlFactory* factory); + boost::signals2::connection setDoubleClickCallback( const mouse_signal_t::slot_type& cb ); + + // *TODO: Deprecate; for backwards compatability only: + boost::signals2::connection setCommitCallback( boost::function<void (LLUICtrl*,void*)> cb, void* data); + boost::signals2::connection setValidateBeforeCommit( boost::function<bool (const LLSD& data)> cb ); - LLUICtrl* findRootMostFocusRoot(); + LLUICtrl* findRootMostFocusRoot(); class LLTextInputFilter : public LLQueryFilter, public LLSingleton<LLTextInputFilter> { @@ -151,22 +256,57 @@ public: return filterResult_t(view->isCtrl() && static_cast<const LLUICtrl *>(view)->acceptsTextInput(), TRUE); } }; + + template <typename F, typename DERIVED> class CallbackRegistry : public LLRegistrySingleton<std::string, F, DERIVED > + {}; + class CommitCallbackRegistry : public CallbackRegistry<commit_callback_t, CommitCallbackRegistry>{}; + // the enable callback registry is also used for visiblity callbacks + class EnableCallbackRegistry : public CallbackRegistry<enable_callback_t, EnableCallbackRegistry>{}; + protected: - void (*mCommitCallback)( LLUICtrl* ctrl, void* userdata ); - void (*mLostTopCallback)( LLUICtrl* ctrl, void* userdata ); - BOOL (*mValidateCallback)( LLUICtrl* ctrl, void* userdata ); + static bool controlListener(const LLSD& newvalue, LLHandle<LLUICtrl> 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; - void* mCallbackUserData; + 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; private: BOOL mTabStop; BOOL mIsChrome; BOOL mTentative; + LLRootHandle<LLUICtrl> mUICtrlHandle; class DefaultTabGroupFirstSorter; }; +// Build time optimization, generate once in .cpp file +#ifndef LLUICTRL_CPP +extern template class LLUICtrl* LLView::getChild<class LLUICtrl>( + 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 983cc53f69..27237800d4 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -32,8 +32,11 @@ #include "linden_common.h" +#define LLUICTRLFACTORY_CPP #include "lluictrlfactory.h" +#include "llxmlnode.h" + #include <fstream> #include <boost/tokenizer.hpp> @@ -41,216 +44,199 @@ #include "llcontrol.h" #include "lldir.h" #include "v4color.h" +#include "v3dmath.h" +#include "llquaternion.h" // this library includes -#include "llbutton.h" -#include "llcheckboxctrl.h" -//#include "llcolorswatch.h" -#include "llcombobox.h" -#include "llcontrol.h" -#include "lldir.h" -#include "llevent.h" #include "llfloater.h" -#include "lliconctrl.h" -#include "lllineeditor.h" -#include "llmenugl.h" -#include "llradiogroup.h" -#include "llscrollcontainer.h" -#include "llscrollingpanellist.h" -#include "llscrolllistctrl.h" -#include "llslider.h" -#include "llsliderctrl.h" -#include "llmultislider.h" -#include "llmultisliderctrl.h" -#include "llspinctrl.h" -#include "lltabcontainer.h" -#include "lltabcontainervertical.h" -#include "lltextbox.h" -#include "lltexteditor.h" -#include "llui.h" -#include "llviewborder.h" - -const char XML_HEADER[] = "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>\n"; - -const S32 HPAD = 4; -const S32 VPAD = 4; -const S32 FLOATER_H_MARGIN = 15; -const S32 MIN_WIDGET_HEIGHT = 10; - -std::vector<std::string> LLUICtrlFactory::sXUIPaths; + +LLFastTimer::DeclareTimer FTM_WIDGET_CONSTRUCTION("Widget Construction"); +LLFastTimer::DeclareTimer FTM_INIT_FROM_PARAMS("Widget InitFromParams"); +LLFastTimer::DeclareTimer FTM_WIDGET_SETUP("Widget Setup"); + +//----------------------------------------------------------------------------- // UI Ctrl class for padding class LLUICtrlLocate : public LLUICtrl { public: - LLUICtrlLocate() : LLUICtrl(std::string("locate"), LLRect(0,0,0,0), FALSE, NULL, NULL) { setTabStop(FALSE); } - virtual void draw() { } - - static LLView *fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> { - std::string name("pad"); - node->getAttributeString("name", name); + Params() + { + name = "locate"; + tab_stop = false; + } + }; + + LLUICtrlLocate(const Params& p) : LLUICtrl(p) {} + virtual void draw() { } - LLUICtrlLocate *new_ctrl = new LLUICtrlLocate(); - new_ctrl->setName(name); - new_ctrl->initFromXML(node, parent); - return new_ctrl; - } }; -static LLRegisterWidget<LLUICtrlLocate> r1("locate"); -static LLRegisterWidget<LLUICtrlLocate> r2("pad"); +static LLDefaultChildRegistry::Register<LLUICtrlLocate> r1("locate"); + +// Build time optimization, generate this once in .cpp file +template class LLUICtrlFactory* LLSingleton<class LLUICtrlFactory>::getInstance(); //----------------------------------------------------------------------------- // LLUICtrlFactory() //----------------------------------------------------------------------------- LLUICtrlFactory::LLUICtrlFactory() - : mDummyPanel(NULL) + : mDummyPanel(NULL) // instantiated when first needed { - setupPaths(); } LLUICtrlFactory::~LLUICtrlFactory() { - delete mDummyPanel; - mDummyPanel = NULL; + // go ahead and leak mDummyPanel since this is static destructor time + //delete mDummyPanel; + //mDummyPanel = NULL; } -void LLUICtrlFactory::setupPaths() +void LLUICtrlFactory::loadWidgetTemplate(const std::string& widget_tag, LLInitParam::BaseBlock& block) { - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_SKINS, "paths.xml"); + std::string filename = std::string("widgets") + gDirUtilp->getDirDelimiter() + widget_tag + ".xml"; + LLXMLNodePtr root_node; - LLXMLNodePtr root; - BOOL success = LLXMLNode::parseFile(filename, root, NULL); - sXUIPaths.clear(); - - if (success) + if (LLUICtrlFactory::getLayeredXMLNode(filename, root_node)) { - LLXMLNodePtr path; - - for (path = root->getFirstChild(); path.notNull(); path = path->getNextSibling()) + LLXUIParser::instance().readXUI(root_node, block, filename); + } +} + +static LLFastTimer::DeclareTimer FTM_CREATE_CHILDREN("Create XUI Children"); + +//static +void LLUICtrlFactory::createChildren(LLView* viewp, LLXMLNodePtr node, const widget_registry_t& registry, LLXMLNodePtr output_node) +{ + LLFastTimer ft(FTM_CREATE_CHILDREN); + if (node.isNull()) return; + + for (LLXMLNodePtr child_node = node->getFirstChild(); child_node.notNull(); child_node = child_node->getNextSibling()) + { + LLXMLNodePtr outputChild; + if (output_node) { - LLUIString path_val_ui(path->getValue()); - std::string language = LLUI::getLanguage(); - path_val_ui.setArg("[LANGUAGE]", language); + outputChild = output_node->createChild("", FALSE); + } - if (std::find(sXUIPaths.begin(), sXUIPaths.end(), path_val_ui.getString()) == sXUIPaths.end()) + 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)) { - sXUIPaths.push_back(path_val_ui.getString()); + // 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<ChildWidgetType> register("child_widget_name"); + llwarns << child_name << " is not a valid child of " << node->getName()->mString << llendl; + } + else + { + llwarns << "Could not create widget named " << child_node->getName()->mString << llendl; } } + + if (outputChild && !outputChild->mChildren && outputChild->mAttributes.empty() && outputChild->getValue().empty()) + { + output_node->deleteChild(outputChild); + } } - else // parsing failed - { - std::string slash = gDirUtilp->getDirDelimiter(); - std::string dir = "xui" + slash + "en-us"; - llwarns << "XUI::config file unable to open: " << filename << llendl; - sXUIPaths.push_back(dir); - } -} -// static -const std::vector<std::string>& LLUICtrlFactory::getXUIPaths() -{ - return sXUIPaths; } +static LLFastTimer::DeclareTimer FTM_XML_PARSE("XML Reading/Parsing"); //----------------------------------------------------------------------------- // getLayeredXMLNode() //----------------------------------------------------------------------------- bool LLUICtrlFactory::getLayeredXMLNode(const std::string &xui_filename, LLXMLNodePtr& root) { - std::string full_filename = gDirUtilp->findSkinnedFilename(sXUIPaths.front(), xui_filename); - if (full_filename.empty()) - { - llwarns << "Couldn't find UI description file: " << sXUIPaths.front() + gDirUtilp->getDirDelimiter() + xui_filename << llendl; - return false; - } + LLFastTimer timer(FTM_XML_PARSE); + return LLXMLNode::getLayeredXMLNode(xui_filename, root, LLUI::getXUIPaths()); +} + +//----------------------------------------------------------------------------- +// getLocalizedXMLNode() +//----------------------------------------------------------------------------- +bool LLUICtrlFactory::getLocalizedXMLNode(const std::string &xui_filename, LLXMLNodePtr& root) +{ + LLFastTimer timer(FTM_XML_PARSE); + std::string full_filename = gDirUtilp->findSkinnedFilename(LLUI::getLocalizedSkinPath(), xui_filename); if (!LLXMLNode::parseFile(full_filename, root, NULL)) { - // try filename as passed in since sometimes we load an xml file from a user-supplied path - if (!LLXMLNode::parseFile(xui_filename, root, NULL)) - { - llwarns << "Problem reading UI description file: " << xui_filename << llendl; - return false; - } + return false; } - - LLXMLNodePtr updateRoot; - - std::vector<std::string>::const_iterator itor; - - for (itor = sXUIPaths.begin(), ++itor; itor != sXUIPaths.end(); ++itor) + else { - std::string nodeName; - std::string updateName; - - std::string layer_filename = gDirUtilp->findSkinnedFilename((*itor), xui_filename); - if(layer_filename.empty()) - { - // no localized version of this file, that's ok, keep looking - continue; - } - - if (!LLXMLNode::parseFile(layer_filename, updateRoot, NULL)) - { - llwarns << "Problem reading localized UI description file: " << (*itor) + gDirUtilp->getDirDelimiter() + xui_filename << llendl; - return false; - } - - updateRoot->getAttributeString("name", updateName); - root->getAttributeString("name", nodeName); - - if (updateName == nodeName) - { - LLXMLNode::updateNode(root, updateRoot); - } + return true; } - - return true; } +static LLFastTimer::DeclareTimer FTM_BUILD_FLOATERS("Build Floaters"); //----------------------------------------------------------------------------- // buildFloater() //----------------------------------------------------------------------------- -void LLUICtrlFactory::buildFloater(LLFloater* floaterp, const std::string& filename, - const LLCallbackMap::map_t* factory_map, BOOL open) /* Flawfinder: ignore */ +bool LLUICtrlFactory::buildFloater(LLFloater* floaterp, const std::string& filename, LLXMLNodePtr output_node) { + LLFastTimer timer(FTM_BUILD_FLOATERS); LLXMLNodePtr root; - if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + //if exporting, only load the language being exported, + //instead of layering localized version on top of english + if (output_node) + { + if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root)) + { + llwarns << "Couldn't parse floater from: " << LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; + return false; + } + } + else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) { - return; + llwarns << "Couldn't parse floater from: " << LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; + return false; } // root must be called floater - if( !(root->hasName("floater") || root->hasName("multi_floater") ) ) + if( !(root->hasName("floater") || root->hasName("multi_floater")) ) { llwarns << "Root node should be named floater in: " << filename << llendl; - return; - } - - if (factory_map) - { - mFactoryStack.push_front(factory_map); + return false; } - - floaterp->initFloaterXML(root, NULL, this, open); /* Flawfinder: ignore */ - - if (LLUI::sShowXUINames) + + bool res = true; + + lldebugs << "Building floater " << filename << llendl; + mFileNames.push_back(gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), filename)); { - floaterp->setToolTip(filename); - } + if (!floaterp->getFactoryMap().empty()) + { + mFactoryStack.push_front(&floaterp->getFactoryMap()); + } - if (factory_map) - { - mFactoryStack.pop_front(); + // for local registry callbacks; define in constructor, referenced in XUI or postBuild + floaterp->getCommitCallbackRegistrar().pushScope(); + floaterp->getEnableCallbackRegistrar().pushScope(); + + res = floaterp->initFloaterXML(root, floaterp->getParent(), output_node); + + floaterp->setXMLFilename(filename); + + floaterp->getCommitCallbackRegistrar().popScope(); + floaterp->getEnableCallbackRegistrar().popScope(); + + if (!floaterp->getFactoryMap().empty()) + { + mFactoryStack.pop_front(); + } } - - LLHandle<LLFloater> handle = floaterp->getHandle(); - mBuiltFloaters[handle] = filename; + mFileNames.pop_back(); + + return res; } //----------------------------------------------------------------------------- @@ -258,34 +244,33 @@ void LLUICtrlFactory::buildFloater(LLFloater* floaterp, const std::string& filen //----------------------------------------------------------------------------- S32 LLUICtrlFactory::saveToXML(LLView* viewp, const std::string& filename) { - llofstream out(filename); - if (!out.good()) - { - llwarns << "Unable to open " << filename << " for output." << llendl; - return 1; - } - - out << XML_HEADER; - - LLXMLNodePtr xml_node = viewp->getXML(); - - xml_node->writeToOstream(out); - - out.close(); return 0; } +static LLFastTimer::DeclareTimer FTM_BUILD_PANELS("Build Panels"); + //----------------------------------------------------------------------------- // buildPanel() //----------------------------------------------------------------------------- -BOOL LLUICtrlFactory::buildPanel(LLPanel* panelp, const std::string& filename, - const LLCallbackMap::map_t* factory_map) +BOOL LLUICtrlFactory::buildPanel(LLPanel* panelp, const std::string& filename, LLXMLNodePtr output_node) { + LLFastTimer timer(FTM_BUILD_PANELS); BOOL didPost = FALSE; LLXMLNodePtr root; - if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) + //if exporting, only load the language being exported, + //instead of layering localized version on top of english + if (output_node) + { + if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root)) + { + llwarns << "Couldn't parse panel from: " << LLUI::getLocalizedSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; + return didPost; + } + } + else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) { + llwarns << "Couldn't parse panel from: " << LLUI::getSkinPath() + gDirUtilp->getDirDelimiter() + filename << llendl; return didPost; } @@ -296,167 +281,49 @@ BOOL LLUICtrlFactory::buildPanel(LLPanel* panelp, const std::string& filename, return didPost; } - if (factory_map) - { - mFactoryStack.push_front(factory_map); - } - - didPost = panelp->initPanelXML(root, NULL, this); - - if (LLUI::sShowXUINames) - { - panelp->setToolTip(filename); - } - - LLHandle<LLPanel> handle = panelp->getHandle(); - mBuiltPanels[handle] = filename; - - if (factory_map) - { - mFactoryStack.pop_front(); - } - - return didPost; -} - -//----------------------------------------------------------------------------- -// buildMenu() -//----------------------------------------------------------------------------- -LLMenuGL *LLUICtrlFactory::buildMenu(const std::string &filename, LLView* parentp) -{ - // TomY TODO: Break this function into buildMenu and buildMenuBar - LLXMLNodePtr root; - LLMenuGL* menu; - - if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) - { - return NULL; - } - - // root must be called panel - if( !root->hasName( "menu_bar" ) && !root->hasName( "menu" )) - { - llwarns << "Root node should be named menu bar or menu in : " << filename << llendl; - return NULL; - } - - if (root->hasName("menu")) - { - menu = (LLMenuGL*)LLMenuGL::fromXML(root, parentp, this); - } - else - { - menu = (LLMenuGL*)LLMenuBarGL::fromXML(root, parentp, this); - } - - if (LLUI::sShowXUINames) - { - menu->setToolTip(filename); - } - - return menu; -} - -//----------------------------------------------------------------------------- -// buildMenu() -//----------------------------------------------------------------------------- -LLPieMenu *LLUICtrlFactory::buildPieMenu(const std::string &filename, LLView* parentp) -{ - LLXMLNodePtr root; - - if (!LLUICtrlFactory::getLayeredXMLNode(filename, root)) - { - return NULL; - } - - // root must be called panel - if( !root->hasName( LL_PIE_MENU_TAG )) - { - llwarns << "Root node should be named " << LL_PIE_MENU_TAG << " in : " << filename << llendl; - return NULL; - } - - std::string name("menu"); - root->getAttributeString("name", name); - - LLPieMenu *menu = new LLPieMenu(name); - parentp->addChild(menu); - menu->initXML(root, parentp, this); - - if (LLUI::sShowXUINames) - { - menu->setToolTip(filename); - } - - return menu; -} + lldebugs << "Building panel " << filename << llendl; -//----------------------------------------------------------------------------- -// rebuild() -//----------------------------------------------------------------------------- -void LLUICtrlFactory::rebuild() -{ - built_panel_t::iterator built_panel_it; - for (built_panel_it = mBuiltPanels.begin(); - built_panel_it != mBuiltPanels.end(); - ++built_panel_it) + mFileNames.push_back(gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), filename)); { - std::string filename = built_panel_it->second; - LLPanel* panelp = built_panel_it->first.get(); - if (!panelp) + if (!panelp->getFactoryMap().empty()) { - continue; + mFactoryStack.push_front(&panelp->getFactoryMap()); } - llinfos << "Rebuilding UI panel " << panelp->getName() - << " from " << filename - << llendl; - BOOL visible = panelp->getVisible(); - panelp->setVisible(FALSE); - panelp->setFocus(FALSE); - panelp->deleteAllChildren(); - - buildPanel(panelp, filename.c_str(), &panelp->getFactoryMap()); - panelp->setVisible(visible); - } - - built_floater_t::iterator built_floater_it; - for (built_floater_it = mBuiltFloaters.begin(); - built_floater_it != mBuiltFloaters.end(); - ++built_floater_it) - { - LLFloater* floaterp = built_floater_it->first.get(); - if (!floaterp) + + // for local registry callbacks; define in constructor, referenced in XUI or postBuild + panelp->getCommitCallbackRegistrar().pushScope(); + panelp->getEnableCallbackRegistrar().pushScope(); + + didPost = panelp->initPanelXML(root, NULL, output_node); + + panelp->getCommitCallbackRegistrar().popScope(); + panelp->getEnableCallbackRegistrar().popScope(); + + panelp->setXMLFilename(filename); + + if (!panelp->getFactoryMap().empty()) { - continue; + mFactoryStack.pop_front(); } - std::string filename = built_floater_it->second; - llinfos << "Rebuilding UI floater " << floaterp->getName() - << " from " << filename - << llendl; - BOOL visible = floaterp->getVisible(); - floaterp->setVisible(FALSE); - floaterp->setFocus(FALSE); - floaterp->deleteAllChildren(); - - gFloaterView->removeChild(floaterp); - buildFloater(floaterp, filename, &floaterp->getFactoryMap()); - floaterp->setVisible(visible); } + mFileNames.pop_back(); + return didPost; } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- -LLView *LLUICtrlFactory::createCtrlWidget(LLPanel *parent, LLXMLNodePtr node) +static LLFastTimer::DeclareTimer FTM_CREATE_FROM_XML("Create child widget"); + +LLView *LLUICtrlFactory::createFromXML(LLXMLNodePtr node, LLView* parent, const std::string& filename, const widget_registry_t& registry, LLXMLNodePtr output_node) { + LLFastTimer timer(FTM_CREATE_FROM_XML); std::string ctrl_type = node->getName()->mString; LLStringUtil::toLower(ctrl_type); - - LLWidgetClassRegistry::factory_func_t func = LLWidgetClassRegistry::getInstance()->getCreatorFunc(ctrl_type); - if (func == NULL) + const LLWidgetCreatorFunc* funcp = registry.getValue(ctrl_type); + if (funcp == NULL) { - llwarns << "Unknown control type " << ctrl_type << llendl; return NULL; } @@ -464,27 +331,13 @@ LLView *LLUICtrlFactory::createCtrlWidget(LLPanel *parent, LLXMLNodePtr node) { if (mDummyPanel == NULL) { - mDummyPanel = new LLPanel; + LLPanel::Params p; + mDummyPanel = create<LLPanel>(p); } parent = mDummyPanel; } - LLView *ctrl = func(node, parent, this); - - return ctrl; -} - -LLView* LLUICtrlFactory::createWidget(LLPanel *parent, LLXMLNodePtr node) -{ - LLView* view = createCtrlWidget(parent, node); - - S32 tab_group = parent->getLastTabGroup(); - node->getAttributeS32("tab_group", tab_group); - - if (view) - { - parent->addChild(view, tab_group); - } - + LLView *view = (*funcp)(node, parent, output_node); + return view; } @@ -507,7 +360,8 @@ LLPanel* LLUICtrlFactory::createFactoryPanel(const std::string& name) return ret; } } - return NULL; + LLPanel::Params panel_p; + return create<LLPanel>(panel_p); } //----------------------------------------------------------------------------- @@ -517,11 +371,11 @@ BOOL LLUICtrlFactory::getAttributeColor(LLXMLNodePtr node, const std::string& na { std::string colorstring; BOOL res = node->getAttributeString(name.c_str(), colorstring); - if (res && LLUI::sColorsGroup) + if (res) { - if (LLUI::sColorsGroup->controlExists(colorstring)) + if (LLUIColorTable::instance().colorExists(colorstring)) { - color.setVec(LLUI::sColorsGroup->getColor(colorstring)); + color.setVec(LLUIColorTable::instance().getColor(colorstring)); } else { @@ -539,3 +393,67 @@ BOOL LLUICtrlFactory::getAttributeColor(LLXMLNodePtr node, const std::string& na return res; } +//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); +} + + +// Avoid directly using LLUI and LLDir in the template code +//static +std::string LLUICtrlFactory::findSkinnedFilename(const std::string& filename) +{ + return gDirUtilp->findSkinnedFilename(LLUI::getSkinPath(), filename); +} + +void LLUICtrlFactory::pushFactoryFunctions(const LLCallbackMap::map_t* map) +{ + mFactoryStack.push_back(map); +} + +void LLUICtrlFactory::popFactoryFunctions() +{ + if (!mFactoryStack.empty()) + { + mFactoryStack.pop_back(); + } +} + +//static +void LLUICtrlFactory::copyName(LLXMLNodePtr src, LLXMLNodePtr dest) +{ + dest->setName(src->getName()->mString); +} + +// 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, dummy_widget_creator_func_t creator_func, const std::string& tag) +{ + // associate parameter block type with template .xml file + std::string* existing_tag = LLWidgetNameRegistry::instance().getValue(param_block_type); + if (existing_tag != NULL && *existing_tag != tag) + { + llerrs << "Duplicate entry for T::Params, try creating empty param block in derived classes that inherit T::Params" << llendl; + } + LLWidgetNameRegistry ::instance().defaultRegistrar().add(param_block_type, tag); + // associate widget type with factory function + LLDefaultWidgetRegistry::instance().defaultRegistrar().add(widget_type, creator_func); + //FIXME: comment this in when working on schema generation + //LLWidgetTypeRegistry::instance().defaultRegistrar().add(tag, widget_type); + //LLDefaultParamBlockRegistry::instance().defaultRegistrar().add(widget_type, &getEmptyParamBlock<T>); +} + +//static +dummy_widget_creator_func_t* LLUICtrlFactory::getDefaultWidgetFunc(const std::type_info* widget_type) +{ + return LLDefaultWidgetRegistry::instance().getValue(widget_type); +} + +//static +const std::string* LLUICtrlFactory::getWidgetTag(const std::type_info* widget_type) +{ + return LLWidgetNameRegistry::instance().getValue(widget_type); +} + diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h index 5e7c24efc0..b1fa6add67 100644 --- a/indra/llui/lluictrlfactory.h +++ b/indra/llui/lluictrlfactory.h @@ -33,68 +33,400 @@ #ifndef LLUICTRLFACTORY_H #define LLUICTRLFACTORY_H +#include "llcallbackmap.h" +#include "llinitparam.h" +#include "llregistry.h" +#include "v4color.h" +#include "llfasttimer.h" + +#include "llxuiparser.h" + +#include <boost/function.hpp> #include <iosfwd> #include <stack> +#include <set> -#include "llcallbackmap.h" -#include "llfloater.h" - -class LLView; class LLPanel; +class LLFloater; +class LLView; -class LLUICtrlFactory : public LLSingleton<LLUICtrlFactory> + +// sort functor for typeid maps +struct LLCompareTypeID +{ + bool operator()(const std::type_info* lhs, const std::type_info* rhs) const + { + return lhs->before(*rhs); + } +}; + +// lookup widget constructor funcs by widget name +template <typename DERIVED_TYPE> +class LLChildRegistry : public LLRegistrySingleton<std::string, LLWidgetCreatorFunc, DERIVED_TYPE> { public: + typedef LLRegistrySingleton<std::string, LLWidgetCreatorFunc, DERIVED_TYPE> super_t; + // local static instance for registering a particular widget + template<typename T> + class Register : public super_t::StaticRegistrar + { + public: + // register with either the provided builder, or the generic templated builder + Register(const char* tag, LLWidgetCreatorFunc func = NULL); + }; + +protected: + LLChildRegistry() {} +}; + +class LLDefaultChildRegistry : public LLChildRegistry<LLDefaultChildRegistry> +{ +protected: + LLDefaultChildRegistry(){} + friend class LLSingleton<LLDefaultChildRegistry>; +}; + +// lookup widget name by type +class LLWidgetNameRegistry +: public LLRegistrySingleton<const std::type_info*, std::string, LLWidgetNameRegistry , LLCompareTypeID> +{}; + +// lookup factory functions for default widget instances by widget type +typedef LLView* (*dummy_widget_creator_func_t)(const std::string&); +class LLDefaultWidgetRegistry +: public LLRegistrySingleton<const std::type_info*, dummy_widget_creator_func_t, LLDefaultWidgetRegistry, LLCompareTypeID> +{}; + +// lookup function for generating empty param block by widget type +// this is used for schema generation +//typedef const LLInitParam::BaseBlock& (*empty_param_block_func_t)(); +//class LLDefaultParamBlockRegistry +//: public LLRegistrySingleton<const std::type_info*, empty_param_block_func_t, LLDefaultParamBlockRegistry, LLCompareTypeID> +//{}; + +extern LLFastTimer::DeclareTimer FTM_WIDGET_SETUP; +extern LLFastTimer::DeclareTimer FTM_WIDGET_CONSTRUCTION; +extern LLFastTimer::DeclareTimer FTM_INIT_FROM_PARAMS; + +// Build time optimization, generate this once in .cpp file +#ifndef LLUICTRLFACTORY_CPP +extern template class LLUICtrlFactory* LLSingleton<class LLUICtrlFactory>::getInstance(); +#endif + +class LLUICtrlFactory : public LLSingleton<LLUICtrlFactory> +{ +private: + friend class LLSingleton<LLUICtrlFactory>; LLUICtrlFactory(); - // do not call! needs to be public so run-time can clean up the singleton - virtual ~LLUICtrlFactory(); + ~LLUICtrlFactory(); + + // only partial specialization allowed in inner classes, so use extra dummy parameter + template <typename PARAM_BLOCK, int DUMMY> + class ParamDefaults : public LLSingleton<ParamDefaults<PARAM_BLOCK, DUMMY> > + { + public: + ParamDefaults() + { + // recursively initialize from base class param block + ((typename PARAM_BLOCK::base_block_t&)mPrototype).fillFrom(ParamDefaults<typename PARAM_BLOCK::base_block_t, DUMMY>::instance().get()); + // after initializing base classes, look up template file for this param block + const std::string* param_block_tag = getWidgetTag(&typeid(PARAM_BLOCK)); + if (param_block_tag) + { + LLUICtrlFactory::loadWidgetTemplate(*param_block_tag, mPrototype); + } + } + + const PARAM_BLOCK& get() { return mPrototype; } - void setupPaths(); + private: + PARAM_BLOCK mPrototype; + }; - void buildFloater(LLFloater* floaterp, const std::string &filename, - const LLCallbackMap::map_t* factory_map = NULL, BOOL open = TRUE); - BOOL buildPanel(LLPanel* panelp, const std::string &filename, - const LLCallbackMap::map_t* factory_map = NULL); + // base case for recursion, there are NO base classes of LLInitParam::BaseBlock + template<int DUMMY> + class ParamDefaults<LLInitParam::BaseBlock, DUMMY> : public LLSingleton<ParamDefaults<LLInitParam::BaseBlock, DUMMY> > + { + public: + const LLInitParam::BaseBlock& get() { return mBaseBlock; } + private: + LLInitParam::BaseBlock mBaseBlock; + }; + +public: - void removePanel(LLPanel* panelp) { mBuiltPanels.erase(panelp->getHandle()); } - void removeFloater(LLFloater* floaterp) { mBuiltFloaters.erase(floaterp->getHandle()); } + // get default parameter block for widget of a specific type + template<typename T> + static const typename T::Params& getDefaultParams() + { + //#pragma message("Generating ParamDefaults") + return ParamDefaults<typename T::Params, 0>::instance().get(); + } - class LLMenuGL *buildMenu(const std::string &filename, LLView* parentp); - class LLPieMenu *buildPieMenu(const std::string &filename, LLView* parentp); + bool buildFloater(LLFloater* floaterp, const std::string &filename, LLXMLNodePtr output_node); + BOOL buildPanel(LLPanel* panelp, const std::string &filename, LLXMLNodePtr output_node = NULL); // Does what you want for LLFloaters and LLPanels // Returns 0 on success S32 saveToXML(LLView* viewp, const std::string& filename); - // Rebuilds all currently built panels. - void rebuild(); + std::string getCurFileName() { return mFileNames.empty() ? "" : mFileNames.back(); } static BOOL getAttributeColor(LLXMLNodePtr node, const std::string& name, LLColor4& color); LLPanel* createFactoryPanel(const std::string& name); - virtual LLView* createCtrlWidget(LLPanel *parent, LLXMLNodePtr node); - virtual LLView* createWidget(LLPanel *parent, LLXMLNodePtr node); + void pushFactoryFunctions(const LLCallbackMap::map_t* map); + void popFactoryFunctions(); + + template<typename T> + static T* createWidget(typename T::Params& params, LLView* parent = NULL) + { + T* widget = NULL; + + if (!params.validateBlock()) + { + llwarns << getInstance()->getCurFileName() << ": Invalid parameter block for " << typeid(T).name() << llendl; + //return NULL; + } + + { + LLFastTimer timer(FTM_WIDGET_CONSTRUCTION); + widget = new T(params); + } + { + LLFastTimer timer(FTM_INIT_FROM_PARAMS); + widget->initFromParams(params); + } + + if (parent) + { + S32 tab_group = params.tab_group.isProvided() ? params.tab_group() : S32_MAX; + setCtrlParent(widget, parent, tab_group); + } + return widget; + } + + template<typename T> + static T* create(typename T::Params& params, LLView* parent = NULL) + { + params.fillFrom(ParamDefaults<typename T::Params, 0>::instance().get()); + + T* widget = createWidget<T>(params, parent); + if (widget) + { + widget->postBuild(); + } + + return widget; + } + + LLView* createFromXML(LLXMLNodePtr node, LLView* parent, const std::string& filename, const widget_registry_t&, LLXMLNodePtr output_node ); + + template<typename T> + static T* createFromFile(const std::string &filename, LLView *parent, const widget_registry_t& registry, LLXMLNodePtr output_node = NULL) + { + T* widget = NULL; + + std::string skinned_filename = findSkinnedFilename(filename); + getInstance()->mFileNames.push_back(skinned_filename); + { + LLXMLNodePtr root_node; + + //if exporting, only load the language being exported, + //instead of layering localized version on top of english + if (output_node) + { + if (!LLUICtrlFactory::getLocalizedXMLNode(filename, root_node)) + { + llwarns << "Couldn't parse XUI file: " << filename << llendl; + goto fail; + } + } + else if (!LLUICtrlFactory::getLayeredXMLNode(filename, root_node)) + { + llwarns << "Couldn't parse XUI file: " << skinned_filename << llendl; + goto fail; + } + + LLView* view = getInstance()->createFromXML(root_node, parent, filename, registry, output_node); + if (view) + { + widget = dynamic_cast<T*>(view); + // not of right type, so delete it + if (!widget) + { + delete view; + view = NULL; + } + + } + } +fail: + getInstance()->mFileNames.pop_back(); + return widget; + } + + template<class T> + static T* getDefaultWidget(const std::string& name) + { + dummy_widget_creator_func_t* dummy_func = getDefaultWidgetFunc(&typeid(T)); + return dummy_func ? dynamic_cast<T*>((*dummy_func)(name)) : NULL; + } + + template <class T> + static LLView* createDefaultWidget(const std::string& name) + { + typename T::Params params; + params.name(name); + + return create<T>(params); + } + + static void copyName(LLXMLNodePtr src, LLXMLNodePtr dest); + + template<typename T> + static T* defaultBuilder(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr output_node) + { + LLFastTimer timer(FTM_WIDGET_SETUP); + + typename T::Params params(getDefaultParams<T>()); + + LLXUIParser::instance().readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + + if (output_node) + { + // We always want to output top-left coordinates + typename T::Params output_params(params); + T::setupParamsForExport(output_params, parent); + // Export only the differences between this any default params + typename T::Params default_params(getDefaultParams<T>()); + copyName(node, output_node); + LLXUIParser::instance().writeXUI( + output_node, output_params, &default_params); + } + + // Apply layout transformations, usually munging rect + params.from_xui = true; + T::applyXUILayout(params, parent); + T* widget = createWidget<T>(params, parent); + + typedef typename T::child_registry_t registry_t; + + createChildren(widget, node, registry_t::instance(), output_node); + + if (widget && !widget->postBuild()) + { + delete widget; + widget = NULL; + } + + return widget; + } + + static void createChildren(LLView* viewp, LLXMLNodePtr node, const widget_registry_t&, LLXMLNodePtr output_node = NULL); static bool getLayeredXMLNode(const std::string &filename, LLXMLNodePtr& root); + + static bool getLocalizedXMLNode(const std::string &xui_filename, LLXMLNodePtr& root); - static const std::vector<std::string>& getXUIPaths(); + static void loadWidgetTemplate(const std::string& widget_tag, LLInitParam::BaseBlock& block); + + // helper function for adding widget type info to various registries + static void registerWidget(const std::type_info* widget_type, const std::type_info* param_block_type, dummy_widget_creator_func_t creator_func, const std::string& tag); private: - bool getLayeredXMLNodeImpl(const std::string &filename, LLXMLNodePtr& root); + // return default widget instance factory func for a given type + static dummy_widget_creator_func_t* getDefaultWidgetFunc(const std::type_info* widget_type); + + static const std::string* getWidgetTag(const std::type_info* widget_type); + + // this exists to get around dependency on llview + static void setCtrlParent(LLView* view, LLView* parent, S32 tab_group); + + // Avoid directly using LLUI and LLDir in the template code + static std::string findSkinnedFilename(const std::string& filename); - typedef std::map<LLHandle<LLPanel>, std::string> built_panel_t; - built_panel_t mBuiltPanels; + typedef std::deque<const LLCallbackMap::map_t*> factory_stack_t; + factory_stack_t mFactoryStack; - typedef std::map<LLHandle<LLFloater>, std::string> built_floater_t; - built_floater_t mBuiltFloaters; + LLPanel* mDummyPanel; + std::vector<std::string> mFileNames; +}; + +template<typename T> +const LLInitParam::BaseBlock& getEmptyParamBlock() +{ + static typename T::Params params; + return params; +} + +// this is here to make gcc happy with reference to LLUICtrlFactory +template<typename DERIVED> +template<typename T> +LLChildRegistry<DERIVED>::Register<T>::Register(const char* tag, LLWidgetCreatorFunc func) +: LLChildRegistry<DERIVED>::StaticRegistrar(tag, func.empty() ? (LLWidgetCreatorFunc)&LLUICtrlFactory::defaultBuilder<T> : func) +{ + // add this widget to various registries + LLUICtrlFactory::instance().registerWidget(&typeid(T), &typeid(typename T::Params), &LLUICtrlFactory::createDefaultWidget<T>, tag); + + // since registry_t depends on T, do this in line here + typedef typename T::child_registry_t registry_t; + LLChildRegistryRegistry::instance().defaultRegistrar().add(&typeid(T), registry_t::instance()); +} - std::deque<const LLCallbackMap::map_t*> mFactoryStack; - static std::vector<std::string> sXUIPaths; +typedef boost::function<LLPanel* (void)> LLPanelClassCreatorFunc; + +// local static instance for registering a particular panel class + +class LLRegisterPanelClass +: public LLSingleton< LLRegisterPanelClass > +{ +public: + // reigister 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<typename T> + static T* defaultPanelClassBuilder() + { + T* pT = new T(); + return pT; + } - LLPanel* mDummyPanel; +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<typename T> +class LLRegisterPanelClassWrapper +: public LLRegisterPanelClass +{ +public: + // reigister with either the provided builder, or the generic templated builder + LLRegisterPanelClassWrapper(const std::string& tag); +}; + + +template<typename T> +LLRegisterPanelClassWrapper<T>::LLRegisterPanelClassWrapper(const std::string& tag) +{ + LLRegisterPanelClass::instance().addPanelClass(tag,&LLRegisterPanelClass::defaultPanelClassBuilder<T>); +} + + #endif //LLUICTRLFACTORY_H diff --git a/indra/llui/lluifwd.h b/indra/llui/lluifwd.h index 32d5c9b44f..f99bb39fdd 100644 --- a/indra/llui/lluifwd.h +++ b/indra/llui/lluifwd.h @@ -53,7 +53,6 @@ class LLSlider; class LLSliderCtrl; class LLSpinCtrl; class LLTabContainer; -class LLTabContainerVertical; class LLTextBox; class LLTextEditor; class LLTextureCtrl; diff --git a/indra/llui/lluiimage.cpp b/indra/llui/lluiimage.cpp new file mode 100644 index 0000000000..966d919dc7 --- /dev/null +++ b/indra/llui/lluiimage.cpp @@ -0,0 +1,205 @@ +/** + * @file lluiimage.cpp + * @brief UI implementation + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// Utilities functions the user interface needs + +//#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +// Project includes +#include "lluiimage.h" +#include "llui.h" + +LLUIImage::LLUIImage(const std::string& name, LLPointer<LLTexture> image) +: mName(name), + mImage(image), + mScaleRegion(0.f, 1.f, 1.f, 0.f), + mClipRegion(0.f, 1.f, 1.f, 0.f), + mUniformScaling(TRUE), + mNoClip(TRUE), + mImageLoaded(NULL) +{ +} + +LLUIImage::~LLUIImage() +{ + delete mImageLoaded; +} + +void LLUIImage::setClipRegion(const LLRectf& region) +{ + mClipRegion = region; + mNoClip = mClipRegion.mLeft == 0.f + && mClipRegion.mRight == 1.f + && mClipRegion.mBottom == 0.f + && mClipRegion.mTop == 1.f; +} + +void LLUIImage::setScaleRegion(const LLRectf& region) +{ + mScaleRegion = region; + mUniformScaling = mScaleRegion.mLeft == 0.f + && mScaleRegion.mRight == 1.f + && mScaleRegion.mBottom == 0.f + && mScaleRegion.mTop == 1.f; +} + +//TODO: move drawing implementation inside class +void LLUIImage::draw(S32 x, S32 y, const LLColor4& color) const +{ + gl_draw_scaled_image(x, y, getWidth(), getHeight(), mImage, color, mClipRegion); +} + +void LLUIImage::draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color) const +{ + if (mUniformScaling) + { + gl_draw_scaled_image(x, y, width, height, mImage, color, mClipRegion); + } + else + { + gl_draw_scaled_image_with_border( + x, y, + width, height, + mImage, + color, + FALSE, + mClipRegion, + mScaleRegion); + } +} + +void LLUIImage::drawSolid(S32 x, S32 y, S32 width, S32 height, const LLColor4& color) const +{ + gl_draw_scaled_image_with_border( + x, y, + width, height, + mImage, + color, + TRUE, + mClipRegion, + mScaleRegion); +} + +void LLUIImage::drawBorder(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, S32 border_width) const +{ + LLRect border_rect; + border_rect.setOriginAndSize(x, y, width, height); + border_rect.stretch(border_width, border_width); + drawSolid(border_rect, color); +} + +S32 LLUIImage::getWidth() const +{ + // return clipped dimensions of actual image area + return llround((F32)mImage->getWidth(0) * mClipRegion.getWidth()); +} + +S32 LLUIImage::getHeight() const +{ + // return clipped dimensions of actual image area + return llround((F32)mImage->getHeight(0) * mClipRegion.getHeight()); +} + +S32 LLUIImage::getTextureWidth() const +{ + return mImage->getWidth(0); +} + +S32 LLUIImage::getTextureHeight() const +{ + return mImage->getHeight(0); +} + +boost::signals2::connection LLUIImage::addLoadedCallback( const image_loaded_signal_t::slot_type& cb ) +{ + if (!mImageLoaded) + { + mImageLoaded = new image_loaded_signal_t(); + } + return mImageLoaded->connect(cb); +} + + +void LLUIImage::onImageLoaded() +{ + if (mImageLoaded) + { + (*mImageLoaded)(); + } +} + + +namespace LLInitParam +{ + void TypedParam<LLUIImage*>::setValueFromBlock() const + { + // The keyword "none" is specifically requesting a null image + // do not default to current value. Used to overwrite template images. + if (name() == "none") + { + mData.mValue = NULL; + return; + } + + LLUIImage* imagep = LLUI::getUIImage(name()); + if (imagep) + { + mData.mValue = imagep; + } + } + + void TypedParam<LLUIImage*>::setBlockFromValue() + { + if (mData.mValue == NULL) + { + name = "none"; + } + else + { + name = mData.mValue->getName(); + } + } + + + bool ParamCompare<LLUIImage*, false>::equals( + LLUIImage* const &a, + LLUIImage* const &b) + { + // force all LLUIImages for XML UI export to be "non-default" + if (!a && !b) + return false; + else + return (a == b); + } +} + diff --git a/indra/llui/lluiimage.h b/indra/llui/lluiimage.h new file mode 100644 index 0000000000..bdfc44262d --- /dev/null +++ b/indra/llui/lluiimage.h @@ -0,0 +1,128 @@ +/** + * @file lluiimage.h + * @brief wrapper for images used in the UI that handles smart scaling, etc. + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLUIIMAGE_H +#define LL_LLUIIMAGE_H + +#include "v4color.h" +#include "llpointer.h" +#include "llrefcount.h" +#include "llrefcount.h" +#include "llrect.h" +#include <boost/function.hpp> +#include <boost/signals2.hpp> +#include "llinitparam.h" +#include "lltexture.h" + +extern const LLColor4 UI_VERTEX_COLOR; + +class LLUIImage : public LLRefCount +{ +public: + typedef boost::signals2::signal<void (void)> image_loaded_signal_t; + + LLUIImage(const std::string& name, LLPointer<LLTexture> image); + virtual ~LLUIImage(); + + void setClipRegion(const LLRectf& region); + void setScaleRegion(const LLRectf& region); + + LLPointer<LLTexture> getImage() { return mImage; } + const LLPointer<LLTexture>& getImage() const { return mImage; } + + void draw(S32 x, S32 y, S32 width, S32 height, const LLColor4& color = UI_VERTEX_COLOR) const; + void draw(S32 x, S32 y, const LLColor4& color = UI_VERTEX_COLOR) const; + void draw(const LLRect& rect, const LLColor4& color = UI_VERTEX_COLOR) const { draw(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), color); } + + void drawSolid(S32 x, S32 y, S32 width, S32 height, const LLColor4& color) const; + void drawSolid(const LLRect& rect, const LLColor4& color) const { drawSolid(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), color); } + void drawSolid(S32 x, S32 y, const LLColor4& color) const { drawSolid(x, y, getWidth(), getHeight(), color); } + + void drawBorder(S32 x, S32 y, S32 width, S32 height, const LLColor4& color, S32 border_width) const; + void drawBorder(const LLRect& rect, const LLColor4& color, S32 border_width) const { drawBorder(rect.mLeft, rect.mBottom, rect.getWidth(), rect.getHeight(), color, border_width); } + void drawBorder(S32 x, S32 y, const LLColor4& color, S32 border_width) const { drawBorder(x, y, getWidth(), getHeight(), color, border_width); } + + const std::string& getName() const { return mName; } + + virtual S32 getWidth() const; + virtual S32 getHeight() const; + + // returns dimensions of underlying textures, which might not be equal to ui image portion + S32 getTextureWidth() const; + S32 getTextureHeight() const; + + boost::signals2::connection addLoadedCallback( const image_loaded_signal_t::slot_type& cb ); + + void onImageLoaded(); + +protected: + image_loaded_signal_t* mImageLoaded; + + std::string mName; + LLRectf mScaleRegion; + LLRectf mClipRegion; + LLPointer<LLTexture> mImage; + BOOL mUniformScaling; + BOOL mNoClip; +}; + +namespace LLInitParam +{ + template<> + class TypedParam<LLUIImage*, TypeValues<LLUIImage*>, false> + : public BlockValue<LLUIImage*> + { + typedef boost::add_reference<boost::add_const<LLUIImage*>::type>::type T_const_ref; + typedef BlockValue<LLUIImage*> super_t; + public: + Optional<std::string> name; + + TypedParam(BlockDescriptor& descriptor, const char* name, super_t::value_assignment_t value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count) + : super_t(descriptor, name, value, func, min_count, max_count) + { + } + + void setValueFromBlock() const; + void setBlockFromValue(); + }; + + // Need custom comparison function for our test app, which only loads + // LLUIImage* as NULL. + template<> + struct ParamCompare<LLUIImage*, false> + { + static bool equals(LLUIImage* const &a, LLUIImage* const &b); + }; +} + +typedef LLPointer<LLUIImage> LLUIImagePtr; +#endif diff --git a/indra/llui/lluistring.cpp b/indra/llui/lluistring.cpp index 0b76b8e814..f7a53e87de 100644 --- a/indra/llui/lluistring.cpp +++ b/indra/llui/lluistring.cpp @@ -33,31 +33,35 @@ #include "linden_common.h" #include "lluistring.h" #include "llsd.h" +#include "lltrans.h" -const LLStringUtil::format_map_t LLUIString::sNullArgs; +LLFastTimer::DeclareTimer FTM_UI_STRING("UI String"); LLUIString::LLUIString(const std::string& instring, const LLStringUtil::format_map_t& args) - : mOrig(instring), - mArgs(args) +: mOrig(instring), + mArgs(args) { - format(); + dirty(); } void LLUIString::assign(const std::string& s) { mOrig = s; - format(); + dirty(); } void LLUIString::setArgList(const LLStringUtil::format_map_t& args) + { mArgs = args; - format(); + dirty(); } void LLUIString::setArgs(const LLSD& sd) { + LLFastTimer timer(FTM_UI_STRING); + if (!sd.isMap()) return; for(LLSD::map_const_iterator sd_it = sd.beginMap(); sd_it != sd.endMap(); @@ -65,40 +69,40 @@ void LLUIString::setArgs(const LLSD& sd) { setArg(sd_it->first, sd_it->second.asString()); } - format(); + dirty(); } void LLUIString::setArg(const std::string& key, const std::string& replacement) { mArgs[key] = replacement; - format(); + dirty(); } void LLUIString::truncate(S32 maxchars) { - if (mWResult.size() > (size_t)maxchars) + if (getUpdatedWResult().size() > (size_t)maxchars) { - LLWStringUtil::truncate(mWResult, maxchars); - mResult = wstring_to_utf8str(mWResult); + LLWStringUtil::truncate(getUpdatedWResult(), maxchars); + mResult = wstring_to_utf8str(getUpdatedWResult()); } } void LLUIString::erase(S32 charidx, S32 len) { - mWResult.erase(charidx, len); - mResult = wstring_to_utf8str(mWResult); + getUpdatedWResult().erase(charidx, len); + mResult = wstring_to_utf8str(getUpdatedWResult()); } void LLUIString::insert(S32 charidx, const LLWString& wchars) { - mWResult.insert(charidx, wchars); - mResult = wstring_to_utf8str(mWResult); + getUpdatedWResult().insert(charidx, wchars); + mResult = wstring_to_utf8str(getUpdatedWResult()); } void LLUIString::replace(S32 charidx, llwchar wc) { - mWResult[charidx] = wc; - mResult = wstring_to_utf8str(mWResult); + getUpdatedWResult()[charidx] = wc; + mResult = wstring_to_utf8str(getUpdatedWResult()); } void LLUIString::clear() @@ -109,9 +113,36 @@ void LLUIString::clear() mWResult.clear(); } -void LLUIString::format() +void LLUIString::dirty() +{ + mNeedsResult = true; + mNeedsWResult = true; +} + +void LLUIString::updateResult() const { + mNeedsResult = false; + + LLFastTimer timer(FTM_UI_STRING); + + // optimize for empty strings (don't attempt string replacement) + if (mOrig.empty()) + { + mResult.clear(); + mWResult.clear(); + return; + } mResult = mOrig; - LLStringUtil::format(mResult, mArgs); - mWResult = utf8str_to_wstring(mResult); + + // get the defailt args + local args + LLStringUtil::format_map_t combined_args = LLTrans::getDefaultArgs(); + combined_args.insert(mArgs.begin(), mArgs.end()); + LLStringUtil::format(mResult, combined_args); +} + +void LLUIString::updateWResult() const +{ + mNeedsWResult = false; + + mWResult = utf8str_to_wstring(getUpdatedResult()); } diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h index aedeca27cb..7ec0fd603a 100644 --- a/indra/llui/lluistring.h +++ b/indra/llui/lluistring.h @@ -51,9 +51,9 @@ // llinfos << mMessage.getString() << llendl; // outputs "Welcome Steve to Second Life" // mMessage.setArg("[USERNAME]", "Joe"); // llinfos << mMessage.getString() << llendl; // outputs "Welcome Joe to Second Life" -// mMessage = "Recepci￳n a la [SECONDLIFE] [USERNAME]" +// mMessage = "Bienvenido a la [SECONDLIFE] [USERNAME]" // mMessage.setArg("[SECONDLIFE]", "Segunda Vida"); -// llinfos << mMessage.getString() << llendl; // outputs "Recepci￳n a la Segunda Vida Joe" +// llinfos << mMessage.getString() << llendl; // outputs "Bienvenido a la Segunda Vida Joe" // Implementation Notes: // Attempting to have operator[](const std::string& s) return mArgs[s] fails because we have @@ -76,34 +76,44 @@ public: void setArgs(const class LLSD& sd); void setArg(const std::string& key, const std::string& replacement); - const std::string& getString() const { return mResult; } - operator std::string() const { return mResult; } + const std::string& getString() const { return getUpdatedResult(); } + operator std::string() const { return getUpdatedResult(); } - const LLWString& getWString() const { return mWResult; } - operator LLWString() const { return mWResult; } + const LLWString& getWString() const { return getUpdatedWResult(); } + operator LLWString() const { return getUpdatedWResult(); } - bool empty() const { return mWResult.empty(); } - S32 length() const { return mWResult.size(); } + bool empty() const { return getUpdatedResult().empty(); } + S32 length() const { return getUpdatedWResult().size(); } void clear(); void clearArgs() { mArgs.clear(); } - // These utuilty functions are included for text editing. + // These utility functions are included for text editing. // They do not affect mOrig and do not perform argument substitution void truncate(S32 maxchars); void erase(S32 charidx, S32 len); void insert(S32 charidx, const LLWString& wchars); void replace(S32 charidx, llwchar wc); - static const LLStringUtil::format_map_t sNullArgs; - private: - void format(); + // something changed, requiring reformatting of strings + void dirty(); + + std::string& getUpdatedResult() const { if (mNeedsResult) { updateResult(); } return mResult; } + LLWString& getUpdatedWResult() const{ if (mNeedsWResult) { updateWResult(); } return mWResult; } + + // do actual work of updating strings (non-inlined) + void updateResult() const; + void updateWResult() const; std::string mOrig; - std::string mResult; - LLWString mWResult; // for displaying + mutable std::string mResult; + mutable LLWString mWResult; // for displaying LLStringUtil::format_map_t mArgs; + + // controls lazy evaluation + mutable bool mNeedsResult; + mutable bool mNeedsWResult; }; #endif // LL_LLUISTRING_H diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp new file mode 100644 index 0000000000..679db5e39b --- /dev/null +++ b/indra/llui/llurlaction.cpp @@ -0,0 +1,148 @@ +/** + * @file llurlaction.cpp + * @author Martin Reddy + * @brief A set of actions that can performed on Urls + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llurlaction.h" +#include "llview.h" +#include "llwindow.h" +#include "llurlregistry.h" + +// global state for the callback functions +void (*LLUrlAction::sOpenURLCallback) (const std::string& url) = NULL; +void (*LLUrlAction::sOpenURLInternalCallback) (const std::string& url) = NULL; +void (*LLUrlAction::sOpenURLExternalCallback) (const std::string& url) = NULL; +bool (*LLUrlAction::sExecuteSLURLCallback) (const std::string& url) = NULL; + + +void LLUrlAction::setOpenURLCallback(void (*cb) (const std::string& url)) +{ + sOpenURLCallback = cb; +} + +void LLUrlAction::setOpenURLInternalCallback(void (*cb) (const std::string& url)) +{ + sOpenURLInternalCallback = cb; +} + +void LLUrlAction::setOpenURLExternalCallback(void (*cb) (const std::string& url)) +{ + sOpenURLExternalCallback = cb; +} + +void LLUrlAction::setExecuteSLURLCallback(bool (*cb) (const std::string& url)) +{ + sExecuteSLURLCallback = cb; +} + +void LLUrlAction::openURL(std::string url) +{ + if (sOpenURLCallback) + { + (*sOpenURLCallback)(url); + } +} + +void LLUrlAction::openURLInternal(std::string url) +{ + if (sOpenURLInternalCallback) + { + (*sOpenURLInternalCallback)(url); + } +} + +void LLUrlAction::openURLExternal(std::string url) +{ + if (sOpenURLExternalCallback) + { + (*sOpenURLExternalCallback)(url); + } +} + +void LLUrlAction::executeSLURL(std::string url) +{ + if (sExecuteSLURLCallback) + { + (*sExecuteSLURLCallback)(url); + } +} + +void LLUrlAction::clickAction(std::string url) +{ + // Try to handle as SLURL first, then http Url + if ( (sExecuteSLURLCallback) && !(*sExecuteSLURLCallback)(url) ) + { + if (sOpenURLCallback) + { + (*sOpenURLCallback)(url); + } + } +} + +void LLUrlAction::teleportToLocation(std::string url) +{ + LLUrlMatch match; + if (LLUrlRegistry::instance().findUrl(url, match)) + { + if (! match.getLocation().empty()) + { + executeSLURL("secondlife:///app/teleport/" + match.getLocation()); + } + } +} + +void LLUrlAction::showLocationOnMap(std::string url) +{ + LLUrlMatch match; + if (LLUrlRegistry::instance().findUrl(url, match)) + { + if (! match.getLocation().empty()) + { + executeSLURL("secondlife:///app/worldmap/" + match.getLocation()); + } + } +} + +void LLUrlAction::copyURLToClipboard(std::string url) +{ + LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(url)); +} + +void LLUrlAction::copyLabelToClipboard(std::string url) +{ + LLUrlMatch match; + if (LLUrlRegistry::instance().findUrl(url, match)) + { + LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(match.getLabel())); + } +} diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h new file mode 100644 index 0000000000..4830cf27ef --- /dev/null +++ b/indra/llui/llurlaction.h @@ -0,0 +1,96 @@ +/** + * @file llurlaction.h + * @author Martin Reddy + * @brief A set of actions that can performed on Urls + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLURLACTION_H +#define LL_LLURLACTION_H + +#include <string> + +/// +/// The LLUrlAction class provides a number of static functions that +/// let you open Urls in web browsers, execute SLURLs, and copy Urls +/// to the clipboard. Many of these functions are not available at +/// the llui level, and must be supplied via a set of callbacks. +/// +/// N.B. The action functions specifically do not use const ref +/// strings so that a url parameter can be used into a boost::bind() +/// call under situations when that input string is deallocated before +/// the callback is executed. +/// +class LLUrlAction +{ +public: + LLUrlAction(); + + /// load a Url in the user's preferred web browser + static void openURL(std::string url); + + /// load a Url in the internal Second Life web browser + static void openURLInternal(std::string url); + + /// load a Url in the operating system's default web browser + static void openURLExternal(std::string url); + + /// execute the given secondlife: SLURL + static void executeSLURL(std::string url); + + /// if the Url specifies an SL location, teleport there + static void teleportToLocation(std::string url); + + /// if the Url specifies an SL location, show it on a map + static void showLocationOnMap(std::string url); + + /// perform the appropriate action for left-clicking on a Url + static void clickAction(std::string url); + + /// copy the label for a Url to the clipboard + static void copyLabelToClipboard(std::string url); + + /// copy a Url to the clipboard + static void copyURLToClipboard(std::string url); + + /// specify the callbacks to enable this class's functionality + static void setOpenURLCallback(void (*cb) (const std::string& url)); + static void setOpenURLInternalCallback(void (*cb) (const std::string& url)); + static void setOpenURLExternalCallback(void (*cb) (const std::string& url)); + static void setExecuteSLURLCallback(bool (*cb) (const std::string& url)); + +private: + // callbacks for operations we can perform on Urls + static void (*sOpenURLCallback) (const std::string& url); + static void (*sOpenURLInternalCallback) (const std::string& url); + static void (*sOpenURLExternalCallback) (const std::string& url); + static bool (*sExecuteSLURLCallback) (const std::string& url); +}; + +#endif diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp new file mode 100644 index 0000000000..4927e57a52 --- /dev/null +++ b/indra/llui/llurlentry.cpp @@ -0,0 +1,643 @@ +/** + * @file llurlentry.cpp + * @author Martin Reddy + * @brief Describes the Url types that can be registered in LLUrlRegistry + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llurlentry.h" +#include "lluri.h" + +#include "llcachename.h" +#include "lltrans.h" +#include "lluicolortable.h" + +LLUrlEntryBase::LLUrlEntryBase() +: mColor(LLUIColorTable::instance().getColor("HTMLLinkColor")) +{ +} + +LLUrlEntryBase::~LLUrlEntryBase() +{ +} + +std::string LLUrlEntryBase::getUrl(const std::string &string) +{ + return escapeUrl(string); +} + +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) +{ + // 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) +{ + // 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<std::string, LLUrlEntryObserver>(id, observer)); + } +} + +void LLUrlEntryBase::callObservers(const std::string &id, const std::string &label) +{ + // notify all callbacks waiting on the given uuid + std::multimap<std::string, LLUrlEntryObserver>::iterator it; + for (it = mObservers.find(id); it != mObservers.end();) + { + // call the callback - give it the new label + LLUrlEntryObserver &observer = it->second; + (*observer.signal)(it->second.url, label); + // then remove the signal - we only need to call it once + delete observer.signal; + mObservers.erase(it++); + } +} + +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() +{ + mPattern = boost::regex("https?://([-\\w\\.]+)+(:\\d+)?(:\\w+)?(@\\d+)?(@\\w+)?/?\\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 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) +{ + return getLabelFromWikiLink(url); +} + +std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string) +{ + return getUrlFromWikiLink(string); +} + +// +// LLUrlEntryHTTPNoProtocol Describes generic Urls like www.google.com +// +LLUrlEntryHTTPNoProtocol::LLUrlEntryHTTPNoProtocol() +{ + mPattern = boost::regex("(" + "\\bwww\\.\\S+\\.\\S+" // i.e. www.FOO.BAR + "|" // or + "(?<!@)\\b[^[:space:]:@/]+\\.(?:com|net|edu|org)([/:]\\S*)?\\b" // i.e. FOO.net + ")", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_http.xml"; + mTooltip = LLTrans::getString("TooltipHttpUrl"); +} + +std::string LLUrlEntryHTTPNoProtocol::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return unescapeUrl(url); +} + +std::string LLUrlEntryHTTPNoProtocol::getUrl(const std::string &string) +{ + if (string.find("://") == std::string::npos) + { + return "http://" + escapeUrl(string); + } + return escapeUrl(string); +} + +// +// LLUrlEntrySLURL Describes generic http: and https: Urls +// +LLUrlEntrySLURL::LLUrlEntrySLURL() +{ + // see http://slurl.com/about.php for details on the SLURL format + mPattern = boost::regex("http://slurl.com/secondlife/\\S+/?(\\d+)?/?(\\d+)?/?(\\d+)?/?\\S*", + boost::regex::perl|boost::regex::icase); + 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); +} + +// +// LLUrlEntryAgent Describes a Second Life agent Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about +// +LLUrlEntryAgent::LLUrlEntryAgent() +{ + mPattern = boost::regex("secondlife:///app/agent/[\\da-f-]+/\\w+", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_agent.xml"; + mIcon = "Generic_Person"; + mColor = LLUIColorTable::instance().getColor("AgentLinkColor"); +} + +void LLUrlEntryAgent::onAgentNameReceived(const LLUUID& id, + const std::string& first, + const std::string& last, + BOOL is_group) +{ + // received the agent name from the server - tell our observers + callObservers(id.asString(), first + " " + last); +} + +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); + std::string full_name; + if (agent_id.isNull()) + { + return LLTrans::getString("AvatarNameNobody"); + } + else if (gCacheName->getFullName(agent_id, full_name)) + { + return full_name; + } + else + { + gCacheName->get(agent_id, FALSE, + boost::bind(&LLUrlEntryAgent::onAgentNameReceived, + this, _1, _2, _3, _4)); + addObserver(agent_id_string, url, cb); + return LLTrans::getString("LoadingData"); + } +} + +// +// 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 +// +LLUrlEntryGroup::LLUrlEntryGroup() +{ + mPattern = boost::regex("secondlife:///app/group/[\\da-f-]+/\\w+", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_group.xml"; + mIcon = "Generic_Group"; + mTooltip = LLTrans::getString("TooltipGroupUrl"); + mColor = LLUIColorTable::instance().getColor("GroupLinkColor"); +} + +void LLUrlEntryGroup::onGroupNameReceived(const LLUUID& id, + const std::string& first, + const std::string& last, + BOOL is_group) +{ + // received the group name from the server - tell our observers + callObservers(id.asString(), first); +} + +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->get(group_id, TRUE, + boost::bind(&LLUrlEntryGroup::onGroupNameReceived, + this, _1, _2, _3, _4)); + addObserver(group_id_string, url, cb); + return LLTrans::getString("LoadingData"); + } +} + +// +// 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 + mPattern = boost::regex("secondlife:///app/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); +} + +/// +/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., +/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about +/// +LLUrlEntryParcel::LLUrlEntryParcel() +{ + mPattern = boost::regex("secondlife:///app/parcel/[\\da-f-]+/about", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_parcel.xml"; + mTooltip = LLTrans::getString("TooltipParcelUrl"); +} + +std::string LLUrlEntryParcel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return unescapeUrl(url); +} + +// +// LLUrlEntryPlace Describes secondlife://<location> URLs +// +LLUrlEntryPlace::LLUrlEntryPlace() +{ + mPattern = boost::regex("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, "://"); +} + +// +// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g., +// secondlife:///app/teleport/Ahern/50/50/50/ +// +LLUrlEntryTeleport::LLUrlEntryTeleport() +{ + mPattern = boost::regex("secondlife:///app/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(); + const std::string label = LLTrans::getString("SLurlLabelTeleport"); + 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) +{ + return getLabelFromWikiLink(url); +} + +std::string LLUrlEntrySLLabel::getUrl(const std::string &string) +{ + return getUrlFromWikiLink(string); +} + +// +// LLUrlEntryWorldMap Describes secondlife:///<location> URLs +// +LLUrlEntryWorldMap::LLUrlEntryWorldMap() +{ + mPattern = boost::regex("secondlife:///app/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 = 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/"); +} diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h new file mode 100644 index 0000000000..4adffde99c --- /dev/null +++ b/indra/llui/llurlentry.h @@ -0,0 +1,270 @@ +/** + * @file llurlentry.h + * @author Martin Reddy + * @brief Describes the Url types that can be registered in LLUrlRegistry + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLURLENTRY_H +#define LL_LLURLENTRY_H + +#include "lluuid.h" +#include "lluicolor.h" +#include <boost/signals2.hpp> +#include <boost/regex.hpp> +#include <string> +#include <map> + +typedef boost::signals2::signal<void (const std::string& url, + const std::string& label)> 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); + + /// Given a matched Url, return a label for the Url + virtual std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) { return url; } + + /// Return an icon that can be displayed next to Urls of this type + std::string getIcon() const { return mIcon; } + + /// Return the color to render the displayed text + LLUIColor getColor() const { return mColor; } + + /// Given a matched Url, return a tooltip string for the hyperlink + std::string getTooltip() 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 ""; } + +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); + std::string getUrlFromWikiLink(const std::string &string); + void addObserver(const std::string &id, const std::string &url, const LLUrlLabelCallback &cb); + void callObservers(const std::string &id, const std::string &label); + + typedef struct { + std::string url; + LLUrlLabelSignal *signal; + } LLUrlEntryObserver; + + boost::regex mPattern; + std::string mIcon; + std::string mMenuName; + std::string mTooltip; + LLUIColor mColor; + std::multimap<std::string, LLUrlEntryObserver> 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); +}; + +/// +/// 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 getUrl(const std::string &string); +}; + +/// +/// LLUrlEntryHTTPNoProtocol Describes generic Urls like www.google.com +/// +class LLUrlEntryHTTPNoProtocol : public LLUrlEntryBase +{ +public: + LLUrlEntryHTTPNoProtocol(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getUrl(const std::string &string); +}; + +/// +/// LLUrlEntrySLURL Describes http://slurl.com/... Urls +/// +class LLUrlEntrySLURL : public LLUrlEntryBase +{ +public: + LLUrlEntrySLURL(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLocation(const std::string &url) const; +}; + +/// +/// LLUrlEntryAgent Describes a Second Life agent Url, e.g., +/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about +/// +class LLUrlEntryAgent : public LLUrlEntryBase +{ +public: + LLUrlEntryAgent(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); +private: + void onAgentNameReceived(const LLUUID& id, const std::string& first, + const std::string& last, BOOL is_group); +}; + +/// +/// 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); +private: + void onGroupNameReceived(const LLUUID& id, const std::string& first, + const std::string& last, 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: +}; + + +/// +/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., +/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about +/// +class LLUrlEntryParcel : public LLUrlEntryBase +{ +public: + LLUrlEntryParcel(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); +}; + +/// +/// 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; +}; + +/// +/// 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); +}; + +/// +/// 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; +}; + +#endif diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp new file mode 100644 index 0000000000..3b47145a22 --- /dev/null +++ b/indra/llui/llurlmatch.cpp @@ -0,0 +1,62 @@ +/** + * @file llurlmatch.cpp + * @author Martin Reddy + * @brief Specifies a matched Url in a string, as returned by LLUrlRegistry + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llurlmatch.h" + +LLUrlMatch::LLUrlMatch() : + mStart(0), + mEnd(0), + mUrl(""), + mLabel(""), + mTooltip(""), + mIcon(""), + mMenuName("") +{ +} + +void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, + const std::string &label, const std::string &tooltip, + const std::string &icon, const LLUIColor& color, + const std::string &menu, const std::string &location) +{ + mStart = start; + mEnd = end; + mUrl = url; + mLabel = label; + mTooltip = tooltip; + mIcon = icon; + mColor = color; + mMenuName = menu; + mLocation = location; +} diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h new file mode 100644 index 0000000000..7f5767923a --- /dev/null +++ b/indra/llui/llurlmatch.h @@ -0,0 +1,104 @@ +/** + * @file llurlmatch.h + * @author Martin Reddy + * @brief Specifies a matched Url in a string, as returned by LLUrlRegistry + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLURLMATCH_H +#define LL_LLURLMATCH_H + +#include "linden_common.h" + +#include <string> +#include <vector> +#include "lluicolor.h" + +/// +/// LLUrlMatch describes a single Url that was matched within a string by +/// the LLUrlRegistry::findUrl() method. It includes the actual Url that +/// was matched along with its first/last character offset in the string. +/// An alternate label is also provided for creating a hyperlink, as well +/// as tooltip/status text, an icon, and a XUI file for a context menu +/// that can be used in a popup for a Url (e.g., Open, Copy URL, etc.) +/// +class LLUrlMatch +{ +public: + LLUrlMatch(); + + /// return true if this object does not contain a valid Url match yet + bool empty() const { return mUrl.empty(); } + + /// return the offset in the string for the first character of the Url + U32 getStart() const { return mStart; } + + /// return the offset in the string for the last character of the Url + U32 getEnd() const { return mEnd; } + + /// return the Url that has been matched in the input string + std::string getUrl() const { return mUrl; } + + /// return a label that can be used for the display of this Url + std::string getLabel() const { return mLabel; } + + /// return a message that could be displayed in a tooltip or status bar + std::string getTooltip() const { return mTooltip; } + + /// return the filename for an icon that can be displayed next to this Url + std::string getIcon() const { return mIcon; } + + /// Return the color to render the displayed text + LLUIColor getColor() const { return mColor; } + + /// Return the name of a XUI file containing the context menu items + std::string getMenuName() const { return mMenuName; } + + /// return the SL location that this Url describes, or "" if none. + std::string getLocation() const { return mLocation; } + + /// Change the contents of this match object (used by LLUrlRegistry) + void setValues(U32 start, U32 end, const std::string &url, const std::string &label, + const std::string &tooltip, const std::string &icon, + const LLUIColor& color, const std::string &menu, + const std::string &location); + +private: + U32 mStart; + U32 mEnd; + std::string mUrl; + std::string mLabel; + std::string mTooltip; + std::string mIcon; + std::string mMenuName; + std::string mLocation; + LLUIColor mColor; +}; + +#endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp new file mode 100644 index 0000000000..ad5c0911f8 --- /dev/null +++ b/indra/llui/llurlregistry.cpp @@ -0,0 +1,245 @@ +/** + * @file llurlregistry.cpp + * @author Martin Reddy + * @brief Contains a set of Url types that can be matched in a string + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llurlregistry.h" + +#include <boost/regex.hpp> + +// default dummy callback that ignores any label updates from the server +void LLUrlRegistryNullCallback(const std::string &url, const std::string &label) +{ +} + +LLUrlRegistry::LLUrlRegistry() +{ + // Urls are matched in the order that they were registered + registerUrl(new LLUrlEntrySLURL()); + registerUrl(new LLUrlEntryHTTP()); + registerUrl(new LLUrlEntryHTTPLabel()); + registerUrl(new LLUrlEntryAgent()); + registerUrl(new LLUrlEntryGroup()); + registerUrl(new LLUrlEntryParcel()); + registerUrl(new LLUrlEntryTeleport()); + registerUrl(new LLUrlEntryWorldMap()); + registerUrl(new LLUrlEntryPlace()); + registerUrl(new LLUrlEntryInventory()); + //LLUrlEntrySL and LLUrlEntrySLLabel have more common pattern, + //so it should be registered in the end of list + registerUrl(new LLUrlEntrySL()); + registerUrl(new LLUrlEntrySLLabel()); + // most common pattern is a URL without any protocol, + // e.g., "secondlife.com" + registerUrl(new LLUrlEntryHTTPNoProtocol()); +} + +LLUrlRegistry::~LLUrlRegistry() +{ + // free all of the LLUrlEntryBase objects we are holding + std::vector<LLUrlEntryBase *>::iterator it; + for (it = mUrlEntry.begin(); it != mUrlEntry.end(); ++it) + { + delete *it; + } +} + +void LLUrlRegistry::registerUrl(LLUrlEntryBase *url) +{ + if (url) + { + mUrlEntry.push_back(url); + } +} + +static bool matchRegex(const char *text, boost::regex regex, U32 &start, U32 &end) +{ + boost::cmatch result; + bool found; + + // regex_search can potentially throw an exception, so check for it + try + { + found = boost::regex_search(text, result, regex); + } + catch (std::runtime_error &) + { + return false; + } + + if (! found) + { + return false; + } + + // return the first/last character offset for the matched substring + start = static_cast<U32>(result[0].first - text); + end = static_cast<U32>(result[0].second - text) - 1; + + // we allow certain punctuation to terminate a Url but not match it, + // e.g., "http://foo.com/." should just match "http://foo.com/" + if (text[end] == '.' || text[end] == ',') + { + end--; + } + // ignore a terminating ')' when Url contains no matching '(' + // see DEV-19842 for details + else if (text[end] == ')' && std::string(text+start, end-start).find('(') == std::string::npos) + { + end--; + } + + return true; +} + +static bool stringHasUrl(const std::string &text) +{ + // fast heuristic test for a URL in a string. This is used + // to avoid lots of costly regex calls, BUT it needs to be + // kept in sync with the LLUrlEntry regexes we support. + return (text.find("://") != std::string::npos || + text.find("www.") != std::string::npos || + text.find(".com") != std::string::npos || + text.find(".net") != std::string::npos || + text.find(".edu") != std::string::npos || + text.find(".org") != std::string::npos); +} + +bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb) +{ + // avoid costly regexes if there is clearly no URL in the text + if (! stringHasUrl(text)) + { + return false; + } + + // find the first matching regex from all url entries in the registry + U32 match_start = 0, match_end = 0; + LLUrlEntryBase *match_entry = NULL; + + std::vector<LLUrlEntryBase *>::iterator it; + for (it = mUrlEntry.begin(); it != mUrlEntry.end(); ++it) + { + LLUrlEntryBase *url_entry = *it; + + U32 start = 0, end = 0; + if (matchRegex(text.c_str(), url_entry->getPattern(), start, end)) + { + // does this match occur in the string before any other match + if (start < match_start || match_entry == NULL) + { + match_start = start; + match_end = end; + match_entry = url_entry; + } + } + } + + // did we find a match? if so, return its details in the match object + if (match_entry) + { + // fill in the LLUrlMatch object and return it + std::string url = text.substr(match_start, match_end - match_start + 1); + match.setValues(match_start, match_end, + match_entry->getUrl(url), + match_entry->getLabel(url, cb), + match_entry->getTooltip(), + match_entry->getIcon(), + match_entry->getColor(), + match_entry->getMenuName(), + match_entry->getLocation(url)); + return true; + } + + return false; +} + +bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUrlLabelCallback &cb) +{ + // boost::regex_search() only works on char or wchar_t + // types, but wchar_t is only 2-bytes on Win32 (not 4). + // So we use UTF-8 to make this work the same everywhere. + std::string utf8_text = wstring_to_utf8str(text); + if (findUrl(utf8_text, match, cb)) + { + // we cannot blindly return the start/end offsets from + // the UTF-8 string because it is a variable-length + // character encoding, so we need to update the start + // and end values to be correct for the wide string. + LLWString wurl = utf8str_to_wstring(match.getUrl()); + S32 start = text.find(wurl); + if (start == std::string::npos) + { + return false; + } + S32 end = start + wurl.size() - 1; + + match.setValues(start, end, match.getUrl(), + match.getLabel(), match.getTooltip(), + match.getIcon(), match.getColor(), + match.getMenuName(), match.getLocation()); + return true; + } + return false; +} + +bool LLUrlRegistry::hasUrl(const std::string &text) +{ + LLUrlMatch match; + return findUrl(text, match); +} + +bool LLUrlRegistry::hasUrl(const LLWString &text) +{ + LLUrlMatch match; + return findUrl(text, match); +} + +bool LLUrlRegistry::isUrl(const std::string &text) +{ + LLUrlMatch match; + if (findUrl(text, match)) + { + return (match.getStart() == 0 && match.getEnd() >= text.size()-1); + } + return false; +} + +bool LLUrlRegistry::isUrl(const LLWString &text) +{ + LLUrlMatch match; + if (findUrl(text, match)) + { + return (match.getStart() == 0 && match.getEnd() >= text.size()-1); + } + return false; +} diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h new file mode 100644 index 0000000000..399ee0a988 --- /dev/null +++ b/indra/llui/llurlregistry.h @@ -0,0 +1,99 @@ +/** + * @file llurlregistry.h + * @author Martin Reddy + * @brief Contains a set of Url types that can be matched in a string + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLURLREGISTRY_H +#define LL_LLURLREGISTRY_H + +#include "llurlentry.h" +#include "llurlmatch.h" +#include "llsingleton.h" +#include "llstring.h" + +#include <string> +#include <vector> + +/// This default callback for findUrl() simply ignores any label updates +void LLUrlRegistryNullCallback(const std::string &url, const std::string &label); + +/// +/// LLUrlRegistry is a singleton that contains a set of Url types that +/// can be matched in string. E.g., http:// or secondlife:// Urls. +/// +/// Clients call the findUrl() method on a string to locate the first +/// occurence of a supported Urls in that string. If findUrl() returns +/// true, the LLUrlMatch object will be updated to describe the Url +/// that was matched, including a label that can be used to hyperlink +/// the Url, an icon to display next to the Url, and a XUI menu that +/// can be used as a popup context menu for that Url. +/// +/// New Url types can be added to the registry with the registerUrl +/// method. E.g., to add support for a new secondlife:///app/ Url. +/// +/// Computing the label for a Url could involve a roundtrip request +/// to the server (e.g., to find the actual agent or group name). +/// As such, you can provide a callback method that will get invoked +/// when a new label is available for one of your matched Urls. +/// +class LLUrlRegistry : public LLSingleton<LLUrlRegistry> +{ +public: + ~LLUrlRegistry(); + + /// add a new Url handler to the registry (will be freed on destruction) + void registerUrl(LLUrlEntryBase *url); + + /// get the next Url in an input string, starting at a given character offset + /// your callback is invoked if the matched Url's label changes in the future + bool findUrl(const std::string &text, LLUrlMatch &match, + const LLUrlLabelCallback &cb = &LLUrlRegistryNullCallback); + + /// a slightly less efficient version of findUrl for wide strings + bool findUrl(const LLWString &text, LLUrlMatch &match, + const LLUrlLabelCallback &cb = &LLUrlRegistryNullCallback); + + // return true if the given string contains a URL that findUrl would match + bool hasUrl(const std::string &text); + bool hasUrl(const LLWString &text); + + // return true if the given string is a URL that findUrl would match + bool isUrl(const std::string &text); + bool isUrl(const LLWString &text); + +private: + LLUrlRegistry(); + friend class LLSingleton<LLUrlRegistry>; + + std::vector<LLUrlEntryBase *> mUrlEntry; +}; + +#endif diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 2350ea6050..f1b08c380b 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -33,6 +33,7 @@ #include "linden_common.h" +#define LLVIEW_CPP #include "llview.h" #include <cassert> @@ -40,7 +41,6 @@ #include "llrender.h" #include "llevent.h" -#include "llfontgl.h" #include "llfocusmgr.h" #include "llrect.h" #include "llstl.h" @@ -49,6 +49,7 @@ #include "llwindow.h" #include "v3color.h" #include "lluictrlfactory.h" +#include "lltooltip.h" // for ui edit hack #include "llbutton.h" @@ -56,95 +57,106 @@ #include "lltexteditor.h" #include "lltextbox.h" -//HACK: this allows you to instantiate LLView from xml with "<view/>" which we don't want -static LLRegisterWidget<LLView> r("view"); - -BOOL LLView::sDebugRects = FALSE; -BOOL LLView::sDebugKeys = FALSE; S32 LLView::sDepth = 0; -BOOL LLView::sDebugMouseHandling = FALSE; +bool LLView::sDebugRects = false; +bool LLView::sDebugRectsShowNames = true; +bool LLView::sDebugKeys = false; +bool LLView::sDebugMouseHandling = false; std::string LLView::sMouseHandlerMessage; -BOOL LLView::sEditingUI = FALSE; BOOL LLView::sForceReshape = FALSE; -LLView* LLView::sEditingUIView = NULL; +std::set<LLView*> 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*> LLViewDrawContext::sDrawContextStack; + #if LL_DEBUG BOOL LLView::sIsDrawing = FALSE; #endif -LLView::LLView() : - mParentView(NULL), - mReshapeFlags(FOLLOWS_NONE), - mDefaultTabGroup(0), - mEnabled(TRUE), - mMouseOpaque(TRUE), - mSoundFlags(MOUSE_UP), // default to only make sound on mouse up - mSaveToXML(TRUE), - mIsFocusRoot(FALSE), - mLastVisible(TRUE), - mUseBoundingRect(FALSE), - mVisible(TRUE), - mNextInsertionOrdinal(0), - mHoverCursor(UI_CURSOR_ARROW) -{ -} - -LLView::LLView(const std::string& name, BOOL mouse_opaque) : +// Compiler optimization, generate extern template +template class LLView* LLView::getChild<class LLView>( + const std::string& name, BOOL recurse) const; + +static LLDefaultChildRegistry::Register<LLView> r("view"); + +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), + center_horiz("center_horiz", false), + center_vert("center_vert", false), + from_xui("from_xui", false), + user_resize("user_resize"), + auto_resize("auto_resize"), + 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) +: mName(p.name), mParentView(NULL), - mName(name), mReshapeFlags(FOLLOWS_NONE), - mDefaultTabGroup(0), - mEnabled(TRUE), - mMouseOpaque(mouse_opaque), - mSoundFlags(MOUSE_UP), // default to only make sound on mouse up - mSaveToXML(TRUE), + mFromXUI(p.from_xui), mIsFocusRoot(FALSE), - mLastVisible(TRUE), - mUseBoundingRect(FALSE), - mVisible(TRUE), + mLastVisible(FALSE), mNextInsertionOrdinal(0), - mHoverCursor(UI_CURSOR_ARROW) -{ -} - - -LLView::LLView( - const std::string& name, const LLRect& rect, BOOL mouse_opaque, U32 reshape) : - mParentView(NULL), - mName(name), - mRect(rect), - mBoundingRect(rect), - mReshapeFlags(reshape), - mDefaultTabGroup(0), - mEnabled(TRUE), - mMouseOpaque(mouse_opaque), - mSoundFlags(MOUSE_UP), // default to only make sound on mouse up - mSaveToXML(TRUE), - mIsFocusRoot(FALSE), - mLastVisible(TRUE), - mUseBoundingRect(FALSE), - mVisible(TRUE), - mNextInsertionOrdinal(0), - mHoverCursor(UI_CURSOR_ARROW) + mHoverCursor(getCursorFromString(p.hover_cursor)), + mEnabled(p.enabled), + mVisible(p.visible), + 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(); //llinfos << "Deleting view " << mName << ":" << (void*) this << llendl; // llassert(LLView::sIsDrawing == FALSE); - if( gFocusMgr.getKeyboardFocus() == this ) - { - llwarns << "View holding keyboard focus deleted: " << getName() << ". Keyboard focus removed." << llendl; - gFocusMgr.removeKeyboardFocusWithoutCallback( this ); - } - + +// llassert_always(sDepth == 0); // avoid deleting views while drawing! It can subtly break list iterators + if( hasMouseCapture() ) { - llwarns << "View holding mouse capture deleted: " << getName() << ". Mouse capture removed." << llendl; + //llwarns << "View holding mouse capture deleted: " << getName() << ". Mouse capture removed." << llendl; gFocusMgr.removeMouseCaptureWithoutCallback( this ); } @@ -155,22 +167,13 @@ LLView::~LLView() mParentView->removeChild(this); } - dispatch_list_t::iterator itor; - for (itor = mDispatchList.begin(); itor != mDispatchList.end(); ++itor) + if (mDefaultWidgets) { - (*itor).second->clearDispatchers(); + std::for_each(mDefaultWidgets->begin(), mDefaultWidgets->end(), + DeletePairedPointer()); + delete mDefaultWidgets; + mDefaultWidgets = NULL; } - - std::for_each(mFloaterControls.begin(), mFloaterControls.end(), - DeletePairedPointer()); - std::for_each(mDummyWidgets.begin(), mDummyWidgets.end(), - DeletePairedPointer()); -} - -// virtual -BOOL LLView::isView() const -{ - return TRUE; } // virtual @@ -185,7 +188,6 @@ BOOL LLView::isPanel() const return FALSE; } -// virtual void LLView::setToolTip(const LLStringExplicit& msg) { mToolTipMsg = msg; @@ -224,27 +226,38 @@ BOOL LLView::getUseBoundingRect() } // virtual -const std::string& LLView::getName() const +std::string LLView::getName() const { - static const std::string unnamed("(no name)"); - return mName.empty() ? unnamed : mName; + return mName.empty() ? std::string("(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) { - mChildList.remove( child ); - mChildList.push_front(child); + // 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) { - mChildList.remove( child ); - mChildList.push_back(child); + // minor optimization, but more importantly, + // won't temporarily create an empty list + if (child != mChildList.back()) + { + mChildList.remove( child ); + mChildList.push_back(child); + } } } @@ -264,12 +277,18 @@ void LLView::moveChildToBackOfTabGroup(LLUICtrl* child) } } -void LLView::addChild(LLView* child, S32 tab_group) +// virtual +bool LLView::addChild(LLView* child, S32 tab_group) { + if (!child) + { + return false; + } if (mParentView == child) { llerrs << "Adding view " << child->getName() << " as child of itself" << llendl; } + // remove from current parent if (child->mParentView) { @@ -282,55 +301,46 @@ void LLView::addChild(LLView* child, S32 tab_group) // add to ctrl list if is LLUICtrl if (child->isCtrl()) { - // controls are stored in reverse order from render order - addCtrlAtEnd((LLUICtrl*) child, tab_group); + LLUICtrl* ctrl = static_cast<LLUICtrl*>(child); + mCtrlOrder.insert(tab_order_pair_t(ctrl, + tab_order_t(tab_group, mNextInsertionOrdinal))); + + mNextInsertionOrdinal++; } child->mParentView = this; updateBoundingRect(); + mLastTabGroup = tab_group; + return true; } -void LLView::addChildAtEnd(LLView* child, S32 tab_group) +bool LLView::addChildInBack(LLView* child, S32 tab_group) { - if (mParentView == child) - { - llerrs << "Adding view " << child->getName() << " as child of itself" << llendl; - } - // remove from current parent - if (child->mParentView) + if(addChild(child, tab_group)) { - child->mParentView->removeChild(child); + sendChildToBack(child); + return true; } - // add to back of child list - mChildList.push_back(child); - - // add to ctrl list if is LLUICtrl - if (child->isCtrl()) - { - // controls are stored in reverse order from render order - addCtrl((LLUICtrl*) child, tab_group); - } - - child->mParentView = this; - updateBoundingRect(); + return false; } // remove the specified child from the view, and set it's parent to NULL. -void LLView::removeChild(LLView* child, BOOL deleteIt) +void LLView::removeChild(LLView* child) { + //llassert_always(sDepth == 0); // Avoid re-ordering while drawing; it can cause subtle iterator bugs if (child->mParentView == this) { mChildList.remove( child ); child->mParentView = NULL; if (child->isCtrl()) { - removeCtrl((LLUICtrl*)child); - } - if (deleteIt) - { - delete child; + child_tab_order_t::iterator found = mCtrlOrder.find(static_cast<LLUICtrl*>(child)); + if(found != mCtrlOrder.end()) + { + mCtrlOrder.erase(found); + } } } else @@ -340,28 +350,6 @@ void LLView::removeChild(LLView* child, BOOL deleteIt) updateBoundingRect(); } -void LLView::addCtrlAtEnd(LLUICtrl* ctrl, S32 tab_group) -{ - mCtrlOrder.insert(tab_order_pair_t(ctrl, - tab_order_t(tab_group, mNextInsertionOrdinal++))); -} - -void LLView::addCtrl( LLUICtrl* ctrl, S32 tab_group) -{ - // add to front of list by using negative ordinal, which monotonically increases - mCtrlOrder.insert(tab_order_pair_t(ctrl, - tab_order_t(tab_group, -1 * mNextInsertionOrdinal++))); -} - -void LLView::removeCtrl(LLUICtrl* ctrl) -{ - child_tab_order_t::iterator found = mCtrlOrder.find(ctrl); - if(found != mCtrlOrder.end()) - { - mCtrlOrder.erase(found); - } -} - LLView::ctrl_list_t LLView::getCtrlList() const { ctrl_list_t controls; @@ -411,32 +399,28 @@ bool LLCompareByTabOrder::operator() (const LLView* const a, const LLView* const return (a_score == b_score) ? a < b : a_score < b_score; } -BOOL LLView::isInVisibleChain() const +bool LLView::trueToRoot(const boost::function<bool (const LLView*)>& predicate) const { const LLView* cur_view = this; while(cur_view) { - if (!cur_view->getVisible()) + if(!predicate(cur_view)) { - return FALSE; + return false; } cur_view = cur_view->getParent(); } - return TRUE; + return true; +} + +BOOL LLView::isInVisibleChain() const +{ + return trueToRoot(&LLView::getVisible); } BOOL LLView::isInEnabledChain() const { - const LLView* cur_view = this; - while(cur_view) - { - if (!cur_view->getEnabled()) - { - return FALSE; - } - cur_view = cur_view->getParent(); - } - return TRUE; + return trueToRoot(&LLView::getEnabled); } // virtual @@ -463,31 +447,33 @@ void LLView::setEnabled(BOOL enabled) } //virtual -BOOL LLView::setLabelArg( const std::string& key, const LLStringExplicit& text ) +bool LLView::isAvailable() const { - return FALSE; + return isInEnabledChain() && isInVisibleChain(); } -//virtual -LLRect LLView::getSnapRect() const +//static +bool LLView::isAvailable(const LLView* view) { - return mRect; + return view && view->isAvailable(); } //virtual -LLRect LLView::getRequiredRect() +BOOL LLView::setLabelArg( const std::string& key, const LLStringExplicit& text ) { - return mRect; + return FALSE; } //virtual -void LLView::onFocusLost() +LLRect LLView::getSnapRect() const { + return mRect; } //virtual -void LLView::onFocusReceived() +LLRect LLView::getRequiredRect() { + return mRect; } BOOL LLView::focusNextRoot() @@ -617,14 +603,15 @@ void LLView::setVisible(BOOL visible) if (!getParent() || getParent()->isInVisibleChain()) { // tell all children of this view that the visibility may have changed - onVisibilityChange( visible ); + dirtyRect(); + handleVisibilityChange( visible ); } updateBoundingRect(); } } // virtual -void LLView::onVisibilityChange ( BOOL new_visibility ) +void LLView::handleVisibilityChange ( BOOL new_visibility ) { for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) { @@ -632,7 +619,7 @@ void LLView::onVisibilityChange ( BOOL new_visibility ) // only views that are themselves visible will have their overall visibility affected by their ancestors if (viewp->getVisible()) { - viewp->onVisibilityChange ( new_visibility ); + viewp->handleVisibilityChange ( new_visibility ); } } } @@ -651,119 +638,111 @@ BOOL LLView::canSnapTo(const LLView* other_view) } // virtual -void LLView::snappedTo(const LLView* snap_view) +void LLView::setSnappedTo(const LLView* snap_view) { } BOOL LLView::handleHover(S32 x, S32 y, MASK mask) { - BOOL handled = childrenHandleHover( x, y, mask ) != NULL; - if( !handled - && blockMouseEvent(x, y) ) - { - LLUI::sWindow->setCursor(mHoverCursor); - lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl; - handled = TRUE; - } + return childrenHandleHover( x, y, mask ) != NULL; +} - return handled; +void LLView::onMouseEnter(S32 x, S32 y, MASK mask) +{ + //llinfos << "Mouse entered " << getName() << llendl; } -std::string LLView::getShowNamesToolTip() +void LLView::onMouseLeave(S32 x, S32 y, MASK mask) { - LLView* view = getParent(); - std::string name; - std::string tool_tip = mName; + //llinfos << "Mouse left " << getName() << llendl; +} - while (view) - { - name = view->getName(); - if (name == "root") break; +LLView* LLView::childrenHandleToolTip(S32 x, S32 y, MASK mask) +{ + LLView* handled_view = NULL; + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.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) + || !viewp->getVisible()) + { + continue; + } - if (view->getToolTip().find(".xml") != std::string::npos) + if (viewp->handleToolTip(local_x, local_y, mask) ) { - tool_tip = view->getToolTip() + "/" + tool_tip; + if (sDebugMouseHandling) + { + sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; + } + + handled_view = viewp; break; } - else + + if (viewp->blockMouseEvent(local_x, local_y)) { - tool_tip = view->getName() + "/" + tool_tip; + handled_view = viewp; + break; } - - view = view->getParent(); } - - return "/" + tool_tip; + return handled_view; } -BOOL LLView::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen) +LLView* LLView::childFromPoint(S32 x, S32 y) { - BOOL handled = FALSE; - - std::string tool_tip; - + if (!getVisible() ) + return false; for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) { LLView* viewp = *child_it; - S32 local_x = x - viewp->mRect.mLeft; - S32 local_y = y - viewp->mRect.mBottom; - // Allow tooltips for disabled views so we can explain to the user why - // the view is disabled. JC - if( viewp->pointInView(local_x, local_y) - && viewp->getVisible() - // && viewp->getEnabled() - && viewp->handleToolTip(local_x, local_y, msg, sticky_rect_screen )) - { - // child provided a tooltip, just return - if (!msg.empty()) return TRUE; - - // otherwise, one of our children ate the event so don't traverse - // siblings however, our child did not actually provide a tooltip - // so we might want to - handled = TRUE; - break; + S32 local_x = x - viewp->getRect().mLeft; + S32 local_y = y - viewp->getRect().mBottom; + if (!viewp->pointInView(local_x, local_y) + || !viewp->getVisible() ) + { + continue; } - } + return viewp; - // get our own tooltip - tool_tip = mToolTipMsg.getString(); - if ( - LLUI::sShowXUINames - && (tool_tip.find(".xml", 0) == std::string::npos) - && (mName.find("Drag", 0) == std::string::npos)) - { - tool_tip = getShowNamesToolTip(); } + return 0; +} - BOOL show_names_text_box = LLUI::sShowXUINames && dynamic_cast<LLTextBox*>(this) != NULL; +BOOL LLView::handleToolTip(S32 x, S32 y, MASK mask) +{ + BOOL handled = FALSE; - // don't allow any siblings to handle this event - // even if we don't have a tooltip - if (getMouseOpaque() || show_names_text_box) + // parents provide tooltips first, which are optionally + // overridden by children, in case child is mouse_opaque + if (!mToolTipMsg.empty()) { + // allow "scrubbing" over ui by showing next tooltip immediately + // if previous one was still visible + F32 timeout = LLToolTipMgr::instance().toolTipVisible() + ? 0.f + : LLUI::sSettingGroups["config"]->getF32( "ToolTipDelay" ); + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(mToolTipMsg) + .sticky_rect(calcScreenRect()) + .delay_time(timeout)); + handled = TRUE; } - if(!tool_tip.empty()) + // child tooltips will override our own + LLView* child_handler = childrenHandleToolTip(x, y, mask); + if (child_handler) { - msg = tool_tip; - - // Convert rect local to screen coordinates - localPointToScreen( - 0, 0, - &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) ); - localPointToScreen( - mRect.getWidth(), mRect.getHeight(), - &(sticky_rect_screen->mRight), &(sticky_rect_screen->mTop) ); - handled = TRUE; } return handled; } - BOOL LLView::handleKey(KEY key, MASK mask, BOOL called_from_parent) { BOOL handled = FALSE; @@ -844,20 +823,7 @@ BOOL LLView::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EAcceptance* accept, std::string& tooltip_msg) { - // CRO this is an experiment to allow drag and drop into object inventory based on the DragAndDrop tool's permissions rather than the parent - BOOL handled = childrenHandleDragAndDrop( x, y, mask, drop, - cargo_type, - cargo_data, - accept, - tooltip_msg) != NULL; - if( !handled && blockMouseEvent(x, y) ) - { - *accept = ACCEPT_NO; - handled = TRUE; - lldebugst(LLERR_USER_INPUT) << "dragAndDrop handled by LLView " << getName() << llendl; - } - - return handled; + return childrenHandleDragAndDrop( x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) != NULL; } LLView* LLView::childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, @@ -867,28 +833,33 @@ LLView* LLView::childrenHandleDragAndDrop(S32 x, S32 y, MASK mask, EAcceptance* accept, std::string& tooltip_msg) { - LLView* handled_view = FALSE; - // CRO this is an experiment to allow drag and drop into object inventory based on the DragAndDrop tool's permissions rather than the parent - if( getVisible() ) -// if( getVisible() && getEnabled() ) + LLView* handled_view = NULL; + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) { - for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.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) || + !viewp->getVisible() || + !viewp->getEnabled()) { - 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) && - viewp->getVisible() && - viewp->getEnabled() && - viewp->handleDragAndDrop(local_x, local_y, mask, drop, - cargo_type, - cargo_data, - accept, - tooltip_msg)) - { - handled_view = viewp; - break; - } + continue; + } + if (viewp->handleDragAndDrop(local_x, local_y, mask, drop, + cargo_type, + cargo_data, + accept, + tooltip_msg)) + { + handled_view = viewp; + break; + } + + if (viewp->blockMouseEvent(x, y)) + { + *accept = ACCEPT_NO; + handled_view = viewp; + break; } } return handled_view; @@ -905,110 +876,42 @@ BOOL LLView::hasMouseCapture() BOOL LLView::handleMouseUp(S32 x, S32 y, MASK mask) { - BOOL handled = childrenHandleMouseUp( x, y, mask ) != NULL; - if( !handled && blockMouseEvent(x, y) ) - { - handled = TRUE; - } - return handled; + return childrenHandleMouseUp( x, y, mask ) != NULL; } BOOL LLView::handleMouseDown(S32 x, S32 y, MASK mask) { - LLView* handled_view = childrenHandleMouseDown( x, y, mask ); - BOOL handled = (handled_view != NULL); - if( !handled && blockMouseEvent(x, y) ) - { - handled = TRUE; - handled_view = this; - } - - // HACK If we're editing UI, select the leaf view that ate the click. - if (sEditingUI && handled_view) - { - // need to find leaf views, big hack - LLButton* buttonp = dynamic_cast<LLButton*>(handled_view); - LLLineEditor* line_editorp = dynamic_cast<LLLineEditor*>(handled_view); - LLTextEditor* text_editorp = dynamic_cast<LLTextEditor*>(handled_view); - LLTextBox* text_boxp = dynamic_cast<LLTextBox*>(handled_view); - if (buttonp - || line_editorp - || text_editorp - || text_boxp) - { - sEditingUIView = handled_view; - } - } - - return handled; + return childrenHandleMouseDown( x, y, mask ) != NULL; } BOOL LLView::handleDoubleClick(S32 x, S32 y, MASK mask) { - BOOL handled = childrenHandleDoubleClick( x, y, mask ) != NULL; - if( !handled && blockMouseEvent(x, y) ) - { - handleMouseDown(x, y, mask); - handled = TRUE; - } - return handled; + return childrenHandleDoubleClick( x, y, mask ) != NULL; } BOOL LLView::handleScrollWheel(S32 x, S32 y, S32 clicks) { - BOOL handled = FALSE; - if( getVisible() && getEnabled() ) - { - handled = childrenHandleScrollWheel( x, y, clicks ) != NULL; - if( !handled && blockMouseEvent(x, y) ) - { - handled = TRUE; - } - } - return handled; + return childrenHandleScrollWheel( x, y, clicks ) != NULL; } BOOL LLView::handleRightMouseDown(S32 x, S32 y, MASK mask) { - BOOL handled = childrenHandleRightMouseDown( x, y, mask ) != NULL; - if( !handled && blockMouseEvent(x, y) ) - { - handled = TRUE; - } - return handled; + return childrenHandleRightMouseDown( x, y, mask ) != NULL; } BOOL LLView::handleRightMouseUp(S32 x, S32 y, MASK mask) { - BOOL handled = childrenHandleRightMouseUp( x, y, mask ) != NULL; - if( !handled && blockMouseEvent(x, y) ) - { - handled = TRUE; - } - return handled; + return childrenHandleRightMouseUp( x, y, mask ) != NULL; } BOOL LLView::handleMiddleMouseDown(S32 x, S32 y, MASK mask) { - LLView* handled_view = childrenHandleMiddleMouseDown( x, y, mask ); - BOOL handled = (handled_view != NULL); - if( !handled && blockMouseEvent(x, y) ) - { - handled = TRUE; - handled_view = this; - } - - return handled; + return childrenHandleMiddleMouseDown( x, y, mask ) != NULL; } BOOL LLView::handleMiddleMouseUp(S32 x, S32 y, MASK mask) { - BOOL handled = childrenHandleMiddleMouseUp( x, y, mask ) != NULL; - if( !handled && blockMouseEvent(x, y) ) - { - handled = TRUE; - } - return handled; + return childrenHandleMiddleMouseUp( x, y, mask ) != NULL; } @@ -1022,14 +925,18 @@ LLView* LLView::childrenHandleScrollWheel(S32 x, S32 y, S32 clicks) 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) - && viewp->getVisible() - && viewp->getEnabled() - && viewp->handleScrollWheel( local_x, local_y, clicks )) + if (!viewp->pointInView(local_x, local_y) + || !viewp->getVisible() + || !viewp->getEnabled()) + { + continue; + } + + if (viewp->handleScrollWheel( local_x, local_y, clicks )) { if (sDebugMouseHandling) { - sMouseHandlerMessage = std::string("->") + viewp->mName + sMouseHandlerMessage; + sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; } handled_view = viewp; @@ -1050,19 +957,31 @@ LLView* LLView::childrenHandleHover(S32 x, S32 y, MASK mask) 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) && - viewp->getVisible() && - viewp->getEnabled() && - viewp->handleHover(local_x, local_y, mask) ) + if(!viewp->pointInView(local_x, local_y) + || !viewp->getVisible() + || !viewp->getEnabled()) + { + continue; + } + + if (viewp->handleHover(local_x, local_y, mask) ) { if (sDebugMouseHandling) { - sMouseHandlerMessage = std::string("->") + viewp->mName + sMouseHandlerMessage; + sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; } handled_view = viewp; break; } + + if (viewp->blockMouseEvent(local_x, local_y)) + { + LLUI::sWindow->setCursor(viewp->getHoverCursor()); + + handled_view = viewp; + break; + } } } return handled_view; @@ -1128,18 +1047,28 @@ LLView* LLView::childrenHandleMouseDown(S32 x, S32 y, MASK mask) S32 local_x = x - viewp->getRect().mLeft; S32 local_y = y - viewp->getRect().mBottom; - if (viewp->pointInView(local_x, local_y) && - viewp->getVisible() && - viewp->getEnabled() && - viewp->handleMouseDown( local_x, local_y, mask )) + if (!viewp->pointInView(local_x, local_y) + || !viewp->getVisible() + || !viewp->getEnabled()) + { + continue; + } + + if(viewp->handleMouseDown( local_x, local_y, mask )) { if (sDebugMouseHandling) { - sMouseHandlerMessage = std::string("->") + viewp->mName + sMouseHandlerMessage; + sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; } handled_view = viewp; break; } + + if(viewp->blockMouseEvent(local_x, local_y)) + { + handled_view = viewp; + break; + } } return handled_view; } @@ -1155,15 +1084,27 @@ LLView* LLView::childrenHandleRightMouseDown(S32 x, S32 y, MASK mask) 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) && - viewp->getVisible() && - viewp->getEnabled() && - viewp->handleRightMouseDown( local_x, local_y, mask )) + + if (!viewp->pointInView(local_x, local_y) + || !viewp->getVisible() + || !viewp->getEnabled()) + { + continue; + } + + if (viewp->handleRightMouseDown( local_x, local_y, mask )) { if (sDebugMouseHandling) { - sMouseHandlerMessage = std::string("->") + viewp->mName + sMouseHandlerMessage; + sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; } + + handled_view = viewp; + break; + } + + if (viewp->blockMouseEvent(local_x, local_y)) + { handled_view = viewp; break; } @@ -1183,18 +1124,28 @@ LLView* LLView::childrenHandleMiddleMouseDown(S32 x, S32 y, MASK mask) 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) && - viewp->getVisible() && - viewp->getEnabled() && - viewp->handleMiddleMouseDown( local_x, local_y, mask )) + if (!viewp->pointInView(local_x, local_y) + || !viewp->getVisible() + || !viewp->getEnabled()) + { + continue; + } + + if(viewp->handleMiddleMouseDown( local_x, local_y, mask )) { if (sDebugMouseHandling) { - sMouseHandlerMessage = std::string("->") + viewp->mName + sMouseHandlerMessage; + sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; } handled_view = viewp; break; } + + if (viewp->blockMouseEvent(local_x, local_y)) + { + handled_view = viewp; + break; + } } } return handled_view; @@ -1211,18 +1162,29 @@ LLView* LLView::childrenHandleDoubleClick(S32 x, S32 y, MASK mask) 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) && - viewp->getVisible() && - viewp->getEnabled() && - viewp->handleDoubleClick( local_x, local_y, mask )) + + if (!viewp->pointInView(local_x, local_y) + || !viewp->getVisible() + || !viewp->getEnabled()) + { + continue; + } + + if (viewp->handleDoubleClick( local_x, local_y, mask )) { if (sDebugMouseHandling) { - sMouseHandlerMessage = std::string("->") + viewp->mName + sMouseHandlerMessage; + sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; } handled_view = viewp; break; } + + if (viewp->blockMouseEvent(local_x, local_y)) + { + handled_view = viewp; + break; + } } } return handled_view; @@ -1238,21 +1200,28 @@ LLView* LLView::childrenHandleMouseUp(S32 x, S32 y, MASK mask) 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)) - continue; - if (!viewp->getVisible()) - continue; - if (!viewp->getEnabled()) + if (!viewp->pointInView(local_x, local_y) + || !viewp->getVisible() + || !viewp->getEnabled()) + { continue; + } + if (viewp->handleMouseUp( local_x, local_y, mask )) { if (sDebugMouseHandling) { - sMouseHandlerMessage = std::string("->") + viewp->mName + sMouseHandlerMessage; + sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; } handled_view = viewp; break; } + + if (viewp->blockMouseEvent(local_x, local_y)) + { + handled_view = viewp; + break; + } } } return handled_view; @@ -1268,18 +1237,28 @@ LLView* LLView::childrenHandleRightMouseUp(S32 x, S32 y, MASK mask) 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) && - viewp->getVisible() && - viewp->getEnabled() && - viewp->handleRightMouseUp( local_x, local_y, mask )) + if (!viewp->pointInView(local_x, local_y) + || !viewp->getVisible() + || !viewp->getEnabled() ) + { + continue; + } + + if(viewp->handleRightMouseUp( local_x, local_y, mask )) { if (sDebugMouseHandling) { - sMouseHandlerMessage = std::string("->") + viewp->mName + sMouseHandlerMessage; + sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; } handled_view = viewp; break; } + + if(viewp->blockMouseEvent(local_x, local_y)) + { + handled_view = viewp; + break; + } } } return handled_view; @@ -1295,18 +1274,28 @@ LLView* LLView::childrenHandleMiddleMouseUp(S32 x, S32 y, MASK mask) 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) && - viewp->getVisible() && - viewp->getEnabled() && - viewp->handleMiddleMouseUp( local_x, local_y, mask )) + if (!viewp->pointInView(local_x, local_y) + || !viewp->getVisible() + || !viewp->getEnabled()) + { + continue; + } + + if(viewp->handleMiddleMouseUp( local_x, local_y, mask )) { if (sDebugMouseHandling) { - sMouseHandlerMessage = std::string("->") + viewp->mName + sMouseHandlerMessage; + sMouseHandlerMessage = std::string("/") + viewp->mName + sMouseHandlerMessage; } handled_view = viewp; break; } + + if (viewp->blockMouseEvent(local_x, local_y)) + { + handled_view = viewp; + break; + } } } return handled_view; @@ -1314,67 +1303,77 @@ LLView* LLView::childrenHandleMiddleMouseUp(S32 x, S32 y, MASK mask) void LLView::draw() { - if (sDebugRects) - { - drawDebugRect(); - - // Check for bogus rectangle - if (getRect().mRight <= getRect().mLeft - || getRect().mTop <= getRect().mBottom) - { - llwarns << "Bogus rectangle for " << getName() << " with " << mRect << llendl; - } - } - - LLRect rootRect = getRootView()->getRect(); - LLRect screenRect; + drawChildren(); +} - // draw focused control on top of everything else - LLView* focus_view = gFocusMgr.getKeyboardFocus(); - if (focus_view && focus_view->getParent() != this) +void LLView::drawChildren() +{ + if (!mChildList.empty()) { - focus_view = NULL; - } + LLRect rootRect = getRootView()->getRect(); + LLRect screenRect; - ++sDepth; - for (child_list_reverse_iter_t child_iter = mChildList.rbegin(); child_iter != mChildList.rend(); ++child_iter) - { - LLView *viewp = *child_iter; + ++sDepth; - if (viewp->getVisible() && viewp != focus_view && viewp->getRect().isValid()) + for (child_list_reverse_iter_t child_iter = mChildList.rbegin(); child_iter != mChildList.rend();) // ++child_iter) { - // Only draw views that are within the root view - localRectToScreen(viewp->getRect(),&screenRect); - if ( rootRect.rectInRect(&screenRect) ) + child_list_reverse_iter_t child = child_iter++; + LLView *viewp = *child; + + if (viewp->getVisible() && viewp->getRect().isValid()) { - glMatrixMode(GL_MODELVIEW); - LLUI::pushMatrix(); + // Only draw views that are within the root view + localRectToScreen(viewp->getRect(),&screenRect); + if ( rootRect.overlaps(screenRect) && LLUI::sDirtyRect.overlaps(screenRect)) { - LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom, 0.f); - viewp->draw(); + glMatrixMode(GL_MODELVIEW); + LLUI::pushMatrix(); + { + LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom, 0.f); + viewp->draw(); + + if (sDebugRects) + { + viewp->drawDebugRect(); + + // Check for bogus rectangle + if (!getRect().isValid()) + { + llwarns << "Bogus rectangle for " << getName() << " with " << mRect << llendl; + } + } + } + LLUI::popMatrix(); } - LLUI::popMatrix(); } - } + } + --sDepth; } - --sDepth; - if (focus_view && focus_view->getVisible()) - { - drawChild(focus_view); - } + gGL.getTexUnit(0)->disable(); +} - // HACK - if (sEditingUI && this == sEditingUIView) - { - drawDebugRect(); +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(); } + + LLUI::dirtyRect(cur->calcScreenRect()); } //Draw a box for debugging. void LLView::drawDebugRect() { + std::set<LLView*>::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 @@ -1388,10 +1387,18 @@ void LLView::drawDebugRect() LLRect debug_rect = mUseBoundingRect ? mBoundingRect : mRect; // draw red rectangle for the border - LLColor4 border_color(0.f, 0.f, 0.f, 1.f); - if (sEditingUI) + LLColor4 border_color(0.25f, 0.25f, 0.25f, 1.f); + if(preview_iter != sPreviewHighlightedElements.end()) { - border_color.mV[0] = 1.f; + 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 { @@ -1414,8 +1421,10 @@ void LLView::drawDebugRect() gGL.vertex2i(0, debug_rect.getHeight() - 1); gGL.end(); - // Draw the name if it's not a leaf node - if (mChildList.size() && !sEditingUI) + // 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) { //char temp[256]; S32 x, y; @@ -1425,7 +1434,7 @@ void LLView::drawDebugRect() 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::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE); } } @@ -1529,45 +1538,53 @@ void LLView::reshape(S32 width, S32 height, BOOL called_from_parent) updateBoundingRect(); } -void LLView::updateBoundingRect() +LLRect LLView::calcBoundingRect() { - if (isDead()) return; + LLRect local_bounding_rect = LLRect::null; - if (mUseBoundingRect) + child_list_const_iter_t child_it; + for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) { - LLRect local_bounding_rect = LLRect::null; - - child_list_const_iter_t child_it; - for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + LLView* childp = *child_it; + // ignore invisible and "top" children when calculating bounding rect + // such as combobox popups + if (!childp->getVisible() || childp == gFocusMgr.getTopCtrl()) { - LLView* childp = *child_it; - // ignore invisible and "top" children when calculating bounding rect - // such as combobox popups - if (!childp->getVisible() || childp == gFocusMgr.getTopCtrl()) - { - continue; - } + continue; + } - LLRect child_bounding_rect = childp->getBoundingRect(); + LLRect child_bounding_rect = childp->getBoundingRect(); - if (local_bounding_rect.isNull()) - { - // start out with bounding rect equal to first visible child's bounding rect - local_bounding_rect = child_bounding_rect; - } - else + 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()) { - // accumulate non-null children rectangles - if (!child_bounding_rect.isNull()) - { - local_bounding_rect.unionWith(child_bounding_rect); - } + local_bounding_rect.unionWith(child_bounding_rect); } } + } + + // convert to parent-relative coordinates + local_bounding_rect.translate(mRect.mLeft, mRect.mBottom); + return local_bounding_rect; +} + - mBoundingRect = local_bounding_rect; - // translate into parent-relative coordinates - mBoundingRect.translate(mRect.mLeft, mRect.mBottom); +void LLView::updateBoundingRect() +{ + if (isDead()) return; + + LLRect cur_rect = mBoundingRect; + + if (mUseBoundingRect) + { + mBoundingRect = calcBoundingRect(); } else { @@ -1579,17 +1596,36 @@ void LLView::updateBoundingRect() { getParent()->updateBoundingRect(); } + + if (mBoundingRect != cur_rect) + { + dirtyRect(); + } + } -LLRect LLView::getScreenRect() const +LLRect LLView::calcScreenRect() const { - // *FIX: check for one-off error 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 = mUseBoundingRect ? 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(); @@ -1636,7 +1672,7 @@ BOOL LLView::hasAncestor(const LLView* parentp) const BOOL LLView::childHasKeyboardFocus( const std::string& childname ) const { - LLView *child = getChildView(childname, TRUE, FALSE); + LLView *child = findChildView(childname, TRUE); if (child) { return gFocusMgr.childHasKeyboardFocus(child); @@ -1651,14 +1687,31 @@ BOOL LLView::childHasKeyboardFocus( const std::string& childname ) const BOOL LLView::hasChild(const std::string& childname, BOOL recurse) const { - return getChildView(childname, recurse, FALSE) != NULL; + return findChildView(childname, recurse) != NULL; } //----------------------------------------------------------------------------- // getChildView() //----------------------------------------------------------------------------- -LLView* LLView::getChildView(const std::string& name, BOOL recurse, BOOL create_if_missing) const +LLView* LLView::getChildView(const std::string& name, BOOL recurse) const +{ + LLView* child = findChildView(name, recurse); + if (!child) + { + child = getDefaultWidget<LLView>(name); + if (!child) + { + child = LLUICtrlFactory::createDefaultWidget<LLView>(name); + } + } + return child; +} + +static LLFastTimer::DeclareTimer FTM_FIND_VIEWS("Find Widgets"); + +LLView* LLView::findChildView(const std::string& name, BOOL recurse) const { + LLFastTimer ft(FTM_FIND_VIEWS); //richard: should we allow empty names? //if(name.empty()) // return NULL; @@ -1678,18 +1731,13 @@ LLView* LLView::getChildView(const std::string& name, BOOL recurse, BOOL create_ for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) { LLView* childp = *child_it; - LLView* viewp = childp->getChildView(name, recurse, FALSE); + LLView* viewp = childp->findChildView(name, recurse); if ( viewp ) { return viewp; } } } - - if (create_if_missing) - { - return createDummyWidget<LLView>(name); - } return NULL; } @@ -1777,82 +1825,152 @@ LLView* LLView::getRootView() return view; } -BOOL LLView::deleteViewByHandle(LLHandle<LLView> handle) +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; +} + +void LLView::deleteViewByHandle(LLHandle<LLView> handle) { LLView* viewp = handle.get(); delete viewp; - return viewp != NULL; } -// 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, BOOL allow_partial_outside ) +LLCoordGL getNeededTranslation(const LLRect& input, const LLRect& constraint, BOOL allow_partial_outside) { - S32 delta_x = 0; - S32 delta_y = 0; + LLCoordGL delta; if (allow_partial_outside) { const S32 KEEP_ONSCREEN_PIXELS = 16; - if( getRect().mRight - KEEP_ONSCREEN_PIXELS < constraint.mLeft ) + if( input.mRight - KEEP_ONSCREEN_PIXELS < constraint.mLeft ) { - delta_x = constraint.mLeft - (getRect().mRight - KEEP_ONSCREEN_PIXELS); + delta.mX = constraint.mLeft - (input.mRight - KEEP_ONSCREEN_PIXELS); } else - if( getRect().mLeft + KEEP_ONSCREEN_PIXELS > constraint.mRight ) + if( input.mLeft + KEEP_ONSCREEN_PIXELS > constraint.mRight ) { - delta_x = constraint.mRight - (getRect().mLeft + KEEP_ONSCREEN_PIXELS); + delta.mX = constraint.mRight - (input.mLeft + KEEP_ONSCREEN_PIXELS); } - if( getRect().mTop > constraint.mTop ) + if( input.mTop > constraint.mTop ) { - delta_y = constraint.mTop - getRect().mTop; + delta.mY = constraint.mTop - input.mTop; } else - if( getRect().mTop - KEEP_ONSCREEN_PIXELS < constraint.mBottom ) + if( input.mTop - KEEP_ONSCREEN_PIXELS < constraint.mBottom ) { - delta_y = constraint.mBottom - (getRect().mTop - KEEP_ONSCREEN_PIXELS); + delta.mY = constraint.mBottom - (input.mTop - KEEP_ONSCREEN_PIXELS); } } else { - if( getRect().mLeft < constraint.mLeft ) + if( input.mLeft < constraint.mLeft ) { - delta_x = constraint.mLeft - getRect().mLeft; + delta.mX = constraint.mLeft - input.mLeft; } else - if( getRect().mRight > constraint.mRight ) + if( input.mRight > constraint.mRight ) { - delta_x = constraint.mRight - getRect().mRight; + delta.mX = constraint.mRight - input.mRight; // compensate for left edge possible going off screen - delta_x += llmax( 0, getRect().getWidth() - constraint.getWidth() ); + delta.mX += llmax( 0, input.getWidth() - constraint.getWidth() ); } - if( getRect().mTop > constraint.mTop ) + if( input.mTop > constraint.mTop ) { - delta_y = constraint.mTop - getRect().mTop; + delta.mY = constraint.mTop - input.mTop; } else - if( getRect().mBottom < constraint.mBottom ) + if( input.mBottom < constraint.mBottom ) { - delta_y = constraint.mBottom - getRect().mBottom; + delta.mY = constraint.mBottom - input.mBottom; // compensate for top edge possible going off screen - delta_y -= llmax( 0, getRect().getHeight() - constraint.getHeight() ); + delta.mY -= llmax( 0, input.getHeight() - constraint.getHeight() ); } } - if (delta_x != 0 || delta_y != 0) + 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, BOOL allow_partial_outside ) +{ + LLCoordGL translation = getNeededTranslation(getRect(), constraint, allow_partial_outside); + + 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, BOOL allow_partial_outside ) +{ + LLCoordGL translation = getNeededTranslation(getRect(), inside, allow_partial_outside); + + if (translation.mX != 0 || translation.mY != 0) { - translate(delta_x, delta_y); + // 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; @@ -1943,132 +2061,6 @@ BOOL LLView::localRectToOtherView( const LLRect& local, LLRect* other, LLView* o return FALSE; } -// virtual -LLXMLNodePtr LLView::getXML(bool save_children) const -{ - //FIXME: need to provide actual derived type tag, probably outside this method - LLXMLNodePtr node = new LLXMLNode("view", FALSE); - - node->createChild("name", TRUE)->setStringValue(getName()); - node->createChild("width", TRUE)->setIntValue(getRect().getWidth()); - node->createChild("height", TRUE)->setIntValue(getRect().getHeight()); - - LLView* parent = getParent(); - S32 left = getRect().mLeft; - S32 bottom = getRect().mBottom; - if (parent) bottom -= parent->getRect().getHeight(); - - node->createChild("left", TRUE)->setIntValue(left); - node->createChild("bottom", TRUE)->setIntValue(bottom); - - U32 follows_flags = getFollows(); - if (follows_flags) - { - std::stringstream buffer; - bool pipe = false; - if (followsLeft()) - { - buffer << "left"; - pipe = true; - } - if (followsTop()) - { - if (pipe) buffer << "|"; - buffer << "top"; - pipe = true; - } - if (followsRight()) - { - if (pipe) buffer << "|"; - buffer << "right"; - pipe = true; - } - if (followsBottom()) - { - if (pipe) buffer << "|"; - buffer << "bottom"; - } - node->createChild("follows", TRUE)->setStringValue(buffer.str()); - } - // Export all widgets as enabled and visible - code must disable. - node->createChild("mouse_opaque", TRUE)->setBoolValue(mMouseOpaque ); - if (!mToolTipMsg.getString().empty()) - { - node->createChild("tool_tip", TRUE)->setStringValue(mToolTipMsg.getString()); - } - if (mSoundFlags != MOUSE_UP) - { - node->createChild("sound_flags", TRUE)->setIntValue((S32)mSoundFlags); - } - - node->createChild("enabled", TRUE)->setBoolValue(getEnabled()); - - if (!mControlName.empty()) - { - node->createChild("control_name", TRUE)->setStringValue(mControlName); - } - return node; -} - -//static -LLView* LLView::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - LLView* viewp = new LLView(); - viewp->initFromXML(node, parent); - return viewp; -} - -// static -void LLView::addColorXML(LLXMLNodePtr node, const LLColor4& color, - const char* xml_name, const char* control_name) -{ - if (color != LLUI::sColorsGroup->getColor(ll_safe_string(control_name))) - { - node->createChild(xml_name, TRUE)->setFloatValue(4, color.mV); - } -} - -//static -std::string LLView::escapeXML(const std::string& xml, std::string& indent) -{ - std::string ret = indent + "\"" + LLXMLNode::escapeXML(xml); - - //replace every newline with a close quote, new line, indent, open quote - size_t index = ret.size()-1; - size_t fnd; - - while ((fnd = ret.rfind("\n", index)) != std::string::npos) - { - ret.replace(fnd, 1, "\"\n" + indent + "\""); - index = fnd-1; - } - - //append close quote - ret.append("\""); - - return ret; -} - -// static -LLWString LLView::escapeXML(const LLWString& xml) -{ - LLWString out; - for (LLWString::size_type i = 0; i < xml.size(); ++i) - { - llwchar c = xml[i]; - switch(c) - { - case '"': out.append(utf8string_to_wstring(""")); break; - case '\'': out.append(utf8string_to_wstring("'")); break; - case '&': out.append(utf8string_to_wstring("&")); break; - case '<': out.append(utf8string_to_wstring("<")); break; - case '>': out.append(utf8string_to_wstring(">")); break; - default: out.push_back(c); break; - } - } - return out; -} - // static const LLCtrlQuery & LLView::getTabOrderQuery() { @@ -2105,7 +2097,12 @@ const LLCtrlQuery & LLView::getFocusRootsQuery() } -void LLView::userSetShape(const LLRect& new_rect) +void LLView::setShape(const LLRect& new_rect, bool by_user) +{ + 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); @@ -2353,560 +2350,534 @@ LLView* LLView::findSnapEdge(S32& new_edge_val, const LLCoordGL& mouse_dir, ESna // Listener dispatch functions //----------------------------------------------------------------------------- -void LLView::registerEventListener(std::string name, LLSimpleListener* function) -{ - mDispatchList.insert(std::pair<std::string, LLSimpleListener*>(name, function)); -} - -void LLView::deregisterEventListener(std::string name) -{ - dispatch_list_t::iterator itor = mDispatchList.find(name); - if (itor != mDispatchList.end()) - { - mDispatchList.erase(itor); - } -} -std::string LLView::findEventListener(LLSimpleListener *listener) const +LLControlVariable *LLView::findControl(const std::string& name) { - dispatch_list_t::const_iterator itor; - for (itor = mDispatchList.begin(); itor != mDispatchList.end(); ++itor) + // parse the name to locate which group it belongs to + std::size_t key_pos= name.find("."); + if(key_pos!= std::string::npos ) { - if (itor->second == listener) + 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::sSettingGroups[control_group_key]) { - return itor->first; + control = LLUI::sSettingGroups[control_group_key]->getControl(name); + if (control) + { + return control; + } } } - if (mParentView) - { - return mParentView->findEventListener(listener); - } - return LLStringUtil::null; + + LLControlGroup& control_group = LLUI::getControlControlGroup(name); + return control_group.getControl(name); } -LLSimpleListener* LLView::getListenerByName(const std::string& callback_name) +const S32 FLOATER_H_MARGIN = 15; +const S32 MIN_WIDGET_HEIGHT = 10; +const S32 VPAD = 4; + +void LLView::initFromParams(const LLView::Params& params) { - LLSimpleListener* callback = NULL; - dispatch_list_t::iterator itor = mDispatchList.find(callback_name); - if (itor != mDispatchList.end()) - { - callback = itor->second; - } - else if (mParentView) + 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()) { - callback = mParentView->getListenerByName(callback_name); + setName(params.name()); } - return callback; + + mLayout = params.layout(); } -LLControlVariable *LLView::findControl(const std::string& name) +void LLView::parseFollowsFlags(const LLView::Params& params) { - control_map_t::iterator itor = mFloaterControls.find(name); - if (itor != mFloaterControls.end()) + // preserve follows flags set by code if user did not override + if (!params.follows.isProvided()) { - return itor->second; + return; } - if (mParentView) + + // interpret either string or bitfield version of follows + if (params.follows.string.isChosen()) { - return mParentView->findControl(name); - } - return LLUI::sConfigGroup->getControl(name); -} + setFollows(FOLLOWS_NONE); -const S32 FLOATER_H_MARGIN = 15; -const S32 MIN_WIDGET_HEIGHT = 10; -const S32 VPAD = 4; + std::string follows = params.follows.string; -// static -U32 LLView::createRect(LLXMLNodePtr node, LLRect &rect, LLView* parent_view, const LLRect &required_rect) -{ - U32 follows = 0; - S32 x = rect.mLeft; - S32 y = rect.mBottom; - S32 w = rect.getWidth(); - S32 h = rect.getHeight(); + typedef boost::tokenizer<boost::char_separator<char> > tokenizer; + boost::char_separator<char> sep("|"); + tokenizer tokens(follows, sep); + tokenizer::iterator token_iter = tokens.begin(); - U32 last_x = 0; - U32 last_y = 0; - if (parent_view) - { - last_y = parent_view->getRect().getHeight(); - child_list_t::const_iterator itor = parent_view->getChildList()->begin(); - if (itor != parent_view->getChildList()->end()) + while(token_iter != tokens.end()) { - LLView *last_view = (*itor); - if (last_view->getSaveToXML()) + const std::string& token_str = *token_iter; + if (token_str == "left") { - last_x = last_view->getRect().mLeft; - last_y = last_view->getRect().mBottom; + 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; } } - - std::string rect_control; - node->getAttributeString("rect_control", rect_control); - if (! rect_control.empty()) + else if (params.follows.flags.isChosen()) { - LLRect rect = LLUI::sConfigGroup->getRect(rect_control); - x = rect.mLeft; - y = rect.mBottom; - w = rect.getWidth(); - h = rect.getHeight(); - } - - if (node->hasAttribute("left")) - { - node->getAttributeS32("left", x); - } - if (node->hasAttribute("bottom")) - { - node->getAttributeS32("bottom", y); + setFollows(params.follows.flags); } +} - // Make your width the width of the containing - // view if you don't specify a width. - if (parent_view) - { - if(w == 0) - { - w = llmax(required_rect.getWidth(), parent_view->getRect().getWidth() - (FLOATER_H_MARGIN) - x); - } - if(h == 0) - { - h = llmax(MIN_WIDGET_HEIGHT, required_rect.getHeight()); +// 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; +} - if (node->hasAttribute("width")) - { - node->getAttributeS32("width", w); - } - if (node->hasAttribute("height")) +//static +void LLView::applyXUILayout(LLView::Params& p, LLView* parent) +{ + 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() && parent) { - node->getAttributeS32("height", h); + p.layout = parent->getLayout(); } - if (parent_view) + + if (parent) { - if (node->hasAttribute("left_delta")) - { - S32 left_delta = 0; - node->getAttributeS32("left_delta", left_delta); - x = last_x + left_delta; - } - else if (node->hasAttribute("left") && node->hasAttribute("right")) + LLRect parent_rect = parent->getLocalRect(); + // overwrite uninitialized rect params, using context + LLRect last_rect = parent->getLocalRect(); + + bool layout_topleft = (p.layout() == "topleft"); + if (layout_topleft) { - // compute width based on left and right - S32 right = 0; - node->getAttributeS32("right", right); - if (right < 0) - { - right = parent_view->getRect().getWidth() + right; - } - w = right - x; + //invert top to bottom + if (p.rect.top.isProvided()) p.rect.top = parent_rect.getHeight() - p.rect.top; + if (p.rect.bottom.isProvided()) p.rect.bottom = parent_rect.getHeight() - p.rect.bottom; } - else if (node->hasAttribute("left")) + + // convert negative or centered coordinates to parent relative values + // Note: some of this logic matches the logic in TypedParam<LLRect>::setValueFromBlock() + + if (p.center_horiz) { - if (x < 0) + if (p.rect.left.isProvided() && p.rect.right.isProvided()) { - x = parent_view->getRect().getWidth() + x; - follows |= FOLLOWS_RIGHT; + S32 width = p.rect.right - p.rect.left; + width = llmax(width, 0); + S32 offset = parent_rect.getWidth()/2 - width/2; + p.rect.left = p.rect.left + offset; + p.rect.right = p.rect.right + offset; } else { - follows |= FOLLOWS_LEFT; + p.rect.left = p.rect.left + parent_rect.getWidth()/2 - p.rect.width/2; + p.rect.right.setProvided(false); // recalculate the right } } - else if (node->hasAttribute("width") && node->hasAttribute("right")) + else + { + if (p.rect.left.isProvided() && p.rect.left < 0) p.rect.left = p.rect.left + parent_rect.getWidth(); + if (p.rect.right.isProvided() && p.rect.right < 0) p.rect.right = p.rect.right + parent_rect.getWidth(); + } + if (p.center_vert) { - S32 right = 0; - node->getAttributeS32("right", right); - if (right < 0) + if (p.rect.bottom.isProvided() && p.rect.top.isProvided()) { - right = parent_view->getRect().getWidth() + right; + S32 height = p.rect.top - p.rect.bottom; + height = llmax(height, 0); + S32 offset = parent_rect.getHeight()/2 - height/2; + p.rect.bottom = p.rect.bottom + offset; + p.rect.top = p.rect.top + offset; + } + else + { + p.rect.bottom = p.rect.bottom + parent_rect.getHeight()/2 - p.rect.height/2; + p.rect.top.setProvided(false); // recalculate the top } - x = right - w; } else { - // left not specified, same as last - x = last_x; + if (p.rect.bottom.isProvided() && p.rect.bottom < 0) p.rect.bottom = p.rect.bottom + parent_rect.getHeight(); + if (p.rect.top.isProvided() && p.rect.top < 0) p.rect.top = p.rect.top + parent_rect.getHeight(); } - if (node->hasAttribute("bottom_delta")) + + // DEPRECATE: automatically fall back to height of MIN_WIDGET_HEIGHT pixels + if (!p.rect.height.isProvided() && !p.rect.top.isProvided() && p.rect.height == 0) { - S32 bottom_delta = 0; - node->getAttributeS32("bottom_delta", bottom_delta); - y = last_y + bottom_delta; + p.rect.height = MIN_WIDGET_HEIGHT; } - else if (node->hasAttribute("top")) + + last_rect.translate(0, last_rect.getHeight()); + + // If there was a recently constructed child, use its rectangle + get_last_child_rect(parent, &last_rect); + + if (layout_topleft) { - // compute height based on top - S32 top = 0; - node->getAttributeS32("top", top); - if (top < 0) + p.bottom_delta.setIfNotProvided(0, false); + + // Invert the sense of bottom_delta for topleft layout + if (p.bottom_delta.isProvided()) { - top = parent_view->getRect().getHeight() + top; + p.bottom_delta = -p.bottom_delta; } - h = top - y; - } - else if (node->hasAttribute("bottom")) - { - if (y < 0) + else if (p.top_pad.isProvided()) { - y = parent_view->getRect().getHeight() + y; - follows |= FOLLOWS_TOP; + p.bottom_delta = -(p.rect.height + p.top_pad); } - else + else if (p.top_delta.isProvided()) { - follows |= FOLLOWS_BOTTOM; + p.bottom_delta = + -(p.top_delta + p.rect.height - last_rect.getHeight()); } - } - else - { - // if bottom not specified, generate automatically - if (last_y == 0) + else if (!p.bottom_delta.isProvided() + && !p.left_delta.isProvided() + && !p.top_pad.isProvided() + && !p.left_pad.isProvided()) { - // treat first child as "bottom" - y = parent_view->getRect().getHeight() - (h + VPAD); - follows |= FOLLOWS_TOP; + // set default position is just below last rect + p.bottom_delta.set(-(p.rect.height + VPAD), false); } - else + + // default to same left edge + p.left_delta.setIfNotProvided(0, false); + if (p.left_pad.isProvided()) { - // treat subsequent children as "bottom_delta" - y = last_y - (h + VPAD); + // left_pad is based on prior widget's right edge + p.left_delta.set(p.left_pad + last_rect.getWidth(), false); } + + last_rect.translate(p.left_delta, p.bottom_delta); + } + else + { + // set default position is just below last rect + p.bottom_delta.setIfNotProvided(-(p.rect.height + VPAD), false); + p.left_delta.setIfNotProvided(0, false); + last_rect.translate(p.left_delta, p.bottom_delta); } - } - else - { - x = llmax(x, 0); - y = llmax(y, 0); - follows = FOLLOWS_LEFT | FOLLOWS_TOP; - } - rect.setOriginAndSize(x, y, w, h); - - return follows; -} - -void LLView::initFromXML(LLXMLNodePtr node, LLView* parent) -{ - // create rect first, as this will supply initial follows flags - LLRect view_rect; - U32 follows_flags = createRect(node, view_rect, parent, getRequiredRect()); - // call reshape in case there are any child elements that need to be layed out - reshape(view_rect.getWidth(), view_rect.getHeight()); - setRect(view_rect); - setFollows(follows_flags); - - parseFollowsFlags(node); - if (node->hasAttribute("control_name")) - { - std::string control_name; - node->getAttributeString("control_name", control_name); - setControlName(control_name, NULL); - } + // 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); - if (node->hasAttribute("tool_tip")) - { - std::string tool_tip_msg; - node->getAttributeString("tool_tip", tool_tip_msg); - setToolTip(tool_tip_msg); + // 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 + p.rect.left.setIfNotProvided(last_rect.mLeft, false); + p.rect.bottom.setIfNotProvided(last_rect.mBottom, false); + p.rect.top.setIfNotProvided(last_rect.mTop, false); + p.rect.right.setIfNotProvided(last_rect.mRight, false); + p.rect.width.setIfNotProvided(last_rect.getWidth(), false); + p.rect.height.setIfNotProvided(last_rect.getHeight(), false); } +} - if (node->hasAttribute("enabled")) - { - BOOL enabled; - node->getAttributeBOOL("enabled", enabled); - setEnabled(enabled); - } - - if (node->hasAttribute("visible")) +static S32 invert_vertical(S32 y, LLView* parent) +{ + if (y < 0) { - BOOL visible; - node->getAttributeBOOL("visible", visible); - setVisible(visible); + // already based on top-left, just invert + return -y; } - - if (node->hasAttribute("hover_cursor")) + else if (parent) { - std::string cursor_string; - node->getAttributeString("hover_cursor", cursor_string); - mHoverCursor = getCursorFromString(cursor_string); + // use parent to flip coordinate + S32 parent_height = parent->getRect().getHeight(); + return parent_height - y; } - - node->getAttributeBOOL("use_bounding_rect", mUseBoundingRect); - node->getAttributeBOOL("mouse_opaque", mMouseOpaque); - - node->getAttributeS32("default_tab_group", mDefaultTabGroup); - - reshape(view_rect.getWidth(), view_rect.getHeight()); -} - -void LLView::parseFollowsFlags(LLXMLNodePtr node) -{ - if (node->hasAttribute("follows")) + else { - setFollowsNone(); - - std::string follows; - node->getAttributeString("follows", follows); - - typedef boost::tokenizer<boost::char_separator<char> > tokenizer; - boost::char_separator<char> 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") + llwarns << "Attempting to convert layout to top-left with no parent" << llendl; + 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) { - setFollowsRight(); + p.top_pad = top_pad; + p.top_delta.setProvided(false); } - else if (token_str == "top") + else { - setFollowsTop(); + p.top_pad.setProvided(false); + p.top_delta = top_delta; } - else if (token_str == "bottom") + // 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) { - setFollowsBottom(); + p.left_pad = left_pad; + p.left_delta.setProvided(false); } - else if (token_str == "all") + else { - setFollowsAll(); + p.left_pad.setProvided(false); + p.left_delta = left_delta; } - ++token_iter; + 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 -LLFontGL* LLView::selectFont(LLXMLNodePtr node) +static void convert_coords_to_top_left(LLView::Params& p, LLView* parent) { - std::string font_name, font_size, font_style; - U8 style = 0; - - if (node->hasAttribute("font")) + // Convert the coordinate system to be top-left based. + if (p.rect.top.isProvided()) { - node->getAttributeString("font", font_name); + p.rect.top = invert_vertical(p.rect.top, parent); } - - if (node->hasAttribute("font_size")) + if (p.rect.bottom.isProvided()) { - node->getAttributeString("font_size", font_size); + p.rect.bottom = invert_vertical(p.rect.bottom, parent); } - - if (node->hasAttribute("font_style")) + if (p.top_pad.isProvided()) { - node->getAttributeString("font_style", font_style); - style = LLFontGL::getStyleFromString(font_style); + p.top_pad = -p.top_pad; } - - if (node->hasAttribute("font-style")) + if (p.top_delta.isProvided()) { - node->getAttributeString("font-style", font_style); - style = LLFontGL::getStyleFromString(font_style); + p.top_delta = -p.top_delta; } - - if (font_name.empty()) - return NULL; - - LLFontDescriptor desc(font_name, font_size, style); - LLFontGL* gl_font = LLFontGL::getFont(desc); - - return gl_font; -} - -// static -LLFontGL::HAlign LLView::selectFontHAlign(LLXMLNodePtr node) -{ - LLFontGL::HAlign gl_hfont_align = LLFontGL::LEFT; - - if (node->hasAttribute("halign")) + if (p.bottom_delta.isProvided()) { - std::string horizontal_align_name; - node->getAttributeString("halign", horizontal_align_name); - gl_hfont_align = LLFontGL::hAlignFromName(horizontal_align_name); + p.bottom_delta = -p.bottom_delta; } - return gl_hfont_align; + p.layout = "topleft"; } -// static -LLFontGL::VAlign LLView::selectFontVAlign(LLXMLNodePtr node) +//static +void LLView::setupParamsForExport(Params& p, LLView* parent) { - LLFontGL::VAlign gl_vfont_align = LLFontGL::BASELINE; - - if (node->hasAttribute("valign")) + // Don't convert if already top-left based + if (p.layout() == "topleft") { - std::string vert_align_name; - node->getAttributeString("valign", vert_align_name); - gl_vfont_align = LLFontGL::vAlignFromName(vert_align_name); + return; } - return gl_vfont_align; -} - -// static -LLFontGL::StyleFlags LLView::selectFontStyle(LLXMLNodePtr node) -{ - LLFontGL::StyleFlags gl_font_style = LLFontGL::NORMAL; - if (node->hasAttribute("style")) + // 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()) { - std::string style_flags_name; - node->getAttributeString("style", style_flags_name); - - if (style_flags_name == "normal") - { - gl_font_style = LLFontGL::NORMAL; - } - else if (style_flags_name == "bold") + if (p.rect.bottom.isProvided() && p.rect.left.isProvided()) { - gl_font_style = LLFontGL::BOLD; + // standard bulk export, convert it + convert_to_relative_layout(p, parent); } - else if (style_flags_name == "italic") + else if (p.rect.bottom.isProvided() && p.left_delta.isProvided()) { - gl_font_style = LLFontGL::ITALIC; + // hand layout with left_delta + convert_to_relative_layout(p, parent); } - else if (style_flags_name == "underline") + else if (p.bottom_delta.isProvided()) { - gl_font_style = LLFontGL::UNDERLINE; + // 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); } - //else leave left } - return gl_font_style; + + convert_coords_to_top_left(p, parent); } -bool LLView::setControlValue(const LLSD& value) -{ - std::string ctrlname = getControlName(); - if (!ctrlname.empty()) - { - LLUI::sConfigGroup->setValue(ctrlname, value); - return true; - } - return false; +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)); } -//virtual -void LLView::setControlName(const std::string& control_name, LLView *context) -{ - if (context == NULL) - { - context = this; - } +LLView::tree_iterator_t LLView::endTreeDFS() +{ + // an empty iterator is an "end" iterator + return tree_iterator_t(); +} - if (!mControlName.empty()) - { - llwarns << "setControlName called twice on same control!" << llendl; - mControlConnection.disconnect(); // disconnect current signal - mControlName.clear(); - } - - // Register new listener - if (!control_name.empty()) - { - LLControlVariable *control = context->findControl(control_name); - if (control) - { - mControlName = control_name; - mControlConnection = control->getSignal()->connect(boost::bind(&controlListener, _1, getHandle(), std::string("value"))); - setValue(control->getValue()); - } - } +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)); } -// static -bool LLView::controlListener(const LLSD& newvalue, LLHandle<LLView> handle, std::string type) +LLView::tree_post_iterator_t LLView::endTreeDFSPost() +{ + // an empty iterator is an "end" iterator + return tree_post_iterator_t(); +} + + +LLView::root_to_view_iterator_t LLView::beginRootToView() { - LLView* view = handle.get(); - if (view) - { - if (type == "value") - { - view->setValue(newvalue); - return true; - } - else if (type == "enabled") - { - view->setEnabled(newvalue.asBoolean()); - return true; - } - else if (type == "visible") - { - view->setVisible(newvalue.asBoolean()); - return true; - } - } - return false; + return root_to_view_iterator_t(this, boost::bind(&LLView::getParent, _1)); } -void LLView::addBoolControl(const std::string& name, bool initial_value) +LLView::root_to_view_iterator_t LLView::endRootToView() { - mFloaterControls[name] = new LLControlVariable(name, TYPE_BOOLEAN, initial_value, std::string("Internal floater control")); + return root_to_view_iterator_t(); } -LLControlVariable *LLView::getControl(const std::string& name) + +// only create maps on demand, as they incur heap allocation/deallocation cost +// when a view is constructed/deconstructed +LLView::default_widget_map_t& LLView::getDefaultWidgetMap() const { - control_map_t::iterator itor = mFloaterControls.find(name); - if (itor != mFloaterControls.end()) + if (!mDefaultWidgets) { - return itor->second; + mDefaultWidgets = new default_widget_map_t(); } - return NULL; + return *mDefaultWidgets; } -//virtual -void LLView::setValue(const LLSD& value) +S32 LLView::notifyParent(const LLSD& info) { + LLView* parent = getParent(); + if(parent) + return parent->notifyParent(info); + return 0; } - -//virtual -LLSD LLView::getValue() const +bool LLView::notifyChildren(const LLSD& info) { - return LLSD(); + bool ret = false; + for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) + { + ret |= (*child_it)->notifyChildren(info); + } + return ret; } -LLView* LLView::createWidget(LLXMLNodePtr xml_node) const +// convenient accessor for draw context +const LLViewDrawContext& LLView::getDrawContext() { - // forward requests to ui ctrl factory - return LLUICtrlFactory::getInstance()->createCtrlWidget(NULL, xml_node); -} - -// -// LLWidgetClassRegistry -// - -LLWidgetClassRegistry::LLWidgetClassRegistry() -{ -} - -void LLWidgetClassRegistry::registerCtrl(const std::string& tag, LLWidgetClassRegistry::factory_func_t function) -{ - std::string lower_case_tag = tag; - LLStringUtil::toLower(lower_case_tag); - - mCreatorFunctions[lower_case_tag] = function; + return LLViewDrawContext::getCurrentContext(); } -BOOL LLWidgetClassRegistry::isTagRegistered(const std::string &tag) -{ - return mCreatorFunctions.find(tag) != mCreatorFunctions.end(); -} +const LLViewDrawContext& LLViewDrawContext::getCurrentContext() +{ + static LLViewDrawContext default_context; -LLWidgetClassRegistry::factory_func_t LLWidgetClassRegistry::getCreatorFunc(const std::string& ctrl_type) -{ - factory_map_t::const_iterator found_it = mCreatorFunctions.find(ctrl_type); - if (found_it == mCreatorFunctions.end()) - { - return NULL; - } - return found_it->second; + if (sDrawContextStack.empty()) + return default_context; + + return *sDrawContextStack.back(); } - diff --git a/indra/llui/llview.h b/indra/llui/llview.h index e0e0f6ba47..c4d7313743 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -37,22 +37,26 @@ // 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 "llmortician.h" #include "llmousehandler.h" -#include "llnametable.h" -#include "llsd.h" #include "llstring.h" #include "llrect.h" #include "llui.h" #include "lluistring.h" -#include "lluixmltags.h" #include "llviewquery.h" -#include "llxmlnode.h" #include "stdenums.h" #include "lluistring.h" #include "llcursortypes.h" +#include "lluictrlfactory.h" +#include "lltreeiterators.h" +#include "llfocusmgr.h" + +#include <list> + +class LLSD; const U32 FOLLOWS_NONE = 0x00; const U32 FOLLOWS_LEFT = 0x01; @@ -66,150 +70,106 @@ const BOOL NOT_MOUSE_OPAQUE = FALSE; const U32 GL_NAME_UI_RESERVED = 2; -/* -// virtual functions defined in LLView: - -virtual BOOL isCtrl() const; - LLUICtrl -virtual BOOL isPanel(); - LLPanel -virtual void setRect(const LLRect &rect); - LLLineEditor -virtual void addCtrl( LLUICtrl* ctrl, S32 tab_group); -virtual void addCtrlAtEnd( LLUICtrl* ctrl, S32 tab_group); -virtual void removeCtrl( LLUICtrl* ctrl); - LLPanel -virtual BOOL canFocusChildren() const { return TRUE; } - LLFolderView -virtual void deleteAllChildren(); - LLFolderView, LLPanelInventory -virtual void setTentative(BOOL b) {} - LLUICtrl, LLSliderCtrl, LLSpinCtrl -virtual BOOL getTentative() const { return FALSE; } - LLUICtrl, LLCheckBoxCtrl -virtual void setVisible(BOOL visible); - LLFloater, LLAlertDialog, LLMenuItemGL, LLModalDialog -virtual void setEnabled(BOOL enabled) { mEnabled = enabled; } - LLCheckBoxCtrl, LLComboBox, LLLineEditor, LLMenuGL, LLRadioGroup, etc -virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ) { return FALSE; } - LLUICtrl, LLButton, LLCheckBoxCtrl, LLLineEditor, LLMenuGL, LLSliderCtrl -virtual void onVisibilityChange ( BOOL curVisibilityIn ); - LLMenuGL -virtual LLRect getSnapRect() const { return mRect; } *TODO: Make non virtual - LLFloater -virtual LLRect getRequiredRect() { return mRect; } - LLScrolllistCtrl -virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); - LLUICtrl, et. al. -virtual void translate( S32 x, S32 y ); - LLMenuGL -virtual void userSetShape(const LLRect& new_rect); - LLFloater, LLScrollLIstVtrl -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); - LLScrollListCtrl -virtual BOOL canSnapTo(const LLView* other_view) { return other_view != this && other_view->getVisible(); } - LLFloater -virtual void snappedTo(const LLView* snap_view) {} - LLFloater -virtual BOOL handleKey(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(); - * - - * -virtual LLXMLNodePtr getXML(bool save_children = true) const; - * -virtual void initFromXML(LLXMLNodePtr node, LLView* parent); - * -virtual void onFocusLost() {} - LLUICtrl, LLScrollListCtrl, LLMenuGL, LLLineEditor, LLComboBox -virtual void onFocusReceived() {} - LLUICtrl, LLTextEditor, LLScrollListVtrl, LLMenuGL, LLLineEditor -virtual LLView* getChildView(const std::string& name, BOOL recurse = TRUE, BOOL create_if_missing = TRUE) const; - LLTabContainer, LLPanel, LLMenuGL -virtual void setControlName(const std::string& control, LLView *context); - LLSliderCtrl, LLCheckBoxCtrl -virtual std::string getControlName() const { return mControlName; } - LLSliderCtrl, LLCheckBoxCtrl -virtual bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); - LLMenuItem -virtual void setValue(const LLSD& value); - * - -protected: -virtual BOOL handleKeyHere(KEY key, MASK mask); - * -virtual BOOL handleUnicodeCharHere(llwchar uni_char); - * -*/ -class LLUICtrlFactory; - -// maps xml strings to widget classes -class LLWidgetClassRegistry : public LLSingleton<LLWidgetClassRegistry> +// maintains render state during traversal of UI tree +class LLViewDrawContext { - friend class LLSingleton<LLWidgetClassRegistry>; public: - typedef LLView* (*factory_func_t)(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory); - typedef std::map<std::string, factory_func_t> factory_map_t; - - void registerCtrl(const std::string& xml_tag, factory_func_t function); - BOOL isTagRegistered(const std::string& xml_tag); - factory_func_t getCreatorFunc(const std::string& xml_tag); + F32 mAlpha; - // get (first) xml tag for a given class - template <class T> std::string getTag() + LLViewDrawContext(F32 alpha = 1.f) + : mAlpha(alpha) { - factory_map_t::iterator it; - for(it = mCreatorFunctions.begin(); it != mCreatorFunctions.end(); ++it) + if (!sDrawContextStack.empty()) { - if (it->second == T::fromXML) - { - return it->first; - } + LLViewDrawContext* context_top = sDrawContextStack.back(); + // merge with top of stack + mAlpha *= context_top->mAlpha; } - - return ""; + sDrawContextStack.push_back(this); } -private: - LLWidgetClassRegistry(); - virtual ~LLWidgetClassRegistry() {}; + ~LLViewDrawContext() + { + sDrawContextStack.pop_back(); + } - typedef std::set<std::string> ctrl_name_set_t; - ctrl_name_set_t mUICtrlNames; + static const LLViewDrawContext& getCurrentContext(); - // map of xml tags to widget creator functions - factory_map_t mCreatorFunctions; +private: + static std::vector<LLViewDrawContext*> sDrawContextStack; }; -template<class T> -class LLRegisterWidget +class LLViewWidgetRegistry : public LLChildRegistry<LLViewWidgetRegistry> +{}; + +class LLView : public LLMouseHandler, public LLMortician, public LLFocusableElement { public: - LLRegisterWidget(const std::string& tag) + struct Follows : public LLInitParam::Choice<Follows> { - LLWidgetClassRegistry* registry = LLWidgetClassRegistry::getInstance(); - if (registry->isTagRegistered(tag)) - { - //error! - llerrs << "Widget named " << tag << " already registered!" << llendl; - } - else - { - registry->registerCtrl(tag, T::fromXML); - } - } -}; + Alternative<std::string> string; + Alternative<U32> flags; -class LLView : public LLMouseHandler, public LLMortician -{ + Follows(); + }; + + struct Params : public LLInitParam::Block<Params> + { + Mandatory<std::string> name; + + Optional<bool> enabled, + visible, + mouse_opaque, + use_bounding_rect, + from_xui; + + Optional<S32> tab_group, + default_tab_group; + Optional<std::string> tool_tip; + + Optional<S32> sound_flags; + Optional<Follows> follows; + Optional<std::string> hover_cursor; + + Optional<std::string> layout; + Optional<LLRect> rect; + // Historical bottom-left layout used bottom_delta and left_delta + // for relative positioning. New layout "topleft" prefers specifying + // based on top edge. + Optional<S32> bottom_delta, // deprecated + 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 + + Optional<bool> center_horiz, + center_vert; + + // these are nested attributes for LLLayoutPanel + //FIXME: get parent context involved in parsing traversal + Ignored user_resize, + auto_resize, + needs_translate, + xmlns, + xmlns_xsi, + xsi_schemaLocation, + xsi_type; + + Params(); + }; + + typedef LLViewWidgetRegistry 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; @@ -253,15 +213,8 @@ public: 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; - LLView(); - LLView(const std::string& name, BOOL mouse_opaque); - LLView(const std::string& name, const LLRect& rect, BOOL mouse_opaque, U32 follows=FOLLOWS_NONE); - virtual ~LLView(); - // Hack to support LLFocusMgr (from LLMouseHandler) - /*virtual*/ BOOL isView() const; - // 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 @@ -294,21 +247,24 @@ public: void setUseBoundingRect( BOOL use_bounding_rect ); BOOL getUseBoundingRect(); + ECursorType getHoverCursor() { return mHoverCursor; } + const std::string& getToolTip() const { return mToolTipMsg.getString(); } void sendChildToFront(LLView* child); void sendChildToBack(LLView* child); void moveChildToFrontOfTabGroup(LLUICtrl* child); void moveChildToBackOfTabGroup(LLUICtrl* child); + + virtual bool addChild(LLView* view, S32 tab_group = 0); + + // implemented in terms of addChild() + bool addChildInBack(LLView* view, S32 tab_group = 0); - void addChild(LLView* view, S32 tab_group = 0); - void addChildAtEnd(LLView* view, S32 tab_group = 0); // remove the specified child from the view, and set it's parent to NULL. - void removeChild(LLView* view, BOOL deleteIt = FALSE); + virtual void removeChild(LLView* view); - virtual void addCtrl( LLUICtrl* ctrl, S32 tab_group); - virtual void addCtrlAtEnd( LLUICtrl* ctrl, S32 tab_group); - virtual void removeCtrl( LLUICtrl* ctrl); + virtual BOOL postBuild() { return TRUE; } child_tab_order_t getCtrlOrder() const { return mCtrlOrder; } ctrl_list_t getCtrlList() const; @@ -316,7 +272,9 @@ public: void setDefaultTabGroup(S32 d) { mDefaultTabGroup = d; } S32 getDefaultTabGroup() const { return mDefaultTabGroup; } + S32 getLastTabGroup() { return mLastTabGroup; } + bool trueToRoot(const boost::function<bool (const LLView*)>& predicate) const; BOOL isInVisibleChain() const; BOOL isInEnabledChain() const; @@ -340,14 +298,19 @@ public: 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 curVisibilityIn ); + virtual void handleVisibilityChange ( BOOL new_visibility ); void pushVisible(BOOL visible) { mLastVisible = mVisible; setVisible(visible); } - void popVisible() { setVisible(mLastVisible); mLastVisible = TRUE; } + void popVisible() { setVisible(mLastVisible); } LLHandle<LLView> getHandle() { mHandle.bind(this); return mHandle; } @@ -361,24 +324,43 @@ public: const LLRect& getRect() const { return mRect; } const LLRect& getBoundingRect() const { return mBoundingRect; } LLRect getLocalBoundingRect() const; - LLRect getScreenRect() 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<class _Pr3> 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<LLView, child_list_const_iter_t> tree_iterator_t; + tree_iterator_t beginTreeDFS(); + tree_iterator_t endTreeDFS(); + + typedef LLTreeDFSPostIter<LLView, child_list_const_iter_t> tree_post_iterator_t; + tree_post_iterator_t beginTreeDFSPost(); + tree_post_iterator_t endTreeDFSPost(); + + typedef LLTreeDownIter<LLView> root_to_view_iterator_t; + root_to_view_iterator_t beginRootToView(); + root_to_view_iterator_t endRootToView(); // // UTILITIES @@ -389,40 +371,32 @@ public: 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, BOOL allow_partial_outside ); + BOOL translateIntoRectWithExclusion( const LLRect& inside, const LLRect& exclude, BOOL allow_partial_outside ); void centerWithin(const LLRect& bounds); - virtual void userSetShape(const LLRect& new_rect); + 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); - virtual void snappedTo(const LLView* snap_view); + // inherited from LLFocusableElement + /* virtual */ BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + /* virtual */ BOOL handleUnicodeChar(llwchar uni_char, BOOL called_from_parent); - virtual BOOL handleKey(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); - std::string getShowNamesToolTip(); - virtual void draw(); - virtual LLXMLNodePtr getXML(bool save_children = true) const; - //FIXME: make LLView non-instantiable from XML - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, class LLUICtrlFactory *factory); - virtual void initFromXML(LLXMLNodePtr node, LLView* parent); - void parseFollowsFlags(LLXMLNodePtr node); + void parseFollowsFlags(const LLView::Params& params); // Some widgets, like close box buttons, don't need to be saved - BOOL getSaveToXML() const { return mSaveToXML; } - void setSaveToXML(BOOL b) { mSaveToXML = b; } - - virtual void onFocusLost(); - virtual void onFocusReceived(); + BOOL getFromXUI() const { return mFromXUI; } + void setFromXUI(BOOL b) { mFromXUI = b; } typedef enum e_hit_test_type { @@ -440,25 +414,16 @@ public: void screenRectToLocal( const LLRect& screen, LLRect* local ) const; void localRectToScreen( const LLRect& local, LLRect* screen ) const; - // Listener dispatching functions (Dispatcher deletes pointers to listeners on deregistration or destruction) - LLSimpleListener* getListenerByName(const std::string& callback_name); - void registerEventListener(std::string name, LLSimpleListener* function); - void deregisterEventListener(std::string name); - std::string findEventListener(LLSimpleListener *listener) const; - void addListenerToControl(LLEventDispatcher *observer, const std::string& name, LLSD filter, LLSD userdata); - - void addBoolControl(const std::string& name, bool initial_value); - LLControlVariable *getControl(const std::string& name); LLControlVariable *findControl(const std::string& name); - bool setControlValue(const LLSD& value); - virtual void setControlName(const std::string& control, LLView *context); - virtual std::string getControlName() const { return mControlName; } + // Moved setValue(), getValue(), setControlValue(), setControlName(), + // controlListener() to LLUICtrl because an LLView is NOT assumed to + // contain a value. If that's what you want, use LLUICtrl instead. // virtual bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata); - virtual void setValue(const LLSD& value); - virtual LLSD getValue() const; const child_list_t* getChildList() const { return &mChildList; } + const child_list_const_iter_t beginChild() { return mChildList.begin(); } + const child_list_const_iter_t endChild() { return mChildList.end(); } // LLMouseHandler functions // Default behavior is to pass events to children @@ -471,107 +436,53 @@ public: /*virtual*/ BOOL handleScrollWheel(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, std::string& msg, LLRect* sticky_rect); // Display mToolTipMsg if no child handles it. - /*virtual*/ const std::string& getName() const; + /*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask); + + /*virtual*/ std::string getName() const; /*virtual*/ void onMouseCaptureLost(); /*virtual*/ BOOL hasMouseCapture(); - /*virtual*/ BOOL isView(); // Hack to support LLFocusMgr /*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; - template <class T> T* getChild(const std::string& name, BOOL recurse = TRUE, BOOL create_if_missing = TRUE) const + virtual LLView* childFromPoint(S32 x, S32 y); + + // view-specific handlers + virtual void onMouseEnter(S32 x, S32 y, MASK mask); + virtual void onMouseLeave(S32 x, S32 y, MASK mask); + + + template <class T> T* findChild(const std::string& name, BOOL recurse = TRUE) const { - LLView* child = getChildView(name, recurse, FALSE); + LLView* child = findChildView(name, recurse); T* result = dynamic_cast<T*>(child); - if (!result) - { - // did we find *something* with that name? - if (child) - { - llwarns << "Found child named " << name << " but of wrong type " << typeid(child).name() << ", expecting " << typeid(T).name() << llendl; - } - if (create_if_missing) - { - // create dummy widget instance here - result = createDummyWidget<T>(name); - } - } return result; } + template <class T> T* getChild(const std::string& name, BOOL recurse = TRUE) const; + template <class T> T& getChildRef(const std::string& name, BOOL recurse = TRUE) const { - return *getChild<T>(name, recurse, TRUE); + return *getChild<T>(name, recurse); } - virtual LLView* getChildView(const std::string& name, BOOL recurse = TRUE, BOOL create_if_missing = TRUE) const; + virtual LLView* getChildView(const std::string& name, BOOL recurse = TRUE) const; + virtual LLView* findChildView(const std::string& name, BOOL recurse = TRUE) const; - template <class T> T* createDummyWidget(const std::string& name) const + template <class T> T* getDefaultWidget(const std::string& name) const { - T* widget = getDummyWidget<T>(name); - if (!widget) - { - // get xml tag name corresponding to requested widget type (e.g. "button") - std::string xml_tag = LLWidgetClassRegistry::getInstance()->getTag<T>(); - if (xml_tag.empty()) - { - llwarns << "No xml tag registered for this class " << llendl; - return NULL; - } - // create dummy xml node (<button name="foo"/>) - LLXMLNodePtr new_node_ptr = new LLXMLNode(xml_tag.c_str(), FALSE); - new_node_ptr->createChild("name", TRUE)->setStringValue(name); - - widget = dynamic_cast<T*>(createWidget(new_node_ptr)); - if (widget) - { - // need non-const to update private dummy widget cache - llwarns << "Making dummy " << xml_tag << " named " << name << " in " << getName() << llendl; - mDummyWidgets.insert(std::make_pair(name, widget)); - } - else - { - // dynamic cast will fail if T::fromXML only registered for base class - llwarns << "Failed to create dummy widget of requested type " << llendl; - return NULL; - } - } - return widget; - } - - template <class T> T* getDummyWidget(const std::string& name) const - { - dummy_widget_map_t::const_iterator found_it = mDummyWidgets.find(name); - if (found_it == mDummyWidgets.end()) + default_widget_map_t::const_iterator found_it = getDefaultWidgetMap().find(name); + if (found_it == getDefaultWidgetMap().end()) { return NULL; } return dynamic_cast<T*>(found_it->second); } - LLView* createWidget(LLXMLNodePtr xml_node) const; - - + ////////////////////////////////////////////// // statics - static U32 createRect(LLXMLNodePtr node, LLRect &rect, LLView* parent_view, const LLRect &required_rect = LLRect()); + ////////////////////////////////////////////// + //static LLFontGL::HAlign selectFontHAlign(LLXMLNodePtr node); - static LLFontGL* selectFont(LLXMLNodePtr node); - static LLFontGL::HAlign selectFontHAlign(LLXMLNodePtr node); - static LLFontGL::VAlign selectFontVAlign(LLXMLNodePtr node); - static LLFontGL::StyleFlags selectFontStyle(LLXMLNodePtr node); - - - // Only saves color if different from default setting. - static void addColorXML(LLXMLNodePtr node, const LLColor4& color, - const char* xml_name, const char* control_name); - // Escapes " (quot) ' (apos) & (amp) < (lt) > (gt) - //static std::string escapeXML(const std::string& xml); - static LLWString escapeXML(const LLWString& xml); - - //same as above, but wraps multiple lines in quotes and prepends - //indent as leading white space on each line - static std::string escapeXML(const std::string& xml, std::string& indent); - // 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 @@ -582,16 +493,40 @@ public: // return query for iterating over focus roots in tab order static const LLCtrlQuery & getFocusRootsQuery(); - static BOOL deleteViewByHandle(LLHandle<LLView> handle); + static void deleteViewByHandle(LLHandle<LLView> handle); static LLWindow* getWindow(void) { return LLUI::sWindow; } + // Set up params after XML load before calling new(), + // usually to adjust layout. + static void applyXUILayout(Params& p, LLView* parent); + + // For re-export of floaters and panels, convert the coordinate system + // to be top-left based. + static void setupParamsForExport(Params& p, LLView* parent); -protected: + //virtual BOOL addChildFromParam(const LLInitParam::BaseBlock& params) { return TRUE; } virtual BOOL handleKeyHere(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(); + +protected: void drawDebugRect(); void drawChild(LLView* childp, S32 x_offset = 0, S32 y_offset = 0, BOOL force_draw = FALSE); + void drawChildren(); LLView* childrenHandleKey(KEY key, MASK mask); LLView* childrenHandleUnicodeChar(llwchar uni_char); @@ -611,13 +546,12 @@ protected: LLView* childrenHandleScrollWheel(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); - static bool controlListener(const LLSD& newvalue, LLHandle<LLView> handle, std::string type); - - typedef std::map<std::string, LLControlVariable*> control_map_t; - control_map_t mFloaterControls; - + ECursorType mHoverCursor; + private: + LLView* mParentView; child_list_t mChildList; @@ -625,11 +559,13 @@ private: // location in pixels, relative to surrounding structure, bottom,left=0,0 LLRect mRect; LLRect mBoundingRect; + std::string mLayout; U32 mReshapeFlags; child_tab_order_t mCtrlOrder; 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. @@ -637,7 +573,7 @@ private: LLUIString mToolTipMsg; // isNull() is true if none. U8 mSoundFlags; - BOOL mSaveToXML; + BOOL mFromXUI; BOOL mIsFocusRoot; BOOL mUseBoundingRect; // hit test against bounding rectangle that includes all child elements @@ -651,27 +587,31 @@ private: static LLWindow* sWindow; // All root views must know about their window. - typedef std::map<std::string, LLPointer<LLSimpleListener> > dispatch_list_t; - dispatch_list_t mDispatchList; - - std::string mControlName; + typedef std::map<std::string, LLView*> default_widget_map_t; + // allocate this map no demand, as it is rarely needed + mutable default_widget_map_t* mDefaultWidgets; - typedef std::map<std::string, LLView*> dummy_widget_map_t; - mutable dummy_widget_map_t mDummyWidgets; + default_widget_map_t& getDefaultWidgetMap() const; - boost::signals::connection mControlConnection; - - ECursorType mHoverCursor; - public: - static BOOL sDebugRects; // Draw debug rects behind everything. - static BOOL sDebugKeys; + // Depth in view hierarchy during rendering static S32 sDepth; - static BOOL sDebugMouseHandling; + + // Draw debug rectangles around widgets to help with alignment and spacing + static bool sDebugRects; + + // 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 BOOL sEditingUI; - static LLView* sEditingUIView; + static std::set<LLView*> 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; @@ -688,5 +628,46 @@ private: LLView::child_tab_order_t mTabOrder; }; +template <class T> T* LLView::getChild(const std::string& name, BOOL recurse) const +{ + LLView* child = findChildView(name, recurse); + T* result = dynamic_cast<T*>(child); + if (!result) + { + // did we find *something* with that name? + if (child) + { + llwarns << "Found child named " << name << " but of wrong type " << typeid(*child).name() << ", expecting " << typeid(T*).name() << llendl; + } + result = getDefaultWidget<T>(name); + if (!result) + { + result = LLUICtrlFactory::getDefaultWidget<T>(name); + + if (result) + { + // *NOTE: You cannot call mFoo = getChild<LLFoo>("bar") + // in a floater or panel constructor. The widgets will not + // be ready. Instead, put it in postBuild(). + llwarns << "Making dummy " << typeid(T).name() << " named \"" << name << "\" in " << getName() << llendl; + } + else + { + llwarns << "Failed to create dummy " << typeid(T).name() << llendl; + return NULL; + } + + getDefaultWidgetMap()[name] = 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<class LLView>( + 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 d4a9e9d1bf..30717f87de 100644 --- a/indra/llui/llviewborder.cpp +++ b/indra/llui/llviewborder.cpp @@ -33,25 +33,54 @@ #include "llviewborder.h" #include "llrender.h" #include "llfocusmgr.h" +#include "lluictrlfactory.h" -static LLRegisterWidget<LLViewBorder> r("view_border"); - -LLViewBorder::LLViewBorder( const std::string& name, const LLRect& rect, EBevel bevel, EStyle style, S32 width ) - : - LLView( name, rect, FALSE ), - mBevel( bevel ), - mStyle( style ), - mHighlightLight( LLUI::sColorsGroup->getColor( "DefaultHighlightLight" ) ), - mHighlightDark( LLUI::sColorsGroup->getColor( "DefaultHighlightDark" ) ), - mShadowLight( LLUI::sColorsGroup->getColor( "DefaultShadowLight" ) ), - mShadowDark( LLUI::sColorsGroup->getColor( "DefaultShadowDark" ) ), - mBorderWidth( width ), - mTexture( NULL ), - mHasKeyboardFocus( FALSE ) +static LLDefaultChildRegistry::Register<LLViewBorder> 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() { - setFollowsAll(); + 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"); + name = "view_border"; + mouse_opaque = false; + follows.flags = FOLLOWS_ALL; +} + + +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; @@ -69,7 +98,7 @@ void LLViewBorder::setColorsExtended( const LLColor4& shadow_light, const LLColo void LLViewBorder::setTexture( const LLUUID &image_id ) { - mTexture = LLUI::sImageProvider->getUIImageByID(image_id); + mTexture = LLUI::getUIImageByID(image_id); } @@ -105,26 +134,24 @@ void LLViewBorder::draw() } } - // draw the children LLView::draw(); - } void LLViewBorder::drawOnePixelLines() { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLColor4 top_color = mHighlightLight; - LLColor4 bottom_color = mHighlightLight; + LLColor4 top_color = mHighlightLight.get(); + LLColor4 bottom_color = mHighlightLight.get(); switch( mBevel ) { case BEVEL_OUT: - top_color = mHighlightLight; - bottom_color = mShadowDark; + top_color = mHighlightLight.get(); + bottom_color = mShadowDark.get(); break; case BEVEL_IN: - top_color = mShadowDark; - bottom_color = mHighlightLight; + top_color = mShadowDark.get(); + bottom_color = mHighlightLight.get(); break; case BEVEL_NONE: // use defaults @@ -163,31 +190,36 @@ void LLViewBorder::drawTwoPixelLines() LLColor4 focus_color = gFocusMgr.getFocusColor(); - F32* top_in_color = mShadowDark.mV; - F32* top_out_color = mShadowDark.mV; - F32* bottom_in_color = mShadowDark.mV; - F32* bottom_out_color = mShadowDark.mV; + 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.mV; - top_out_color = mHighlightDark.mV; - bottom_in_color = mShadowLight.mV; - bottom_out_color = mShadowDark.mV; + 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.mV; - top_out_color = mShadowLight.mV; - bottom_in_color = mHighlightDark.mV; - bottom_out_color = mHighlightLight.mV; + 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.mV; - top_out_color = mHighlightLight.mV; - bottom_in_color = mHighlightLight.mV; - bottom_out_color = mHighlightLight.mV; + 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: @@ -196,8 +228,8 @@ void LLViewBorder::drawTwoPixelLines() if( mHasKeyboardFocus ) { - top_out_color = focus_color.mV; - bottom_out_color = focus_color.mV; + top_out_color = focus_color; + bottom_out_color = focus_color; } S32 left = 0; @@ -206,19 +238,19 @@ void LLViewBorder::drawTwoPixelLines() S32 bottom = 0; // draw borders - gGL.color3fv( top_out_color ); + 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 ); + 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 ); + 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 ); + 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); } @@ -302,26 +334,3 @@ BOOL LLViewBorder::getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel return FALSE; } - -// static -LLView* LLViewBorder::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory) -{ - std::string name("view_border"); - node->getAttributeString("name", name); - - LLViewBorder::EBevel bevel_style = LLViewBorder::BEVEL_IN; - getBevelFromAttribute(node, bevel_style); - - S32 border_thickness = 1; - node->getAttributeS32("border_thickness", border_thickness); - - LLViewBorder* border = new LLViewBorder(name, - LLRect(), - bevel_style, - LLViewBorder::STYLE_LINE, - border_thickness); - - border->initFromXML(node, parent); - - return border; -} diff --git a/indra/llui/llviewborder.h b/indra/llui/llviewborder.h index a40e6534a8..92fd569325 100644 --- a/indra/llui/llviewborder.h +++ b/indra/llui/llviewborder.h @@ -35,15 +35,41 @@ #include "llview.h" - class LLViewBorder : public LLView { public: - enum EBevel { BEVEL_IN, BEVEL_OUT, BEVEL_BRIGHT, BEVEL_NONE }; - enum EStyle { STYLE_LINE, STYLE_TEXTURE }; - - LLViewBorder( const std::string& name, const LLRect& rect, EBevel bevel = BEVEL_OUT, EStyle style = STYLE_LINE, S32 width = 1 ); - + 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<LLViewBorder::EBevel, BevelValues> + { + static void declareValues(); + }; + + struct StyleValues + : public LLInitParam::TypeValuesHelper<LLViewBorder::EStyle, StyleValues> + { + static void declareValues(); + }; + + struct Params : public LLInitParam::Block<Params, LLView::Params> + { + Optional<EBevel, BevelValues> bevel_style; + Optional<EStyle, StyleValues> render_style; + Optional<S32> border_thickness; + + Optional<LLUIColor> 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; } @@ -51,7 +77,6 @@ public: // llview functionality virtual void draw(); - static LLView* fromXML(LLXMLNodePtr node, LLView *parent, class LLUICtrlFactory *factory); static BOOL getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel& bevel_style); void setBorderWidth(S32 width) { mBorderWidth = width; } @@ -63,8 +88,8 @@ public: const LLColor4& highlight_light, const LLColor4& highlight_dark ); void setTexture( const class LLUUID &image_id ); - LLColor4 getHighlightLight() {return mHighlightLight;} - LLColor4 getShadowDark() {return mHighlightDark;} + LLColor4 getHighlightLight() {return mHighlightLight.get();} + LLColor4 getShadowDark() {return mHighlightDark.get();} EStyle getStyle() const { return mStyle; } @@ -77,14 +102,14 @@ private: void drawTextureTrapezoid( F32 degrees, S32 width, S32 length, F32 start_x, F32 start_y ); EBevel mBevel; - const EStyle mStyle; - LLColor4 mHighlightLight; - LLColor4 mHighlightDark; - LLColor4 mShadowLight; - LLColor4 mShadowDark; - LLColor4 mBackgroundColor; + EStyle mStyle; + LLUIColor mHighlightLight; + LLUIColor mHighlightDark; + LLUIColor mShadowLight; + LLUIColor mShadowDark; + LLUIColor mBackgroundColor; S32 mBorderWidth; - LLUIImagePtr mTexture; + LLPointer<LLUIImage> mTexture; BOOL mHasKeyboardFocus; }; diff --git a/indra/llui/llviewmodel.cpp b/indra/llui/llviewmodel.cpp new file mode 100644 index 0000000000..4107289e85 --- /dev/null +++ b/indra/llui/llviewmodel.cpp @@ -0,0 +1,163 @@ +/** + * @file llviewmodel.cpp + * @author Nat Goodspeed + * @date 2008-08-08 + * @brief Implementation for llviewmodel. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "llviewmodel.h" +// STL headers +// std headers +// external library headers +// other Linden headers + +/// +LLViewModel::LLViewModel() + : mDirty(false) +{ +} + +/// Instantiate an LLViewModel with an existing data value +LLViewModel::LLViewModel(const LLSD& value) + : mDirty(false) +{ + setValue(value); +} + +/// Update the stored value +void LLViewModel::setValue(const LLSD& value) +{ + mValue = value; + mDirty = true; +} + +LLSD LLViewModel::getValue() const +{ + return mValue; +} + +//////////////////////////////////////////////////////////////////////////// + +/// +LLTextViewModel::LLTextViewModel() + : LLViewModel(false), + mUpdateFromDisplay(false) +{ +} + +/// Instantiate an LLViewModel with an existing data value +LLTextViewModel::LLTextViewModel(const LLSD& value) + : LLViewModel(value), + mUpdateFromDisplay(false) +{ +} + +/// Update the stored value +void LLTextViewModel::setValue(const LLSD& value) +{ + LLViewModel::setValue(value); + mDisplay = utf8str_to_wstring(value.asString()); + // mDisplay and mValue agree + mUpdateFromDisplay = false; +} + +void LLTextViewModel::setDisplay(const LLWString& value) +{ + // This is the strange way to alter the value. Normally we'd setValue() + // and do the utf8str_to_wstring() to get the corresponding mDisplay + // value. But a text editor might want to edit the display string + // directly, then convert back to UTF8 on commit. + mDisplay = value; + mDirty = true; + // Don't immediately convert to UTF8 -- do it lazily -- we expect many + // more setDisplay() calls than getValue() calls. Just flag that it needs + // doing. + mUpdateFromDisplay = true; +} + +LLSD LLTextViewModel::getValue() const +{ + // Has anyone called setDisplay() since the last setValue()? If so, have + // to convert mDisplay back to UTF8. + if (mUpdateFromDisplay) + { + // The fact that we're lazily updating fields in this object should be + // transparent to clients, which is why this method is left + // conventionally const. Nor do we particularly want to make these + // members mutable. Just cast away constness in this one place. + LLTextViewModel* nthis = const_cast<LLTextViewModel*>(this); + nthis->mUpdateFromDisplay = false; + nthis->mValue = wstring_to_utf8str(mDisplay); + } + return LLViewModel::getValue(); +} + + +//////////////////////////////////////////////////////////////////////////// + +LLListViewModel::LLListViewModel(const LLSD& values) + : LLViewModel() +{ +} + +void LLListViewModel::addColumn(const LLSD& column, EAddPosition pos) +{ +} + +void LLListViewModel::clearColumns() +{ +} + +void LLListViewModel::setColumnLabel(const std::string& column, const std::string& label) +{ +} + +LLScrollListItem* LLListViewModel::addElement(const LLSD& value, EAddPosition pos, + void* userdata) +{ + return NULL; +} + +LLScrollListItem* LLListViewModel::addSimpleElement(const std::string& value, EAddPosition pos, + const LLSD& id) +{ + return NULL; +} + +void LLListViewModel::clearRows() +{ +} + +void LLListViewModel::sortByColumn(const std::string& name, bool ascending) +{ +} diff --git a/indra/llui/llviewmodel.h b/indra/llui/llviewmodel.h new file mode 100644 index 0000000000..c8a9b52cca --- /dev/null +++ b/indra/llui/llviewmodel.h @@ -0,0 +1,219 @@ +/** + * @file llviewmodel.h + * @author Nat Goodspeed + * @date 2008-08-08 + * @brief Define "View Model" classes intended to store data values for use + * by LLUICtrl subclasses. The phrase is borrowed from Microsoft + * terminology, in which "View Model" means the storage object + * underlying a specific widget object -- as in our case -- rather + * than the business "model" object underlying the overall "view" + * presented by the collection of widgets. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLVIEWMODEL_H) +#define LL_LLVIEWMODEL_H + +#include "llpointer.h" +#include "llsd.h" +#include "llrefcount.h" +#include "stdenums.h" +#include "llstring.h" +#include <string> + +class LLScrollListItem; + +class LLViewModel; +class LLTextViewModel; +class LLListViewModel; +// Because LLViewModel is derived from LLRefCount, always pass, store +// and return LLViewModelPtr rather than plain LLViewModel*. +typedef LLPointer<LLViewModel> LLViewModelPtr; +typedef LLPointer<LLTextViewModel> LLTextViewModelPtr; +typedef LLPointer<LLListViewModel> LLListViewModelPtr; + +/** + * LLViewModel stores a scalar LLSD data item, the current display value of a + * scalar LLUICtrl widget. LLViewModel subclasses are used to store data + * collections used for aggregate widgets. LLViewModel is ref-counted because + * -- for multiple skins -- we may have distinct widgets sharing the same + * LLViewModel data. This way, the LLViewModel is quietly deleted when the + * last referencing widget is destroyed. + */ +class LLViewModel: public LLRefCount +{ +public: + LLViewModel(); + /// Instantiate an LLViewModel with an existing data value + LLViewModel(const LLSD& value); + + /// Update the stored value + virtual void setValue(const LLSD& value); + /// Get the stored value, in appropriate type. + virtual LLSD getValue() const; + + /// Has the value been changed since last time we checked? + bool isDirty() const { return mDirty; } + /// Once the value has been saved to a file, or otherwise consumed by the + /// app, we no longer need to enable the Save button + void resetDirty() { mDirty = false; } + // + void setDirty() { mDirty = true; } + +protected: + LLSD mValue; + bool mDirty; +}; + +/** + * LLTextViewModel stores a value displayed as text. + */ +class LLTextViewModel: public LLViewModel +{ +public: + LLTextViewModel(); + /// Instantiate an LLViewModel with an existing data value + LLTextViewModel(const LLSD& value); + + // LLViewModel functions + virtual void setValue(const LLSD& value); + virtual LLSD getValue() const; + + // New functions + /// Get the stored value in string form + LLWString getDisplay() const { return mDisplay; } + /** + * Set the display string directly (see LLTextEditor). What the user is + * editing is actually the LLWString value rather than the underlying + * UTF-8 value. + */ + void setDisplay(const LLWString& value); + +private: + /// To avoid converting every widget's stored value from LLSD to LLWString + /// every frame, cache the converted value + LLWString mDisplay; + /// As the user edits individual characters (setDisplay()), defer + /// LLWString-to-UTF8 conversions until s/he's done. + bool mUpdateFromDisplay; +}; + +/** + * LLListViewModel stores a list of data items. The semantics are borrowed + * from LLScrollListCtrl. + */ +class LLListViewModel: public LLViewModel +{ +public: + LLListViewModel() {} + LLListViewModel(const LLSD& values); + + 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, + const LLSD& id); + virtual void clearRows(); + virtual void sortByColumn(const std::string& name, bool ascending); +}; + +//namespace LLViewModel +//{ +// class Value +// { +// public: +// Value(const LLSD& value = LLSD()); +// +// LLSD getValue() const { return mValue; } +// void setValue(const LLSD& value) { mValue = value; } +// +// bool isAvailable() const { return false; } +// bool isReadOnly() const { return false; } +// +// bool undo() { return false; } +// bool redo() { return false; } +// +// /// Has the value been changed since last time we checked? +// bool isDirty() const { return mDirty; } +// /// Once the value has been saved to a file, or otherwise consumed by the +// /// app, we no longer need to enable the Save button +// void resetDirty() { mDirty = false; } +// // +// void setDirty() { mDirty = true; } +// +// protected: +// LLSD mValue; +// bool mDirty; +// }; +// +// class Numeric : public Value +// { +// public: +// Numeric(S32 value = 0); +// Numeric(F32 value); +// +// F32 getPrecision(); +// F32 getMin(); +// F32 getMax(); +// +// void increment(); +// void decrement(); +// }; +// +// class MultipleValues : public Value +// { +// class Selector +// {}; +// +// MultipleValues(); +// virtual S32 numElements(); +// }; +// +// class Tuple : public MultipleValues +// { +// Tuple(S32 size); +// LLSD getValue(S32 which) const; +// void setValue(S32 which, const LLSD& value); +// }; +// +// class List : public MultipleValues +// { +// List(); +// +// void add(const ValueModel& value); +// bool remove(const Selector& item); +// +// void setSortElement(const Selector& element); +// void sort(); +// }; +// +//}; +#endif /* ! defined(LL_LLVIEWMODEL_H) */ diff --git a/indra/llui/llviewquery.h b/indra/llui/llviewquery.h index e87795f9a3..98d9bf8796 100644 --- a/indra/llui/llviewquery.h +++ b/indra/llui/llviewquery.h @@ -35,7 +35,7 @@ #include <list> -#include "llmemory.h" +#include "llsingleton.h" #include "llui.h" class LLView; diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp new file mode 100644 index 0000000000..26d1f2e067 --- /dev/null +++ b/indra/llui/tests/llurlentry_stub.cpp @@ -0,0 +1,64 @@ +/** + * @file llurlentry_stub.cpp + * @author Martin Reddy + * @brief Stub implementations for LLUrlEntry unit test dependencies + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of + * this source code is governed by the Linden Lab Source Code Disclosure + * Agreement ("Agreement") previously entered between you and Linden + * Lab. By accessing, using, copying, modifying or distributing this + * software, you acknowledge that you have been informed of your + * obligations under the Agreement and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llstring.h" +#include "llfile.h" +#include "llcachename.h" +#include "lluuid.h" + +#include <string> + +// +// Stub implementation for LLCacheName +// +BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname) +{ + fullname = "Lynx Linden"; + return TRUE; +} + +BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group) +{ + group = "My Group"; + return TRUE; +} + +boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback) +{ + return boost::signals2::connection(); +} + +LLCacheName* gCacheName = NULL; + +// +// Stub implementation for LLTrans +// +class LLTrans +{ +public: + static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args); +}; + +std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args) +{ + return std::string(); +} diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp new file mode 100644 index 0000000000..80be8fcbf7 --- /dev/null +++ b/indra/llui/tests/llurlentry_test.cpp @@ -0,0 +1,614 @@ +/** + * @file llurlentry_test.cpp + * @author Martin Reddy + * @brief Unit tests for LLUrlEntry objects + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of + * this source code is governed by the Linden Lab Source Code Disclosure + * Agreement ("Agreement") previously entered between you and Linden + * Lab. By accessing, using, copying, modifying or distributing this + * software, you acknowledge that you have been informed of your + * obligations under the Agreement and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "../llurlentry.h" +#include "llurlentry_stub.cpp" +#include "lltut.h" +#include "../lluicolortable.h" + +#include <boost/regex.hpp> + +LLUIColor LLUIColorTable::getColor(const std::string& name, const LLColor4& default_color) const +{ + return LLUIColor(); +} + +LLUIColor::LLUIColor() {} + +namespace tut +{ + struct LLUrlEntryData + { + }; + + typedef test_group<LLUrlEntryData> factory; + typedef factory::object object; +} + +namespace +{ + tut::factory tf("LLUrlEntry"); +} + +namespace tut +{ + void testRegex(const std::string &testname, boost::regex regex, + const char *text, const std::string &expected) + { + std::string url = ""; + boost::cmatch result; + bool found = boost::regex_search(text, result, regex); + if (found) + { + S32 start = static_cast<U32>(result[0].first - text); + S32 end = static_cast<U32>(result[0].second - text); + url = std::string(text+start, end-start); + } + ensure_equals(testname, url, expected); + } + + template<> template<> + void object::test<1>() + { + // + // test LLUrlEntryHTTP - standard http Urls + // + LLUrlEntryHTTP url; + boost::regex r = url.getPattern(); + + testRegex("no valid url", r, + "htp://slurl.com/", + ""); + + testRegex("simple http (1)", r, + "http://slurl.com/", + "http://slurl.com/"); + + testRegex("simple http (2)", r, + "http://slurl.com", + "http://slurl.com"); + + testRegex("simple http (3)", r, + "http://slurl.com/about.php", + "http://slurl.com/about.php"); + + testRegex("simple https", r, + "https://slurl.com/about.php", + "https://slurl.com/about.php"); + + testRegex("http in text (1)", r, + "XX http://slurl.com/ XX", + "http://slurl.com/"); + + testRegex("http in text (2)", r, + "XX http://slurl.com/about.php XX", + "http://slurl.com/about.php"); + + testRegex("https in text", r, + "XX https://slurl.com/about.php XX", + "https://slurl.com/about.php"); + + testRegex("two http urls", r, + "XX http://slurl.com/about.php http://secondlife.com/ XX", + "http://slurl.com/about.php"); + + testRegex("http url with port and username", r, + "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", r, + "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", r, + "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", r, + "XX http://slurl.com/index.php. XX", + "http://slurl.com/index.php."); + + // DEV-19842: Closing parenthesis ")" breaks urls + testRegex("http url with brackets (1)", r, + "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)", r, + "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", r, + "XX\nhttp://www.secondlife.com/\nXX", + "http://www.secondlife.com/"); + } + + template<> template<> + void object::test<2>() + { + // + // test LLUrlEntryHTTPLabel - wiki-style http Urls with labels + // + LLUrlEntryHTTPLabel url; + boost::regex r = url.getPattern(); + + testRegex("invalid wiki url [1]", r, + "[http://www.example.org]", + ""); + + testRegex("invalid wiki url [2]", r, + "[http://www.example.org", + ""); + + testRegex("invalid wiki url [3]", r, + "[http://www.example.org Label", + ""); + + testRegex("example.org with label (spaces)", r, + "[http://www.example.org Text]", + "[http://www.example.org Text]"); + + testRegex("example.org with label (tabs)", r, + "[http://www.example.org\t Text]", + "[http://www.example.org\t Text]"); + + testRegex("SL http URL with label", r, + "[http://www.secondlife.com/ Second Life]", + "[http://www.secondlife.com/ Second Life]"); + + testRegex("SL https URL with label", r, + "XXX [https://www.secondlife.com/ Second Life] YYY", + "[https://www.secondlife.com/ Second Life]"); + + testRegex("SL http URL with label", r, + "[http://www.secondlife.com/?test=Hi%20There Second Life]", + "[http://www.secondlife.com/?test=Hi%20There Second Life]"); + } + + template<> template<> + void object::test<3>() + { + // + // test LLUrlEntrySLURL - second life URLs + // + LLUrlEntrySLURL url; + boost::regex r = url.getPattern(); + + testRegex("no valid slurl [1]", r, + "htp://slurl.com/secondlife/Ahern/50/50/50/", + ""); + + testRegex("no valid slurl [2]", r, + "http://slurl.com/secondlife/", + ""); + + testRegex("no valid slurl [3]", r, + "hhtp://slurl.com/secondlife/Ahern/50/FOO/50/", + ""); + + testRegex("Ahern (50,50,50) [1]", r, + "http://slurl.com/secondlife/Ahern/50/50/50/", + "http://slurl.com/secondlife/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [2]", r, + "XXX http://slurl.com/secondlife/Ahern/50/50/50/ XXX", + "http://slurl.com/secondlife/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [3]", r, + "XXX http://slurl.com/secondlife/Ahern/50/50/50 XXX", + "http://slurl.com/secondlife/Ahern/50/50/50"); + + testRegex("Ahern (50,50,50) multicase", r, + "XXX http://SLUrl.com/SecondLife/Ahern/50/50/50/ XXX", + "http://SLUrl.com/SecondLife/Ahern/50/50/50/"); + + testRegex("Ahern (50,50) [1]", r, + "XXX http://slurl.com/secondlife/Ahern/50/50/ XXX", + "http://slurl.com/secondlife/Ahern/50/50/"); + + testRegex("Ahern (50,50) [2]", r, + "XXX http://slurl.com/secondlife/Ahern/50/50 XXX", + "http://slurl.com/secondlife/Ahern/50/50"); + + testRegex("Ahern (50)", r, + "XXX http://slurl.com/secondlife/Ahern/50 XXX", + "http://slurl.com/secondlife/Ahern/50"); + + testRegex("Ahern", r, + "XXX http://slurl.com/secondlife/Ahern/ XXX", + "http://slurl.com/secondlife/Ahern/"); + + testRegex("Ahern SLURL with title", r, + "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", r, + "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", r, + "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", r, + "XXX http://slurl.com/secondlife/A'ksha%20Oasis/41/166/701 XXX", + "http://slurl.com/secondlife/A'ksha%20Oasis/41/166/701"); + } + + template<> template<> + void object::test<4>() + { + // + // test LLUrlEntryAgent - secondlife://app/agent Urls + // + LLUrlEntryAgent url; + boost::regex r = url.getPattern(); + + testRegex("Invalid Agent Url", r, + "secondlife:///app/agent/0e346d8b-4433-4d66-XXXX-fd37083abc4c/about", + ""); + + testRegex("Agent Url ", r, + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("Agent Url in text", r, + "XXX secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about XXX", + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("Agent Url multicase", r, + "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", r, + "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar", + "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar"); + + } + + template<> template<> + void object::test<5>() + { + // + // test LLUrlEntryGroup - secondlife://app/group Urls + // + LLUrlEntryGroup url; + boost::regex r = url.getPattern(); + + testRegex("Invalid Group Url", r, + "secondlife:///app/group/00005ff3-4044-c79f-XXXX-fb28ae0df991/about", + ""); + + testRegex("Group Url ", r, + "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about", + "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about"); + + testRegex("Group Url ", r, + "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect", + "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect"); + + testRegex("Group Url in text", r, + "XXX secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about XXX", + "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about"); + + testRegex("Group Url multicase", r, + "XXX secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About XXX", + "secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About"); + } + + template<> template<> + void object::test<6>() + { + // + // test LLUrlEntryPlace - secondlife://<location> URLs + // + LLUrlEntryPlace url; + boost::regex r = url.getPattern(); + + testRegex("no valid slurl [1]", r, + "secondlife://Ahern/FOO/50/", + ""); + + testRegex("Ahern (50,50,50) [1]", r, + "secondlife://Ahern/50/50/50/", + "secondlife://Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [2]", r, + "XXX secondlife://Ahern/50/50/50/ XXX", + "secondlife://Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [3]", r, + "XXX secondlife://Ahern/50/50/50 XXX", + "secondlife://Ahern/50/50/50"); + + testRegex("Ahern (50,50,50) multicase", r, + "XXX SecondLife://Ahern/50/50/50/ XXX", + "SecondLife://Ahern/50/50/50/"); + + testRegex("Ahern (50,50) [1]", r, + "XXX secondlife://Ahern/50/50/ XXX", + "secondlife://Ahern/50/50/"); + + testRegex("Ahern (50,50) [2]", r, + "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", r, + "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", r, + "XXX secondlife://A'ksha%20Oasis/41/166/701 XXX", + "secondlife://A'ksha%20Oasis/41/166/701"); + } + + template<> template<> + void object::test<7>() + { + // + // test LLUrlEntryParcel - secondlife://app/parcel Urls + // + LLUrlEntryParcel url; + boost::regex r = url.getPattern(); + + testRegex("Invalid Classified Url", r, + "secondlife:///app/parcel/0000060e-4b39-e00b-XXXX-d98b1934e3a8/about", + ""); + + testRegex("Classified Url ", r, + "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about", + "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about"); + + testRegex("Classified Url in text", r, + "XXX secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about XXX", + "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about"); + + testRegex("Classified Url multicase", r, + "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; + boost::regex r = url.getPattern(); + + testRegex("no valid teleport [1]", r, + "http://slurl.com/secondlife/Ahern/50/50/50/", + ""); + + testRegex("no valid teleport [2]", r, + "secondlife:///app/teleport/", + ""); + + testRegex("no valid teleport [3]", r, + "second-life:///app/teleport/Ahern/50/50/50/", + ""); + + testRegex("no valid teleport [3]", r, + "hhtp://slurl.com/secondlife/Ahern/50/FOO/50/", + ""); + + testRegex("Ahern (50,50,50) [1]", r, + "secondlife:///app/teleport/Ahern/50/50/50/", + "secondlife:///app/teleport/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [2]", r, + "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX", + "secondlife:///app/teleport/Ahern/50/50/50/"); + + testRegex("Ahern (50,50,50) [3]", r, + "XXX secondlife:///app/teleport/Ahern/50/50/50 XXX", + "secondlife:///app/teleport/Ahern/50/50/50"); + + testRegex("Ahern (50,50,50) multicase", r, + "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX", + "secondlife:///app/teleport/Ahern/50/50/50/"); + + testRegex("Ahern (50,50) [1]", r, + "XXX secondlife:///app/teleport/Ahern/50/50/ XXX", + "secondlife:///app/teleport/Ahern/50/50/"); + + testRegex("Ahern (50,50) [2]", r, + "XXX secondlife:///app/teleport/Ahern/50/50 XXX", + "secondlife:///app/teleport/Ahern/50/50"); + + testRegex("Ahern (50)", r, + "XXX secondlife:///app/teleport/Ahern/50 XXX", + "secondlife:///app/teleport/Ahern/50"); + + testRegex("Ahern", r, + "XXX secondlife:///app/teleport/Ahern/ XXX", + "secondlife:///app/teleport/Ahern/"); + + testRegex("Ahern teleport with title", r, + "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", r, + "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", r, + "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", r, + "XXX secondlife:///app/teleport/A'ksha%20Oasis/41/166/701 XXX", + "secondlife:///app/teleport/A'ksha%20Oasis/41/166/701"); + } + + template<> template<> + void object::test<9>() + { + // + // test LLUrlEntrySL - general secondlife:// URLs + // + LLUrlEntrySL url; + boost::regex r = url.getPattern(); + + testRegex("no valid slapp [1]", r, + "http:///app/", + ""); + + testRegex("valid slapp [1]", r, + "secondlife:///app/", + "secondlife:///app/"); + + testRegex("valid slapp [2]", r, + "secondlife:///app/teleport/Ahern/50/50/50/", + "secondlife:///app/teleport/Ahern/50/50/50/"); + + testRegex("valid slapp [3]", r, + "secondlife:///app/foo", + "secondlife:///app/foo"); + + testRegex("valid slapp [4]", r, + "secondlife:///APP/foo?title=Hi%20There", + "secondlife:///APP/foo?title=Hi%20There"); + + testRegex("valid slapp [5]", r, + "secondlife://host/app/", + "secondlife://host/app/"); + + testRegex("valid slapp [6]", r, + "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; + boost::regex r = url.getPattern(); + + testRegex("invalid wiki url [1]", r, + "[secondlife:///app/]", + ""); + + testRegex("invalid wiki url [2]", r, + "[secondlife:///app/", + ""); + + testRegex("invalid wiki url [3]", r, + "[secondlife:///app/ Label", + ""); + + testRegex("agent slurl with label (spaces)", r, + "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about Text]", + "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about Text]"); + + testRegex("agent slurl with label (tabs)", r, + "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about\t Text]", + "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about\t Text]"); + + testRegex("agent slurl with label", r, + "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about FirstName LastName]", + "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about FirstName LastName]"); + + testRegex("teleport slurl with label", r, + "XXX [secondlife:///app/teleport/Ahern/50/50/50/ Teleport to Ahern] YYY", + "[secondlife:///app/teleport/Ahern/50/50/50/ Teleport to Ahern]"); + } + + template<> template<> + void object::test<11>() + { + // + // test LLUrlEntryHTTPNoProtocol - general URLs without a protocol + // + LLUrlEntryHTTPNoProtocol url; + boost::regex r = url.getPattern(); + + testRegex("naked .com URL", r, + "see google.com", + "google.com"); + + testRegex("naked .org URL", r, + "see en.wikipedia.org for details", + "en.wikipedia.org"); + + testRegex("naked .net URL", r, + "example.net", + "example.net"); + + testRegex("naked .edu URL (2 instances)", r, + "MIT web site is at web.mit.edu and also www.mit.edu", + "web.mit.edu"); + + testRegex("don't match e-mail addresses", r, + "test@lindenlab.com", + ""); + + testRegex(".com URL with path", r, + "see secondlife.com/status for grid status", + "secondlife.com/status"); + + testRegex(".com URL with port", r, + "secondlife.com:80", + "secondlife.com:80"); + + testRegex(".com URL with port and path", r, + "see secondlife.com:80/status", + "secondlife.com:80/status"); + + testRegex("www.*.com URL with port and path", r, + "see www.secondlife.com:80/status", + "www.secondlife.com:80/status"); + + testRegex("invalid .com URL [1]", r, + "..com", + ""); + + testRegex("invalid .com URL [2]", r, + "you.come", + ""); + + testRegex("invalid .com URL [3]", r, + "recommended", + ""); + + testRegex("invalid .edu URL", r, + "hi there scheduled maitenance has begun", + ""); + + testRegex("invalid .net URL", r, + "foo.netty", + ""); + } +} diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp new file mode 100644 index 0000000000..e8cf135346 --- /dev/null +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -0,0 +1,181 @@ +/** + * @file llurlmatch_test.cpp + * @author Martin Reddy + * @brief Unit tests for LLUrlMatch + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of + * this source code is governed by the Linden Lab Source Code Disclosure + * Agreement ("Agreement") previously entered between you and Linden + * Lab. By accessing, using, copying, modifying or distributing this + * software, you acknowledge that you have been informed of your + * obligations under the Agreement and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "../llurlmatch.h" +#include "lltut.h" + +// link seam +LLUIColor::LLUIColor() +{} + +namespace tut +{ + struct LLUrlMatchData + { + }; + + typedef test_group<LLUrlMatchData> factory; + typedef factory::object object; +} + +namespace +{ + tut::factory tf("LLUrlMatch"); +} + +namespace tut +{ + template<> template<> + void object::test<1>() + { + // + // test the empty() method + // + LLUrlMatch match; + ensure("empty()", match.empty()); + + match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLUIColor(), "", ""); + ensure("! empty()", ! match.empty()); + } + + template<> template<> + void object::test<2>() + { + // + // test the getStart() method + // + LLUrlMatch match; + ensure_equals("getStart() == 0", match.getStart(), 0); + + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + ensure_equals("getStart() == 10", match.getStart(), 10); + } + + template<> template<> + void object::test<3>() + { + // + // test the getEnd() method + // + LLUrlMatch match; + ensure_equals("getEnd() == 0", match.getEnd(), 0); + + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + ensure_equals("getEnd() == 20", match.getEnd(), 20); + } + + template<> template<> + void object::test<4>() + { + // + // test the getUrl() method + // + LLUrlMatch match; + ensure_equals("getUrl() == ''", match.getUrl(), ""); + + match.setValues(10, 20, "http://slurl.com/", "", "", "", LLUIColor(), "", ""); + ensure_equals("getUrl() == 'http://slurl.com/'", match.getUrl(), "http://slurl.com/"); + + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + ensure_equals("getUrl() == '' (2)", match.getUrl(), ""); + } + + template<> template<> + void object::test<5>() + { + // + // test the getLabel() method + // + LLUrlMatch match; + ensure_equals("getLabel() == ''", match.getLabel(), ""); + + match.setValues(10, 20, "", "Label", "", "", LLUIColor(), "", ""); + ensure_equals("getLabel() == 'Label'", match.getLabel(), "Label"); + + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + ensure_equals("getLabel() == '' (2)", match.getLabel(), ""); + } + + template<> template<> + void object::test<6>() + { + // + // test the getTooltip() method + // + LLUrlMatch match; + ensure_equals("getTooltip() == ''", match.getTooltip(), ""); + + match.setValues(10, 20, "", "", "Info", "", LLUIColor(), "", ""); + ensure_equals("getTooltip() == 'Info'", match.getTooltip(), "Info"); + + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + ensure_equals("getTooltip() == '' (2)", match.getTooltip(), ""); + } + + template<> template<> + void object::test<7>() + { + // + // test the getIcon() method + // + LLUrlMatch match; + ensure_equals("getIcon() == ''", match.getIcon(), ""); + + match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "", ""); + ensure_equals("getIcon() == 'Icon'", match.getIcon(), "Icon"); + + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + ensure_equals("getIcon() == '' (2)", match.getIcon(), ""); + } + + template<> template<> + void object::test<8>() + { + // + // test the getMenuName() method + // + LLUrlMatch match; + ensure("getMenuName() empty", match.getMenuName().empty()); + + match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", ""); + ensure_equals("getMenuName() == \"xui_file.xml\"", match.getMenuName(), "xui_file.xml"); + + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + ensure("getMenuName() empty (2)", match.getMenuName().empty()); + } + + template<> template<> + void object::test<9>() + { + // + // test the getLocation() method + // + LLUrlMatch match; + ensure("getLocation() empty", match.getLocation().empty()); + + match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", "Paris"); + ensure_equals("getLocation() == \"Paris\"", match.getLocation(), "Paris"); + + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + ensure("getLocation() empty (2)", match.getLocation().empty()); + } +} |