diff options
Diffstat (limited to 'indra/llui')
81 files changed, 3837 insertions, 1111 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 82ec02d2eb..853f6f173d 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -26,6 +26,8 @@ include_directories( ) set(llui_SOURCE_FILES + llaccordionctrl.cpp + llaccordionctrltab.cpp llbutton.cpp llcheckboxctrl.cpp llclipboard.cpp @@ -88,6 +90,7 @@ set(llui_SOURCE_FILES lltextbox.cpp lltexteditor.cpp lltextparser.cpp + lltextvalidate.cpp lltransutil.cpp lltoggleablemenu.cpp lltooltip.cpp @@ -111,6 +114,8 @@ set(llui_SOURCE_FILES set(llui_HEADER_FILES CMakeLists.txt + llaccordionctrl.h + llaccordionctrltab.h llbutton.h llcallbackmap.h llcheckboxctrl.h @@ -178,6 +183,7 @@ set(llui_HEADER_FILES lltextbox.h lltexteditor.h lltextparser.h + lltextvalidate.h lltoggleablemenu.h lltooltip.h lltransutil.h diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp new file mode 100644 index 0000000000..aa69dfe0cc --- /dev/null +++ b/indra/llui/llaccordionctrl.cpp @@ -0,0 +1,712 @@ +/** + * @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) + , mAutoScrolling( false ) + , mAutoScrollRate( 0.f ) +{ + mSingleExpansion = params.single_expansion; + if(mFitParent && !mSingleExpansion) + { + llinfos << "fit_parent works best when combined with single_expansion" << llendl; + } +} + +LLAccordionCtrl::LLAccordionCtrl() : LLPanel() + , mAutoScrolling( false ) + , mAutoScrollRate( 0.f ) +{ + mSingleExpansion = false; + mFitParent = false; + LLUICtrlFactory::getInstance()->buildPanel(this, "accordion_parent.xml"); +} + +//--------------------------------------------------------------------------------- +void LLAccordionCtrl::draw() +{ + if (mAutoScrolling) + { + // add acceleration to autoscroll + mAutoScrollRate = llmin(mAutoScrollRate + (LLFrameTimer::getFrameDeltaTimeF32() * AUTO_SCROLL_RATE_ACCEL), MAX_AUTO_SCROLL_RATE); + } + else + { + // reset to minimum for next time + mAutoScrollRate = MIN_AUTO_SCROLL_RATE; + } + // clear this flag to be set on next call to autoScroll + mAutoScrolling = false; + + LLRect local_rect(0, getRect().getHeight(), getRect().getWidth(), 0); + + LLLocalClipRect clip(local_rect); + + LLPanel::draw(); + /* + S32 width = getRect().getWidth(); + S32 height = getRect().getHeight(); + + gl_rect_2d(0, 0 , width - 1 ,height - 1,LLColor4::green,true); + gl_line_2d(0, 0 , width - 1 ,height - 1,LLColor4::black); + */ +} + + +//--------------------------------------------------------------------------------- +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::arrangeSinge() +{ + 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 + S32 panel_height; + + S32 collapsed_height = 0; + + 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() ) + { + collapsed_height+=mAccordionTabs[i]->getRect().getHeight(); + } + } + + S32 expanded_height = getRect().getHeight() - BORDER_MARGIN - collapsed_height; + + for(size_t i=0;i<mAccordionTabs.size();++i) + { + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); + + if(accordion_tab->getVisible() == false) //skip hidden accordion tabs + continue; + if(!accordion_tab->isExpanded() ) + { + panel_height = accordion_tab->getRect().getHeight(); + } + else + { + panel_height = expanded_height; + } + ctrlSetLeftTopAndSize(mAccordionTabs[i], panel_left, panel_top, panel_width, panel_height); + panel_top-=mAccordionTabs[i]->getRect().getHeight(); + } +} + +void LLAccordionCtrl::arrangeMultiple() +{ + S32 panel_left = BORDER_MARGIN; // Margin from left side of Splitter + S32 panel_top = getRect().getHeight() - BORDER_MARGIN; // Top coordinate of the first panel + S32 panel_width = getRect().getWidth() - 4; // Top coordinate of the first panel + + //Calculate params + 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()); +} + + +void LLAccordionCtrl::arrange() +{ + if( mAccordionTabs.size() == 0) + { + //We do not arrange if we do not have what should be arranged + return; + } + + + if(mAccordionTabs.size() == 1) + { + 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 + + 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; + + } + + if(mSingleExpansion) + arrangeSinge (); + else + arrangeMultiple (); +} + +//--------------------------------------------------------------------------------- + +BOOL LLAccordionCtrl::handleScrollWheel ( S32 x, S32 y, S32 clicks ) +{ + if(LLPanel::handleScrollWheel(x,y,clicks)) + return TRUE; + if( mScrollbar->getVisible() && mScrollbar->handleScrollWheel( 0, 0, clicks ) ) + return TRUE; + return false; + +} + +BOOL LLAccordionCtrl::handleKeyHere (KEY key, MASK mask) +{ + if( mScrollbar->getVisible() && mScrollbar->handleKeyHere( key,mask ) ) + return TRUE; + return LLPanel::handleKeyHere(key,mask); +} + +BOOL LLAccordionCtrl::handleDragAndDrop (S32 x, S32 y, MASK mask, + BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // Scroll folder view if needed. Never accepts a drag or drop. + *accept = ACCEPT_NO; + BOOL handled = autoScroll(x, y); + + if( !handled ) + { + handled = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, + cargo_data, accept, tooltip_msg) != NULL; + } + return TRUE; +} + +BOOL LLAccordionCtrl::autoScroll (S32 x, S32 y) +{ + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + + bool scrolling = false; + if( mScrollbar->getVisible() ) + { + LLRect rect_local( 0, getRect().getHeight(), getRect().getWidth() - scrollbar_size, 0 ); + LLRect screen_local_extents; + + // clip rect against root view + screenRectToLocal(getRootView()->getLocalRect(), &screen_local_extents); + rect_local.intersectWith(screen_local_extents); + + // autoscroll region should take up no more than one third of visible scroller area + S32 auto_scroll_region_height = llmin(rect_local.getHeight() / 3, 10); + S32 auto_scroll_speed = llround(mAutoScrollRate * LLFrameTimer::getFrameDeltaTimeF32()); + + LLRect bottom_scroll_rect = screen_local_extents; + bottom_scroll_rect.mTop = rect_local.mBottom + auto_scroll_region_height; + if( bottom_scroll_rect.pointInRect( x, y ) && (mScrollbar->getDocPos() < mScrollbar->getDocPosMax()) ) + { + mScrollbar->setDocPos( mScrollbar->getDocPos() + auto_scroll_speed ); + mAutoScrolling = true; + scrolling = true; + } + + LLRect top_scroll_rect = screen_local_extents; + top_scroll_rect.mBottom = rect_local.mTop - auto_scroll_region_height; + if( top_scroll_rect.pointInRect( x, y ) && (mScrollbar->getDocPos() > 0) ) + { + mScrollbar->setDocPos( mScrollbar->getDocPos() - auto_scroll_speed ); + mAutoScrolling = true; + scrolling = true; + } + } + return scrolling; +} + +void LLAccordionCtrl::updateLayout (S32 width, S32 height) +{ + S32 panel_top = height - BORDER_MARGIN ; + if(mScrollbar->getVisible()) + panel_top+=mScrollbar->getDocPos(); + + S32 panel_width = width - 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..7c29e545b7 --- /dev/null +++ b/indra/llui/llaccordionctrl.h @@ -0,0 +1,135 @@ +/** + * @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); + virtual BOOL handleDragAndDrop (S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + // + + // Call reshape after changing splitter's size + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + void addCollapsibleCtrl(LLView* view); + void arrange(); + + + void draw(); + + void onScrollPosChangeCallback(S32, LLScrollbar*); + + void onOpen (const LLSD& key); + S32 notifyParent(const LLSD& info); + + void reset (); + +private: + void arrangeSinge(); + void arrangeMultiple(); + + // Calc Splitter's height that is necessary to display all child content + S32 calcRecuiredHeight(); + S32 getRecuiredHeight() const { return mInnerRect.getHeight(); } + S32 calcExpandedTabHeight(S32 tab_index = 0, S32 available_height = 0); + + void updateLayout (S32 width, S32 height); + + void show_hide_scrollbar (S32 width, S32 height); + + void showScrollbar (S32 width, S32 height); + void hideScrollbar (S32 width, S32 height); + + BOOL autoScroll (S32 x, S32 y); + +private: + LLRect mInnerRect; + LLScrollbar* mScrollbar; + bool mSingleExpansion; + bool mFitParent; + bool mAutoScrolling; + F32 mAutoScrollRate; +}; + + +#endif // LL_LLSPLITTER_H diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp new file mode 100644 index 0000000000..daa9e08f14 --- /dev/null +++ b/indra/llui/llaccordionctrltab.cpp @@ -0,0 +1,634 @@ +/** + * @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 const F32 AUTO_OPEN_TIME = 1.f; + +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); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); +private: + + LLTextBox* mHeaderTextbox; + + // Overlay images (arrows) + LLPointer<LLUIImage> mImageCollapsed; + LLPointer<LLUIImage> mImageExpanded; + LLPointer<LLUIImage> mImageCollapsedPressed; + LLPointer<LLUIImage> mImageExpandedPressed; + + // Background images + LLPointer<LLUIImage> mImageHeader; + LLPointer<LLUIImage> mImageHeaderOver; + LLPointer<LLUIImage> mImageHeaderPressed; + LLPointer<LLUIImage> mImageHeaderFocused; + + LLUIColor mHeaderBGColor; + + bool mNeedsHighlight; + + LLFrameTimer mAutoOpenTimer; +}; + +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; + mAutoOpenTimer.stop(); +} +BOOL LLAccordionCtrlTab::LLAccordionCtrlTabHeader::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + if ( ( key == KEY_LEFT || key == KEY_RIGHT) && mask == MASK_NONE) + { + return getParent()->handleKey(key, mask, called_from_parent); + } + return LLUICtrl::handleKey(key, mask, called_from_parent); +} +BOOL LLAccordionCtrlTab::LLAccordionCtrlTabHeader::handleDragAndDrop(S32 x, S32 y, MASK mask, + BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + LLAccordionCtrlTab* parent = dynamic_cast<LLAccordionCtrlTab*>(getParent()); + + if ( parent && !parent->getDisplayChildren() && parent->getCollapsible() && parent->canOpenClose() ) + { + if (mAutoOpenTimer.getStarted()) + { + if (mAutoOpenTimer.getElapsedTimeF32() > AUTO_OPEN_TIME) + { + parent->changeOpenClose(false); + mAutoOpenTimer.stop(); + return TRUE; + } + } + else + mAutoOpenTimer.start(); + } + + return LLUICtrl::handleDragAndDrop(x, y, mask, drop, cargo_type, + cargo_data, accept, tooltip_msg); +} +LLAccordionCtrlTab::Params::Params() + : title("title") + ,display_children("expanded", true) + ,header_height("header_height", HEADER_HEIGHT), + min_width("min_width", 0), + min_height("min_height", 0) + ,collapsible("collapsible", true) + ,header_bg_color("header_bg_color") + ,dropdown_bg_color("dropdown_bg_color") + ,header_visible("header_visible",true) + ,padding_left("padding_left",2) + ,padding_right("padding_right",2) + ,padding_top("padding_top",2) + ,padding_bottom("padding_bottom",2) + ,header_expand_img("header_expand_img") + ,header_expand_img_pressed("header_expand_img_pressed") + ,header_collapse_img("header_collapse_img") + ,header_collapse_img_pressed("header_collapse_img_pressed") + ,header_image("header_image") + ,header_image_over("header_image_over") + ,header_image_pressed("header_image_pressed") + ,header_image_focused("header_image_focused") + ,header_text_color("header_text_color") +{ + 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..2e0260ab16 --- /dev/null +++ b/indra/llui/llaccordionctrltab.h @@ -0,0 +1,192 @@ +/** + * @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;}; + bool canOpenClose() const { return mCanOpenClose; }; + + 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 e9f6288f44..1d4dc35cee 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -81,6 +81,9 @@ LLButton::Params::Params() image_pressed_selected("image_pressed_selected"), image_overlay("image_overlay"), image_overlay_alignment("image_overlay_alignment", std::string("center")), + image_top_pad("image_top_pad"), + image_bottom_pad("image_bottom_pad"), + imgoverlay_label_space("imgoverlay_label_space", 1), label_color("label_color"), label_color_selected("label_color_selected"), // requires is_toggle true label_color_disabled("label_color_disabled"), @@ -140,6 +143,9 @@ LLButton::LLButton(const LLButton::Params& p) mImageOverlay(p.image_overlay()), mImageOverlayColor(p.image_overlay_color()), mImageOverlayAlignment(LLFontGL::hAlignFromName(p.image_overlay_alignment)), + mImageOverlayTopPad(p.image_top_pad), + mImageOverlayBottomPad(p.image_bottom_pad), + mImgOverlayLabelSpace(p.imgoverlay_label_space), mIsToggle(p.is_toggle), mScaleImage(p.scale_image), mDropShadowedText(p.label_shadow), @@ -763,6 +769,7 @@ void LLButton::draw() center_x++; } + center_y += (mImageOverlayBottomPad - mImageOverlayTopPad); // fade out overlay images on disabled buttons LLColor4 overlay_color = mImageOverlayColor.get(); if (!enabled) @@ -774,10 +781,9 @@ void LLButton::draw() switch(mImageOverlayAlignment) { case LLFontGL::LEFT: - text_left += overlay_width + 1; - text_width -= overlay_width + 1; + text_left += overlay_width + mImgOverlayLabelSpace; mImageOverlay->draw( - mLeftHPad, + mLeftHPad, center_y - (overlay_height / 2), overlay_width, overlay_height, @@ -792,10 +798,9 @@ void LLButton::draw() overlay_color); break; case LLFontGL::RIGHT: - text_right -= overlay_width + 1; - text_width -= overlay_width + 1; + text_right -= overlay_width + mImgOverlayLabelSpace; mImageOverlay->draw( - getRect().getWidth() - mRightHPad - overlay_width, + getRect().getWidth() - mRightHPad - overlay_width, center_y - (overlay_height / 2), overlay_width, overlay_height, @@ -1022,6 +1027,20 @@ 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() { resetMouseDownTimer(); diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index 5e28b8cdff..6a0d8ef3d6 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -106,6 +106,15 @@ public: Optional<S32> pad_left; Optional<S32> pad_bottom; // under text label + //image overlay paddings + Optional<S32> image_top_pad; + Optional<S32> image_bottom_pad; + + /** + * Space between image_overlay and label + */ + Optional<S32> imgoverlay_label_space; + // callbacks Optional<CommitCallbackParam> click_callback, // alias -> commit_callback mouse_down_callback, @@ -186,6 +195,11 @@ public: void setLeftHPad( S32 pad ) { mLeftHPad = pad; } void setRightHPad( S32 pad ) { mRightHPad = pad; } + void setImageOverlayTopPad( S32 pad ) { mImageOverlayTopPad = pad; } + S32 getImageOverlayTopPad() const { return mImageOverlayTopPad; } + void setImageOverlayBottomPad( S32 pad ) { mImageOverlayBottomPad = pad; } + S32 getImageOverlayBottomPad() const { return mImageOverlayBottomPad; } + const std::string getLabelUnselected() const { return wstring_to_utf8str(mUnselectedLabel); } const std::string getLabelSelected() const { return wstring_to_utf8str(mSelectedLabel); } @@ -200,6 +214,7 @@ public: 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; } void autoResize(); // resize with label of current btn state @@ -242,6 +257,8 @@ public: void setForcePressedState(bool b) { mForcePressedState = b; } + void setAutoResize(bool auto_resize) { mAutoResize = auto_resize; } + protected: LLPointer<LLUIImage> getImageUnselected() const { return mImageUnselected; } LLPointer<LLUIImage> getImageSelected() const { return mImageSelected; } @@ -312,6 +329,14 @@ private: S32 mRightHPad; S32 mBottomVPad; // under text label + S32 mImageOverlayTopPad; + S32 mImageOverlayBottomPad; + + /* + * Space between image_overlay and label + */ + S32 mImgOverlayLabelSpace; + F32 mHoverGlowStrength; F32 mCurGlowStrength; diff --git a/indra/llui/llcheckboxctrl.cpp b/indra/llui/llcheckboxctrl.cpp index cd10dfdb1c..3d32157406 100644 --- a/indra/llui/llcheckboxctrl.cpp +++ b/indra/llui/llcheckboxctrl.cpp @@ -107,8 +107,8 @@ LLCheckBoxCtrl::LLCheckBoxCtrl(const LLCheckBoxCtrl::Params& p) { 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 diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index f29e8785eb..9d23daf56d 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -103,7 +103,8 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p) mPrearrangeCallback(p.prearrange_callback()), mTextEntryCallback(p.text_entry_callback()), mListPosition(p.list_position), - mLastSelectedIndex(-1) + mLastSelectedIndex(-1), + mLabel(p.label) { // Text label button @@ -490,6 +491,7 @@ void LLComboBox::createLineEditor(const LLComboBox::Params& p) params.handle_edit_keys_directly(true); params.commit_on_focus_lost(false); params.follows.flags(FOLLOWS_ALL); + params.label(mLabel); mTextEntry = LLUICtrlFactory::create<LLLineEditor> (params); mTextEntry->setText(cur_label); mTextEntry->setIgnoreTab(TRUE); @@ -505,7 +507,8 @@ void LLComboBox::createLineEditor(const LLComboBox::Params& p) mButton->setRect(rect); mButton->setTabStop(TRUE); mButton->setHAlign(LLFontGL::LEFT); - + mButton->setLabel(mLabel.getString()); + if (mTextEntry) { mTextEntry->setVisible(FALSE); @@ -633,7 +636,7 @@ void LLComboBox::hideList() if(mLastSelectedIndex >= 0) mList->selectNthItem(mLastSelectedIndex); } - else + else if(mLastSelectedIndex >= 0) mList->selectNthItem(mLastSelectedIndex); mButton->setToggleState(FALSE); diff --git a/indra/llui/llconsole.cpp b/indra/llui/llconsole.cpp index fa0abd55d0..badbddc3cc 100644 --- a/indra/llui/llconsole.cpp +++ b/indra/llui/llconsole.cpp @@ -66,7 +66,9 @@ LLConsole::LLConsole(const LLConsole::Params& p) : LLUICtrl(p), LLFixedBuffer(p.max_lines), mLinePersistTime(p.persist_time), // seconds - mFont(p.font) + mFont(p.font), + mConsoleWidth(0), + mConsoleHeight(0) { if (p.font_size_index.isProvided()) { @@ -180,7 +182,7 @@ void LLConsole::draw() // draw remaining lines F32 y_pos = 0.f; - LLUIImagePtr imagep = LLUI::getUIImage("rounded_square.tga"); + 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); @@ -242,23 +244,6 @@ void LLConsole::draw() } } -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) { @@ -315,7 +300,8 @@ void LLConsole::Paragraph::updateLines(F32 screen_width, const LLFontGL* font, b 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() ) + while( paragraph_offset < (S32)mParagraphText.length() && + mParagraphText[paragraph_offset] != 0) { S32 skip_chars; // skip '\n' // Figure out if a word-wrapped line fits here. @@ -330,7 +316,7 @@ void LLConsole::Paragraph::updateLines(F32 screen_width, const LLFontGL* font, b skip_chars = 0; } - U32 drawable = font->maxDrawableChars(mParagraphText.c_str()+paragraph_offset, screen_width, line_end - paragraph_offset, TRUE); + U32 drawable = font->maxDrawableChars(mParagraphText.c_str()+paragraph_offset, screen_width, line_end - paragraph_offset, LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); if (drawable != 0) { @@ -381,15 +367,45 @@ void LLConsole::Paragraph::updateLines(F32 screen_width, const LLFontGL* font, b //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) +: mParagraphText(str), mAddTime(add_time), mMaxWidth(-1) { makeParagraphColorSegments(color); updateLines( screen_width, font ); } -void LLConsole::addLine(const LLWString& wline, F32 size, const LLColor4 &color) +// called once per frame regardless of console visibility +// static +void LLConsole::updateClass() { - Paragraph paragraph(wline, color, mTimer.getElapsedTimeF32(), mFont, (F32)getRect().getWidth() ); - - mParagraphs.push_back ( paragraph ); + LLInstanceTrackerScopedGuard guard; + + for (instance_iter it = guard.beginInstances(); it != guard.endInstances(); ++it) + { + it->update(); + } } + +void LLConsole::update() +{ + { + LLMutexLock lock(&mMutex); + + while (!mLines.empty()) + { + mParagraphs.push_back( + Paragraph( mLines.front(), + LLColor4::white, + mTimer.getElapsedTimeF32(), + mFont, + (F32)getRect().getWidth())); + mLines.pop_front(); + } + } + + // remove old paragraphs which can't possibly be visible any more. ::draw() will do something similar but more conservative - we do this here because ::draw() isn't guaranteed to ever be called! (i.e. the console isn't visible) + while ((S32)mParagraphs.size() > llmax((S32)0, (S32)(mMaxLines))) + { + mParagraphs.pop_front(); + } +} + diff --git a/indra/llui/llconsole.h b/indra/llui/llconsole.h index 5800a82922..f38e2bc9c2 100644 --- a/indra/llui/llconsole.h +++ b/indra/llui/llconsole.h @@ -40,7 +40,7 @@ class LLSD; -class LLConsole : public LLFixedBuffer, public LLUICtrl +class LLConsole : public LLFixedBuffer, public LLUICtrl, public LLInstanceTracker<LLConsole> { public: typedef enum e_font_size @@ -68,6 +68,9 @@ protected: friend class LLUICtrlFactory; public: + // call once per frame to pull data out of LLFixedBuffer + static void updateClass(); + //A paragraph color segment defines the color of text in a line //of text that was received for console display. It has no //notion of line wraps, screen position, or the text it contains. @@ -139,19 +142,15 @@ public: // -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: + void update(); + 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; diff --git a/indra/llui/lldockablefloater.cpp b/indra/llui/lldockablefloater.cpp index 6a5b475134..3d8670fef2 100644 --- a/indra/llui/lldockablefloater.cpp +++ b/indra/llui/lldockablefloater.cpp @@ -57,6 +57,7 @@ LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, , mOverlapsScreenChannel(false) { init(this); + mUseTongue = true; } LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, @@ -64,6 +65,14 @@ LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, bool uniqueDock 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() @@ -86,7 +95,7 @@ void LLDockableFloater::toggleInstance(const LLSD& sdname) LLDockableFloater* instance = dynamic_cast<LLDockableFloater*> (LLFloaterReg::findInstance(name)); // if floater closed or docked - if (instance == NULL || instance != NULL && instance->isDocked()) + if (instance == NULL || (instance && instance->isDocked())) { LLFloaterReg::toggleInstance(name, key); // restore button toggle state @@ -137,7 +146,7 @@ void LLDockableFloater::setVisible(BOOL visible) if (visible) { - LLFloater::setFrontmost(TRUE); + LLFloater::setFrontmost(getAutoFocus()); } LLFloater::setVisible(visible); } @@ -193,10 +202,6 @@ void LLDockableFloater::setDocked(bool docked, bool pop_on_undock) translate(0, UNDOCK_LEAP_HEIGHT); } } - else - { - docked = false; - } LLFloater::setDocked(docked, pop_on_undock); } @@ -217,7 +222,7 @@ void LLDockableFloater::draw() void LLDockableFloater::setDockControl(LLDockControl* dockControl) { mDockControl.reset(dockControl); - setDocked(mDockControl.get() != NULL && mDockControl.get()->isDockVisible()); + setDocked(isDocked()); } const LLUIImagePtr& LLDockableFloater::getDockTongue() diff --git a/indra/llui/lldockablefloater.h b/indra/llui/lldockablefloater.h index ae4f99e205..2b1ce99ae2 100644 --- a/indra/llui/lldockablefloater.h +++ b/indra/llui/lldockablefloater.h @@ -62,6 +62,20 @@ public: */ 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; } @@ -104,6 +118,7 @@ public: virtual void setOverlapsScreenChannel(bool overlaps) { mOverlapsScreenChannel = overlaps; } bool getUniqueDocking() { return mUniqueDocking; } + bool getUseTongue() { return mUseTongue; } private: /** * Provides unique of dockable floater. @@ -125,6 +140,8 @@ private: */ bool mUniqueDocking; + bool mUseTongue; + bool mOverlapsScreenChannel; }; diff --git a/indra/llui/lldockcontrol.cpp b/indra/llui/lldockcontrol.cpp index 1c3c8449c5..d738b10130 100644 --- a/indra/llui/lldockcontrol.cpp +++ b/indra/llui/lldockcontrol.cpp @@ -37,7 +37,11 @@ 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) + mDockWidget(dockWidget), + mDockableFloater(dockableFloater), + mDockTongue(dockTongue), + mDockTongueX(0), + mDockTongueY(0) { mDockAt = dockAt; @@ -158,7 +162,9 @@ bool LLDockControl::isDockVisible() { case LEFT: // to keep compiler happy break; + case BOTTOM: case TOP: + { // check is dock inside parent rect LLRect dockParentRect = mDockWidget->getParent()->calcScreenRect(); @@ -169,6 +175,9 @@ bool LLDockControl::isDockVisible() } break; } + default: + break; + } } } @@ -182,12 +191,12 @@ void LLDockControl::moveDockable() LLRect rootRect; mGetAllowedRectCallback(rootRect); - bool unique_docking = false; + bool use_tongue = false; LLDockableFloater* dockable_floater = dynamic_cast<LLDockableFloater*> (mDockableFloater); if (dockable_floater != NULL) { - unique_docking = dockable_floater->getUniqueDocking(); + use_tongue = dockable_floater->getUseTongue(); } LLRect dockableRect = mDockableFloater->calcScreenRect(); @@ -218,7 +227,7 @@ void LLDockControl::moveDockable() 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 (unique_docking) + if (use_tongue) { y += mDockTongue->getHeight(); } @@ -251,6 +260,42 @@ void LLDockControl::moveDockable() mDockTongueY = dockRect.mTop; break; + case BOTTOM: + x = dockRect.getCenterX() - dockableRect.getWidth() / 2; + y = dockRect.mBottom; + // 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.mBottom - mDockTongue->getHeight(); + + break; } // move dockable @@ -287,15 +332,15 @@ void LLDockControl::forceRecalculatePosition() void LLDockControl::drawToungue() { - bool unique_docking = false; + bool use_tongue = false; LLDockableFloater* dockable_floater = dynamic_cast<LLDockableFloater*> (mDockableFloater); if (dockable_floater != NULL) { - unique_docking = dockable_floater->getUniqueDocking(); + use_tongue = dockable_floater->getUseTongue(); } - if (mEnabled && unique_docking) + if (mEnabled && use_tongue) { mDockTongue->draw(mDockTongueX, mDockTongueY); } diff --git a/indra/llui/lldockcontrol.h b/indra/llui/lldockcontrol.h index 550955c4c5..a5caf68001 100644 --- a/indra/llui/lldockcontrol.h +++ b/indra/llui/lldockcontrol.h @@ -47,8 +47,9 @@ class LLDockControl public: enum DocAt { - TOP - ,LEFT + TOP, + LEFT, + BOTTOM }; public: diff --git a/indra/llui/lldraghandle.cpp b/indra/llui/lldraghandle.cpp index a93c666648..832f148902 100644 --- a/indra/llui/lldraghandle.cpp +++ b/indra/llui/lldraghandle.cpp @@ -113,6 +113,7 @@ void LLDragHandleTop::setTitle(const std::string& title) params.follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT | FOLLOWS_RIGHT); params.font_shadow(LLFontGL::DROP_SHADOW_SOFT); params.use_ellipses = true; + params.allow_html = false; //cancel URL replacement in floater title mTitleBox = LLUICtrlFactory::create<LLTextBox> (params); addChild( mTitleBox ); } diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index 3754d155cf..2481249f91 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -42,8 +42,6 @@ static const LLDefaultChildRegistry::Register<LLFlatListView> flat_list_view("fl 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); @@ -51,7 +49,8 @@ LLFlatListView::Params::Params() : item_pad("item_pad"), allow_select("allow_select"), multi_select("multi_select"), - keep_one_selected("keep_one_selected") + keep_one_selected("keep_one_selected"), + no_items_text("no_items_text") {}; void LLFlatListView::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */) @@ -289,25 +288,12 @@ void LLFlatListView::resetSelection(bool no_commit_on_deselection /*= false*/) onCommit(); } - // Stretch selected items rect to ensure it won't be clipped - mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1)); + // Stretch selected item rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getLastSelectedItemRect().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); } @@ -361,7 +347,6 @@ bool LLFlatListView::updateValue(const LLSD& old_value, const LLSD& new_value) // PROTECTED STUFF ////////////////////////////////////////////////////////////////////////// - LLFlatListView::LLFlatListView(const LLFlatListView::Params& p) : LLScrollContainer(p) , mItemComparator(NULL) @@ -393,11 +378,30 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p) LLViewBorder::Params params; params.name("scroll border"); - params.rect(getSelectedItemsRect()); + params.rect(getLastSelectedItemRect()); params.visible(false); params.bevel_style(LLViewBorder::BEVEL_IN); mSelectedItemsBorder = LLUICtrlFactory::create<LLViewBorder> (params); mItemsPanel->addChild( mSelectedItemsBorder ); + + { + // create textbox for "No Items" comment text + LLTextBox::Params text_p = p.no_items_text; + if (!text_p.rect.isProvided()) + { + LLRect comment_rect = getRect(); + comment_rect.setOriginAndSize(0, 0, comment_rect.getWidth(), comment_rect.getHeight()); + comment_rect.stretch(-getBorderWidth()); + text_p.rect(comment_rect); + } + text_p.border_visible(false); + + if (!text_p.follows.isProvided()) + { + text_p.follows.flags(FOLLOWS_ALL); + } + mNoItemsCommentTextbox = LLUICtrlFactory::create<LLTextBox>(text_p, this); + } }; // virtual @@ -480,8 +484,8 @@ void LLFlatListView::rearrangeItems() item_new_top -= (rc.getHeight() + mItemPad); } - // Stretch selected items rect to ensure it won't be clipped - mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1)); + // Stretch selected item rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); } void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) @@ -664,8 +668,8 @@ bool LLFlatListView::selectItemPair(item_pair_t* item_pair, bool select) onCommit(); } - // Stretch selected items rect to ensure it won't be clipped - mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1)); + // Stretch selected item rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); return true; } @@ -680,23 +684,6 @@ LLRect LLFlatListView::getLastSelectedItemRect() 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); @@ -711,19 +698,12 @@ void LLFlatListView::selectLastItem () void LLFlatListView::ensureSelectedVisible() { - LLRect visible_rc = getVisibleContentRect(); LLRect selected_rc = getLastSelectedItemRect(); - if ( !visible_rc.contains (selected_rc) ) + if ( selected_rc.isValid() ) { - // 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())); } @@ -826,8 +806,8 @@ bool LLFlatListView::selectAll() onCommit(); } - // Stretch selected items rect to ensure it won't be clipped - mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1)); + // Stretch selected item rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); return true; } @@ -885,7 +865,11 @@ void LLFlatListView::notifyParentItemsRectChanged() // take into account comment text height if exists if (mNoItemsCommentTextbox && mNoItemsCommentTextbox->getVisible()) { + // top text padding inside the textbox is included into the height comment_height = mNoItemsCommentTextbox->getTextPixelHeight(); + + // take into account a distance from parent's top border to textbox's top + comment_height += getRect().getHeight() - mNoItemsCommentTextbox->getRect().mTop; } LLRect req_rect = getItemsRect(); @@ -906,7 +890,8 @@ void LLFlatListView::notifyParentItemsRectChanged() params["width"] = req_rect.getWidth(); params["height"] = req_rect.getHeight(); - getParent()->notifyParent(params); + if (getParent()) // dummy widgets don't have a parent + getParent()->notifyParent(params); } void LLFlatListView::setNoItemsCommentVisible(bool visible) const @@ -915,6 +900,10 @@ void LLFlatListView::setNoItemsCommentVisible(bool visible) const { if (visible) { +/* +// *NOTE: MA 2010-02-04 +// Deprecated after params of the comment text box were moved into widget (flat_list_view.xml) +// can be removed later if nothing happened. // 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(); @@ -926,6 +915,7 @@ void LLFlatListView::setNoItemsCommentVisible(bool visible) const LLViewBorder* scroll_border = getChild<LLViewBorder>("scroll border"); comment_rect.stretch(-scroll_border->getBorderWidth()); mNoItemsCommentTextbox->setRect(comment_rect); +*/ } mNoItemsCommentTextbox->setVisible(visible); } diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 5999e79f61..92cb40332e 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -35,8 +35,8 @@ #include "llpanel.h" #include "llscrollcontainer.h" +#include "lltextbox.h" -class LLTextBox; /** * LLFlatListView represents a flat list ui control that operates on items in a form of LLPanel's. @@ -108,6 +108,9 @@ public: /** padding between items */ Optional<U32> item_pad; + /** textbox with info message when list is empty*/ + Optional<LLTextBox::Params> no_items_text; + Params(); }; @@ -368,8 +371,6 @@ protected: LLRect getLastSelectedItemRect(); - LLRect getSelectedItemsRect(); - void ensureSelectedVisible(); private: diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 845203b420..b6d73cda3c 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -233,6 +233,7 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) mAutoFocus(TRUE), // automatically take focus when opened mCanDock(false), mDocked(false), + mTornOff(false), mHasBeenDraggedWhileMinimized(FALSE), mPreviousMinimizedBottom(0), mPreviousMinimizedLeft(0) @@ -878,9 +879,11 @@ void LLFloater::setSnappedTo(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()); + } } } @@ -1065,10 +1068,6 @@ void LLFloater::setMinimized(BOOL minimize) reshape( mExpandedRect.getWidth(), mExpandedRect.getHeight(), TRUE ); } - // don't show the help button while minimized - it's - // not very useful when minimized and uses up space - mButtonsEnabled[BUTTON_HELP] = !minimize; - applyTitle (); make_ui_sound("UISndWindowClose"); @@ -1361,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); } @@ -1458,6 +1458,7 @@ void LLFloater::onClickTearOff(LLFloater* self) } self->setTornOff(false); } + self->updateButtons(); } // static @@ -1564,43 +1565,34 @@ void LLFloater::draw() // draw background if( isBackgroundVisible() ) { + drawShadow(this); + S32 left = LLPANEL_BORDER_WIDTH; S32 top = getRect().getHeight() - LLPANEL_BORDER_WIDTH; S32 right = getRect().getWidth() - LLPANEL_BORDER_WIDTH; S32 bottom = LLPANEL_BORDER_WIDTH; - 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 % alpha, - llround(shadow_offset)); - LLUIImage* image = NULL; LLColor4 color; + LLColor4 overlay_color; if (isBackgroundOpaque()) { // NOTE: image may not be set image = getBackgroundImage(); color = getBackgroundColor(); + overlay_color = getBackgroundImageOverlay(); } else { image = getTransparentImage(); color = getTransparentColor(); + overlay_color = getTransparentImageOverlay(); } if (image) { // We're using images for this floater's backgrounds - image->draw(getLocalRect(), UI_VERTEX_COLOR % alpha); + image->draw(getLocalRect(), overlay_color % alpha); } else { @@ -1649,24 +1641,8 @@ void LLFloater::draw() } else { - //FIXME: get rid of this hack - // draw children - LLView* focused_child = dynamic_cast<LLView*>(gFocusMgr.getKeyboardFocus()); - BOOL focused_child_visible = FALSE; - if (focused_child && focused_child->getParent() == this) - { - focused_child_visible = focused_child->getVisible(); - focused_child->setVisible(FALSE); - } - // don't call LLPanel::draw() since we've implemented custom background rendering LLView::draw(); - - if (focused_child_visible) - { - focused_child->setVisible(TRUE); - } - drawChild(focused_child); } // update tearoff button for torn off floaters @@ -1681,6 +1657,29 @@ void LLFloater::draw() } } +void LLFloater::drawShadow(LLPanel* panel) +{ + F32 alpha = panel->getDrawContext().mAlpha; + S32 left = LLPANEL_BORDER_WIDTH; + S32 top = panel->getRect().getHeight() - LLPANEL_BORDER_WIDTH; + S32 right = panel->getRect().getWidth() - LLPANEL_BORDER_WIDTH; + S32 bottom = LLPANEL_BORDER_WIDTH; + + static 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 (!panel->isBackgroundOpaque()) + { + shadow_offset *= 0.2f; + shadow_color.mV[VALPHA] *= 0.5f; + } + gl_drop_shadow(left, top, right, bottom, + shadow_color % alpha, + llround(shadow_offset)); +} + void LLFloater::setCanMinimize(BOOL can_minimize) { // if removing minimize/restore button programmatically, @@ -1741,14 +1740,32 @@ void LLFloater::updateButtons() 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++; @@ -1775,7 +1792,7 @@ 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); } @@ -1897,9 +1914,10 @@ static LLDefaultChildRegistry::Register<LLFloaterView> r("floater_view"); LLFloaterView::LLFloaterView (const Params& p) : LLUICtrl (p), + mFocusCycleMode(FALSE), - mSnapOffsetBottom(0) - ,mSnapOffsetRight(0) + mSnapOffsetBottom(0), + mSnapOffsetRight(0) { } @@ -2344,7 +2362,7 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out LLRect::tCoordType screen_width = getSnapRect().getWidth(); LLRect::tCoordType screen_height = getSnapRect().getHeight(); - + // only automatically resize non-minimized, resizable floaters if( floater->isResizable() && !floater->isMinimized() ) { @@ -2369,7 +2387,11 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out new_width = llmax(new_width, min_width); new_height = llmax(new_height, min_height); - floater->reshape( new_width, new_height, TRUE ); + LLRect new_rect; + new_rect.setLeftTopAndSize(view_rect.mLeft,view_rect.mTop,new_width, new_height); + + floater->setShape(new_rect); + if (floater->followsRight()) { floater->translate(old_width - new_width, 0); @@ -2562,6 +2584,8 @@ void LLFloaterView::pushVisibleAll(BOOL visible, const skip_list_t& skip_list) view->pushVisible(visible); } } + + LLFloaterReg::blockShowFloaters(true); } void LLFloaterView::popVisibleAll(const skip_list_t& skip_list) @@ -2579,6 +2603,8 @@ void LLFloaterView::popVisibleAll(const skip_list_t& skip_list) view->popVisible(); } } + + LLFloaterReg::blockShowFloaters(false); } void LLFloater::setInstanceName(const std::string& name) diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index daf558de24..19e8288807 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -222,6 +222,7 @@ public: virtual BOOL handleScrollWheel(S32 x, S32 y, S32 mask); virtual void draw(); + virtual void drawShadow(LLPanel* panel); virtual void onOpen(const LLSD& key) {} virtual void onClose(bool app_quitting) {} @@ -256,7 +257,7 @@ public: bool isDocked() const { return mDocked; } virtual void setDocked(bool docked, bool pop_on_undock = true); - virtual void setTornOff(bool torn_off) {} + virtual void setTornOff(bool torn_off) { mTornOff = torn_off; } // Return a closeable floater, if any, given the current focus. static LLFloater* getClosableFloaterFromFocus(); @@ -301,6 +302,7 @@ protected: const LLRect& getExpandedRect() const { return mExpandedRect; } void setAutoFocus(BOOL focus) { mAutoFocus = focus; } // whether to automatically take focus when opened + BOOL getAutoFocus() const { return mAutoFocus; } LLDragHandle* getDragHandle() const { return mDragHandle; } void destroy() { die(); } // Don't call this directly. You probably want to call closeFloater() @@ -387,6 +389,7 @@ private: bool mCanDock; bool mDocked; + bool mTornOff; static LLMultiFloater* sHostp; static BOOL sQuitting; @@ -467,9 +470,6 @@ public: void setSnapOffsetRight(S32 offset) { mSnapOffsetRight = offset; } private: - S32 mColumn; - S32 mNextLeft; - S32 mNextTop; BOOL mFocusCycleMode; S32 mSnapOffsetBottom; S32 mSnapOffsetRight; diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp index eb67e3a561..5de3934c8a 100644 --- a/indra/llui/llfloaterreg.cpp +++ b/indra/llui/llfloaterreg.cpp @@ -34,6 +34,7 @@ #include "llfloaterreg.h" +//#include "llagent.h" #include "llfloater.h" #include "llmultifloater.h" #include "llfloaterreglistener.h" @@ -45,6 +46,7 @@ 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; +bool LLFloaterReg::sBlockShowFloaters = false; static LLFloaterRegListener sFloaterRegListener; @@ -217,6 +219,8 @@ LLFloaterReg::const_instance_list_t& LLFloaterReg::getFloaterList(const std::str //static LLFloater* LLFloaterReg::showInstance(const std::string& name, const LLSD& key, BOOL focus) { + if( sBlockShowFloaters ) + return 0;// LLFloater* instance = getInstance(name, key); if (instance) { diff --git a/indra/llui/llfloaterreg.h b/indra/llui/llfloaterreg.h index 634a235926..8a11d5c3f2 100644 --- a/indra/llui/llfloaterreg.h +++ b/indra/llui/llfloaterreg.h @@ -75,6 +75,7 @@ private: static instance_map_t sInstanceMap; static build_map_t sBuildMap; static std::map<std::string,std::string> sGroupMap; + static bool sBlockShowFloaters; public: // Registration @@ -152,6 +153,8 @@ public: { return dynamic_cast<T*>(showInstance(name, key, focus)); } + + static void blockShowFloaters(bool value) { sBlockShowFloaters = value;} }; diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp index ede32084d0..75342afbe2 100644 --- a/indra/llui/llkeywords.cpp +++ b/indra/llui/llkeywords.cpp @@ -218,6 +218,86 @@ void LLKeywords::addToken(LLKeywordToken::TOKEN_TYPE type, llassert(0); } } +LLKeywords::WStringMapIndex::WStringMapIndex(const WStringMapIndex& other) +{ + if(other.mOwner) + { + copyData(other.mData, other.mLength); + } + else + { + mOwner = false; + mLength = other.mLength; + mData = other.mData; + } +} + +LLKeywords::WStringMapIndex::WStringMapIndex(const LLWString& str) +{ + copyData(str.data(), str.size()); +} + +LLKeywords::WStringMapIndex::WStringMapIndex(const llwchar *start, size_t length): +mData(start), mLength(length), mOwner(false) +{ +} + +LLKeywords::WStringMapIndex::~WStringMapIndex() +{ + if(mOwner) + delete[] mData; +} + +void LLKeywords::WStringMapIndex::copyData(const llwchar *start, size_t length) +{ + llwchar *data = new llwchar[length]; + memcpy((void*)data, (const void*)start, length * sizeof(llwchar)); + + mOwner = true; + mLength = length; + mData = data; +} + +bool LLKeywords::WStringMapIndex::operator<(const LLKeywords::WStringMapIndex &other) const +{ + // NOTE: Since this is only used to organize a std::map, it doesn't matter if it uses correct collate order or not. + // The comparison only needs to strictly order all possible strings, and be stable. + + bool result = false; + const llwchar* self_iter = mData; + const llwchar* self_end = mData + mLength; + const llwchar* other_iter = other.mData; + const llwchar* other_end = other.mData + other.mLength; + + while(true) + { + if(other_iter >= other_end) + { + // We've hit the end of other. + // This covers two cases: other being shorter than self, or the strings being equal. + // In either case, we want to return false. + result = false; + break; + } + else if(self_iter >= self_end) + { + // self is shorter than other. + result = true; + break; + } + else if(*self_iter != *other_iter) + { + // The current character differs. The strings are not equal. + result = *self_iter < *other_iter; + break; + } + + self_iter++; + other_iter++; + } + + return result; +} LLColor3 LLKeywords::readColor( const std::string& s ) { @@ -429,7 +509,7 @@ void LLKeywords::findSegments(std::vector<LLTextSegmentPtr>* seg_list, const LLW S32 seg_len = p - cur; if( seg_len > 0 ) { - LLWString word( cur, 0, seg_len ); + WStringMapIndex word( cur, seg_len ); word_token_map_t::iterator map_iter = mWordTokenMap.find(word); if( map_iter != mWordTokenMap.end() ) { diff --git a/indra/llui/llkeywords.h b/indra/llui/llkeywords.h index 53377869ca..e5b66dfa56 100644 --- a/indra/llui/llkeywords.h +++ b/indra/llui/llkeywords.h @@ -92,8 +92,33 @@ public: const std::string& key, const LLColor3& color, const std::string& tool_tip = LLStringUtil::null); - - typedef std::map<LLWString, LLKeywordToken*> word_token_map_t; + + // This class is here as a performance optimization. + // The word token map used to be defined as std::map<LLWString, LLKeywordToken*>. + // This worked, but caused a performance bottleneck due to memory allocation and string copies + // because it's not possible to search such a map without creating an LLWString. + // Using this class as the map index instead allows us to search using segments of an existing + // text run without copying them first, which greatly reduces overhead in LLKeywords::findSegments(). + class WStringMapIndex + { + public: + // copy constructor + WStringMapIndex(const WStringMapIndex& other); + // constructor from a string (copies the string's data into the new object) + WStringMapIndex(const LLWString& str); + // constructor from pointer and length + // NOTE: does NOT copy data, caller must ensure that the lifetime of the pointer exceeds that of the new object! + WStringMapIndex(const llwchar *start, size_t length); + ~WStringMapIndex(); + bool operator<(const WStringMapIndex &other) const; + private: + void copyData(const llwchar *start, size_t length); + const llwchar *mData; + size_t mLength; + bool mOwner; + }; + + typedef std::map<WStringMapIndex, LLKeywordToken*> word_token_map_t; typedef word_token_map_t::const_iterator keyword_iterator_t; keyword_iterator_t begin() const { return mWordTokenMap.begin(); } keyword_iterator_t end() const { return mWordTokenMap.end(); } diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp index 1aaba88c49..4512091371 100644 --- a/indra/llui/lllayoutstack.cpp +++ b/indra/llui/lllayoutstack.cpp @@ -49,9 +49,11 @@ static LLDefaultChildRegistry::Register<LLLayoutStack> register_layout_stack("la // struct LLLayoutStack::LayoutPanel { - LayoutPanel(LLPanel* panelp, ELayoutOrientation orientation, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize) : mPanel(panelp), + LayoutPanel(LLPanel* panelp, ELayoutOrientation orientation, S32 min_width, S32 min_height, S32 max_width, S32 max_height, BOOL auto_resize, BOOL user_resize) : mPanel(panelp), mMinWidth(min_width), mMinHeight(min_height), + mMaxWidth(max_width), + mMaxHeight(max_height), mAutoResize(auto_resize), mUserResize(user_resize), mOrientation(orientation), @@ -112,6 +114,11 @@ struct LLLayoutStack::LayoutPanel LLPanel* mPanel; S32 mMinWidth; S32 mMinHeight; + + // mMaxWidth & mMaxHeight are added to make configurable max width of the nearby chat bar. EXT-5589 + // they are not processed by LLLayoutStack but they can be if necessary + S32 mMaxWidth; + S32 mMaxHeight; BOOL mAutoResize; BOOL mUserResize; BOOL mCollapsed; @@ -261,10 +268,14 @@ LLView* LLLayoutStack::fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr o { const S32 DEFAULT_MIN_WIDTH = 0; const S32 DEFAULT_MIN_HEIGHT = 0; + const S32 DEFAULT_MAX_WIDTH = S32_MAX; + const S32 DEFAULT_MAX_HEIGHT = S32_MAX; const BOOL DEFAULT_AUTO_RESIZE = TRUE; S32 min_width = DEFAULT_MIN_WIDTH; S32 min_height = DEFAULT_MIN_HEIGHT; + S32 max_width = DEFAULT_MAX_WIDTH; + S32 max_height = DEFAULT_MAX_HEIGHT; BOOL auto_resize = DEFAULT_AUTO_RESIZE; LLXMLNodePtr output_child; @@ -281,6 +292,10 @@ LLView* LLLayoutStack::fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr o DEFAULT_MIN_WIDTH, output_child); get_attribute_s32_and_write(child_node, "min_height", &min_height, DEFAULT_MIN_HEIGHT, output_child); + get_attribute_s32_and_write(child_node, "max_width", &max_width, + DEFAULT_MAX_WIDTH, output_child); + get_attribute_s32_and_write(child_node, "max_height", &max_height, + DEFAULT_MAX_HEIGHT, output_child); get_attribute_bool_and_write(child_node, "auto_resize", &auto_resize, DEFAULT_AUTO_RESIZE, output_child); @@ -293,7 +308,7 @@ LLView* LLLayoutStack::fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr o if (panelp) { panelp->setFollowsNone(); - layout_stackp->addPanel(panelp, min_width, min_height, auto_resize, user_resize); + layout_stackp->addPanel(panelp, min_width, min_height, max_width, max_height, auto_resize, user_resize); } } else @@ -309,7 +324,7 @@ LLView* LLLayoutStack::fromXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr o if (new_child) { // put child in new embedded panel - layout_stackp->addPanel(panelp, min_width, min_height, auto_resize, user_resize); + layout_stackp->addPanel(panelp, min_width, min_height, max_width, max_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); @@ -359,14 +374,14 @@ S32 LLLayoutStack::getDefaultWidth(S32 cur_width) return cur_width; } -void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, BOOL auto_resize, BOOL user_resize, EAnimate animate, S32 index) +void LLLayoutStack::addPanel(LLPanel* panel, S32 min_width, S32 min_height, S32 max_width, S32 max_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); + LayoutPanel* embedded_panel = new LayoutPanel(panel, mOrientation, min_width, min_height, max_width, max_height, auto_resize, user_resize); mPanels.insert(mPanels.begin() + llclamp(index, 0, (S32)mPanels.size()), embedded_panel); @@ -437,6 +452,19 @@ bool LLLayoutStack::getPanelMinSize(const std::string& panel_name, S32* min_widt return NULL != panel; } +bool LLLayoutStack::getPanelMaxSize(const std::string& panel_name, S32* max_widthp, S32* max_heightp) +{ + LayoutPanel* panel = findEmbeddedPanelByName(panel_name); + + if (panel) + { + if (max_widthp) *max_widthp = panel->mMaxWidth; + if (max_heightp) *max_heightp = panel->mMaxHeight; + } + + return NULL != panel; +} + static LLFastTimer::DeclareTimer FTM_UPDATE_LAYOUT("Update LayoutStacks"); void LLLayoutStack::updateLayout(BOOL force_resize) { @@ -816,7 +844,10 @@ void LLLayoutStack::calcMinExtents() //static void LLLayoutStack::updateClass() { - for (LLLayoutStack::instance_iter it = beginInstances(); it != endInstances(); ++it) + LLInstanceTrackerScopedGuard guard; + for (LLLayoutStack::instance_iter it = guard.beginInstances(); + it != guard.endInstances(); + ++it) { it->updateLayout(); } diff --git a/indra/llui/lllayoutstack.h b/indra/llui/lllayoutstack.h index cde383b047..e454454fe2 100644 --- a/indra/llui/lllayoutstack.h +++ b/indra/llui/lllayoutstack.h @@ -74,7 +74,7 @@ public: 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 addPanel(LLPanel* panel, S32 min_width, S32 min_height, S32 max_width, S32 max_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(); } @@ -89,8 +89,21 @@ public: * @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); + + /** + * Gets maximal 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 getPanelMaxSize(const std::string& panel_name, S32* max_width, S32* max_height); 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: diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 8a21155cc3..483a394bbd 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -55,6 +55,7 @@ #include "llui.h" #include "lluictrlfactory.h" #include "llclipboard.h" +#include "llmenugl.h" // // Imported globals @@ -70,6 +71,8 @@ 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 std::string PASSWORD_ASTERISK( "\xE2\x80\xA2" ); // U+2022 BULLET + static LLDefaultChildRegistry::Register<LLLineEditor> r1("line_editor"); // Compiler optimization, generate extern template @@ -80,19 +83,6 @@ template class LLLineEditor* LLView::getChild<class LLLineEditor>( // Member functions // -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"), @@ -162,7 +152,8 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) mTentativeFgColor(p.text_tentative_color()), mHighlightColor(p.highlight_color()), mPreeditBgColor(p.preedit_bg_color()), - mGLFont(p.font) + mGLFont(p.font), + mContextMenuHandle() { llassert( mMaxLengthBytes > 0 ); @@ -189,6 +180,12 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) setCursor(mText.length()); setPrevalidate(p.prevalidate_callback()); + + LLContextMenu* menu = LLUICtrlFactory::instance().createFromFile<LLContextMenu> + ("menu_text_editor.xml", + LLMenuGL::sMenuContainer, + LLMenuHolderGL::child_registry_t::instance()); + setContextMenu(menu); } LLLineEditor::~LLLineEditor() @@ -401,7 +398,7 @@ void LLLineEditor::setCursorAtLocalPos( S32 local_mouse_x ) { for (S32 i = 0; i < mText.length(); i++) { - asterix_text += '*'; + asterix_text += utf8str_to_wstring(PASSWORD_ASTERISK); } wtext = asterix_text.c_str(); } @@ -420,12 +417,16 @@ void LLLineEditor::setCursor( S32 pos ) S32 old_cursor_pos = getCursor(); mCursorPos = llclamp( pos, 0, mText.length()); + // position of end of next character after cursor S32 pixels_after_scroll = findPixelNearestPos(); if( pixels_after_scroll > mTextRightEdge ) { S32 width_chars_to_left = mGLFont->getWidth(mText.getWString().c_str(), 0, mScrollHPos); S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mTextRightEdge - mTextLeftEdge + width_chars_to_left))); - S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), getCursor()); + // character immediately to left of cursor should be last one visible (SCROLL_INCREMENT_ADD will scroll in more characters) + // or first character if cursor is at beginning + S32 new_last_visible_char = llmax(0, getCursor() - 1); + S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), new_last_visible_char); if (old_cursor_pos == last_visible_char) { mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD)); @@ -661,6 +662,16 @@ BOOL LLLineEditor::handleMiddleMouseDown(S32 x, S32 y, MASK mask) return TRUE; } +BOOL LLLineEditor::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + setFocus(TRUE); + if (!LLUICtrl::handleRightMouseDown(x, y, mask)) + { + showContextMenu(x, y); + } + return TRUE; +} + BOOL LLLineEditor::handleHover(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; @@ -1583,7 +1594,6 @@ void LLLineEditor::draw() F32 alpha = getDrawContext().mAlpha; S32 text_len = mText.length(); static LLUICachedControl<S32> lineeditor_cursor_thickness ("UILineEditorCursorThickness", 0); - static LLUICachedControl<S32> lineeditor_v_pad ("UILineEditorVPad", 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); @@ -1600,7 +1610,7 @@ void LLLineEditor::draw() std::string text; for (S32 i = 0; i < mText.length(); i++) { - text += '*'; + text += PASSWORD_ASTERISK; } mText = text; } @@ -1609,6 +1619,8 @@ void LLLineEditor::draw() LLRect background( 0, getRect().getHeight(), getRect().getWidth(), 0 ); background.stretch( -mBorderThickness ); + S32 lineeditor_v_pad = llround((background.getHeight() - mGLFont->getLineHeight())/2); + drawBackground(); // draw text @@ -1959,51 +1971,12 @@ void LLLineEditor::setRect(const LLRect& rect) } } -void LLLineEditor::setPrevalidate(LLLinePrevalidateFunc func) +void LLLineEditor::setPrevalidate(LLTextValidate::validate_func_t func) { mPrevalidateFunc = func; updateAllowingLanguageInput(); } -// Limits what characters can be used to [1234567890.-] with [-] only valid in the first position. -// Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for -// the simple reasons that intermediate states may be invalid even if the final result is valid. -// -// static -BOOL LLLineEditor::prevalidateFloat(const LLWString &str) -{ - LLLocale locale(LLLocale::USER_LOCALE); - - BOOL success = TRUE; - LLWString trimmed = str; - LLWStringUtil::trim(trimmed); - S32 len = trimmed.length(); - if( 0 < len ) - { - // May be a comma or period, depending on the locale - llwchar decimal_point = (llwchar)LLResMgr::getInstance()->getDecimalPoint(); - - S32 i = 0; - - // First character can be a negative sign - if( '-' == trimmed[0] ) - { - i++; - } - - for( ; i < len; i++ ) - { - if( (decimal_point != trimmed[i] ) && !LLStringOps::isDigit( trimmed[i] ) ) - { - success = FALSE; - break; - } - } - } - - return success; -} - // static BOOL LLLineEditor::postvalidateFloat(const std::string &str) { @@ -2063,223 +2036,6 @@ BOOL LLLineEditor::postvalidateFloat(const std::string &str) return success; } -// Limits what characters can be used to [1234567890-] with [-] only valid in the first position. -// Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for -// the simple reasons that intermediate states may be invalid even if the final result is valid. -// -// static -BOOL LLLineEditor::prevalidateInt(const LLWString &str) -{ - LLLocale locale(LLLocale::USER_LOCALE); - - BOOL success = TRUE; - LLWString trimmed = str; - LLWStringUtil::trim(trimmed); - S32 len = trimmed.length(); - if( 0 < len ) - { - S32 i = 0; - - // First character can be a negative sign - if( '-' == trimmed[0] ) - { - i++; - } - - for( ; i < len; i++ ) - { - if( !LLStringOps::isDigit( trimmed[i] ) ) - { - success = FALSE; - break; - } - } - } - - return success; -} - -// static -BOOL LLLineEditor::prevalidatePositiveS32(const LLWString &str) -{ - LLLocale locale(LLLocale::USER_LOCALE); - - LLWString trimmed = str; - LLWStringUtil::trim(trimmed); - S32 len = trimmed.length(); - BOOL success = TRUE; - if(0 < len) - { - if(('-' == trimmed[0]) || ('0' == trimmed[0])) - { - success = FALSE; - } - S32 i = 0; - while(success && (i < len)) - { - if(!LLStringOps::isDigit(trimmed[i++])) - { - success = FALSE; - } - } - } - if (success) - { - S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10); - if (val <= 0) - { - success = FALSE; - } - } - return success; -} - -BOOL LLLineEditor::prevalidateNonNegativeS32(const LLWString &str) -{ - LLLocale locale(LLLocale::USER_LOCALE); - - LLWString trimmed = str; - LLWStringUtil::trim(trimmed); - S32 len = trimmed.length(); - BOOL success = TRUE; - if(0 < len) - { - if('-' == trimmed[0]) - { - success = FALSE; - } - S32 i = 0; - while(success && (i < len)) - { - if(!LLStringOps::isDigit(trimmed[i++])) - { - success = FALSE; - } - } - } - if (success) - { - S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10); - if (val < 0) - { - success = FALSE; - } - } - return success; -} - -BOOL LLLineEditor::prevalidateAlphaNum(const LLWString &str) -{ - LLLocale locale(LLLocale::USER_LOCALE); - - BOOL rv = TRUE; - S32 len = str.length(); - if(len == 0) return rv; - while(len--) - { - if( !LLStringOps::isAlnum((char)str[len]) ) - { - rv = FALSE; - break; - } - } - return rv; -} - -// static -BOOL LLLineEditor::prevalidateAlphaNumSpace(const LLWString &str) -{ - LLLocale locale(LLLocale::USER_LOCALE); - - BOOL rv = TRUE; - S32 len = str.length(); - if(len == 0) return rv; - while(len--) - { - if(!(LLStringOps::isAlnum((char)str[len]) || (' ' == str[len]))) - { - rv = FALSE; - break; - } - } - 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::prevalidateASCIIPrintableNoPipe(const LLWString &str) -{ - BOOL rv = TRUE; - S32 len = str.length(); - if(len == 0) return rv; - while(len--) - { - llwchar wc = str[len]; - if (wc < 0x20 - || wc > 0x7f - || wc == '|') - { - rv = FALSE; - break; - } - if(!(wc == ' ' - || LLStringOps::isAlnum((char)wc) - || LLStringOps::isPunct((char)wc) ) ) - { - rv = FALSE; - break; - } - } - return rv; -} - - -// Used for avatar names -// static -BOOL LLLineEditor::prevalidateASCIIPrintableNoSpace(const LLWString &str) -{ - BOOL rv = TRUE; - S32 len = str.length(); - if(len == 0) return rv; - while(len--) - { - llwchar wc = str[len]; - if (wc < 0x20 - || wc > 0x7f - || LLStringOps::isSpace(wc)) - { - rv = FALSE; - break; - } - if( !(LLStringOps::isAlnum((char)str[len]) || - LLStringOps::isPunct((char)str[len]) ) ) - { - rv = FALSE; - break; - } - } - return rv; -} - - -// static -BOOL LLLineEditor::prevalidateASCII(const LLWString &str) -{ - BOOL rv = TRUE; - S32 len = str.length(); - while(len--) - { - if (str[len] < 0x20 || str[len] > 0x7f) - { - rv = FALSE; - break; - } - } - return rv; -} - void LLLineEditor::onMouseCaptureLost() { endSelection(); @@ -2557,3 +2313,25 @@ LLWString LLLineEditor::getConvertedText() const } return text; } + +void LLLineEditor::showContextMenu(S32 x, S32 y) +{ + LLContextMenu* menu = static_cast<LLContextMenu*>(mContextMenuHandle.get()); + + if (menu) + { + gEditMenuHandler = this; + + S32 screen_x, screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + menu->show(screen_x, screen_y); + } +} + +void LLLineEditor::setContextMenu(LLContextMenu* new_context_menu) +{ + if (new_context_menu) + mContextMenuHandle = new_context_menu->getHandle(); + else + mContextMenuHandle.markDead(); +} diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index 49e9539b16..b62138426b 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -51,26 +51,18 @@ #include "llviewborder.h" #include "llpreeditor.h" -#include <boost/function.hpp> +#include "lltextvalidate.h" class LLFontGL; class LLLineEditorRollback; class LLButton; - -typedef boost::function<BOOL (const LLWString &wstr)> LLLinePrevalidateFunc; +class LLContextMenu; class LLLineEditor : public LLUICtrl, public LLEditMenuHandler, protected LLPreeditor { public: - struct PrevalidateNamedFuncs - : public LLInitParam::TypeValuesHelper<LLLinePrevalidateFunc, PrevalidateNamedFuncs> - - { - static void declareValues(); - }; - typedef boost::function<void (LLLineEditor* caller)> keystroke_callback_t; struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> @@ -80,7 +72,7 @@ public: Optional<keystroke_callback_t> keystroke_callback; - Optional<LLLinePrevalidateFunc, PrevalidateNamedFuncs> prevalidate_callback; + Optional<LLTextValidate::validate_func_t, LLTextValidate::ValidateTextNamedFuncs> prevalidate_callback; Optional<LLViewBorder::Params> border; @@ -113,6 +105,7 @@ protected: LLLineEditor(const Params&); friend class LLUICtrlFactory; friend class LLFloaterEditUI; + void showContextMenu(S32 x, S32 y); public: virtual ~LLLineEditor(); @@ -122,6 +115,7 @@ public: /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleDoubleClick(S32 x,S32 y,MASK mask); /*virtual*/ BOOL handleMiddleMouseDown(S32 x,S32 y,MASK mask); + /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask ); /*virtual*/ BOOL handleUnicodeCharHere(llwchar uni_char); /*virtual*/ void onMouseCaptureLost(); @@ -204,6 +198,8 @@ public: const LLColor4& getReadOnlyFgColor() const { return mReadOnlyFgColor.get(); } const LLColor4& getTentativeFgColor() const { return mTentativeFgColor.get(); } + const LLFontGL* getFont() const { return mGLFont; } + void setIgnoreArrowKeys(BOOL b) { mIgnoreArrowKeys = b; } void setIgnoreTab(BOOL b) { mIgnoreTab = b; } void setPassDelete(BOOL b) { mPassDelete = b; } @@ -231,17 +227,7 @@ public: void setTextPadding(S32 left, S32 right); // Prevalidation controls which keystrokes can affect the editor - 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 prevalidateASCIIPrintableNoPipe(const LLWString &str); - static BOOL prevalidateASCIIPrintableNoSpace(const LLWString &str); - static BOOL prevalidateASCII(const LLWString &str); - + void setPrevalidate( LLTextValidate::validate_func_t func ); static BOOL postvalidateFloat(const std::string &str); // line history support: @@ -249,7 +235,9 @@ public: void updateHistory(); // stores current line in history void setReplaceNewlinesWithSpaces(BOOL replace); - + + void setContextMenu(LLContextMenu* new_context_menu); + private: // private helper methods @@ -319,7 +307,7 @@ protected: S32 mLastSelectionStart; S32 mLastSelectionEnd; - LLLinePrevalidateFunc mPrevalidateFunc; + LLTextValidate::validate_func_t mPrevalidateFunc; LLFrameTimer mKeystrokeTimer; LLTimer mTripleClickTimer; @@ -348,6 +336,8 @@ protected: std::vector<S32> mPreeditPositions; LLPreeditor::standouts_t mPreeditStandouts; + LLHandle<LLView> mContextMenuHandle; + private: // Instances that by default point to the statics but can be overidden in XML. LLPointer<LLUIImage> mBgImage; diff --git a/indra/llui/llmenubutton.h b/indra/llui/llmenubutton.h index 02eb9d3806..d0e99d9f40 100644 --- a/indra/llui/llmenubutton.h +++ b/indra/llui/llmenubutton.h @@ -55,6 +55,7 @@ public: /*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; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 60ef01074b..0d56c5ed31 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,6 +46,7 @@ #include "llmenugl.h" +#include "llgl.h" #include "llmath.h" #include "llrender.h" #include "llfocusmgr.h" @@ -98,7 +99,7 @@ const S32 TEAROFF_SEPARATOR_HEIGHT_PIXELS = 10; const S32 MENU_ITEM_PADDING = 4; const std::string BOOLEAN_TRUE_PREFIX( "\xE2\x9C\x94" ); // U+2714 HEAVY CHECK MARK -const std::string BRANCH_SUFFIX( ">" ); +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"); @@ -477,6 +478,7 @@ void LLMenuItemGL::draw( void ) if (dynamic_cast<LLMenuItemCallGL*>(this)) debug_count++; gGL.color4fv( mHighlightBackground.get().mV ); + gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); } @@ -655,11 +657,38 @@ LLMenuItemVerticalSeparatorGL::LLMenuItemVerticalSeparatorGL( void ) // Class LLMenuItemTearOffGL //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ LLMenuItemTearOffGL::LLMenuItemTearOffGL(const LLMenuItemTearOffGL::Params& p) -: LLMenuItemGL(p), - mParentHandle(p.parent_floater_handle) +: LLMenuItemGL(p) { } +// Returns the first floater ancestor if there is one +LLFloater* LLMenuItemTearOffGL::getParentFloater() +{ + LLView* parent_view = getMenu(); + + while (parent_view) + { + if (dynamic_cast<LLFloater*>(parent_view)) + { + return dynamic_cast<LLFloater*>(parent_view); + } + + bool parent_is_menu = dynamic_cast<LLMenuGL*>(parent_view) && !dynamic_cast<LLMenuBarGL*>(parent_view); + + if (parent_is_menu) + { + // use menu parent + parent_view = dynamic_cast<LLMenuGL*>(parent_view)->getParentMenuItem(); + } + else + { + // just use regular view parent + parent_view = parent_view->getParent(); + } + } + + return NULL; +} void LLMenuItemTearOffGL::onCommit() { @@ -678,7 +707,7 @@ void LLMenuItemTearOffGL::onCommit() getMenu()->needsArrange(); - LLFloater* parent_floater = mParentHandle.get(); + LLFloater* parent_floater = getParentFloater(); LLFloater* tear_off_menu = LLTearOffMenu::create(getMenu()); if (tear_off_menu) @@ -1143,37 +1172,41 @@ BOOL LLMenuItemBranchGL::handleKeyHere( KEY key, MASK mask ) if (!branch) return LLMenuItemGL::handleKeyHere(key, mask); - if (getMenu()->getVisible() && branch->getVisible() && key == KEY_LEFT) + // 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())) { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(TRUE); - - BOOL handled = branch->clearHoverItem(); - if (branch->getTornOff()) - { - ((LLFloater*)branch->getParent())->setFocus(FALSE); - } - if (handled && getMenu()->getTornOff()) + 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 && !branch->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 = branch->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); } @@ -1208,22 +1241,41 @@ void LLMenuItemBranchGL::openMenu() branch->arrange(); - LLRect rect = branch->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, branch->getParent()); - rect.setLeftTopAndSize( left, top, - rect.getWidth(), rect.getHeight() ); + branch_rect.setLeftTopAndSize( left, top, + branch_rect.getWidth(), branch_rect.getHeight() ); if (branch->getCanTearOff()) { - rect.translate(0, TEAROFF_SEPARATOR_HEIGHT_PIXELS); + branch_rect.translate(0, TEAROFF_SEPARATOR_HEIGHT_PIXELS); + } + branch->setRect( branch_rect ); + + // if branch extends outside of menu region change the direction it opens in + S32 x, y; + S32 delta_x = 0; + S32 delta_y = 0; + branch->localPointToOtherView( 0, 0, &x, &y, branch->getParent() ); + if( y < menu_region_rect.mBottom ) + { + // open upwards if menu extends past bottom + // adjust by the height of the menu item branch since it is a submenu + delta_y = branch_rect.getHeight() - getRect().getHeight(); } - branch->setRect( rect ); - branch->translateIntoRectWithExclusion( menu_region_rect, getMenu()->getRect(), FALSE ); + + if( x + branch_rect.getWidth() > menu_region_rect.mRight ) + { + // move sub-menu over to left side + delta_x = llmax(-x, ( -(branch_rect.getWidth() + getRect().getWidth()))); + } + branch->translate( delta_x, delta_y ); + branch->setVisible( TRUE ); branch->getParent()->sendChildToFront(branch); @@ -1412,7 +1464,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) { @@ -1626,6 +1678,7 @@ LLMenuGL::LLMenuGL(const LLMenuGL::Params& p) mBackgroundColor( p.bg_color() ), mBgVisible( p.bg_visible ), mDropShadowed( p.drop_shadow ), + mHasSelection(false), mHorizontalLayout( p.horizontal_layout ), mScrollable(mHorizontalLayout ? FALSE : p.scrollable), // Scrolling is supported only for vertical layout mMaxScrollableItems(p.max_scrollable_items), @@ -1645,7 +1698,6 @@ LLMenuGL::LLMenuGL(const LLMenuGL::Params& p) mSpilloverMenu(NULL), mJumpKey(p.jump_key), mCreateJumpKeys(p.create_jump_keys), - mParentFloaterHandle(p.parent_floater), mNeedsArrange(FALSE), mShortcutPad(p.shortcut_pad) { @@ -1673,7 +1725,7 @@ LLMenuGL::LLMenuGL(const LLMenuGL::Params& p) void LLMenuGL::initFromParams(const LLMenuGL::Params& p) { LLUICtrl::initFromParams(p); - setCanTearOff(p.can_tear_off, p.parent_floater); + setCanTearOff(p.can_tear_off); } // Destroys the object @@ -1685,12 +1737,11 @@ LLMenuGL::~LLMenuGL( void ) mJumpKeys.clear(); } -void LLMenuGL::setCanTearOff(BOOL tear_off, LLHandle<LLFloater> parent_floater_handle ) +void LLMenuGL::setCanTearOff(BOOL tear_off) { if (tear_off && mTearOffItem == NULL) { LLMenuItemTearOffGL::Params p; - p.parent_floater_handle = parent_floater_handle; mTearOffItem = LLUICtrlFactory::create<LLMenuItemTearOffGL>(p); addChildInBack(mTearOffItem); } @@ -1850,17 +1901,21 @@ void LLMenuGL::scrollItemsDown() item_list_t::iterator next_item_iter; - for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++) + if (cur_item_iter != mItems.end()) { - if( (*next_item_iter)->getVisible()) + for (next_item_iter = ++cur_item_iter; next_item_iter != mItems.end(); next_item_iter++) { - break; + if( (*next_item_iter)->getVisible()) + { + break; + } + } + + if (next_item_iter != mItems.end() && + (*next_item_iter)->getVisible()) + { + mFirstVisibleItem = *next_item_iter; } - } - - if ((*next_item_iter)->getVisible()) - { - mFirstVisibleItem = *next_item_iter; } mNeedsArrange = TRUE; @@ -2203,7 +2258,6 @@ void LLMenuGL::createSpilloverBranch() 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); @@ -2784,7 +2838,7 @@ BOOL LLMenuGL::handleHover( S32 x, S32 y, MASK mask ) ((LLMenuItemGL*)viewp)->setHighlight(TRUE); LLMenuGL::setKeyboardMode(FALSE); } - mHasSelection = TRUE; + mHasSelection = true; } } } @@ -2817,6 +2871,7 @@ BOOL LLMenuGL::handleScrollWheel( S32 x, S32 y, S32 clicks ) return TRUE; } + void LLMenuGL::draw( void ) { if (mNeedsArrange) @@ -2862,7 +2917,7 @@ void LLMenuGL::setVisible(BOOL visible) } else { - mHasSelection = FALSE; + mHasSelection = true; mFadeTimer.stop(); } @@ -2940,7 +2995,7 @@ void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) LLUI::getMousePositionLocal(menu->getParent(), &mouse_x, &mouse_y); LLMenuHolderGL::sContextMenuSpawnPos.set(mouse_x,mouse_y); - const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect(); + const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getRect(); const S32 HPAD = 2; LLRect rect = menu->getRect(); @@ -3910,7 +3965,6 @@ BOOL LLContextMenu::appendContextSubMenu(LLContextMenu *menu) item = LLUICtrlFactory::create<LLContextMenuBranch>(p); LLMenuGL::sMenuContainer->addChild(item->getBranch()); - item->setFont( LLFontGL::getFontSansSerif() ); return append( item ); } diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 61e06f9e5f..39d1986461 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -355,7 +355,6 @@ class LLMenuGL 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, @@ -430,7 +429,7 @@ public: 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>()); + void setCanTearOff(BOOL tear_off); // add a separator to this menu virtual BOOL addSeparator(); @@ -546,14 +545,13 @@ private: LLHandle<LLView> mParentMenuItem; LLUIString mLabel; BOOL mDropShadowed; // Whether to drop shadow - BOOL mHasSelection; + 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; @@ -814,7 +812,6 @@ class LLMenuItemTearOffGL : public LLMenuItemGL public: struct Params : public LLInitParam::Block<Params, LLMenuItemGL::Params> { - Optional<LLHandle<LLFloater> > parent_floater_handle; Params() { name = "tear off"; @@ -823,13 +820,12 @@ public: }; LLMenuItemTearOffGL( const Params& ); - + virtual void onCommit(void); virtual void draw(void); virtual U32 getNominalHeight() const; -private: - LLHandle<LLFloater> mParentHandle; + LLFloater* getParentFloater(); }; diff --git a/indra/llui/llmultifloater.cpp b/indra/llui/llmultifloater.cpp index 78738c826d..33d47a3f0e 100644 --- a/indra/llui/llmultifloater.cpp +++ b/indra/llui/llmultifloater.cpp @@ -92,14 +92,6 @@ void LLMultiFloater::draw() } 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(); } } diff --git a/indra/llui/llmultislider.cpp b/indra/llui/llmultislider.cpp index aea7c5f87c..27a727fdf5 100644 --- a/indra/llui/llmultislider.cpp +++ b/indra/llui/llmultislider.cpp @@ -490,7 +490,7 @@ void LLMultiSlider::draw() F32 opacity = getEnabled() ? 1.f : 0.3f; // Track - LLUIImagePtr thumb_imagep = LLUI::getUIImage("rounded_square.tga"); + LLUIImagePtr thumb_imagep = LLUI::getUIImage("Rounded_Square"); static LLUICachedControl<S32> multi_track_height ("UIMultiTrackHeight", 0); S32 height_offset = (getRect().getHeight() - multi_track_height) / 2; diff --git a/indra/llui/llmultisliderctrl.cpp b/indra/llui/llmultisliderctrl.cpp index f4434a0f78..cc26c00a3e 100644 --- a/indra/llui/llmultisliderctrl.cpp +++ b/indra/llui/llmultisliderctrl.cpp @@ -138,7 +138,7 @@ LLMultiSliderCtrl::LLMultiSliderCtrl(const LLMultiSliderCtrl::Params& p) params.font(p.font); params.max_length_bytes(MAX_STRING_LENGTH); params.commit_callback.function(LLMultiSliderCtrl::onEditorCommit); - params.prevalidate_callback(&LLLineEditor::prevalidateFloat); + params.prevalidate_callback(&LLTextValidate::validateFloat); params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); mEditor = LLUICtrlFactory::create<LLLineEditor> (params); mEditor->setFocusReceivedCallback( boost::bind(LLMultiSliderCtrl::onEditorGainFocus, _1, this) ); @@ -330,9 +330,14 @@ void LLMultiSliderCtrl::updateText() // static void LLMultiSliderCtrl::onEditorCommit( LLUICtrl* ctrl, const LLSD& userdata) { - LLMultiSliderCtrl* self = dynamic_cast<LLMultiSliderCtrl*>(ctrl->getParent()); + llassert(ctrl); if (!ctrl) return; + + LLMultiSliderCtrl* self = dynamic_cast<LLMultiSliderCtrl*>(ctrl->getParent()); + llassert(self); + if (!self) // cast failed - wrong type! :O + return; BOOL success = FALSE; F32 val = self->mCurValue; diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index 86989012ee..5816cef6af 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -283,6 +283,7 @@ LLNotificationForm::LLNotificationForm(const std::string& name, const LLXMLNodeP } LLNotificationForm::LLNotificationForm(const LLSD& sd) + : mIgnore(IGNORE_NO) { if (sd.isArray()) { @@ -384,7 +385,8 @@ LLNotificationTemplate::LLNotificationTemplate() : mExpireSeconds(0), mExpireOption(-1), mURLOption(-1), - mURLOpenExternally(-1), + mURLOpenExternally(-1), + mPersist(false), mUnique(false), mPriority(NOTIFICATION_PRIORITY_NORMAL) { @@ -1058,6 +1060,7 @@ LLNotificationChannelPtr LLNotifications::getChannel(const std::string& channelN if(p == mChannels.end()) { llerrs << "Did not find channel named " << channelName << llendl; + return LLNotificationChannelPtr(); } return p->second; } diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index aeb4cebf1b..8d993b71d7 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -371,7 +371,7 @@ private: // this is just for making it easy to look things up in a set organized by UUID -- DON'T USE IT // for anything real! - LLNotification(LLUUID uuid) : mId(uuid) {} + LLNotification(LLUUID uuid) : mId(uuid), mCancelled(false), mRespondedTo(false), mIgnored(false), mPriority(NOTIFICATION_PRIORITY_UNSPECIFIED), mTemporaryResponder(false) {} void cancel(); @@ -621,7 +621,7 @@ namespace LLNotificationComparators struct orderBy { typedef boost::function<T (LLNotificationPtr)> field_t; - orderBy(field_t field, EDirection = ORDER_INCREASING) : mField(field) {} + orderBy(field_t field, EDirection direction = ORDER_INCREASING) : mField(field), mDirection(direction) {} bool operator()(LLNotificationPtr lhs, LLNotificationPtr rhs) { if (mDirection == ORDER_DECREASING) diff --git a/indra/llui/llnotificationsutil.cpp b/indra/llui/llnotificationsutil.cpp index f343d27cb4..54bdb4bd66 100644 --- a/indra/llui/llnotificationsutil.cpp +++ b/indra/llui/llnotificationsutil.cpp @@ -94,3 +94,8 @@ void LLNotificationsUtil::cancel(LLNotificationPtr pNotif) { LLNotifications::instance().cancel(pNotif); } + +LLNotificationPtr LLNotificationsUtil::find(LLUUID uuid) +{ + return LLNotifications::instance().find(uuid); +} diff --git a/indra/llui/llnotificationsutil.h b/indra/llui/llnotificationsutil.h index d552fa915b..338204924a 100644 --- a/indra/llui/llnotificationsutil.h +++ b/indra/llui/llnotificationsutil.h @@ -65,6 +65,8 @@ namespace LLNotificationsUtil S32 getSelectedOption(const LLSD& notification, const LLSD& response); void cancel(LLNotificationPtr pNotif); + + LLNotificationPtr find(LLUUID uuid); } #endif diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index db32882438..0cd052eefa 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -43,6 +43,7 @@ #include "llerror.h" #include "lltimer.h" +#include "llaccordionctrltab.h" #include "llbutton.h" #include "llmenugl.h" //#include "llstatusbar.h" @@ -79,6 +80,8 @@ LLPanel::Params::Params() background_opaque("background_opaque", false), bg_opaque_color("bg_opaque_color"), bg_alpha_color("bg_alpha_color"), + bg_opaque_image_overlay("bg_opaque_image_overlay"), + bg_alpha_image_overlay("bg_alpha_image_overlay"), bg_opaque_image("bg_opaque_image"), bg_alpha_image("bg_alpha_image"), min_width("min_width", 100), @@ -102,6 +105,8 @@ LLPanel::LLPanel(const LLPanel::Params& p) mBgOpaque(p.background_opaque), mBgOpaqueColor(p.bg_opaque_color()), mBgAlphaColor(p.bg_alpha_color()), + mBgOpaqueImageOverlay(p.bg_opaque_image_overlay), + mBgAlphaImageOverlay(p.bg_alpha_image_overlay), mBgOpaqueImage(p.bg_opaque_image()), mBgAlphaImage(p.bg_alpha_image()), mDefaultBtn(NULL), @@ -198,7 +203,7 @@ void LLPanel::draw() // opaque, in-front look if (mBgOpaqueImage.notNull()) { - mBgOpaqueImage->draw( local_rect, UI_VERTEX_COLOR % alpha ); + mBgOpaqueImage->draw( local_rect, mBgOpaqueImageOverlay % alpha ); } else { @@ -211,7 +216,7 @@ void LLPanel::draw() // transparent, in-back look if (mBgAlphaImage.notNull()) { - mBgAlphaImage->draw( local_rect, UI_VERTEX_COLOR % alpha ); + mBgAlphaImage->draw( local_rect, mBgAlphaImageOverlay % alpha ); } else { @@ -396,6 +401,12 @@ LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parent, LLXMLNodePtr output_ if (!panelp) { panelp = LLUICtrlFactory::getInstance()->createFactoryPanel(name); + llassert(panelp); + + if (!panelp) + { + return NULL; // :( + } } } @@ -413,7 +424,7 @@ LLView* LLPanel::fromXML(LLXMLNodePtr node, LLView* parent, LLXMLNodePtr output_ panelp->mCommitCallbackRegistrar.popScope(); panelp->mEnableCallbackRegistrar.popScope(); - if (panelp && !panelp->getFactoryMap().empty()) + if (!panelp->getFactoryMap().empty()) { LLUICtrlFactory::instance().popFactoryFunctions(); } @@ -474,6 +485,8 @@ void LLPanel::initFromParams(const LLPanel::Params& p) setTransparentColor(p.bg_alpha_color().get()); mBgOpaqueImage = p.bg_opaque_image(); mBgAlphaImage = p.bg_alpha_image(); + mBgOpaqueImageOverlay = p.bg_opaque_image_overlay; + mBgAlphaImageOverlay = p.bg_alpha_image_overlay; } static LLFastTimer::DeclareTimer FTM_PANEL_SETUP("Panel Setup"); @@ -851,14 +864,26 @@ static LLPanel *childGetVisibleTabWithHelp(LLView *parent) // look through immediate children first for an active tab with help for (child = parent->getFirstChild(); child; child = parent->findNextSibling(child)) { + LLPanel *curTabPanel = NULL; + + // do we have a tab container? LLTabContainer *tab = dynamic_cast<LLTabContainer *>(child); if (tab && tab->getVisible()) { - LLPanel *curTabPanel = tab->getCurrentPanel(); - if (curTabPanel && !curTabPanel->getHelpTopic().empty()) - { - return curTabPanel; - } + curTabPanel = tab->getCurrentPanel(); + } + + // do we have an accordion tab? + LLAccordionCtrlTab* accordion = dynamic_cast<LLAccordionCtrlTab *>(child); + if (accordion && accordion->getDisplayChildren()) + { + curTabPanel = dynamic_cast<LLPanel *>(accordion->getAccordionView()); + } + + // if we found a valid tab, does it have a help topic? + if (curTabPanel && !curTabPanel->getHelpTopic().empty()) + { + return curTabPanel; } } @@ -885,7 +910,45 @@ LLPanel *LLPanel::childGetVisibleTabWithHelp() return ::childGetVisibleTabWithHelp(this); } -void LLPanel::childSetPrevalidate(const std::string& id, BOOL (*func)(const LLWString &) ) +static LLPanel *childGetVisiblePanelWithHelp(LLView *parent) +{ + LLView *child; + + // look through immediate children first for an active panel with help + for (child = parent->getFirstChild(); child; child = parent->findNextSibling(child)) + { + // do we have a panel with a help topic? + LLPanel *panel = dynamic_cast<LLPanel *>(child); + if (panel && panel->getVisible() && !panel->getHelpTopic().empty()) + { + return panel; + } + } + + // then try a bit harder and recurse through all children + for (child = parent->getFirstChild(); child; child = parent->findNextSibling(child)) + { + if (child->getVisible()) + { + LLPanel* panel = ::childGetVisiblePanelWithHelp(child); + if (panel) + { + return panel; + } + } + } + + // couldn't find any active panels with a help topic string + return NULL; +} + +LLPanel *LLPanel::childGetVisiblePanelWithHelp() +{ + // find a visible tab with a help topic (to determine help context) + return ::childGetVisiblePanelWithHelp(this); +} + +void LLPanel::childSetPrevalidate(const std::string& id, bool (*func)(const LLWString &) ) { LLLineEditor* child = findChild<LLLineEditor>(id); if (child) diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index d0986a06d3..03e3dc0c0e 100644 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -77,7 +77,9 @@ public: background_opaque; Optional<LLUIColor> bg_opaque_color, - bg_alpha_color; + bg_alpha_color, + bg_opaque_image_overlay, + bg_alpha_image_overlay; // opaque image is for "panel in foreground" look Optional<LLUIImage*> bg_opaque_image, bg_alpha_image; @@ -137,6 +139,8 @@ public: const LLColor4& getTransparentColor() const { return mBgAlphaColor; } LLPointer<LLUIImage> getBackgroundImage() const { return mBgOpaqueImage; } LLPointer<LLUIImage> getTransparentImage() const { return mBgAlphaImage; } + LLColor4 getBackgroundImageOverlay() { return mBgOpaqueImageOverlay; } + LLColor4 getTransparentImageOverlay() { return mBgAlphaImageOverlay; } void setBackgroundVisible( BOOL b ) { mBgVisible = b; } BOOL isBackgroundVisible() const { return mBgVisible; } void setBackgroundOpaque(BOOL b) { mBgOpaque = b; } @@ -214,7 +218,10 @@ public: // LLTabContainer void childShowTab(const std::string& id, const std::string& tabname, bool visible = true); LLPanel *childGetVisibleTab(const std::string& id) const; + + // 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)); } @@ -223,7 +230,7 @@ public: std::string childGetText(const std::string& id) const { return childGetValue(id).asString(); } // LLLineEditor - void childSetPrevalidate(const std::string& id, BOOL (*func)(const LLWString &) ); + void childSetPrevalidate(const std::string& id, bool (*func)(const LLWString &) ); // LLButton void childSetAction(const std::string& id, boost::function<void(void*)> function, void* value = NULL); @@ -259,6 +266,8 @@ private: BOOL mBgOpaque; // use opaque color or image LLUIColor mBgOpaqueColor; LLUIColor mBgAlphaColor; + LLUIColor mBgOpaqueImageOverlay; + LLUIColor mBgAlphaImageOverlay; LLPointer<LLUIImage> mBgOpaqueImage; // "panel in front" look LLPointer<LLUIImage> mBgAlphaImage; // "panel in back" look LLViewBorder* mBorder; diff --git a/indra/llui/llresizehandle.cpp b/indra/llui/llresizehandle.cpp index 6239a8f721..00214d451c 100644 --- a/indra/llui/llresizehandle.cpp +++ b/indra/llui/llresizehandle.cpp @@ -136,9 +136,10 @@ BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) if( resizing_view ) { // undock floater when user resize it - if (((LLFloater*)getParent())->isDocked()) + LLFloater* floater_parent = dynamic_cast<LLFloater*>(getParent()); + if (floater_parent && floater_parent->isDocked()) { - ((LLFloater*)getParent())->setDocked(false, false); + floater_parent->setDocked(false, false); } // Resize the parent diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index a5e47e8547..94465a67ce 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -668,6 +668,11 @@ void LLScrollContainer::scrollToShowRect(const LLRect& rect, const LLRect& const // propagate scroll to document updateScroll(); + + // In case we are in accordion tab notify parent to show selected rectangle + LLRect screen_rc; + localRectToScreen(rect_to_constrain, &screen_rc); + notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue())); } void LLScrollContainer::pageUp(S32 overlap) diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp index 7238d903a3..3cc92baa8d 100644 --- a/indra/llui/llscrolllistcell.cpp +++ b/indra/llui/llscrolllistcell.cpp @@ -188,7 +188,7 @@ LLScrollListText::LLScrollListText(const LLScrollListCell::Params& p) // initialize rounded rect image if (!mRoundedRectImage) { - mRoundedRectImage = LLUI::getUIImage("rounded_square.tga"); + mRoundedRectImage = LLUI::getUIImage("Rounded_Square"); } } diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index a53a30b501..77caaaa425 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -71,8 +71,9 @@ static LLDefaultChildRegistry::Register<LLScrollListCtrl> r("scroll_list"); // local structures & classes. struct SortScrollListItem { - SortScrollListItem(const std::vector<std::pair<S32, BOOL> >& sort_orders) + SortScrollListItem(const std::vector<std::pair<S32, BOOL> >& sort_orders,const LLScrollListCtrl::sort_signal_t* sort_signal) : mSortOrders(sort_orders) + , mSortSignal(sort_signal) {} bool operator()(const LLScrollListItem* i1, const LLScrollListItem* i2) @@ -85,12 +86,20 @@ struct SortScrollListItem S32 col_idx = it->first; BOOL sort_ascending = it->second; + S32 order = sort_ascending ? 1 : -1; // ascending or descending sort for this column? + const LLScrollListCell *cell1 = i1->getColumn(col_idx); const LLScrollListCell *cell2 = i2->getColumn(col_idx); - S32 order = sort_ascending ? 1 : -1; // ascending or descending sort for this column? if (cell1 && cell2) { - sort_result = order * LLStringUtil::compareDict(cell1->getValue().asString(), cell2->getValue().asString()); + if(mSortSignal) + { + sort_result = order * (*mSortSignal)(col_idx,i1, i2); + } + else + { + sort_result = order * LLStringUtil::compareDict(cell1->getValue().asString(), cell2->getValue().asString()); + } if (sort_result != 0) { break; // we have a sort order! @@ -100,8 +109,10 @@ struct SortScrollListItem return sort_result < 0; } + typedef std::vector<std::pair<S32, BOOL> > sort_order_t; + const LLScrollListCtrl::sort_signal_t* mSortSignal; const sort_order_t& mSortOrders; }; @@ -142,6 +153,7 @@ LLScrollListCtrl::Params::Params() 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; @@ -168,6 +180,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) mOnSortChangedCallback( NULL ), mHighlightedItem(-1), mBorder(NULL), + mSortCallback(NULL), mPopupMenu(NULL), mNumDynamicWidthColumns(0), mTotalStaticColumnWidth(0), @@ -231,10 +244,8 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) if (p.has_border) { LLRect border_rect = getLocalRect(); - LLViewBorder::Params params; - params.name("dig border"); + LLViewBorder::Params params = p.border; params.rect(border_rect); - params.bevel_style(LLViewBorder::BEVEL_IN); mBorder = LLUICtrlFactory::create<LLViewBorder> (params); addChild(mBorder); } @@ -310,6 +321,8 @@ bool LLScrollListCtrl::preProcessChildNode(LLXMLNodePtr child) LLScrollListCtrl::~LLScrollListCtrl() { + delete mSortCallback; + std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); if( gEditMenuHandler == this ) @@ -499,7 +512,7 @@ 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 ); + height = llmin( mPageLines * mLineHeight + 2*mBorderThickness + (mDisplayColumnHeaders ? mHeadingHeight : 0), height ); S32 width = getRect().getWidth(); @@ -541,7 +554,7 @@ BOOL LLScrollListCtrl::addItem( LLScrollListItem* item, EAddPosition pos, BOOL r std::stable_sort( mItemList.begin(), mItemList.end(), - SortScrollListItem(single_sort_column)); + SortScrollListItem(single_sort_column,mSortCallback)); // ADD_SORTED just sorts by first column... // this might not match user sort criteria, so flag list as being in unsorted state @@ -1389,6 +1402,8 @@ void LLScrollListCtrl::drawItems() LLGLSUIDefault gls_ui; + F32 alpha = getDrawContext().mAlpha; + { LLLocalClipRect clip(mItemListRect); @@ -1464,7 +1479,7 @@ void LLScrollListCtrl::drawItems() bg_color = mBgReadOnlyColor.get(); } - item->draw(item_rect, fg_color, bg_color, highlight_color, mColumnPadding); + item->draw(item_rect, fg_color % alpha, bg_color% alpha, highlight_color % alpha, mColumnPadding); cur_y -= mLineHeight; } @@ -1535,7 +1550,7 @@ LLRect LLScrollListCtrl::getCellRect(S32 row_index, S32 column_index) S32 rect_bottom = getRowOffsetFromIndex(row_index); LLScrollListColumn* columnp = getColumn(column_index); cell_rect.setOriginAndSize(rect_left, rect_bottom, - rect_left + columnp->getWidth(), mLineHeight); + /*rect_left + */columnp->getWidth(), mLineHeight); return cell_rect; } @@ -2394,7 +2409,7 @@ void LLScrollListCtrl::updateSort() const std::stable_sort( mItemList.begin(), mItemList.end(), - SortScrollListItem(mSortColumns)); + SortScrollListItem(mSortColumns,mSortCallback)); mSorted = true; } @@ -2410,7 +2425,7 @@ void LLScrollListCtrl::sortOnce(S32 column, BOOL ascending) std::stable_sort( mItemList.begin(), mItemList.end(), - SortScrollListItem(sort_column)); + SortScrollListItem(sort_column,mSortCallback)); } void LLScrollListCtrl::dirtyColumns() @@ -2761,9 +2776,13 @@ LLScrollListItem* LLScrollListCtrl::addElement(const LLSD& element, EAddPosition LLScrollListItem* LLScrollListCtrl::addRow(const LLScrollListItem::Params& item_p, EAddPosition pos) { - if (!item_p.validateBlock()) return NULL; - LLScrollListItem *new_item = new LLScrollListItem(item_p); + return addRow(new_item, item_p, pos); +} + +LLScrollListItem* LLScrollListCtrl::addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& item_p, EAddPosition pos) +{ + if (!item_p.validateBlock() || !new_item) return NULL; new_item->setNumColumns(mColumns.size()); // Add any columns we don't already have diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index 78bc60db6e..e819c5fdea 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -51,6 +51,7 @@ #include "lldate.h" #include "llscrolllistitem.h" #include "llscrolllistcolumn.h" +#include "llviewborder.h" class LLScrollListCell; class LLTextBox; @@ -72,6 +73,30 @@ public: // *TODO: Add callbacks to Params typedef boost::function<void (void)> callback_t; + + template<typename T> struct maximum + { + typedef T result_type; + + template<typename InputIterator> + T operator()(InputIterator first, InputIterator last) const + { + // If there are no slots to call, just return the + // default-constructed value + if(first == last ) return T(); + T max_value = *first++; + while (first != last) { + if (max_value < *first) + max_value = *first; + ++first; + } + + return max_value; + } + }; + + + typedef boost::signals2::signal<S32 (S32,const LLScrollListItem*,const LLScrollListItem*),maximum<S32> > sort_signal_t; struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> { @@ -109,6 +134,8 @@ public: scroll_bar_bg_color; Optional<Contents> contents; + + Optional<LLViewBorder::Params> border; Params(); }; @@ -145,6 +172,7 @@ public: // "columns" => [ "column" => column name, "value" => value, "type" => type, "font" => font, "font-style" => style ], "id" => uuid // Creates missing columns automatically. virtual LLScrollListItem* addElement(const LLSD& element, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); + virtual LLScrollListItem* addRow(LLScrollListItem *new_item, const LLScrollListItem::Params& value, EAddPosition pos = ADD_BOTTOM); virtual LLScrollListItem* addRow(const LLScrollListItem::Params& value, EAddPosition pos = ADD_BOTTOM); // Simple add element. Takes a single array of: // [ "value" => value, "font" => font, "font-style" => style ] @@ -358,6 +386,13 @@ public: void setNeedsSort(bool val = true) { mSorted = !val; } void dirtyColumns(); // some operation has potentially affected column layout or ordering + boost::signals2::connection setSortCallback(sort_signal_t::slot_type cb ) + { + if (!mSortCallback) mSortCallback = new sort_signal_t(); + return mSortCallback->connect(cb); + } + + protected: // "Full" interface: use this when you're creating a list that has one or more of the following: // * contains icons @@ -470,6 +505,8 @@ private: typedef std::pair<S32, BOOL> sort_column_t; std::vector<sort_column_t> mSortColumns; + + sort_signal_t* mSortCallback; }; // end class LLScrollListCtrl #endif // LL_SCROLLLISTCTRL_H diff --git a/indra/llui/llscrolllistitem.h b/indra/llui/llscrolllistitem.h index 15b86cc945..25ce846d90 100644 --- a/indra/llui/llscrolllistitem.h +++ b/indra/llui/llscrolllistitem.h @@ -95,7 +95,7 @@ public: void setUserdata( void* userdata ) { mUserdata = userdata; } void* getUserdata() const { return mUserdata; } - LLUUID getUUID() const { return mItemValue.asUUID(); } + virtual LLUUID getUUID() const { return mItemValue.asUUID(); } LLSD getValue() const { return mItemValue; } void setRect(LLRect rect) { mRectangle = rect; } diff --git a/indra/llui/llsearcheditor.cpp b/indra/llui/llsearcheditor.cpp index 6fa99df82e..ec2ad5e5fa 100644 --- a/indra/llui/llsearcheditor.cpp +++ b/indra/llui/llsearcheditor.cpp @@ -44,22 +44,34 @@ LLSearchEditor::LLSearchEditor(const LLSearchEditor::Params& p) S32 srch_btn_top = p.search_button.top_pad + p.search_button.rect.height; S32 srch_btn_right = p.search_button.rect.width + p.search_button.left_pad; LLRect srch_btn_rect(p.search_button.left_pad, srch_btn_top, srch_btn_right, p.search_button.top_pad); + + S32 clr_btn_top = p.clear_button.rect.bottom + p.clear_button.rect.height; + S32 clr_btn_right = getRect().getWidth() - p.clear_button.pad_right; + S32 clr_btn_left = clr_btn_right - p.clear_button.rect.width; + LLRect clear_btn_rect(clr_btn_left, clr_btn_top, clr_btn_right, p.clear_button.rect.bottom); + S32 text_pad_left = p.text_pad_left; + S32 text_pad_right = p.text_pad_right; if (p.search_button_visible) text_pad_left += srch_btn_rect.getWidth(); + if (p.clear_button_visible) + text_pad_right = getRect().getWidth() - clr_btn_left + p.clear_button.pad_left; + // Set up line editor. LLLineEditor::Params line_editor_params(p); line_editor_params.name("filter edit box"); line_editor_params.rect(getLocalRect()); line_editor_params.follows.flags(FOLLOWS_ALL); line_editor_params.text_pad_left(text_pad_left); + line_editor_params.text_pad_right(text_pad_right); line_editor_params.revert_on_esc(false); line_editor_params.commit_callback.function(boost::bind(&LLUICtrl::onCommit, this)); line_editor_params.keystroke_callback(boost::bind(&LLSearchEditor::handleKeystroke, this)); mSearchEditor = LLUICtrlFactory::create<LLLineEditor>(line_editor_params); + mSearchEditor->setPassDelete(TRUE); addChild(mSearchEditor); if (p.search_button_visible) @@ -79,8 +91,6 @@ LLSearchEditor::LLSearchEditor(const LLSearchEditor::Params& p) 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) ; @@ -153,7 +163,7 @@ void LLSearchEditor::setFocus( BOOL b ) void LLSearchEditor::onClearButtonClick(const LLSD& data) { setText(LLStringUtil::null); - mSearchEditor->doDelete(); // force keystroke callback + mSearchEditor->onCommit(); // force keystroke callback } void LLSearchEditor::handleKeystroke() diff --git a/indra/llui/llsliderctrl.cpp b/indra/llui/llsliderctrl.cpp index 01c274bb4e..80ee5d0984 100644 --- a/indra/llui/llsliderctrl.cpp +++ b/indra/llui/llsliderctrl.cpp @@ -141,7 +141,7 @@ LLSliderCtrl::LLSliderCtrl(const LLSliderCtrl::Params& p) 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); + line_p.prevalidate_callback(&LLTextValidate::validateFloat); mEditor = LLUICtrlFactory::create<LLLineEditor>(line_p); mEditor->setFocusReceivedCallback( boost::bind(&LLSliderCtrl::onEditorGainFocus, _1, this )); diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index 20a1ab7af3..491cd7b6f3 100644 --- a/indra/llui/llspinctrl.cpp +++ b/indra/llui/llspinctrl.cpp @@ -127,7 +127,7 @@ LLSpinCtrl::LLSpinCtrl(const LLSpinCtrl::Params& p) } params.max_length_bytes(MAX_STRING_LENGTH); params.commit_callback.function((boost::bind(&LLSpinCtrl::onEditorCommit, this, _2))); - params.prevalidate_callback(&LLLineEditor::prevalidateFloat); + params.prevalidate_callback(&LLTextValidate::validateFloat); params.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); mEditor = LLUICtrlFactory::create<LLLineEditor> (params); mEditor->setFocusReceivedCallback( boost::bind(&LLSpinCtrl::onEditorGainFocus, _1, this )); @@ -270,13 +270,19 @@ void LLSpinCtrl::clear() mbHasBeenSet = FALSE; } - +void LLSpinCtrl::updateLabelColor() +{ + if( mLabelBox ) + { + mLabelBox->setColor( getEnabled() ? mTextEnabledColor.get() : mTextDisabledColor.get() ); + } +} void LLSpinCtrl::updateEditor() { LLLocale locale(LLLocale::USER_LOCALE); - // Don't display very small negative values as -0.000 + // Don't display very small negative valu es as -0.000 F32 displayed_value = clamp_precision((F32)getValue().asReal(), mPrecision); // if( S32( displayed_value * pow( 10, mPrecision ) ) == 0 ) @@ -339,10 +345,7 @@ void LLSpinCtrl::setEnabled(BOOL b) { LLView::setEnabled( b ); mEditor->setEnabled( b ); - if( mLabelBox ) - { - mLabelBox->setColor( b ? mTextEnabledColor.get() : mTextDisabledColor.get() ); - } + updateLabelColor(); } @@ -390,6 +393,7 @@ void LLSpinCtrl::setLabel(const LLStringExplicit& label) { llwarns << "Attempting to set label on LLSpinCtrl constructed without one " << getName() << llendl; } + updateLabelColor(); } void LLSpinCtrl::setAllowEdit(BOOL allow_edit) diff --git a/indra/llui/llspinctrl.h b/indra/llui/llspinctrl.h index 0e610b7741..00d6f86f83 100644 --- a/indra/llui/llspinctrl.h +++ b/indra/llui/llspinctrl.h @@ -81,8 +81,8 @@ public: virtual void setPrecision(S32 precision); void setLabel(const LLStringExplicit& label); - void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; } - void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; } + void setLabelColor(const LLColor4& c) { mTextEnabledColor = c; updateLabelColor(); } + void setDisabledLabelColor(const LLColor4& c) { mTextDisabledColor = c; updateLabelColor();} void setAllowEdit(BOOL allow_edit); virtual void onTabInto(); @@ -103,6 +103,7 @@ public: void onDownBtn(const LLSD& data); private: + void updateLabelColor(); void updateEditor(); void reportInvalidData(); diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp index 71511f69a4..b8f93b6a0e 100644 --- a/indra/llui/llstyle.cpp +++ b/indra/llui/llstyle.cpp @@ -49,7 +49,10 @@ LLStyle::Params::Params() LLStyle::LLStyle(const LLStyle::Params& p) -: mVisible(p.visible), +: mItalic(FALSE), + mBold(FALSE), + mUnderline(FALSE), + mVisible(p.visible), mColor(p.color()), mReadOnlyColor(p.readonly_color()), mFont(p.font()), diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h index ee9ca730e9..2067e8e8be 100644 --- a/indra/llui/llstyle.h +++ b/indra/llui/llstyle.h @@ -59,11 +59,12 @@ public: void setColor(const LLColor4 &color) { mColor = color; } const LLColor4& getReadOnlyColor() const { return mReadOnlyColor; } + void setReadOnlyColor(const LLColor4& color) { mReadOnlyColor = color; } BOOL isVisible() const; void setVisible(BOOL is_visible); - LLFontGL::ShadowType getShadowType() { return mDropShadow; } + LLFontGL::ShadowType getShadowType() const { return mDropShadow; } void setFont(const LLFontGL* font); const LLFontGL* getFont() const; @@ -116,5 +117,6 @@ private: }; typedef LLPointer<LLStyle> LLStyleSP; +typedef LLPointer<const LLStyle> LLStyleConstSP; #endif // LL_LLSTYLE_H diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index 327dd01612..30fc7babae 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -35,7 +35,6 @@ #include "lltabcontainer.h" #include "llfocusmgr.h" -#include "llbutton.h" #include "lllocalcliprect.h" #include "llrect.h" #include "llresizehandle.h" @@ -96,6 +95,95 @@ public: //---------------------------------------------------------------------------- +//============================================================================ +/* + * @file lltabcontainer.cpp + * @brief class implements LLButton with LLIconCtrl on it + */ +class LLCustomButtonIconCtrl : public LLButton +{ +public: + struct Params + : public LLInitParam::Block<Params, LLButton::Params> + { + // LEFT, RIGHT, TOP, BOTTOM paddings of LLIconCtrl in this class has same value + Optional<S32> icon_ctrl_pad; + + Params(): + icon_ctrl_pad("icon_ctrl_pad", 1) + {} + }; + +protected: + friend class LLUICtrlFactory; + LLCustomButtonIconCtrl(const Params& p): + LLButton(p), + mIcon(NULL), + mIconAlignment(LLFontGL::HCENTER), + mIconCtrlPad(p.icon_ctrl_pad) + {} + +public: + + void updateLayout() + { + LLRect button_rect = getRect(); + LLRect icon_rect = mIcon->getRect(); + + S32 icon_size = button_rect.getHeight() - 2*mIconCtrlPad; + + switch(mIconAlignment) + { + case LLFontGL::LEFT: + icon_rect.setLeftTopAndSize(button_rect.mLeft + mIconCtrlPad, button_rect.mTop - mIconCtrlPad, + icon_size, icon_size); + setLeftHPad(icon_size + mIconCtrlPad * 2); + break; + case LLFontGL::HCENTER: + icon_rect.setLeftTopAndSize(button_rect.mRight - (button_rect.getWidth() + mIconCtrlPad - icon_size)/2, button_rect.mTop - mIconCtrlPad, + icon_size, icon_size); + setRightHPad(icon_size + mIconCtrlPad * 2); + break; + case LLFontGL::RIGHT: + icon_rect.setLeftTopAndSize(button_rect.mRight - mIconCtrlPad - icon_size, button_rect.mTop - mIconCtrlPad, + icon_size, icon_size); + setRightHPad(icon_size + mIconCtrlPad * 2); + break; + default: + break; + } + mIcon->setRect(icon_rect); + } + + void setIcon(LLIconCtrl* icon, LLFontGL::HAlign alignment = LLFontGL::LEFT) + { + if(icon) + { + if(mIcon) + { + removeChild(mIcon); + mIcon->die(); + } + mIcon = icon; + mIconAlignment = alignment; + + addChild(mIcon); + updateLayout(); + } + } + + LLIconCtrl* getIconCtrl() const + { + return mIcon; + } + +private: + LLIconCtrl* mIcon; + LLFontGL::HAlign mIconAlignment; + S32 mIconCtrlPad; +}; +//============================================================================ + struct LLPlaceHolderPanel : public LLPanel { // create dummy param block to register with "placeholder" nane @@ -127,7 +215,11 @@ LLTabContainer::Params::Params() tab_padding_right("tab_padding_right"), first_tab("first_tab"), middle_tab("middle_tab"), - last_tab("last_tab") + last_tab("last_tab"), + use_custom_icon_ctrl("use_custom_icon_ctrl", false), + tab_icon_ctrl_pad("tab_icon_ctrl_pad", 0), + use_ellipses("use_ellipses"), + font_halign("halign") { name(std::string("tab_container")); mouse_opaque = false; @@ -162,7 +254,10 @@ LLTabContainer::LLTabContainer(const LLTabContainer::Params& p) mFont(p.font), mFirstTabParams(p.first_tab), mMiddleTabParams(p.middle_tab), - mLastTabParams(p.last_tab) + mLastTabParams(p.last_tab), + mCustomIconCtrlUsed(p.use_custom_icon_ctrl), + mTabIconCtrlPad(p.tab_icon_ctrl_pad), + mUseTabEllipses(p.use_ellipses) { static LLUICachedControl<S32> tabcntr_vert_tab_min_width ("UITabCntrVertTabMinWidth", 0); @@ -402,15 +497,15 @@ void LLTabContainer::draw() if( mIsVertical && has_scroll_arrows ) { // Redraw the arrows so that they appears on top. - gGL.pushMatrix(); - gGL.translatef((F32)mPrevArrowBtn->getRect().mLeft, (F32)mPrevArrowBtn->getRect().mBottom, 0.f); + gGL.pushUIMatrix(); + gGL.translateUI((F32)mPrevArrowBtn->getRect().mLeft, (F32)mPrevArrowBtn->getRect().mBottom, 0.f); mPrevArrowBtn->draw(); - gGL.popMatrix(); + gGL.popUIMatrix(); - gGL.pushMatrix(); - gGL.translatef((F32)mNextArrowBtn->getRect().mLeft, (F32)mNextArrowBtn->getRect().mBottom, 0.f); + gGL.pushUIMatrix(); + gGL.translateUI((F32)mNextArrowBtn->getRect().mLeft, (F32)mNextArrowBtn->getRect().mBottom, 0.f); mNextArrowBtn->draw(); - gGL.popMatrix(); + gGL.popUIMatrix(); } } @@ -801,6 +896,10 @@ void LLTabContainer::update_images(LLTabTuple* tuple, TabParams params, LLTabCon void LLTabContainer::addTabPanel(const TabPanelParams& panel) { LLPanel* child = panel.panel(); + + llassert(child); + if (!child) return; + const std::string& label = panel.label.isProvided() ? panel.label() : panel.panel()->getLabel(); @@ -856,7 +955,7 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) LLRect tab_panel_rect; if (!getTabsHidden() && mIsVertical) { - tab_panel_rect = LLRect(mMinTabWidth + (LLPANEL_BORDER_WIDTH * 2) + tabcntrv_pad, + tab_panel_rect = LLRect(mMinTabWidth + mRightTabBtnOffset + (LLPANEL_BORDER_WIDTH * 2) + tabcntrv_pad, getRect().getHeight() - LLPANEL_BORDER_WIDTH, getRect().getWidth() - LLPANEL_BORDER_WIDTH, LLPANEL_BORDER_WIDTH); @@ -905,6 +1004,11 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) LLTextBox* textbox = NULL; LLButton* btn = NULL; + LLCustomButtonIconCtrl::Params custom_btn_params; + { + custom_btn_params.icon_ctrl_pad(mTabIconCtrlPad); + } + LLButton::Params normal_btn_params; if (placeholder) { @@ -924,7 +1028,9 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) { if (mIsVertical) { - LLButton::Params p; + LLButton::Params& p = (mCustomIconCtrlUsed)? + custom_btn_params:normal_btn_params; + p.name(std::string("vert tab button")); p.rect(btn_rect); p.follows.flags(FOLLOWS_TOP | FOLLOWS_LEFT); @@ -942,11 +1048,22 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) { p.pad_left(indent); } - btn = LLUICtrlFactory::create<LLButton>(p); + + + if(mCustomIconCtrlUsed) + { + btn = LLUICtrlFactory::create<LLCustomButtonIconCtrl>(custom_btn_params); + + } + else + { + btn = LLUICtrlFactory::create<LLButton>(p); + } } else { - LLButton::Params p; + LLButton::Params& p = (mCustomIconCtrlUsed)? + custom_btn_params:normal_btn_params; p.name(std::string(child->getName()) + " tab"); p.rect(btn_rect); p.click_callback.function(boost::bind(&LLTabContainer::onTabBtn, this, _2, child)); @@ -980,7 +1097,14 @@ void LLTabContainer::addTabPanel(const TabPanelParams& panel) p.follows.flags = p.follows.flags() | FOLLOWS_BOTTOM; } -++ btn = LLUICtrlFactory::create<LLButton>(p); + if(mCustomIconCtrlUsed) + { + btn = LLUICtrlFactory::create<LLCustomButtonIconCtrl>(custom_btn_params); + } + else + { + btn = LLUICtrlFactory::create<LLButton>(p); + } } } @@ -1373,6 +1497,8 @@ BOOL LLTabContainer::setTab(S32 which) { LLTabTuple* tuple = *iter; BOOL is_selected = ( tuple == selected_tuple ); + tuple->mButton->setUseEllipses(mUseTabEllipses); + tuple->mButton->setHAlign(mFontHalign); tuple->mTabPanel->setVisible( is_selected ); // tuple->mTabPanel->setFocus(is_selected); // not clear that we want to do this here. tuple->mButton->setToggleState( is_selected ); @@ -1478,32 +1604,71 @@ 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 ) { - tuple->mButton->setImageOverlay(image_name, LLFontGL::RIGHT, color); + tuple->mButton->setImageOverlay(image_name, LLFontGL::LEFT, color); + reshapeTuple(tuple); + } +} + +void LLTabContainer::setTabImage(LLPanel* child, const LLUUID& image_id, const LLColor4& color) +{ + LLTabTuple* tuple = getTabByPanel(child); + if( tuple ) + { + tuple->mButton->setImageOverlay(image_id, LLFontGL::LEFT, color); + reshapeTuple(tuple); + } +} - if (!mIsVertical) - { - // remove current width from total tab strip width - mTotalTabWidth -= tuple->mButton->getRect().getWidth(); +void LLTabContainer::setTabImage(LLPanel* child, LLIconCtrl* icon) +{ + LLTabTuple* tuple = getTabByPanel(child); + LLCustomButtonIconCtrl* button; - S32 image_overlay_width = tuple->mButton->getImageOverlay().notNull() ? - tuple->mButton->getImageOverlay()->getImage()->getWidth(0) : - 0; + if(tuple) + { + button = dynamic_cast<LLCustomButtonIconCtrl*>(tuple->mButton); + if(button) + { + button->setIcon(icon); + reshapeTuple(tuple); + } + } +} - tuple->mPadding = image_overlay_width; +void LLTabContainer::reshapeTuple(LLTabTuple* tuple) +{ + static LLUICachedControl<S32> tab_padding ("UITabPadding", 0); - 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(); + if (!mIsVertical) + { + S32 image_overlay_width = 0; - // tabs have changed size, might need to scroll to see current tab - updateMaxScrollPos(); + if(mCustomIconCtrlUsed) + { + LLCustomButtonIconCtrl* button = dynamic_cast<LLCustomButtonIconCtrl*>(tuple->mButton); + LLIconCtrl* icon_ctrl = button ? button->getIconCtrl() : NULL; + image_overlay_width = icon_ctrl ? icon_ctrl->getRect().getWidth() : 0; + } + else + { + image_overlay_width = tuple->mButton->getImageOverlay().notNull() ? + tuple->mButton->getImageOverlay()->getImage()->getWidth(0) : 0; } + // remove current width from total tab strip width + mTotalTabWidth -= tuple->mButton->getRect().getWidth(); + + tuple->mPadding = image_overlay_width; + + tuple->mButton->reshape(llclamp(mFont->getWidth(tuple->mButton->getLabelSelected()) + tab_padding + tuple->mPadding, mMinTabWidth, mMaxTabWidth), + tuple->mButton->getRect().getHeight()); + // add back in button width to total tab strip width + mTotalTabWidth += tuple->mButton->getRect().getWidth(); + + // tabs have changed size, might need to scroll to see current tab + updateMaxScrollPos(); } } @@ -1566,7 +1731,10 @@ void LLTabContainer::onTabBtn( const LLSD& data, LLPanel* panel ) LLTabTuple* tuple = getTabByPanel(panel); selectTabPanel( panel ); - tuple->mTabPanel->setFocus(TRUE); + if (tuple) + { + tuple->mTabPanel->setFocus(TRUE); + } } void LLTabContainer::onNextBtn( const LLSD& data ) diff --git a/indra/llui/lltabcontainer.h b/indra/llui/lltabcontainer.h index 5d0f194bf9..50ec2679f6 100644 --- a/indra/llui/lltabcontainer.h +++ b/indra/llui/lltabcontainer.h @@ -36,6 +36,8 @@ #include "llpanel.h" #include "lltextbox.h" #include "llframetimer.h" +#include "lliconctrl.h" +#include "llbutton.h" class LLTabTuple; @@ -90,6 +92,26 @@ public: middle_tab, last_tab; + /** + * Tab label horizontal alignment + */ + Optional<LLFontGL::HAlign> font_halign; + + /** + * Tab label ellipses + */ + Optional<bool> use_ellipses; + + /** + * Use LLCustomButtonIconCtrl or LLButton in LLTabTuple + */ + Optional<bool> use_custom_icon_ctrl; + + /** + * Paddings for LLIconCtrl in case of LLCustomButtonIconCtrl usage(use_custom_icon_ctrl = true) + */ + Optional<S32> tab_icon_ctrl_pad; + Params(); }; @@ -172,6 +194,8 @@ public: BOOL getTabPanelFlashing(LLPanel* child); void setTabPanelFlashing(LLPanel* child, BOOL state); void setTabImage(LLPanel* child, std::string img_name, const LLColor4& color = LLColor4::white); + void setTabImage(LLPanel* child, const LLUUID& img_id, const LLColor4& color = LLColor4::white); + void setTabImage(LLPanel* child, LLIconCtrl* icon); void setTitle( const std::string& title ); const std::string getPanelTitle(S32 index); @@ -227,6 +251,7 @@ private: // updates tab button images given the tuple, tab position and the corresponding params void update_images(LLTabTuple* tuple, TabParams params, LLTabContainer::TabPosition pos); + void reshapeTuple(LLTabTuple* tuple); // Variables @@ -276,6 +301,10 @@ private: TabParams mFirstTabParams; TabParams mMiddleTabParams; TabParams mLastTabParams; + + bool mCustomIconCtrlUsed; + S32 mTabIconCtrlPad; + bool mUseTabEllipses; }; #endif // LL_TABCONTAINER_H diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 7447a984ac..851fb966ec 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -185,7 +185,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mWriteableBgColor(p.bg_writeable_color), mReadOnlyBgColor(p.bg_readonly_color), mFocusBgColor(p.bg_focus_color), - mReflowNeeded(FALSE), + mReflowIndex(S32_MAX), mCursorPos( 0 ), mScrollNeeded(FALSE), mDesiredXPixel(-1), @@ -244,7 +244,8 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) LLTextBase::~LLTextBase() { - delete mPopupMenu; + // Menu, like any other LLUICtrl, is deleted by its parent - gMenuHolder + clearSegments(); } @@ -291,9 +292,13 @@ bool LLTextBase::truncate() return did_truncate; } -LLStyle::Params LLTextBase::getDefaultStyle() +LLStyle::Params LLTextBase::getDefaultStyleParams() { - return LLStyle::Params().color(mFgColor.get()).readonly_color(mReadOnlyFgColor.get()).font(mDefaultFont).drop_shadow(mFontShadow); + return LLStyle::Params() + .color(LLUIColor(&mFgColor)) + .readonly_color(LLUIColor(&mReadOnlyFgColor)) + .font(mDefaultFont) + .drop_shadow(mFontShadow); } void LLTextBase::onValueChange(S32 start, S32 end) @@ -307,7 +312,6 @@ 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 ); @@ -346,7 +350,8 @@ void LLTextBase::drawSelectionBackground() S32 segment_line_start = segmentp->getStart() + segment_offset; S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd); - S32 segment_width, segment_height; + S32 segment_width = 0; + S32 segment_height = 0; // if selection after beginning of segment if(selection_left >= segment_line_start) @@ -405,7 +410,7 @@ void LLTextBase::drawCursor() && gFocusMgr.getAppHasFocus() && !mReadOnly) { - LLWString wtext = getWText(); + const LLWString &wtext = getWText(); const llwchar* text = wtext.c_str(); LLRect cursor_rect = getLocalRectFromDocIndex(mCursorPos); @@ -433,7 +438,8 @@ void LLTextBase::drawCursor() if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) { - S32 segment_width, segment_height; + 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; @@ -490,7 +496,6 @@ void LLTextBase::drawCursor() void LLTextBase::drawText() { - LLWString text = getWText(); const S32 text_len = getLength(); if( text_len <= 0 ) { @@ -617,7 +622,8 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s else { // create default editable segment to hold new text - default_segment = new LLNormalTextSegment( new LLStyle(getDefaultStyle()), pos, pos + insert_len, *this); + LLStyleConstSP sp(new LLStyle(getDefaultStyleParams())); + default_segment = new LLNormalTextSegment( sp, pos, pos + insert_len, *this); } // shift remaining segments to right @@ -654,7 +660,7 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s } onValueChange(pos, pos + insert_len); - needsReflow(); + needsReflow(pos); return insert_len; } @@ -714,7 +720,7 @@ S32 LLTextBase::removeStringNoUndo(S32 pos, S32 length) createDefaultSegment(); onValueChange(pos, pos); - needsReflow(); + needsReflow(pos); return -length; // This will be wrong if someone calls removeStringNoUndo with an excessive length } @@ -730,7 +736,7 @@ S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) getViewModel()->setDisplay(text); onValueChange(pos, pos + 1); - needsReflow(); + needsReflow(pos); return 1; } @@ -741,7 +747,8 @@ 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); + LLStyleConstSP sp(new LLStyle(getDefaultStyleParams())); + LLTextSegmentPtr default_segment = new LLNormalTextSegment( sp, 0, getLength() + 1, *this); mSegments.insert(default_segment); default_segment->linkToDocument(this); } @@ -755,15 +762,18 @@ void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) } segment_set_t::iterator cur_seg_iter = getSegIterContaining(segment_to_insert->getStart()); + S32 reflow_start_index = 0; if (cur_seg_iter == mSegments.end()) { mSegments.insert(segment_to_insert); segment_to_insert->linkToDocument(this); + reflow_start_index = segment_to_insert->getStart(); } else { LLTextSegmentPtr cur_segmentp = *cur_seg_iter; + reflow_start_index = cur_segmentp->getStart(); if (cur_segmentp->getStart() < segment_to_insert->getStart()) { S32 old_segment_end = cur_segmentp->getEnd(); @@ -771,7 +781,8 @@ void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) 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); + LLStyleConstSP sp = cur_segmentp->getStyle(); + LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( sp, segment_to_insert->getStart(), old_segment_end, *this); mSegments.insert(cur_seg_iter, remainder_segment); remainder_segment->linkToDocument(this); // insert new segment before remainder of old segment @@ -821,7 +832,7 @@ void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) } // layout potentially changed - needsReflow(); + needsReflow(reflow_start_index); } BOOL LLTextBase::handleMouseDown(S32 x, S32 y, MASK mask) @@ -1016,6 +1027,16 @@ void LLTextBase::setReadOnlyColor(const LLColor4 &c) } //virtual +void LLTextBase::handleVisibilityChange( BOOL new_visibility ) +{ + if(!new_visibility && mPopupMenu) + { + mPopupMenu->hide(); + } + LLUICtrl::handleVisibilityChange(new_visibility); +} + +//virtual void LLTextBase::setValue(const LLSD& value ) { setText(value.asString()); @@ -1066,15 +1087,16 @@ S32 LLTextBase::getLeftOffset(S32 width) static LLFastTimer::DeclareTimer FTM_TEXT_REFLOW ("Text Reflow"); -void LLTextBase::reflow(S32 start_index) +void LLTextBase::reflow() { LLFastTimer ft(FTM_TEXT_REFLOW); updateSegments(); - while(mReflowNeeded) + while(mReflowIndex < S32_MAX) { - mReflowNeeded = false; + S32 start_index = mReflowIndex; + mReflowIndex = S32_MAX; // shrink document to minimum size (visible portion of text widget) // to force inlined widgets with follows set to shrink @@ -1106,7 +1128,6 @@ void LLTextBase::reflow(S32 start_index) 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 @@ -1116,6 +1137,7 @@ void LLTextBase::reflow(S32 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; + cur_top = iter->mRect.mTop; getSegmentAndOffset(iter->mDocIndexStart, &seg_iter, &seg_offset); mLineInfoList.erase(iter, mLineInfoList.end()); } @@ -1423,10 +1445,10 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 i } // Finds the text segment (if any) at the give local screen position -LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y ) +LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line) { // Find the cursor position at the requested local screen position - S32 offset = getDocIndexFromLocalCoord( x, y, FALSE ); + S32 offset = getDocIndexFromLocalCoord( x, y, FALSE, hit_past_end_of_line); segment_set_t::iterator seg_iter = getSegIterContaining(offset); if (seg_iter != mSegments.end()) { @@ -1508,16 +1530,7 @@ std::string LLTextBase::getText() const 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; - } + style_params.fillFrom(getDefaultStyleParams()); S32 part = (S32)LLTextParser::WHOLE; if(mParseHTML) @@ -1534,13 +1547,7 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c 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 @@ -1568,25 +1575,35 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c { LLStyle::Params icon; icon.image = image; - // HACK: fix spacing of images and remove the fixed char spacing - appendAndHighlightText(" ", prepend_newline, part, icon); + // Text will be replaced during rendering with the icon, + // but string cannot be empty or the segment won't be + // added (or drawn). + 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()) + // output the styled Url (unless we've been asked to suppress hyperlinking) + if (match.isLinkDisabled()) { - segment_set_t::iterator it = getSegIterContaining(getLength()-1); - if (it != mSegments.end()) + appendAndHighlightText(match.getLabel(), prepend_newline, part, style_params); + } + else + { + appendAndHighlightText(match.getLabel(), prepend_newline, part, link_params); + + // set the tooltip for the Url label + if (! match.getTooltip().empty()) { - LLTextSegmentPtr segment = *it; - segment->setToolTip(match.getTooltip()); + segment_set_t::iterator it = getSegIterContaining(getLength()-1); + if (it != mSegments.end()) + { + LLTextSegmentPtr segment = *it; + segment->setToolTip(match.getTooltip()); + } } } + prepend_newline = false; // move on to the rest of the text after the Url if (end < (S32)text.length()) @@ -1609,9 +1626,15 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c } } -void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepend_newline, S32 highlight_part, const LLStyle::Params& stylep) +void LLTextBase::needsReflow(S32 index) { - if (new_text.empty()) return; + lldebugs << "reflow on object " << (void*)this << " index = " << mReflowIndex << ", new index = " << index << llendl; + mReflowIndex = llmin(mReflowIndex, index); +} + +void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepend_newline, S32 highlight_part, const LLStyle::Params& style_params) +{ + if (new_text.empty()) return; // Save old state S32 selection_start = mSelectionStart; @@ -1629,7 +1652,7 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepen if (mParseHighlights && highlight) { - LLStyle::Params highlight_params = stylep; + LLStyle::Params highlight_params(style_params); LLSD pieces = highlight->parsePartialLineHighlights(new_text, highlight_params.color(), (LLTextParser::EHighlightPosition)highlight_part); for (S32 i = 0; i < pieces.size(); i++) @@ -1649,7 +1672,8 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepen 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); + LLStyleConstSP sp(new LLStyle(highlight_params)); + LLTextSegmentPtr segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + wide_text.size(), *this); segment_vec_t segments; segments.push_back(segmentp); insertStringNoUndo(cur_length, wide_text, &segments); @@ -1673,7 +1697,8 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, bool prepen 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 )); + LLStyleConstSP sp(new LLStyle(style_params)); + segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this )); insertStringNoUndo(getLength(), wide_text, &segments); } @@ -1717,7 +1742,7 @@ void LLTextBase::replaceUrlLabel(const std::string &url, for (it = mSegments.begin(); it != mSegments.end(); ++it) { LLTextSegment *seg = *it; - const LLStyleSP style = seg->getStyle(); + LLStyleConstSP style = seg->getStyle(); // update segment start/end length in case we replaced text earlier S32 seg_length = seg->getEnd() - seg->getStart(); @@ -1754,7 +1779,7 @@ void LLTextBase::setWText(const LLWString& text) setText(wstring_to_utf8str(text)); } -LLWString LLTextBase::getWText() const +const LLWString& LLTextBase::getWText() const { return getViewModel()->getDisplay(); } @@ -1763,7 +1788,7 @@ LLWString LLTextBase::getWText() const // 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 +S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, bool hit_past_end_of_line) const { // Figure out which line we're nearest to. LLRect visible_region = getVisibleDocumentRect(); @@ -1792,7 +1817,7 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round 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 + || (hit_past_end_of_line && (segmentp->getEnd() >= line_iter->mDocIndexEnd - 1))) // or this segment wraps to next line { // Figure out which character we're nearest to. S32 offset; @@ -2210,9 +2235,9 @@ 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::setColor(const LLColor4 &color) {} +LLStyleConstSP LLTextSegment::getStyle() const {static LLStyleConstSP sp(new LLStyle()); return sp; } +void LLTextSegment::setStyle(LLStyleConstSP style) {} void LLTextSegment::setToken( LLKeywordToken* token ) {} LLKeywordToken* LLTextSegment::getToken() const { return NULL; } void LLTextSegment::setToolTip( const std::string &msg ) {} @@ -2237,7 +2262,7 @@ BOOL LLTextSegment::hasMouseCapture() { return FALSE; } // LLNormalTextSegment // -LLNormalTextSegment::LLNormalTextSegment( const LLStyleSP& style, S32 start, S32 end, LLTextBase& editor ) +LLNormalTextSegment::LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ) : LLTextSegment(start, end), mStyle( style ), mToken(NULL), @@ -2248,7 +2273,7 @@ LLNormalTextSegment::LLNormalTextSegment( const LLStyleSP& style, S32 start, S32 LLUIImagePtr image = mStyle->getImage(); if (image.notNull()) { - mImageLoadedConnection = image->addLoadedCallback(boost::bind(&LLTextBase::needsReflow, &mEditor)); + mImageLoadedConnection = image->addLoadedCallback(boost::bind(&LLTextBase::needsReflow, &mEditor, start)); } } @@ -2274,14 +2299,21 @@ F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selec { if ( mStyle->isImage() && (start >= 0) && (end <= mEnd - mStart)) { + // ...for images, only render the image, not the underlying text, + // which is only a placeholder space 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); + // Text is drawn from the top of the draw_rect downward + S32 text_center = draw_rect.mTop - (mFontHeight / 2); + // Align image to center of text + S32 image_bottom = text_center - (style_image_height / 2); image->draw(draw_rect.mLeft, image_bottom, style_image_width, style_image_height, color); + + const S32 IMAGE_HPAD = 3; + return draw_rect.mLeft + style_image_width + IMAGE_HPAD; } return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect); @@ -2311,8 +2343,6 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele LLColor4 color = (mEditor.getReadOnly() ? mStyle->getReadOnlyColor() : mStyle->getColor()) % alpha; - font = mStyle->getFont(); - if( selection_start > seg_start ) { // Draw normally @@ -2372,8 +2402,12 @@ BOOL LLNormalTextSegment::handleHover(S32 x, S32 y, MASK mask) { if (getStyle() && getStyle()->isLink()) { - LLUI::getWindow()->setCursor(UI_CURSOR_HAND); - return TRUE; + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + LLUI::getWindow()->setCursor(UI_CURSOR_HAND); + return TRUE; + } } return FALSE; } @@ -2382,8 +2416,12 @@ BOOL LLNormalTextSegment::handleRightMouseDown(S32 x, S32 y, MASK mask) { if (getStyle() && getStyle()->isLink()) { - mEditor.createUrlContextMenu(x, y, getStyle()->getLinkHREF()); - return TRUE; + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + mEditor.createUrlContextMenu(x, y, getStyle()->getLinkHREF()); + return TRUE; + } } return FALSE; } @@ -2392,8 +2430,12 @@ 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; + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + // eat mouse down event on hyperlinks, so we get the mouse up + return TRUE; + } } return FALSE; @@ -2403,8 +2445,12 @@ BOOL LLNormalTextSegment::handleMouseUp(S32 x, S32 y, MASK mask) { if (getStyle() && getStyle()->isLink()) { - LLUrlAction::clickAction(getStyle()->getLinkHREF()); - return TRUE; + // Only process the click if it's actually in this segment, not to the right of the end-of-line. + if(mEditor.getSegmentAtLocalPos(x, y, false) == this) + { + LLUrlAction::clickAction(getStyle()->getLinkHREF()); + return TRUE; + } } return FALSE; @@ -2443,11 +2489,13 @@ void LLNormalTextSegment::setToolTip(const std::string& tooltip) bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { - height = mFontHeight; + height = 0; + width = 0; bool force_newline = false; if (num_chars > 0) { - LLWString text = mEditor.getWText(); + height = mFontHeight; + const 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') @@ -2461,10 +2509,6 @@ bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& widt width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars); } } - else - { - width = 0; - } LLUIImagePtr image = mStyle->getImage(); if( image.notNull()) @@ -2478,7 +2522,7 @@ bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& widt S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { - LLWString text = mEditor.getWText(); + const LLWString &text = mEditor.getWText(); return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset, (F32)segment_local_x_coord, F32_MAX, @@ -2488,12 +2532,12 @@ S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { - LLWString text = mEditor.getWText(); + const LLWString &text = mEditor.getWText(); LLUIImagePtr image = mStyle->getImage(); if( image.notNull()) { - num_pixels -= image->getWidth(); + num_pixels = llmax(0, num_pixels - image->getWidth()); } // search for newline and if found, truncate there @@ -2509,10 +2553,15 @@ S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin // 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, - TRUE); + word_wrap_style); if (num_chars == 0 && line_offset == 0 diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 038b9eaa62..5b24c63557 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -41,12 +41,14 @@ #include "llpanel.h" #include <string> +#include <vector> #include <set> #include <boost/signals2.hpp> class LLContextMenu; class LLTextSegment; +class LLNormalTextSegment; typedef LLPointer<LLTextSegment> LLTextSegmentPtr; @@ -60,6 +62,9 @@ class LLTextBase protected LLEditMenuHandler { public: + friend class LLTextSegment; + friend class LLNormalTextSegment; + struct LineSpacingParams : public LLInitParam::Choice<LineSpacingParams> { Alternative<F32> multiple; @@ -121,6 +126,7 @@ public: /*virtual*/ BOOL acceptsTextInput() const { return !mReadOnly; } /*virtual*/ void setColor( const LLColor4& c ); virtual void setReadOnlyColor(const LLColor4 &c); + virtual void handleVisibilityChange( BOOL new_visibility ); /*virtual*/ void setValue(const LLSD& value ); /*virtual*/ LLTextViewModel* getViewModel() const; @@ -144,11 +150,11 @@ public: // wide-char versions void setWText(const LLWString& text); - LLWString getWText() const; + const 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; } + void needsReflow(S32 index = 0); S32 getLength() const { return getWText().length(); } S32 getLineCount() const { return mLineInfoList.size(); } @@ -163,7 +169,7 @@ public: S32 getVPad() { return mVPad; } S32 getHPad() { return mHPad; } - S32 getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const; + S32 getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round, bool hit_past_end_of_line = true) const; LLRect getLocalRectFromDocIndex(S32 pos) const; LLRect getDocRectFromDocIndex(S32 pos) const; @@ -184,7 +190,6 @@ public: bool scrolledToEnd(); const LLFontGL* getDefaultFont() const { return mDefaultFont; } - LLStyle::Params getDefaultStyle(); public: // Fired when a URL link is clicked @@ -274,14 +279,15 @@ protected: // 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 ); + LLTextSegmentPtr getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line = true); 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); - + LLStyle::Params getDefaultStyleParams(); + // manage lines S32 getLineStart( S32 line ) const; S32 getLineEnd( S32 line ) const; @@ -290,7 +296,7 @@ protected: S32 getFirstVisibleLine() const; std::pair<S32, S32> getVisibleLines(bool fully_visible = false); S32 getLeftOffset(S32 width); - void reflow(S32 start_index = 0); + void reflow(); // cursor void updateCursorXPos(); @@ -360,7 +366,7 @@ protected: class LLScrollContainer* mScroller; // transient state - bool mReflowNeeded; // need to reflow text because of change to text contents or display region + S32 mReflowIndex; // index at which to start reflow. S32_MAX indicates no reflow needed. bool mScrollNeeded; // need to change scroll region because of change to cursor position S32 mScrollIndex; // index of first character to keep visible in scroll region @@ -388,9 +394,9 @@ public: 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 setColor(const LLColor4 &color); + virtual LLStyleConstSP getStyle() const; + virtual void setStyle(LLStyleConstSP style); virtual void setToken( LLKeywordToken* token ); virtual LLKeywordToken* getToken() const; virtual void setToolTip(const std::string& tooltip); @@ -426,7 +432,7 @@ protected: class LLNormalTextSegment : public LLTextSegment { public: - LLNormalTextSegment( const LLStyleSP& style, S32 start, S32 end, LLTextBase& editor ); + LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ); LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible = TRUE); ~LLNormalTextSegment(); @@ -436,9 +442,8 @@ public: /*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*/ LLStyleConstSP getStyle() const { return mStyle; } + /*virtual*/ void setStyle(LLStyleConstSP style) { mStyle = style; } /*virtual*/ void setToken( LLKeywordToken* token ) { mToken = token; } /*virtual*/ LLKeywordToken* getToken() const { return mToken; } /*virtual*/ BOOL getToolTip( std::string& msg ) const; @@ -456,7 +461,7 @@ protected: protected: class LLTextBase& mEditor; - LLStyleSP mStyle; + LLStyleConstSP mStyle; S32 mFontHeight; LLKeywordToken* mToken; std::string mTooltip; diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index e8fc9475a5..7d230f7d42 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -237,13 +237,17 @@ private: /////////////////////////////////////////////////////////////////// LLTextEditor::Params::Params() : default_text("default_text"), + prevalidate_callback("prevalidate_callback"), 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) -{} + commit_on_focus_lost("commit_on_focus_lost", false), + show_context_menu("show_context_menu") +{ + addSynonym(prevalidate_callback, "text_type"); +} LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : LLTextBase(p), @@ -258,7 +262,9 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : mMouseDownX(0), mMouseDownY(0), mTabsToNextField(p.ignore_tab), - mContextMenu(NULL) + mPrevalidateFunc(p.prevalidate_callback()), + mContextMenu(NULL), + mShowContextMenu(p.show_context_menu) { mDefaultFont = p.font; @@ -308,7 +314,8 @@ LLTextEditor::~LLTextEditor() // Scrollbar is deleted by LLView std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); - delete mContextMenu; + // context menu is owned by menu holder, not us + //delete mContextMenu; } //////////////////////////////////////////////////////////// @@ -317,6 +324,17 @@ LLTextEditor::~LLTextEditor() void LLTextEditor::setText(const LLStringExplicit &utf8str, const LLStyle::Params& input_params) { + // validate incoming text if necessary + if (mPrevalidateFunc) + { + LLWString test_text = utf8str_to_wstring(utf8str); + if (!mPrevalidateFunc(test_text)) + { + // not valid text, nothing to do + return; + } + } + blockUndo(); deselect(); @@ -719,7 +737,10 @@ BOOL LLTextEditor::handleRightMouseDown(S32 x, S32 y, MASK mask) } if (!LLTextBase::handleRightMouseDown(x, y, mask)) { - showContextMenu(x, y); + if(getShowContextMenu()) + { + showContextMenu(x, y); + } } return TRUE; } @@ -905,6 +926,21 @@ S32 LLTextEditor::execute( TextCmd* cmd ) // Push the new command is now on the top (front) of the undo stack. mUndoStack.push_front(cmd); mLastCmd = cmd; + + bool need_to_rollback = mPrevalidateFunc + && !mPrevalidateFunc(getViewModel()->getDisplay()); + if (need_to_rollback) + { + // get rid of this last command and clean up undo stack + undo(); + + // remove any evidence of this command from redo history + mUndoStack.pop_front(); + delete cmd; + + // failure, nothing changed + delta = 0; + } } else { @@ -1028,7 +1064,21 @@ S32 LLTextEditor::addChar(S32 pos, llwchar wc) if (mLastCmd && mLastCmd->canExtend(pos)) { S32 delta = 0; + if (mPrevalidateFunc) + { + // get a copy of current text contents + LLWString test_string(getViewModel()->getDisplay()); + + // modify text contents as if this addChar succeeded + llassert(pos <= (S32)test_string.size()); + test_string.insert(pos, 1, wc); + if (!mPrevalidateFunc( test_string)) + { + return 0; + } + } mLastCmd->extendAndExecute(this, pos, wc, &delta); + return delta; } else @@ -1284,8 +1334,6 @@ void LLTextEditor::cut() gClipboard.copyFromSubstring( getWText(), left_pos, length, mSourceID ); deleteSelection( FALSE ); - needsReflow(); - onKeyStroke(); } @@ -1390,8 +1438,6 @@ void LLTextEditor::pasteHelper(bool is_primary) setCursorPos(mCursorPos + insert(mCursorPos, clean_string, FALSE, LLTextSegmentPtr())); deselect(); - needsReflow(); - onKeyStroke(); } @@ -1786,8 +1832,6 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) if(text_may_have_changed) { - needsReflow(); - onKeyStroke(); } needsScroll(); @@ -1830,8 +1874,6 @@ BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char) // Most keystrokes will make the selection box go away, but not all will. deselect(); - needsReflow(); - onKeyStroke(); } @@ -1890,8 +1932,6 @@ void LLTextEditor::doDelete() } onKeyStroke(); - - needsReflow(); } //---------------------------------------------------------------------------- @@ -1934,8 +1974,6 @@ void LLTextEditor::undo() setCursorPos(pos); - needsReflow(); - onKeyStroke(); } @@ -1978,8 +2016,6 @@ void LLTextEditor::redo() setCursorPos(pos); - needsReflow(); - onKeyStroke(); } @@ -2039,6 +2075,20 @@ void LLTextEditor::showContextMenu(S32 x, S32 y) LLMenuHolderGL::child_registry_t::instance()); } + // Route menu to this class + // previously this was done in ::handleRightMoseDown: + //if(hasTabStop()) + // setFocus(TRUE) - why? weird... + // and then inside setFocus + // .... + // gEditMenuHandler = this; + // .... + // but this didn't work in all cases and just weird... + //why not here? + // (all this was done for EXT-4443) + + gEditMenuHandler = this; + S32 screen_x, screen_y; localPointToScreen(x, y, &screen_x, &screen_y); mContextMenu->show(screen_x, screen_y); @@ -2324,8 +2374,6 @@ void LLTextEditor::insertText(const std::string &new_text) setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE, LLTextSegmentPtr() )); - needsReflow(); - setEnabled( enabled ); } @@ -2348,8 +2396,6 @@ void LLTextEditor::appendWidget(const LLInlineViewSegment::Params& params, const LLTextSegmentPtr segment = new LLInlineViewSegment(params, old_length, old_length + widget_wide_text.size()); insert(getLength(), widget_wide_text, FALSE, segment); - needsReflow(); - // Set the cursor and scroll position if( selection_start != selection_end ) { @@ -2374,52 +2420,6 @@ void LLTextEditor::appendWidget(const LLInlineViewSegment::Params& params, const } } - -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; - - // 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 LLTextEditor::removeTextFromEnd(S32 num_chars) { if (num_chars <= 0) return; @@ -2431,7 +2431,6 @@ void LLTextEditor::removeTextFromEnd(S32 num_chars) mSelectionStart = llclamp(mSelectionStart, 0, len); mSelectionEnd = llclamp(mSelectionEnd, 0, len); - needsReflow(); needsScroll(); } @@ -2490,8 +2489,6 @@ BOOL LLTextEditor::tryToRevertToPristineState() i--; } } - - needsReflow(); } return isPristine(); // TRUE => success @@ -2527,9 +2524,9 @@ void LLTextEditor::loadKeywords(const std::string& filename, void LLTextEditor::updateSegments() { - LLFastTimer ft(FTM_SYNTAX_HIGHLIGHTING); - if (mKeywords.isLoaded()) + if (mReflowIndex < S32_MAX && mKeywords.isLoaded()) { + LLFastTimer ft(FTM_SYNTAX_HIGHLIGHTING); // HACK: No non-ascii keywords for now segment_vec_t segment_list; mKeywords.findSegments(&segment_list, getWText(), mDefaultColor.get(), *this); @@ -2558,13 +2555,16 @@ void LLTextEditor::updateLinkSegments() // 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()); + LLStyleConstSP style = segment->getStyle(); + LLStyleSP new_style(new LLStyle(*style)); LLWString url_label = wtext.substr(segment->getStart(), segment->getEnd()-segment->getStart()); if (LLUrlRegistry::instance().hasUrl(url_label)) { std::string new_url = wstring_to_utf8str(url_label); LLStringUtil::trim(new_url); - style->setLinkHREF(new_url); + new_style->setLinkHREF(new_url); + LLStyleConstSP sp(new_style); + segment->setStyle(sp); } } } @@ -2663,7 +2663,6 @@ BOOL LLTextEditor::importBuffer(const char* buffer, S32 length ) startOfDoc(); deselect(); - needsReflow(); return success; } @@ -2767,7 +2766,6 @@ void LLTextEditor::updatePreedit(const LLWString &preedit_string, mPreeditStandouts = preedit_standouts; - needsReflow(); setCursorPos(insert_preedit_at + caret_position); // Update of the preedit should be caused by some key strokes. diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 043dda8fa6..71d937b2c4 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -44,6 +44,7 @@ #include "lldarray.h" #include "llviewborder.h" // for params #include "lltextbase.h" +#include "lltextvalidate.h" #include "llpreeditor.h" #include "llcontrol.h" @@ -63,12 +64,14 @@ public: struct Params : public LLInitParam::Block<Params, LLTextBase::Params> { Optional<std::string> default_text; + Optional<LLTextValidate::validate_func_t, LLTextValidate::ValidateTextNamedFuncs> prevalidate_callback; Optional<bool> embedded_items, ignore_tab, handle_edit_keys_directly, show_line_numbers, - commit_on_focus_lost; + commit_on_focus_lost, + show_context_menu; //colors Optional<LLUIColor> default_color; @@ -149,7 +152,6 @@ 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(); @@ -201,6 +203,9 @@ public: const LLTextSegmentPtr getPreviousSegment() const; void getSelectedSegments(segment_vec_t& segments) const; + void setShowContextMenu(bool show) { mShowContextMenu = show; } + bool getShowContextMenu() const { return mShowContextMenu; } + protected: void showContextMenu(S32 x, S32 y); void drawPreeditMarker(); @@ -320,6 +325,7 @@ private: BOOL mTakesFocus; BOOL mAllowEmbeddedItems; + bool mShowContextMenu; LLUUID mSourceID; @@ -330,6 +336,7 @@ private: LLCoordGL mLastIMEPosition; // Last position of the IME editor keystroke_signal_t mKeystrokeSignal; + LLTextValidate::validate_func_t mPrevalidateFunc; LLContextMenu* mContextMenu; }; // end class LLTextEditor diff --git a/indra/llui/lltextvalidate.cpp b/indra/llui/lltextvalidate.cpp new file mode 100644 index 0000000000..8b6bc5bd7d --- /dev/null +++ b/indra/llui/lltextvalidate.cpp @@ -0,0 +1,302 @@ +/** + * @file lltextvalidate.cpp + * @brief Text validation helper functions + * + * $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 "lltextvalidate.h" +#include "llresmgr.h" // for LLLocale + +namespace LLTextValidate +{ + void ValidateTextNamedFuncs::declareValues() + { + declare("ascii", validateASCII); + declare("float", validateFloat); + declare("int", validateInt); + declare("positive_s32", validatePositiveS32); + declare("non_negative_s32", validateNonNegativeS32); + declare("alpha_num", validateAlphaNum); + declare("alpha_num_space", validateAlphaNumSpace); + declare("ascii_printable_no_pipe", validateASCIIPrintableNoPipe); + declare("ascii_printable_no_space", validateASCIIPrintableNoSpace); + } + + // Limits what characters can be used to [1234567890.-] with [-] only valid in the first position. + // Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for + // the simple reasons that intermediate states may be invalid even if the final result is valid. + // + bool validateFloat(const LLWString &str) + { + LLLocale locale(LLLocale::USER_LOCALE); + + bool success = TRUE; + LLWString trimmed = str; + LLWStringUtil::trim(trimmed); + S32 len = trimmed.length(); + if( 0 < len ) + { + // May be a comma or period, depending on the locale + llwchar decimal_point = (llwchar)LLResMgr::getInstance()->getDecimalPoint(); + + S32 i = 0; + + // First character can be a negative sign + if( '-' == trimmed[0] ) + { + i++; + } + + for( ; i < len; i++ ) + { + if( (decimal_point != trimmed[i] ) && !LLStringOps::isDigit( trimmed[i] ) ) + { + success = FALSE; + break; + } + } + } + + return success; + } + + // Limits what characters can be used to [1234567890-] with [-] only valid in the first position. + // Does NOT ensure that the string is a well-formed number--that's the job of post-validation--for + // the simple reasons that intermediate states may be invalid even if the final result is valid. + // + bool validateInt(const LLWString &str) + { + LLLocale locale(LLLocale::USER_LOCALE); + + bool success = TRUE; + LLWString trimmed = str; + LLWStringUtil::trim(trimmed); + S32 len = trimmed.length(); + if( 0 < len ) + { + S32 i = 0; + + // First character can be a negative sign + if( '-' == trimmed[0] ) + { + i++; + } + + for( ; i < len; i++ ) + { + if( !LLStringOps::isDigit( trimmed[i] ) ) + { + success = FALSE; + break; + } + } + } + + return success; + } + + bool validatePositiveS32(const LLWString &str) + { + LLLocale locale(LLLocale::USER_LOCALE); + + LLWString trimmed = str; + LLWStringUtil::trim(trimmed); + S32 len = trimmed.length(); + bool success = TRUE; + if(0 < len) + { + if(('-' == trimmed[0]) || ('0' == trimmed[0])) + { + success = FALSE; + } + S32 i = 0; + while(success && (i < len)) + { + if(!LLStringOps::isDigit(trimmed[i++])) + { + success = FALSE; + } + } + } + if (success) + { + S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10); + if (val <= 0) + { + success = FALSE; + } + } + return success; + } + + bool validateNonNegativeS32(const LLWString &str) + { + LLLocale locale(LLLocale::USER_LOCALE); + + LLWString trimmed = str; + LLWStringUtil::trim(trimmed); + S32 len = trimmed.length(); + bool success = TRUE; + if(0 < len) + { + if('-' == trimmed[0]) + { + success = FALSE; + } + S32 i = 0; + while(success && (i < len)) + { + if(!LLStringOps::isDigit(trimmed[i++])) + { + success = FALSE; + } + } + } + if (success) + { + S32 val = strtol(wstring_to_utf8str(trimmed).c_str(), NULL, 10); + if (val < 0) + { + success = FALSE; + } + } + return success; + } + + bool validateAlphaNum(const LLWString &str) + { + LLLocale locale(LLLocale::USER_LOCALE); + + bool rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + if( !LLStringOps::isAlnum((char)str[len]) ) + { + rv = FALSE; + break; + } + } + return rv; + } + + bool validateAlphaNumSpace(const LLWString &str) + { + LLLocale locale(LLLocale::USER_LOCALE); + + bool rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + if(!(LLStringOps::isAlnum((char)str[len]) || (' ' == str[len]))) + { + rv = FALSE; + break; + } + } + 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. + bool validateASCIIPrintableNoPipe(const LLWString &str) + { + bool rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + llwchar wc = str[len]; + if (wc < 0x20 + || wc > 0x7f + || wc == '|') + { + rv = FALSE; + break; + } + if(!(wc == ' ' + || LLStringOps::isAlnum((char)wc) + || LLStringOps::isPunct((char)wc) ) ) + { + rv = FALSE; + break; + } + } + return rv; + } + + + // Used for avatar names + bool validateASCIIPrintableNoSpace(const LLWString &str) + { + bool rv = TRUE; + S32 len = str.length(); + if(len == 0) return rv; + while(len--) + { + llwchar wc = str[len]; + if (wc < 0x20 + || wc > 0x7f + || LLStringOps::isSpace(wc)) + { + rv = FALSE; + break; + } + if( !(LLStringOps::isAlnum((char)str[len]) || + LLStringOps::isPunct((char)str[len]) ) ) + { + rv = FALSE; + break; + } + } + return rv; + } + + bool validateASCII(const LLWString &str) + { + bool rv = TRUE; + S32 len = str.length(); + while(len--) + { + if (str[len] < 0x20 || str[len] > 0x7f) + { + rv = FALSE; + break; + } + } + return rv; + } +} diff --git a/indra/llui/lltextvalidate.h b/indra/llui/lltextvalidate.h new file mode 100644 index 0000000000..ffb4e85e7c --- /dev/null +++ b/indra/llui/lltextvalidate.h @@ -0,0 +1,63 @@ +/** + * @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_LLTEXTVALIDATE_H +#define LL_LLTEXTVALIDATE_H + +#include "llstring.h" +#include "llinitparam.h" +#include <boost/function.hpp> + +namespace LLTextValidate +{ + typedef boost::function<BOOL (const LLWString &wstr)> validate_func_t; + + struct ValidateTextNamedFuncs + : public LLInitParam::TypeValuesHelper<validate_func_t, ValidateTextNamedFuncs> + { + static void declareValues(); + }; + + bool validateFloat(const LLWString &str ); + bool validateInt(const LLWString &str ); + bool validatePositiveS32(const LLWString &str); + bool validateNonNegativeS32(const LLWString &str); + bool validateAlphaNum(const LLWString &str ); + bool validateAlphaNumSpace(const LLWString &str ); + bool validateASCIIPrintableNoPipe(const LLWString &str); + bool validateASCIIPrintableNoSpace(const LLWString &str); + bool validateASCII(const LLWString &str); +} + + +#endif diff --git a/indra/llui/lltooltip.cpp b/indra/llui/lltooltip.cpp index 01c7a81309..ed7fd02e14 100644 --- a/indra/llui/lltooltip.cpp +++ b/indra/llui/lltooltip.cpp @@ -129,12 +129,6 @@ BOOL LLToolTipView::handleScrollWheel( S32 x, S32 y, S32 clicks ) 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); @@ -400,7 +394,8 @@ bool LLToolTip::hasClickCallback() // LLToolTipMgr::LLToolTipMgr() -: mToolTip(NULL), +: mToolTipsBlocked(false), + mToolTip(NULL), mNeedsToolTip(false) {} diff --git a/indra/llui/lltooltip.h b/indra/llui/lltooltip.h index 7978b6a583..24e32b9b24 100644 --- a/indra/llui/lltooltip.h +++ b/indra/llui/lltooltip.h @@ -56,8 +56,6 @@ public: /*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(); @@ -129,7 +127,8 @@ private: class LLInspector : public LLToolTip { public: - struct Params : public LLInitParam::Block<Params, LLToolTip::Params> {}; + struct Params : public LLInitParam::Block<Params, LLToolTip::Params> + {}; }; class LLToolTipMgr : public LLSingleton<LLToolTipMgr> diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index d0ed3b6fca..b348ec2d29 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -39,6 +39,7 @@ // Linden library includes #include "v2math.h" +#include "m3math.h" #include "v4color.h" #include "llrender.h" #include "llrect.h" @@ -180,19 +181,19 @@ void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, const LL void gl_rect_2d_offset_local( S32 left, S32 top, S32 right, S32 bottom, S32 pixel_offset, BOOL filled) { - gGL.pushMatrix(); + gGL.pushUIMatrix(); left += LLFontGL::sCurOrigin.mX; right += LLFontGL::sCurOrigin.mX; bottom += LLFontGL::sCurOrigin.mY; top += LLFontGL::sCurOrigin.mY; - glLoadIdentity(); + gGL.loadUIIdentity(); gl_rect_2d(llfloor((F32)left * LLUI::sGLScaleFactor.mV[VX]) - pixel_offset, llfloor((F32)top * LLUI::sGLScaleFactor.mV[VY]) + pixel_offset, llfloor((F32)right * LLUI::sGLScaleFactor.mV[VX]) + pixel_offset, llfloor((F32)bottom * LLUI::sGLScaleFactor.mV[VY]) - pixel_offset, filled); - gGL.popMatrix(); + gGL.popUIMatrix(); } @@ -508,9 +509,9 @@ void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLTex gGL.getTexUnit(0)->setTextureAlphaBlend(LLTexUnit::TBO_MULT, LLTexUnit::TBS_TEX_ALPHA, LLTexUnit::TBS_VERT_ALPHA); } - gGL.pushMatrix(); + gGL.pushUIMatrix(); { - gGL.translatef((F32)x, (F32)y, 0.f); + gGL.translateUI((F32)x, (F32)y, 0.f); gGL.getTexUnit(0)->bind(image); @@ -637,7 +638,7 @@ void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLTex } gGL.end(); } - gGL.popMatrix(); + gGL.popUIMatrix(); if (solid_color) { @@ -660,39 +661,72 @@ void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degre LLGLSUIDefault gls_ui; - gGL.pushMatrix(); + + gGL.getTexUnit(0)->bind(image); + + gGL.color4fv(color.mV); + + if (degrees == 0.f) { - gGL.translatef((F32)x, (F32)y, 0.f); - if( degrees ) + gGL.pushUIMatrix(); + gGL.translateUI((F32)x, (F32)y, 0.f); + + gGL.begin(LLRender::QUADS); { - F32 offset_x = F32(width/2); - F32 offset_y = F32(height/2); - gGL.translatef( offset_x, offset_y, 0.f); - glRotatef( degrees, 0.f, 0.f, 1.f ); - gGL.translatef( -offset_x, -offset_y, 0.f ); + gGL.texCoord2f(uv_rect.mRight, uv_rect.mTop); + gGL.vertex2i(width, height ); + + gGL.texCoord2f(uv_rect.mLeft, uv_rect.mTop); + gGL.vertex2i(0, height ); + + gGL.texCoord2f(uv_rect.mLeft, uv_rect.mBottom); + gGL.vertex2i(0, 0); + + gGL.texCoord2f(uv_rect.mRight, uv_rect.mBottom); + gGL.vertex2i(width, 0); } + gGL.end(); + gGL.popUIMatrix(); + } + else + { + gGL.pushUIMatrix(); + gGL.translateUI((F32)x, (F32)y, 0.f); + + F32 offset_x = F32(width/2); + F32 offset_y = F32(height/2); + gGL.translateUI(offset_x, offset_y, 0.f); + + LLMatrix3 quat(0.f, 0.f, degrees*DEG_TO_RAD); + gGL.getTexUnit(0)->bind(image); gGL.color4fv(color.mV); gGL.begin(LLRender::QUADS); { + LLVector3 v; + + v = LLVector3(offset_x, offset_y, 0.f) * quat; gGL.texCoord2f(uv_rect.mRight, uv_rect.mTop); - gGL.vertex2i(width, height ); + gGL.vertex2f(v.mV[0], v.mV[1] ); + v = LLVector3(-offset_x, offset_y, 0.f) * quat; gGL.texCoord2f(uv_rect.mLeft, uv_rect.mTop); - gGL.vertex2i(0, height ); + gGL.vertex2f(v.mV[0], v.mV[1] ); + v = LLVector3(-offset_x, -offset_y, 0.f) * quat; gGL.texCoord2f(uv_rect.mLeft, uv_rect.mBottom); - gGL.vertex2i(0, 0); + gGL.vertex2f(v.mV[0], v.mV[1] ); + v = LLVector3(offset_x, -offset_y, 0.f) * quat; gGL.texCoord2f(uv_rect.mRight, uv_rect.mBottom); - gGL.vertex2i(width, 0); + gGL.vertex2f(v.mV[0], v.mV[1] ); } gGL.end(); + gGL.popUIMatrix(); } - gGL.popMatrix(); } @@ -747,9 +781,9 @@ void gl_arc_2d(F32 center_x, F32 center_y, F32 radius, S32 steps, BOOL filled, F end_angle += F_TWO_PI; } - gGL.pushMatrix(); + gGL.pushUIMatrix(); { - gGL.translatef(center_x, center_y, 0.f); + gGL.translateUI(center_x, center_y, 0.f); // Inexact, but reasonably fast. F32 delta = (end_angle - start_angle) / steps; @@ -780,15 +814,15 @@ void gl_arc_2d(F32 center_x, F32 center_y, F32 radius, S32 steps, BOOL filled, F } gGL.end(); } - gGL.popMatrix(); + gGL.popUIMatrix(); } void gl_circle_2d(F32 center_x, F32 center_y, F32 radius, S32 steps, BOOL filled) { - gGL.pushMatrix(); + gGL.pushUIMatrix(); { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.translatef(center_x, center_y, 0.f); + gGL.translateUI(center_x, center_y, 0.f); // Inexact, but reasonably fast. F32 delta = F_TWO_PI / steps; @@ -819,7 +853,7 @@ void gl_circle_2d(F32 center_x, F32 center_y, F32 radius, S32 steps, BOOL filled } gGL.end(); } - gGL.popMatrix(); + gGL.popUIMatrix(); } // Renders a ring with sides (tube shape) @@ -846,9 +880,9 @@ void gl_deep_circle( F32 radius, F32 depth, S32 steps ) void gl_ring( F32 radius, F32 width, const LLColor4& center_color, const LLColor4& side_color, S32 steps, BOOL render_center ) { - gGL.pushMatrix(); + gGL.pushUIMatrix(); { - gGL.translatef(0.f, 0.f, -width / 2); + gGL.translateUI(0.f, 0.f, -width / 2); if( render_center ) { gGL.color4fv(center_color.mV); @@ -857,11 +891,11 @@ void gl_ring( F32 radius, F32 width, const LLColor4& center_color, const LLColor else { gl_washer_2d(radius, radius - width, steps, side_color, side_color); - gGL.translatef(0.f, 0.f, width); + gGL.translateUI(0.f, 0.f, width); gl_washer_2d(radius - width, radius, steps, side_color, side_color); } } - gGL.popMatrix(); + gGL.popUIMatrix(); } // Draw gray and white checkerboard with black border @@ -1050,9 +1084,9 @@ void gl_segmented_rect_2d_tex(const S32 left, S32 width = llabs(right - left); S32 height = llabs(top - bottom); - gGL.pushMatrix(); + gGL.pushUIMatrix(); - gGL.translatef((F32)left, (F32)bottom, 0.f); + gGL.translateUI((F32)left, (F32)bottom, 0.f); LLVector2 border_uv_scale((F32)border_size / (F32)texture_width, (F32)border_size / (F32)texture_height); if (border_uv_scale.mV[VX] > 0.5f) @@ -1193,7 +1227,7 @@ void gl_segmented_rect_2d_tex(const S32 left, } gGL.end(); - gGL.popMatrix(); + gGL.popUIMatrix(); } void gl_segmented_rect_2d_fragment_tex(const S32 left, @@ -1210,9 +1244,9 @@ void gl_segmented_rect_2d_fragment_tex(const S32 left, S32 width = llabs(right - left); S32 height = llabs(top - bottom); - gGL.pushMatrix(); + gGL.pushUIMatrix(); - gGL.translatef((F32)left, (F32)bottom, 0.f); + gGL.translateUI((F32)left, (F32)bottom, 0.f); LLVector2 border_uv_scale((F32)border_size / (F32)texture_width, (F32)border_size / (F32)texture_height); if (border_uv_scale.mV[VX] > 0.5f) @@ -1383,7 +1417,7 @@ void gl_segmented_rect_2d_fragment_tex(const S32 left, } gGL.end(); - gGL.popMatrix(); + gGL.popUIMatrix(); } void gl_segmented_rect_3d_tex(const LLVector2& border_scale, const LLVector3& border_width, @@ -1591,7 +1625,7 @@ void LLUI::dirtyRect(LLRect rect) //static void LLUI::translate(F32 x, F32 y, F32 z) { - gGL.translatef(x,y,z); + gGL.translateUI(x,y,z); LLFontGL::sCurOrigin.mX += (S32) x; LLFontGL::sCurOrigin.mY += (S32) y; LLFontGL::sCurOrigin.mZ += z; @@ -1600,14 +1634,14 @@ void LLUI::translate(F32 x, F32 y, F32 z) //static void LLUI::pushMatrix() { - gGL.pushMatrix(); + gGL.pushUIMatrix(); LLFontGL::sOriginStack.push_back(LLFontGL::sCurOrigin); } //static void LLUI::popMatrix() { - gGL.popMatrix(); + gGL.popUIMatrix(); LLFontGL::sCurOrigin = *LLFontGL::sOriginStack.rbegin(); LLFontGL::sOriginStack.pop_back(); } @@ -1615,7 +1649,7 @@ void LLUI::popMatrix() //static void LLUI::loadIdentity() { - glLoadIdentity(); + gGL.loadUIIdentity(); LLFontGL::sCurOrigin.mX = 0; LLFontGL::sCurOrigin.mY = 0; LLFontGL::sCurOrigin.mZ = 0; @@ -1894,7 +1928,9 @@ namespace LLInitParam blue("blue"), alpha("alpha"), control("") - {} + { + setBlockFromValue(); + } void TypedParam<LLUIColor>::setValueFromBlock() const { @@ -1911,10 +1947,10 @@ namespace LLInitParam 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]; + red.set(color.mV[VRED], false); + green.set(color.mV[VGREEN], false); + blue.set(color.mV[VBLUE], false); + alpha.set(color.mV[VALPHA], false); control.set("", false); } @@ -1939,7 +1975,9 @@ namespace LLInitParam size("size"), style("style") { + setBlockFromValue(); addSynonym(name, ""); + setBlockFromValue(); } void TypedParam<const LLFontGL*>::setValueFromBlock() const @@ -1965,9 +2003,9 @@ namespace LLInitParam { if (mData.mValue) { - name = LLFontGL::nameFromFont(mData.mValue); - size = LLFontGL::sizeFromFont(mData.mValue); - style = LLFontGL::getStringFromStyle(mData.mValue->getFontDesc().getStyle()); + name.set(LLFontGL::nameFromFont(mData.mValue), false); + size.set(LLFontGL::sizeFromFont(mData.mValue), false); + style.set(LLFontGL::getStringFromStyle(mData.mValue->getFontDesc().getStyle()), false); } } @@ -1979,7 +2017,9 @@ namespace LLInitParam bottom("bottom"), width("width"), height("height") - {} + { + setBlockFromValue(); + } void TypedParam<LLRect>::setValueFromBlock() const { @@ -2064,6 +2104,7 @@ namespace LLInitParam x("x"), y("y") { + setBlockFromValue(); } void TypedParam<LLCoordGL>::setValueFromBlock() const @@ -2073,8 +2114,8 @@ namespace LLInitParam void TypedParam<LLCoordGL>::setBlockFromValue() { - x = mData.mValue.mX; - y = mData.mValue.mY; + x.set(mData.mValue.mX, false); + y.set(mData.mValue.mY, false); } diff --git a/indra/llui/llui.h b/indra/llui/llui.h index 5840e76f5c..af8d4ea03b 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -426,8 +426,8 @@ namespace LLInitParam { typedef BlockValue<const LLFontGL*> super_t; public: - Mandatory<std::string> name; - Optional<std::string> size, + Optional<std::string> name, + size, style; TypedParam(BlockDescriptor& descriptor, const char* name, const LLFontGL* const value, ParamDescriptor::validation_func_t func, S32 min_count, S32 max_count); diff --git a/indra/llui/lluicolortable.cpp b/indra/llui/lluicolortable.cpp index 9be33483d0..1b64ef3abe 100644 --- a/indra/llui/lluicolortable.cpp +++ b/indra/llui/lluicolortable.cpp @@ -56,7 +56,7 @@ LLUIColorTable::Params::Params() { } -void LLUIColorTable::insertFromParams(const Params& p) +void LLUIColorTable::insertFromParams(const Params& p, string_color_map_t& table) { // this map will contain all color references after the following loop typedef std::map<std::string, std::string> string_string_map_t; @@ -69,14 +69,7 @@ void LLUIColorTable::insertFromParams(const Params& p) 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); - } + setColor(color_entry.name, color_entry.color.value, table); } else { @@ -220,16 +213,16 @@ bool LLUIColorTable::loadFromSettings() bool result = false; std::string default_filename = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "colors.xml"); - result |= loadFromFilename(default_filename); + result |= loadFromFilename(default_filename, mLoadedColors); std::string current_filename = gDirUtilp->getExpandedFilename(LL_PATH_TOP_SKIN, "colors.xml"); if(current_filename != default_filename) { - result |= loadFromFilename(current_filename); + result |= loadFromFilename(current_filename, mLoadedColors); } std::string user_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "colors.xml"); - loadFromFilename(user_filename); + loadFromFilename(user_filename, mUserSetColors); return result; } @@ -299,7 +292,7 @@ void LLUIColorTable::setColor(const std::string& name, const LLColor4& color, st } } -bool LLUIColorTable::loadFromFilename(const std::string& filename) +bool LLUIColorTable::loadFromFilename(const std::string& filename, string_color_map_t& table) { LLXMLNodePtr root; @@ -320,7 +313,7 @@ bool LLUIColorTable::loadFromFilename(const std::string& filename) if(params.validateBlock()) { - insertFromParams(params); + insertFromParams(params, table); } else { @@ -330,3 +323,11 @@ bool LLUIColorTable::loadFromFilename(const std::string& filename) return true; } + +void LLUIColorTable::insertFromParams(const Params& p) +{ + insertFromParams(p, mUserSetColors); +} + +// EOF + diff --git a/indra/llui/lluicolortable.h b/indra/llui/lluicolortable.h index 59be0c4f9a..d401e5e724 100644 --- a/indra/llui/lluicolortable.h +++ b/indra/llui/lluicolortable.h @@ -45,6 +45,10 @@ class LLUIColor; class LLUIColorTable : public LLSingleton<LLUIColorTable> { LOG_CLASS(LLUIColorTable); + + // consider using sorted vector, can be much faster + typedef std::map<std::string, LLUIColor> string_color_map_t; + public: struct ColorParams : LLInitParam::Choice<ColorParams> { @@ -91,10 +95,9 @@ public: void saveUserSettings() const; private: - bool loadFromFilename(const std::string& filename); + bool loadFromFilename(const std::string& filename, string_color_map_t& table); - // consider using sorted vector, can be much faster - typedef std::map<std::string, LLColor4> string_color_map_t; + void insertFromParams(const Params& p, string_color_map_t& table); void clearTable(string_color_map_t& table); void setColor(const std::string& name, const LLColor4& color, string_color_map_t& table); diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 6044908ca7..3ade46d367 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -868,6 +868,14 @@ bool LLUICtrl::findHelpTopic(std::string& help_topic_out) 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) diff --git a/indra/llui/lluiimage.cpp b/indra/llui/lluiimage.cpp index 966d919dc7..8cd6460b66 100644 --- a/indra/llui/lluiimage.cpp +++ b/indra/llui/lluiimage.cpp @@ -182,11 +182,11 @@ namespace LLInitParam { if (mData.mValue == NULL) { - name = "none"; + name.set("none", false); } else { - name = mData.mValue->getName(); + name.set(mData.mValue->getName(), false); } } diff --git a/indra/llui/lluiimage.h b/indra/llui/lluiimage.h index bdfc44262d..4ea0738026 100644 --- a/indra/llui/lluiimage.h +++ b/indra/llui/lluiimage.h @@ -109,6 +109,7 @@ namespace LLInitParam 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) { + setBlockFromValue(); } void setValueFromBlock() const; diff --git a/indra/llui/lluistring.h b/indra/llui/lluistring.h index 7ec0fd603a..32cfc0d9cd 100644 --- a/indra/llui/lluistring.h +++ b/indra/llui/lluistring.h @@ -64,7 +64,7 @@ class LLUIString public: // These methods all perform appropriate argument substitution // and modify mOrig where appropriate - LLUIString() {} + LLUIString() : mNeedsResult(false), mNeedsWResult(false) {} LLUIString(const std::string& instring, const LLStringUtil::format_map_t& args); LLUIString(const std::string& instring) { assign(instring); } diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 983f0a2d49..20c939874b 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -39,8 +39,9 @@ #include "lltrans.h" #include "lluicolortable.h" -LLUrlEntryBase::LLUrlEntryBase() -: mColor(LLUIColorTable::instance().getColor("HTMLLinkColor")) +LLUrlEntryBase::LLUrlEntryBase() : + mColor(LLUIColorTable::instance().getColor("HTMLLinkColor")), + mDisabledLink(false) { } @@ -48,7 +49,7 @@ LLUrlEntryBase::~LLUrlEntryBase() { } -std::string LLUrlEntryBase::getUrl(const std::string &string) +std::string LLUrlEntryBase::getUrl(const std::string &string) const { return escapeUrl(string); } @@ -88,7 +89,7 @@ std::string LLUrlEntryBase::escapeUrl(const std::string &url) const return LLURI::escape(url, no_escape_chars, true); } -std::string LLUrlEntryBase::getLabelFromWikiLink(const std::string &url) +std::string LLUrlEntryBase::getLabelFromWikiLink(const std::string &url) const { // return the label part from [http://www.example.org Label] const char *text = url.c_str(); @@ -101,10 +102,10 @@ std::string LLUrlEntryBase::getLabelFromWikiLink(const std::string &url) { start++; } - return url.substr(start, url.size()-start-1); + return unescapeUrl(url.substr(start, url.size()-start-1)); } -std::string LLUrlEntryBase::getUrlFromWikiLink(const std::string &string) +std::string LLUrlEntryBase::getUrlFromWikiLink(const std::string &string) const { // return the url part from [http://www.example.org Label] const char *text = string.c_str(); @@ -191,7 +192,7 @@ std::string LLUrlEntryHTTPLabel::getLabel(const std::string &url, const LLUrlLab return getLabelFromWikiLink(url); } -std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string) +std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string) const { return getUrlFromWikiLink(string); } @@ -201,8 +202,12 @@ std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string) // LLUrlEntryHTTPNoProtocol::LLUrlEntryHTTPNoProtocol() { - mPattern = boost::regex("(\\bwww\\.\\S+\\.\\S+|\\b[^ \t\n\r\f\v:/]+.com\\S*|\\b[^ \t\n\r\f\v:/]+.net\\S*|\\b[^ \t\n\r\f\v:/]+.edu\\S*|\\b[^ \t\n\r\f\v:/]+.org\\S*)", - boost::regex::perl|boost::regex::icase); + mPattern = boost::regex("(" + "\\bwww\\.\\S+\\.\\S+" // i.e. www.FOO.BAR + "|" // or + "(?<!@)\\b[^[:space:]:@/>]+\\.(?:com|net|edu|org)([/:][^[:space:]<]*)?\\b" // i.e. FOO.net + ")", + boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_http.xml"; mTooltip = LLTrans::getString("TooltipHttpUrl"); } @@ -212,7 +217,7 @@ std::string LLUrlEntryHTTPNoProtocol::getLabel(const std::string &url, const LLU return unescapeUrl(url); } -std::string LLUrlEntryHTTPNoProtocol::getUrl(const std::string &string) +std::string LLUrlEntryHTTPNoProtocol::getUrl(const std::string &string) const { if (string.find("://") == std::string::npos) { @@ -227,7 +232,7 @@ std::string LLUrlEntryHTTPNoProtocol::getUrl(const std::string &string) 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*", + mPattern = boost::regex("http://(maps.secondlife.com|slurl.com)/secondlife/[^ /]+(/\\d+){0,3}(/?(\\?title|\\?img|\\?msg)=\\S*)?/?", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_slurl.xml"; mTooltip = LLTrans::getString("TooltipSLURL"); @@ -282,7 +287,7 @@ std::string LLUrlEntrySLURL::getLabel(const std::string &url, const LLUrlLabelCa 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"; + const std::string search_string = "/secondlife"; size_t pos = url.find(search_string); if (pos == std::string::npos) { @@ -592,7 +597,7 @@ std::string LLUrlEntrySLLabel::getLabel(const std::string &url, const LLUrlLabel return getLabelFromWikiLink(url); } -std::string LLUrlEntrySLLabel::getUrl(const std::string &string) +std::string LLUrlEntrySLLabel::getUrl(const std::string &string) const { return getUrlFromWikiLink(string); } @@ -637,3 +642,24 @@ std::string LLUrlEntryWorldMap::getLocation(const std::string &url) const // return the part of the Url after secondlife:///app/worldmap/ part return ::getStringAfterToken(url, "app/worldmap/"); } + +// +// LLUrlEntryNoLink lets us turn of URL detection with <nolink>...</nolink> tags +// +LLUrlEntryNoLink::LLUrlEntryNoLink() +{ + mPattern = boost::regex("<nolink>[^<]*</nolink>", + boost::regex::perl|boost::regex::icase); + mDisabledLink = true; +} + +std::string LLUrlEntryNoLink::getUrl(const std::string &url) const +{ + // return the text between the <nolink> and </nolink> tags + return url.substr(8, url.size()-8-9); +} + +std::string LLUrlEntryNoLink::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return getUrl(url); +} diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 4adffde99c..3abada0f24 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -71,7 +71,7 @@ public: boost::regex getPattern() const { return mPattern; } /// Return the url from a string that matched the regex - virtual std::string getUrl(const std::string &string); + virtual std::string getUrl(const std::string &string) const; /// Given a matched Url, return a label for the Url virtual std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) { return url; } @@ -91,12 +91,15 @@ public: /// Return the name of a SL location described by this Url, if any virtual std::string getLocation(const std::string &url) const { return ""; } + /// is this a match for a URL that should not be hyperlinked? + bool isLinkDisabled() const { return mDisabledLink; } + 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); + std::string getLabelFromWikiLink(const std::string &url) const; + std::string getUrlFromWikiLink(const std::string &string) const; void addObserver(const std::string &id, const std::string &url, const LLUrlLabelCallback &cb); void callObservers(const std::string &id, const std::string &label); @@ -111,6 +114,7 @@ protected: std::string mTooltip; LLUIColor mColor; std::multimap<std::string, LLUrlEntryObserver> mObservers; + bool mDisabledLink; }; /// @@ -131,7 +135,7 @@ 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); + /*virtual*/ std::string getUrl(const std::string &string) const; }; /// @@ -142,7 +146,7 @@ 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); + /*virtual*/ std::string getUrl(const std::string &string) const; }; /// @@ -252,7 +256,7 @@ 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); + /*virtual*/ std::string getUrl(const std::string &string) const; }; /// @@ -267,4 +271,15 @@ public: /*virtual*/ std::string getLocation(const std::string &url) const; }; +/// +/// LLUrlEntryNoLink lets us turn of URL detection with <nolink>...</nolink> tags +/// +class LLUrlEntryNoLink : public LLUrlEntryBase +{ +public: + LLUrlEntryNoLink(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getUrl(const std::string &string) const; +}; + #endif diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp index 3b47145a22..72a199c220 100644 --- a/indra/llui/llurlmatch.cpp +++ b/indra/llui/llurlmatch.cpp @@ -41,14 +41,17 @@ LLUrlMatch::LLUrlMatch() : mLabel(""), mTooltip(""), mIcon(""), - mMenuName("") + mMenuName(""), + mLocation(""), + mDisabledLink(false) { } 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) + const std::string &menu, const std::string &location, + bool disabled_link) { mStart = start; mEnd = end; @@ -59,4 +62,5 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, mColor = color; mMenuName = menu; mLocation = location; + mDisabledLink = disabled_link; } diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index 7f5767923a..e86762548b 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -83,11 +83,14 @@ public: /// return the SL location that this Url describes, or "" if none. std::string getLocation() const { return mLocation; } + /// is this a match for a URL that should not be hyperlinked? + bool isLinkDisabled() const { return mDisabledLink; } + /// 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); + const std::string &location, bool disabled_link); private: U32 mStart; @@ -99,6 +102,7 @@ private: std::string mMenuName; std::string mLocation; LLUIColor mColor; + bool mDisabledLink; }; #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index ad5c0911f8..722dbe41b3 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -44,6 +44,7 @@ void LLUrlRegistryNullCallback(const std::string &url, const std::string &label) LLUrlRegistry::LLUrlRegistry() { // Urls are matched in the order that they were registered + registerUrl(new LLUrlEntryNoLink()); registerUrl(new LLUrlEntrySLURL()); registerUrl(new LLUrlEntryHTTP()); registerUrl(new LLUrlEntryHTTPLabel()); @@ -131,7 +132,8 @@ static bool stringHasUrl(const std::string &text) text.find(".com") != std::string::npos || text.find(".net") != std::string::npos || text.find(".edu") != std::string::npos || - text.find(".org") != std::string::npos); + text.find(".org") != std::string::npos || + text.find("<nolink>") != std::string::npos); } bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb) @@ -176,7 +178,8 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL match_entry->getIcon(), match_entry->getColor(), match_entry->getMenuName(), - match_entry->getLocation(url)); + match_entry->getLocation(url), + match_entry->isLinkDisabled()); return true; } @@ -204,9 +207,13 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr S32 end = start + wurl.size() - 1; match.setValues(start, end, match.getUrl(), - match.getLabel(), match.getTooltip(), - match.getIcon(), match.getColor(), - match.getMenuName(), match.getLocation()); + match.getLabel(), + match.getTooltip(), + match.getIcon(), + match.getColor(), + match.getMenuName(), + match.getLocation(), + match.isLinkDisabled()); return true; } return false; diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index f1b08c380b..63e627ceb5 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -1720,6 +1720,7 @@ LLView* LLView::findChildView(const std::string& name, BOOL recurse) const for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) { LLView* childp = *child_it; + llassert(childp); if (childp->getName() == name) { return childp; @@ -1731,6 +1732,7 @@ LLView* LLView::findChildView(const std::string& name, BOOL recurse) const for ( child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it) { LLView* childp = *child_it; + llassert(childp); LLView* viewp = childp->findChildView(name, recurse); if ( viewp ) { diff --git a/indra/llui/llviewborder.cpp b/indra/llui/llviewborder.cpp index 30717f87de..bd9c43c97f 100644 --- a/indra/llui/llviewborder.cpp +++ b/indra/llui/llviewborder.cpp @@ -125,14 +125,6 @@ void LLViewBorder::draw() llassert( FALSE ); // not implemented } } - else - if( STYLE_TEXTURE == mStyle ) - { - if( mTexture ) - { - drawTextures(); - } - } LLView::draw(); } @@ -255,56 +247,6 @@ void LLViewBorder::drawTwoPixelLines() gl_line_2d(left+1, bottom+1, right-1, bottom+1); } -void LLViewBorder::drawTextures() -{ - //LLGLSUIDefault gls_ui; - - //llassert( FALSE ); // TODO: finish implementing - - //gGL.color4fv(UI_VERTEX_COLOR.mV); - - //gGL.getTexUnit(0)->bind(mTexture); - //gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_WRAP); - - //drawTextureTrapezoid( 0.f, mBorderWidth, getRect().getWidth(), 0, 0 ); - //drawTextureTrapezoid( 90.f, mBorderWidth, getRect().getHeight(), (F32)getRect().getWidth(),0 ); - //drawTextureTrapezoid( 180.f, mBorderWidth, getRect().getWidth(), (F32)getRect().getWidth(),(F32)getRect().getHeight() ); - //drawTextureTrapezoid( 270.f, mBorderWidth, getRect().getHeight(), 0, (F32)getRect().getHeight() ); -} - - -void LLViewBorder::drawTextureTrapezoid( F32 degrees, S32 width, S32 length, F32 start_x, F32 start_y ) -{ - gGL.pushMatrix(); - { - gGL.translatef(start_x, start_y, 0.f); - glRotatef( degrees, 0, 0, 1 ); - - gGL.begin(LLRender::QUADS); - { - // width, width /---------\ length-width, width // - // / \ // - // / \ // - // /---------------\ // - // 0,0 length, 0 // - - gGL.texCoord2f( 0, 0 ); - gGL.vertex2i( 0, 0 ); - - gGL.texCoord2f( (GLfloat)length, 0 ); - gGL.vertex2i( length, 0 ); - - gGL.texCoord2f( (GLfloat)(length - width), (GLfloat)width ); - gGL.vertex2i( length - width, width ); - - gGL.texCoord2f( (GLfloat)width, (GLfloat)width ); - gGL.vertex2i( width, width ); - } - gGL.end(); - } - gGL.popMatrix(); -} - BOOL LLViewBorder::getBevelFromAttribute(LLXMLNodePtr node, LLViewBorder::EBevel& bevel_style) { if (node->hasAttribute("bevel_style")) diff --git a/indra/llui/llviewborder.h b/indra/llui/llviewborder.h index 92fd569325..342e84fd93 100644 --- a/indra/llui/llviewborder.h +++ b/indra/llui/llviewborder.h @@ -99,8 +99,7 @@ private: void drawOnePixelLines(); void drawTwoPixelLines(); void drawTextures(); - void drawTextureTrapezoid( F32 degrees, S32 width, S32 length, F32 start_x, F32 start_y ); - + EBevel mBevel; EStyle mStyle; LLUIColor mHighlightLight; diff --git a/indra/llui/llviewmodel.h b/indra/llui/llviewmodel.h index c8a9b52cca..992365d44d 100644 --- a/indra/llui/llviewmodel.h +++ b/indra/llui/llviewmodel.h @@ -107,7 +107,8 @@ public: // New functions /// Get the stored value in string form - LLWString getDisplay() const { return mDisplay; } + const 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 diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index 128cd134c1..cbb303a059 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -33,7 +33,7 @@ LLUIColor LLUIColorTable::getColor(const std::string& name, const LLColor4& defa return LLUIColor(); } -LLUIColor::LLUIColor() {} +LLUIColor::LLUIColor() : mColorPtr(NULL) {} namespace tut { @@ -52,9 +52,10 @@ namespace namespace tut { - void testRegex(const std::string &testname, boost::regex regex, + void testRegex(const std::string &testname, LLUrlEntryBase &entry, const char *text, const std::string &expected) { + boost::regex regex = entry.getPattern(); std::string url = ""; boost::cmatch result; bool found = boost::regex_search(text, result, regex); @@ -62,7 +63,7 @@ namespace tut { 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); + url = entry.getUrl(std::string(text+start, end-start)); } ensure_equals(testname, url, expected); } @@ -74,74 +75,73 @@ namespace tut // test LLUrlEntryHTTP - standard http Urls // LLUrlEntryHTTP url; - boost::regex r = url.getPattern(); - testRegex("no valid url", r, + testRegex("no valid url", url, "htp://slurl.com/", ""); - testRegex("simple http (1)", r, + testRegex("simple http (1)", url, "http://slurl.com/", "http://slurl.com/"); - testRegex("simple http (2)", r, + testRegex("simple http (2)", url, "http://slurl.com", "http://slurl.com"); - testRegex("simple http (3)", r, + testRegex("simple http (3)", url, "http://slurl.com/about.php", "http://slurl.com/about.php"); - testRegex("simple https", r, + testRegex("simple https", url, "https://slurl.com/about.php", "https://slurl.com/about.php"); - testRegex("http in text (1)", r, + testRegex("http in text (1)", url, "XX http://slurl.com/ XX", "http://slurl.com/"); - testRegex("http in text (2)", r, + testRegex("http in text (2)", url, "XX http://slurl.com/about.php XX", "http://slurl.com/about.php"); - testRegex("https in text", r, + testRegex("https in text", url, "XX https://slurl.com/about.php XX", "https://slurl.com/about.php"); - testRegex("two http urls", r, + testRegex("two http urls", url, "XX http://slurl.com/about.php http://secondlife.com/ XX", "http://slurl.com/about.php"); - testRegex("http url with port and username", r, + testRegex("http url with port and username", url, "XX http://nobody@slurl.com:80/about.php http://secondlife.com/ XX", "http://nobody@slurl.com:80/about.php"); - testRegex("http url with port, username, and query string", r, + testRegex("http url with port, username, and query string", url, "XX http://nobody@slurl.com:80/about.php?title=hi%20there http://secondlife.com/ XX", "http://nobody@slurl.com:80/about.php?title=hi%20there"); // note: terminating commas will be removed by LLUrlRegistry:findUrl() - testRegex("http url with commas in middle and terminating", r, + testRegex("http url with commas in middle and terminating", url, "XX http://slurl.com/?title=Hi,There, XX", "http://slurl.com/?title=Hi,There,"); // note: terminating periods will be removed by LLUrlRegistry:findUrl() - testRegex("http url with periods in middle and terminating", r, + testRegex("http url with periods in middle and terminating", url, "XX http://slurl.com/index.php. XX", "http://slurl.com/index.php."); // DEV-19842: Closing parenthesis ")" breaks urls - testRegex("http url with brackets (1)", r, + testRegex("http url with brackets (1)", url, "XX http://en.wikipedia.org/wiki/JIRA_(software) XX", "http://en.wikipedia.org/wiki/JIRA_(software)"); // DEV-19842: Closing parenthesis ")" breaks urls - testRegex("http url with brackets (2)", r, + testRegex("http url with brackets (2)", url, "XX http://jira.secondlife.com/secure/attachment/17990/eggy+avs+in+1.21.0+(93713)+public+nightly.jpg XX", "http://jira.secondlife.com/secure/attachment/17990/eggy+avs+in+1.21.0+(93713)+public+nightly.jpg"); // DEV-10353: URLs in chat log terminated incorrectly when newline in chat - testRegex("http url with newlines", r, + testRegex("http url with newlines", url, "XX\nhttp://www.secondlife.com/\nXX", "http://www.secondlife.com/"); } @@ -153,39 +153,38 @@ namespace tut // test LLUrlEntryHTTPLabel - wiki-style http Urls with labels // LLUrlEntryHTTPLabel url; - boost::regex r = url.getPattern(); - testRegex("invalid wiki url [1]", r, + testRegex("invalid wiki url [1]", url, "[http://www.example.org]", ""); - testRegex("invalid wiki url [2]", r, + testRegex("invalid wiki url [2]", url, "[http://www.example.org", ""); - testRegex("invalid wiki url [3]", r, + testRegex("invalid wiki url [3]", url, "[http://www.example.org Label", ""); - testRegex("example.org with label (spaces)", r, + testRegex("example.org with label (spaces)", url, "[http://www.example.org Text]", - "[http://www.example.org Text]"); + "http://www.example.org"); - testRegex("example.org with label (tabs)", r, + testRegex("example.org with label (tabs)", url, "[http://www.example.org\t Text]", - "[http://www.example.org\t Text]"); + "http://www.example.org"); - testRegex("SL http URL with label", r, + testRegex("SL http URL with label", url, "[http://www.secondlife.com/ Second Life]", - "[http://www.secondlife.com/ Second Life]"); + "http://www.secondlife.com/"); - testRegex("SL https URL with label", r, + testRegex("SL https URL with label", url, "XXX [https://www.secondlife.com/ Second Life] YYY", - "[https://www.secondlife.com/ Second Life]"); + "https://www.secondlife.com/"); - testRegex("SL http URL with label", r, + testRegex("SL http URL with label", url, "[http://www.secondlife.com/?test=Hi%20There Second Life]", - "[http://www.secondlife.com/?test=Hi%20There Second Life]"); + "http://www.secondlife.com/?test=Hi%20There"); } template<> template<> @@ -195,69 +194,68 @@ namespace tut // test LLUrlEntrySLURL - second life URLs // LLUrlEntrySLURL url; - boost::regex r = url.getPattern(); - testRegex("no valid slurl [1]", r, + testRegex("no valid slurl [1]", url, "htp://slurl.com/secondlife/Ahern/50/50/50/", ""); - testRegex("no valid slurl [2]", r, + testRegex("no valid slurl [2]", url, "http://slurl.com/secondlife/", ""); - testRegex("no valid slurl [3]", r, + testRegex("no valid slurl [3]", url, "hhtp://slurl.com/secondlife/Ahern/50/FOO/50/", ""); - testRegex("Ahern (50,50,50) [1]", r, + testRegex("Ahern (50,50,50) [1]", url, "http://slurl.com/secondlife/Ahern/50/50/50/", "http://slurl.com/secondlife/Ahern/50/50/50/"); - testRegex("Ahern (50,50,50) [2]", r, + testRegex("Ahern (50,50,50) [2]", url, "XXX http://slurl.com/secondlife/Ahern/50/50/50/ XXX", "http://slurl.com/secondlife/Ahern/50/50/50/"); - testRegex("Ahern (50,50,50) [3]", r, + testRegex("Ahern (50,50,50) [3]", url, "XXX http://slurl.com/secondlife/Ahern/50/50/50 XXX", "http://slurl.com/secondlife/Ahern/50/50/50"); - testRegex("Ahern (50,50,50) multicase", r, + testRegex("Ahern (50,50,50) multicase", url, "XXX http://SLUrl.com/SecondLife/Ahern/50/50/50/ XXX", "http://SLUrl.com/SecondLife/Ahern/50/50/50/"); - testRegex("Ahern (50,50) [1]", r, + testRegex("Ahern (50,50) [1]", url, "XXX http://slurl.com/secondlife/Ahern/50/50/ XXX", "http://slurl.com/secondlife/Ahern/50/50/"); - testRegex("Ahern (50,50) [2]", r, + testRegex("Ahern (50,50) [2]", url, "XXX http://slurl.com/secondlife/Ahern/50/50 XXX", "http://slurl.com/secondlife/Ahern/50/50"); - testRegex("Ahern (50)", r, + testRegex("Ahern (50)", url, "XXX http://slurl.com/secondlife/Ahern/50 XXX", "http://slurl.com/secondlife/Ahern/50"); - testRegex("Ahern", r, + testRegex("Ahern", url, "XXX http://slurl.com/secondlife/Ahern/ XXX", "http://slurl.com/secondlife/Ahern/"); - testRegex("Ahern SLURL with title", r, + testRegex("Ahern SLURL with title", url, "XXX http://slurl.com/secondlife/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE! XXX", "http://slurl.com/secondlife/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE!"); - testRegex("Ahern SLURL with msg", r, + testRegex("Ahern SLURL with msg", url, "XXX http://slurl.com/secondlife/Ahern/50/50/50/?msg=Your%20text%20here. XXX", "http://slurl.com/secondlife/Ahern/50/50/50/?msg=Your%20text%20here."); // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat - testRegex("SLURL with brackets", r, + testRegex("SLURL with brackets", url, "XXX http://slurl.com/secondlife/Burning%20Life%20(Hyper)/27/210/30 XXX", "http://slurl.com/secondlife/Burning%20Life%20(Hyper)/27/210/30"); // DEV-35459: SLURLs and teleport Links not parsed properly - testRegex("SLURL with quote", r, + testRegex("SLURL with quote", url, "XXX http://slurl.com/secondlife/A'ksha%20Oasis/41/166/701 XXX", - "http://slurl.com/secondlife/A'ksha%20Oasis/41/166/701"); + "http://slurl.com/secondlife/A%27ksha%20Oasis/41/166/701"); } template<> template<> @@ -267,25 +265,24 @@ namespace tut // test LLUrlEntryAgent - secondlife://app/agent Urls // LLUrlEntryAgent url; - boost::regex r = url.getPattern(); - testRegex("Invalid Agent Url", r, + testRegex("Invalid Agent Url", url, "secondlife:///app/agent/0e346d8b-4433-4d66-XXXX-fd37083abc4c/about", ""); - testRegex("Agent Url ", r, + testRegex("Agent Url ", url, "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - testRegex("Agent Url in text", r, + testRegex("Agent Url in text", url, "XXX secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about XXX", "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - testRegex("Agent Url multicase", r, + testRegex("Agent Url multicase", url, "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About XXX", "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About"); - testRegex("Agent Url alternate command", r, + testRegex("Agent Url alternate command", url, "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar", "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar"); @@ -298,25 +295,24 @@ namespace tut // test LLUrlEntryGroup - secondlife://app/group Urls // LLUrlEntryGroup url; - boost::regex r = url.getPattern(); - testRegex("Invalid Group Url", r, + testRegex("Invalid Group Url", url, "secondlife:///app/group/00005ff3-4044-c79f-XXXX-fb28ae0df991/about", ""); - testRegex("Group Url ", r, + testRegex("Group Url ", url, "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about", "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about"); - testRegex("Group Url ", r, + testRegex("Group Url ", url, "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect", "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect"); - testRegex("Group Url in text", r, + testRegex("Group Url in text", url, "XXX secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about XXX", "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about"); - testRegex("Group Url multicase", r, + testRegex("Group Url multicase", url, "XXX secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About XXX", "secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About"); } @@ -328,45 +324,44 @@ namespace tut // test LLUrlEntryPlace - secondlife://<location> URLs // LLUrlEntryPlace url; - boost::regex r = url.getPattern(); - testRegex("no valid slurl [1]", r, + testRegex("no valid slurl [1]", url, "secondlife://Ahern/FOO/50/", ""); - testRegex("Ahern (50,50,50) [1]", r, + testRegex("Ahern (50,50,50) [1]", url, "secondlife://Ahern/50/50/50/", "secondlife://Ahern/50/50/50/"); - testRegex("Ahern (50,50,50) [2]", r, + testRegex("Ahern (50,50,50) [2]", url, "XXX secondlife://Ahern/50/50/50/ XXX", "secondlife://Ahern/50/50/50/"); - testRegex("Ahern (50,50,50) [3]", r, + testRegex("Ahern (50,50,50) [3]", url, "XXX secondlife://Ahern/50/50/50 XXX", "secondlife://Ahern/50/50/50"); - testRegex("Ahern (50,50,50) multicase", r, + testRegex("Ahern (50,50,50) multicase", url, "XXX SecondLife://Ahern/50/50/50/ XXX", "SecondLife://Ahern/50/50/50/"); - testRegex("Ahern (50,50) [1]", r, + testRegex("Ahern (50,50) [1]", url, "XXX secondlife://Ahern/50/50/ XXX", "secondlife://Ahern/50/50/"); - testRegex("Ahern (50,50) [2]", r, + testRegex("Ahern (50,50) [2]", url, "XXX secondlife://Ahern/50/50 XXX", "secondlife://Ahern/50/50"); // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat - testRegex("SLURL with brackets", r, + testRegex("SLURL with brackets", url, "XXX secondlife://Burning%20Life%20(Hyper)/27/210/30 XXX", "secondlife://Burning%20Life%20(Hyper)/27/210/30"); // DEV-35459: SLURLs and teleport Links not parsed properly - testRegex("SLURL with quote", r, + testRegex("SLURL with quote", url, "XXX secondlife://A'ksha%20Oasis/41/166/701 XXX", - "secondlife://A'ksha%20Oasis/41/166/701"); + "secondlife://A%27ksha%20Oasis/41/166/701"); } template<> template<> @@ -376,21 +371,20 @@ namespace tut // test LLUrlEntryParcel - secondlife://app/parcel Urls // LLUrlEntryParcel url; - boost::regex r = url.getPattern(); - testRegex("Invalid Classified Url", r, + testRegex("Invalid Classified Url", url, "secondlife:///app/parcel/0000060e-4b39-e00b-XXXX-d98b1934e3a8/about", ""); - testRegex("Classified Url ", r, + testRegex("Classified Url ", url, "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about", "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about"); - testRegex("Classified Url in text", r, + testRegex("Classified Url in text", url, "XXX secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about XXX", "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about"); - testRegex("Classified Url multicase", r, + testRegex("Classified Url multicase", url, "XXX secondlife:///APP/Parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/About XXX", "secondlife:///APP/Parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/About"); } @@ -401,73 +395,72 @@ namespace tut // test LLUrlEntryTeleport - secondlife://app/teleport URLs // LLUrlEntryTeleport url; - boost::regex r = url.getPattern(); - testRegex("no valid teleport [1]", r, + testRegex("no valid teleport [1]", url, "http://slurl.com/secondlife/Ahern/50/50/50/", ""); - testRegex("no valid teleport [2]", r, + testRegex("no valid teleport [2]", url, "secondlife:///app/teleport/", ""); - testRegex("no valid teleport [3]", r, + testRegex("no valid teleport [3]", url, "second-life:///app/teleport/Ahern/50/50/50/", ""); - testRegex("no valid teleport [3]", r, + testRegex("no valid teleport [3]", url, "hhtp://slurl.com/secondlife/Ahern/50/FOO/50/", ""); - testRegex("Ahern (50,50,50) [1]", r, + testRegex("Ahern (50,50,50) [1]", url, "secondlife:///app/teleport/Ahern/50/50/50/", "secondlife:///app/teleport/Ahern/50/50/50/"); - testRegex("Ahern (50,50,50) [2]", r, + testRegex("Ahern (50,50,50) [2]", url, "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX", "secondlife:///app/teleport/Ahern/50/50/50/"); - testRegex("Ahern (50,50,50) [3]", r, + testRegex("Ahern (50,50,50) [3]", url, "XXX secondlife:///app/teleport/Ahern/50/50/50 XXX", "secondlife:///app/teleport/Ahern/50/50/50"); - testRegex("Ahern (50,50,50) multicase", r, + testRegex("Ahern (50,50,50) multicase", url, "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX", "secondlife:///app/teleport/Ahern/50/50/50/"); - testRegex("Ahern (50,50) [1]", r, + testRegex("Ahern (50,50) [1]", url, "XXX secondlife:///app/teleport/Ahern/50/50/ XXX", "secondlife:///app/teleport/Ahern/50/50/"); - testRegex("Ahern (50,50) [2]", r, + testRegex("Ahern (50,50) [2]", url, "XXX secondlife:///app/teleport/Ahern/50/50 XXX", "secondlife:///app/teleport/Ahern/50/50"); - testRegex("Ahern (50)", r, + testRegex("Ahern (50)", url, "XXX secondlife:///app/teleport/Ahern/50 XXX", "secondlife:///app/teleport/Ahern/50"); - testRegex("Ahern", r, + testRegex("Ahern", url, "XXX secondlife:///app/teleport/Ahern/ XXX", "secondlife:///app/teleport/Ahern/"); - testRegex("Ahern teleport with title", r, + testRegex("Ahern teleport with title", url, "XXX secondlife:///app/teleport/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE! XXX", "secondlife:///app/teleport/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE!"); - testRegex("Ahern teleport with msg", r, + testRegex("Ahern teleport with msg", url, "XXX secondlife:///app/teleport/Ahern/50/50/50/?msg=Your%20text%20here. XXX", "secondlife:///app/teleport/Ahern/50/50/50/?msg=Your%20text%20here."); // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat - testRegex("Teleport with brackets", r, + testRegex("Teleport with brackets", url, "XXX secondlife:///app/teleport/Burning%20Life%20(Hyper)/27/210/30 XXX", "secondlife:///app/teleport/Burning%20Life%20(Hyper)/27/210/30"); // DEV-35459: SLURLs and teleport Links not parsed properly - testRegex("Teleport url with quote", r, + testRegex("Teleport url with quote", url, "XXX secondlife:///app/teleport/A'ksha%20Oasis/41/166/701 XXX", - "secondlife:///app/teleport/A'ksha%20Oasis/41/166/701"); + "secondlife:///app/teleport/A%27ksha%20Oasis/41/166/701"); } template<> template<> @@ -477,33 +470,32 @@ namespace tut // test LLUrlEntrySL - general secondlife:// URLs // LLUrlEntrySL url; - boost::regex r = url.getPattern(); - testRegex("no valid slapp [1]", r, + testRegex("no valid slapp [1]", url, "http:///app/", ""); - testRegex("valid slapp [1]", r, + testRegex("valid slapp [1]", url, "secondlife:///app/", "secondlife:///app/"); - testRegex("valid slapp [2]", r, + testRegex("valid slapp [2]", url, "secondlife:///app/teleport/Ahern/50/50/50/", "secondlife:///app/teleport/Ahern/50/50/50/"); - testRegex("valid slapp [3]", r, + testRegex("valid slapp [3]", url, "secondlife:///app/foo", "secondlife:///app/foo"); - testRegex("valid slapp [4]", r, + testRegex("valid slapp [4]", url, "secondlife:///APP/foo?title=Hi%20There", "secondlife:///APP/foo?title=Hi%20There"); - testRegex("valid slapp [5]", r, + testRegex("valid slapp [5]", url, "secondlife://host/app/", "secondlife://host/app/"); - testRegex("valid slapp [6]", r, + testRegex("valid slapp [6]", url, "secondlife://host:8080/foo/bar", "secondlife://host:8080/foo/bar"); } @@ -515,34 +507,135 @@ namespace tut // test LLUrlEntrySLLabel - general secondlife:// URLs with labels // LLUrlEntrySLLabel url; - boost::regex r = url.getPattern(); - testRegex("invalid wiki url [1]", r, + testRegex("invalid wiki url [1]", url, "[secondlife:///app/]", ""); - testRegex("invalid wiki url [2]", r, + testRegex("invalid wiki url [2]", url, "[secondlife:///app/", ""); - testRegex("invalid wiki url [3]", r, + testRegex("invalid wiki url [3]", url, "[secondlife:///app/ Label", ""); - testRegex("agent slurl with label (spaces)", r, + testRegex("agent slurl with label (spaces)", url, "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about Text]", - "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about Text]"); + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - testRegex("agent slurl with label (tabs)", r, + testRegex("agent slurl with label (tabs)", url, "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about\t Text]", - "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about\t Text]"); + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - testRegex("agent slurl with label", r, + testRegex("agent slurl with label", url, "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about FirstName LastName]", - "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about FirstName LastName]"); + "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); - testRegex("teleport slurl with label", r, + testRegex("teleport slurl with label", url, "XXX [secondlife:///app/teleport/Ahern/50/50/50/ Teleport to Ahern] YYY", - "[secondlife:///app/teleport/Ahern/50/50/50/ Teleport to Ahern]"); + "secondlife:///app/teleport/Ahern/50/50/50/"); + } + + template<> template<> + void object::test<11>() + { + // + // test LLUrlEntryHTTPNoProtocol - general URLs without a protocol + // + LLUrlEntryHTTPNoProtocol url; + + testRegex("naked .com URL", url, + "see google.com", + "http://google.com"); + + testRegex("naked .org URL", url, + "see en.wikipedia.org for details", + "http://en.wikipedia.org"); + + testRegex("naked .net URL", url, + "example.net", + "http://example.net"); + + testRegex("naked .edu URL (2 instances)", url, + "MIT web site is at web.mit.edu and also www.mit.edu", + "http://web.mit.edu"); + + testRegex("don't match e-mail addresses", url, + "test@lindenlab.com", + ""); + + testRegex(".com URL with path", url, + "see secondlife.com/status for grid status", + "http://secondlife.com/status"); + + testRegex(".com URL with port", url, + "secondlife.com:80", + "http://secondlife.com:80"); + + testRegex(".com URL with port and path", url, + "see secondlife.com:80/status", + "http://secondlife.com:80/status"); + + testRegex("www.*.com URL with port and path", url, + "see www.secondlife.com:80/status", + "http://www.secondlife.com:80/status"); + + testRegex("invalid .com URL [1]", url, + "..com", + ""); + + testRegex("invalid .com URL [2]", url, + "you.come", + ""); + + testRegex("invalid .com URL [3]", url, + "recommended", + ""); + + testRegex("invalid .edu URL", url, + "hi there scheduled maitenance has begun", + ""); + + testRegex("invalid .net URL", url, + "foo.netty", + ""); + + testRegex("XML tags around URL [1]", url, + "<foo>secondlife.com</foo>", + "http://secondlife.com"); + + testRegex("XML tags around URL [2]", url, + "<foo>secondlife.com/status?bar=1</foo>", + "http://secondlife.com/status?bar=1"); + } + + template<> template<> + void object::test<12>() + { + // + // test LLUrlEntryNoLink - turn off hyperlinking + // + LLUrlEntryNoLink url; + + testRegex("<nolink> [1]", url, + "<nolink>google.com</nolink>", + "google.com"); + + testRegex("<nolink> [2]", url, + "<nolink>google.com", + ""); + + testRegex("<nolink> [3]", url, + "google.com</nolink>", + ""); + + testRegex("<nolink> [4]", url, + "<nolink>Hello World</nolink>", + "Hello World"); + + testRegex("<nolink> [5]", url, + "<nolink>My Object</nolink>", + "My Object"); } } diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp index e8cf135346..24a32de268 100644 --- a/indra/llui/tests/llurlmatch_test.cpp +++ b/indra/llui/tests/llurlmatch_test.cpp @@ -25,6 +25,7 @@ // link seam LLUIColor::LLUIColor() + : mColorPtr(NULL) {} namespace tut @@ -53,7 +54,7 @@ namespace tut LLUrlMatch match; ensure("empty()", match.empty()); - match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLUIColor(), "", ""); + match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", LLUIColor(), "", "", false); ensure("! empty()", ! match.empty()); } @@ -66,7 +67,7 @@ namespace tut LLUrlMatch match; ensure_equals("getStart() == 0", match.getStart(), 0); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); ensure_equals("getStart() == 10", match.getStart(), 10); } @@ -79,7 +80,7 @@ namespace tut LLUrlMatch match; ensure_equals("getEnd() == 0", match.getEnd(), 0); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); ensure_equals("getEnd() == 20", match.getEnd(), 20); } @@ -92,10 +93,10 @@ namespace tut LLUrlMatch match; ensure_equals("getUrl() == ''", match.getUrl(), ""); - match.setValues(10, 20, "http://slurl.com/", "", "", "", LLUIColor(), "", ""); + match.setValues(10, 20, "http://slurl.com/", "", "", "", LLUIColor(), "", "", false); ensure_equals("getUrl() == 'http://slurl.com/'", match.getUrl(), "http://slurl.com/"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); ensure_equals("getUrl() == '' (2)", match.getUrl(), ""); } @@ -108,10 +109,10 @@ namespace tut LLUrlMatch match; ensure_equals("getLabel() == ''", match.getLabel(), ""); - match.setValues(10, 20, "", "Label", "", "", LLUIColor(), "", ""); + match.setValues(10, 20, "", "Label", "", "", LLUIColor(), "", "", false); ensure_equals("getLabel() == 'Label'", match.getLabel(), "Label"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); ensure_equals("getLabel() == '' (2)", match.getLabel(), ""); } @@ -124,10 +125,10 @@ namespace tut LLUrlMatch match; ensure_equals("getTooltip() == ''", match.getTooltip(), ""); - match.setValues(10, 20, "", "", "Info", "", LLUIColor(), "", ""); + match.setValues(10, 20, "", "", "Info", "", LLUIColor(), "", "", false); ensure_equals("getTooltip() == 'Info'", match.getTooltip(), "Info"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); ensure_equals("getTooltip() == '' (2)", match.getTooltip(), ""); } @@ -140,10 +141,10 @@ namespace tut LLUrlMatch match; ensure_equals("getIcon() == ''", match.getIcon(), ""); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "", ""); + match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "", "", false); ensure_equals("getIcon() == 'Icon'", match.getIcon(), "Icon"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); ensure_equals("getIcon() == '' (2)", match.getIcon(), ""); } @@ -156,10 +157,10 @@ namespace tut LLUrlMatch match; ensure("getMenuName() empty", match.getMenuName().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", ""); + match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", "", false); ensure_equals("getMenuName() == \"xui_file.xml\"", match.getMenuName(), "xui_file.xml"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); ensure("getMenuName() empty (2)", match.getMenuName().empty()); } @@ -172,10 +173,10 @@ namespace tut LLUrlMatch match; ensure("getLocation() empty", match.getLocation().empty()); - match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", "Paris"); + match.setValues(10, 20, "", "", "", "Icon", LLUIColor(), "xui_file.xml", "Paris", false); ensure_equals("getLocation() == \"Paris\"", match.getLocation(), "Paris"); - match.setValues(10, 20, "", "", "", "", LLUIColor(), "", ""); + match.setValues(10, 20, "", "", "", "", LLUIColor(), "", "", false); ensure("getLocation() empty (2)", match.getLocation().empty()); } } |