diff options
Diffstat (limited to 'indra/llui')
97 files changed, 3750 insertions, 1809 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index ce068618e2..3ecab90756 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -52,6 +52,7 @@ set(llui_SOURCE_FILES llkeywords.cpp lllayoutstack.cpp lllineeditor.cpp + llloadingindicator.cpp lllocalcliprect.cpp llmenubutton.cpp llmenugl.cpp @@ -90,6 +91,7 @@ set(llui_SOURCE_FILES lltextbox.cpp lltexteditor.cpp lltextparser.cpp + lltextvalidate.cpp lltransutil.cpp lltoggleablemenu.cpp lltooltip.cpp @@ -143,6 +145,7 @@ set(llui_HEADER_FILES lllayoutstack.h lllazyvalue.h lllineeditor.h + llloadingindicator.h lllocalcliprect.h llmenubutton.h llmenugl.h @@ -182,6 +185,7 @@ set(llui_HEADER_FILES lltextbox.h lltexteditor.h lltextparser.h + lltextvalidate.h lltoggleablemenu.h lltooltip.h lltransutil.h @@ -207,6 +211,16 @@ set(llui_HEADER_FILES set_source_files_properties(${llui_HEADER_FILES} PROPERTIES HEADER_FILE_ONLY TRUE) +SET(llurlentry_TEST_DEPENDENCIES + llurlmatch.cpp + llurlregistry.cpp + ) + +set_source_files_properties(llurlentry.cpp + PROPERTIES LL_TEST_ADDITIONAL_SOURCE_FILES + "${llurlentry_TEST_DEPENDENCIES}" + ) + list(APPEND llui_SOURCE_FILES ${llui_HEADER_FILES}) add_library (llui ${llui_SOURCE_FILES}) diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp index b5e870228a..5d1d57cbb2 100644 --- a/indra/llui/llaccordionctrl.cpp +++ b/indra/llui/llaccordionctrl.cpp @@ -63,6 +63,8 @@ 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) @@ -72,6 +74,8 @@ LLAccordionCtrl::LLAccordionCtrl(const Params& params):LLPanel(params) } LLAccordionCtrl::LLAccordionCtrl() : LLPanel() + , mAutoScrolling( false ) + , mAutoScrollRate( 0.f ) { mSingleExpansion = false; mFitParent = false; @@ -81,6 +85,19 @@ LLAccordionCtrl::LLAccordionCtrl() : LLPanel() //--------------------------------------------------------------------------------- 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); @@ -101,7 +118,6 @@ BOOL LLAccordionCtrl::postBuild() scrollbar_size, getRect().getHeight() - 1); - LLScrollbar::Params sbparams; sbparams.name("scrollable vertical"); sbparams.rect(scroll_rect); @@ -313,44 +329,105 @@ 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()) + if(std::find(beginChild(), endChild(), accordion_tab) == endChild()) addChild(accordion_tab); mAccordionTabs.push_back(accordion_tab); - + accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLAccordionCtrl::onCollapseCtrlCloseOpen, this, mAccordionTabs.size() - 1) ); } - -void LLAccordionCtrl::arrange() +void LLAccordionCtrl::removeCollapsibleCtrl(LLView* view) { - if( mAccordionTabs.size() == 0) - { - //We do not arrange if we do not have what should be arranged + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(view); + if(!accordion_tab) return; + + if(std::find(beginChild(), endChild(), accordion_tab) != endChild()) + removeChild(accordion_tab); + + for (std::vector<LLAccordionCtrlTab*>::iterator iter = mAccordionTabs.begin(); + iter != mAccordionTabs.end(); ++iter) + { + if (accordion_tab == (*iter)) + { + mAccordionTabs.erase(iter); + break; + } } +} - //Calculate params +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; - - if(mAccordionTabs.size() == 1) + S32 collapsed_height = 0; + + for(size_t i=0;i<mAccordionTabs.size();++i) { - LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[0]); - - LLRect panel_rect = accordion_tab->getRect(); + LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); - S32 panel_height = getRect().getHeight() - 2*BORDER_MARGIN; + if(accordion_tab->getVisible() == false) //skip hidden accordion tabs + continue; + if(!accordion_tab->isExpanded() ) + { + collapsed_height+=mAccordionTabs[i]->getRect().getHeight(); + } + } - ctrlSetLeftTopAndSize(accordion_tab,panel_rect.mLeft,panel_top,panel_width,panel_height); + 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]); - show_hide_scrollbar(getRect().getWidth(),getRect().getHeight()); - return; + if(accordion_tab->getVisible() == false) //skip hidden accordion tabs + continue; + if(!accordion_tab->isExpanded() ) + { + panel_height = accordion_tab->getRect().getHeight(); + } + else + { + if(mFitParent) + { + panel_height = expanded_height; + } + else + { + if(accordion_tab->getAccordionView()) + { + panel_height = accordion_tab->getAccordionView()->getRect().getHeight() + + accordion_tab->getHeaderHeight() + 2*BORDER_MARGIN; + } + else + { + panel_height = accordion_tab->getRect().getHeight(); + } + } + } + // make sure at least header is shown + panel_height = llmax(panel_height, accordion_tab->getHeaderHeight()); + + ctrlSetLeftTopAndSize(mAccordionTabs[i], panel_left, panel_top, panel_width, panel_height); + panel_top-=mAccordionTabs[i]->getRect().getHeight(); } + show_hide_scrollbar(getRect().getWidth(), getRect().getHeight()); + updateLayout(getRect().getWidth(), getRect().getHeight()); +} + +void LLAccordionCtrl::arrangeMultiple() +{ + S32 panel_left = BORDER_MARGIN; // Margin from left side of Splitter + S32 panel_top = getRect().getHeight() - BORDER_MARGIN; // Top coordinate of the first panel + S32 panel_width = getRect().getWidth() - 4; // 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]); @@ -398,7 +475,40 @@ void LLAccordionCtrl::arrange() 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 (); } //--------------------------------------------------------------------------------- @@ -420,6 +530,64 @@ BOOL LLAccordionCtrl::handleKeyHere (KEY key, MASK mask) 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 ; @@ -500,15 +668,23 @@ S32 LLAccordionCtrl::notifyParent(const LLSD& info) LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); if(accordion_tab->hasFocus() && i>0) { + bool prev_visible_tab_found = false; while(i>0) { if(mAccordionTabs[--i]->getVisible()) + { + prev_visible_tab_found = true; break; + } + } + + if (prev_visible_tab_found) + { + accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); + accordion_tab->notify(LLSD().with("action","select_last")); + return 1; } - - accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]); - accordion_tab->notify(LLSD().with("action","select_last")); - return 1; + break; } } return 0; diff --git a/indra/llui/llaccordionctrl.h b/indra/llui/llaccordionctrl.h index 4cb0f38281..ab7d6548ca 100644 --- a/indra/llui/llaccordionctrl.h +++ b/indra/llui/llaccordionctrl.h @@ -81,12 +81,18 @@ public: virtual BOOL handleRightMouseDown ( S32 x, S32 y, MASK mask); virtual BOOL handleScrollWheel ( S32 x, S32 y, S32 clicks ); virtual BOOL handleKeyHere (KEY key, MASK mask); + virtual BOOL handleDragAndDrop (S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); // // Call reshape after changing splitter's size virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); void addCollapsibleCtrl(LLView* view); + void removeCollapsibleCtrl(LLView* view); void arrange(); @@ -100,6 +106,9 @@ public: 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(); } @@ -112,11 +121,15 @@ private: 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; }; diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp index 4bfe44135a..3c706ce90e 100644 --- a/indra/llui/llaccordionctrltab.cpp +++ b/indra/llui/llaccordionctrltab.cpp @@ -33,8 +33,9 @@ #include "linden_common.h" #include "lluictrl.h" - +#include "llscrollbar.h" #include "llaccordionctrltab.h" +#include "lllocalcliprect.h" #include "lltextbox.h" @@ -45,6 +46,9 @@ 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 const S32 VERTICAL_MULTIPLE = 16; +static const S32 PARENT_BORDER_MARGIN = 5; static LLDefaultChildRegistry::Register<LLAccordionCtrlTab> t1("accordion_tab"); @@ -73,6 +77,11 @@ public: 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; @@ -92,6 +101,8 @@ private: LLUIColor mHeaderBGColor; bool mNeedsHighlight; + + LLFrameTimer mAutoOpenTimer; }; LLAccordionCtrlTab::LLAccordionCtrlTabHeader::Params::Params() @@ -209,6 +220,7 @@ void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::onMouseLeave(S32 x, S32 y, MA { LLUICtrl::onMouseLeave(x, y, mask); mNeedsHighlight = false; + mAutoOpenTimer.stop(); } BOOL LLAccordionCtrlTab::LLAccordionCtrlTabHeader::handleKey(KEY key, MASK mask, BOOL called_from_parent) { @@ -218,8 +230,33 @@ BOOL LLAccordionCtrlTab::LLAccordionCtrlTabHeader::handleKey(KEY key, MASK mask, } 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) @@ -243,6 +280,7 @@ LLAccordionCtrlTab::Params::Params() ,header_image_pressed("header_image_pressed") ,header_image_focused("header_image_focused") ,header_text_color("header_text_color") + ,fit_panel("fit_panel",true) { mouse_opaque(false); } @@ -259,6 +297,9 @@ LLAccordionCtrlTab::LLAccordionCtrlTab(const LLAccordionCtrlTab::Params&p) ,mPaddingTop(p.padding_top) ,mPaddingBottom(p.padding_bottom) ,mCanOpenClose(true) + ,mFitPanel(p.fit_panel) + ,mContainerPanel(NULL) + ,mScrollbar(NULL) { mStoredOpenCloseState = false; mWasStateStored = false; @@ -287,54 +328,42 @@ void LLAccordionCtrlTab::setDisplayChildren(bool display) 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; + if(mContainerPanel) + mContainerPanel->setVisible(getDisplayChildren()); - child->setVisible(getDisplayChildren()); + if(mDisplayChildren) + { + adjustContainerPanel(); } + else + { + if(mScrollbar) + mScrollbar->setVisible(false); + } + } void LLAccordionCtrlTab::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */) { LLRect headerRect; - 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(); + if(!mDisplayChildren) + return; - child->reshape(childWidth,childHeight); - - childRect.setLeftTopAndSize( - getPaddingLeft(), - childHeight + getPaddingBottom(), - childWidth, - childHeight); + LLRect childRect; - child->setRect(childRect); - - break;//suppose that there is only one panel - } + childRect.setLeftTopAndSize( + getPaddingLeft(), + height - getHeaderHeight() - getPaddingTop(), + width - getPaddingLeft() - getPaddingRight(), + height - getHeaderHeight() - getPaddingTop() - getPaddingBottom() ); + adjustContainerPanel(childRect); } void LLAccordionCtrlTab::changeOpenClose(bool is_open) @@ -396,6 +425,9 @@ bool LLAccordionCtrlTab::addChild(LLView* child, S32 tab_group) setDisplayChildren(getDisplayChildren()); } + if (!mContainerPanel) + mContainerPanel = findContainerView(); + return res; } @@ -405,7 +437,7 @@ void LLAccordionCtrlTab::setAccordionView(LLView* panel) } -LLView* LLAccordionCtrlTab::getAccordionView() +LLView* LLAccordionCtrlTab::findContainerView() { for(child_list_const_iter_t it = getChildList()->begin(); getChildList()->end() != it; ++it) @@ -436,10 +468,49 @@ void LLAccordionCtrlTab::setHeaderVisible(bool value) reshape(getRect().getWidth(), getRect().getHeight(), FALSE); }; -//vurtual +//virtual BOOL LLAccordionCtrlTab::postBuild() { - mHeader->setVisible(mHeaderVisible); + if(mHeader) + mHeader->setVisible(mHeaderVisible); + + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + + LLRect scroll_rect; + scroll_rect.setOriginAndSize( + getRect().getWidth() - scrollbar_size, + 1, + scrollbar_size, + getRect().getHeight() - 1); + + mContainerPanel = findContainerView(); + + if(!mFitPanel) + { + LLScrollbar::Params sbparams; + sbparams.name("scrollable vertical"); + sbparams.rect(scroll_rect); + sbparams.orientation(LLScrollbar::VERTICAL); + sbparams.doc_size(getRect().getHeight()); + sbparams.doc_pos(0); + sbparams.page_size(getRect().getHeight()); + sbparams.step_size(VERTICAL_MULTIPLE); + sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM); + sbparams.change_callback(boost::bind(&LLAccordionCtrlTab::onScrollPosChangeCallback, this, _1, _2)); + + + mScrollbar = LLUICtrlFactory::create<LLScrollbar> (sbparams); + LLView::addChild( mScrollbar ); + mScrollbar->setFollowsRight(); + mScrollbar->setFollowsTop(); + mScrollbar->setFollowsBottom(); + + mScrollbar->setVisible(false); + } + + if(mContainerPanel) + mContainerPanel->setVisible(mDisplayChildren); + return LLUICtrl::postBuild(); } bool LLAccordionCtrlTab::notifyChildren (const LLSD& info) @@ -483,7 +554,8 @@ S32 LLAccordionCtrlTab::notifyParent(const LLSD& info) } //LLAccordionCtrl should rearrange accodion tab if one of accordion change its size - getParent()->notifyParent(info); + if (getParent()) // A parent may not be set if tabs are added dynamically. + getParent()->notifyParent(info); return 1; } else if(str_action == "select_prev") @@ -528,6 +600,12 @@ BOOL LLAccordionCtrlTab::handleKey(KEY key, MASK mask, BOOL called_from_parent) if( !header->hasFocus() ) return LLUICtrl::handleKey(key, mask, called_from_parent); + if ( (key == KEY_RETURN )&& mask == MASK_NONE) + { + changeOpenClose(getDisplayChildren()); + return TRUE; + } + if ( (key == KEY_ADD || key == KEY_RIGHT)&& mask == MASK_NONE) { if(getDisplayChildren() == false) @@ -588,6 +666,7 @@ void LLAccordionCtrlTab::storeOpenCloseState() mStoredOpenCloseState = getDisplayChildren(); mWasStateStored = true; } + void LLAccordionCtrlTab::restoreOpenCloseState() { if(!mWasStateStored) @@ -598,3 +677,203 @@ void LLAccordionCtrlTab::restoreOpenCloseState() } mWasStateStored = false; } + +void LLAccordionCtrlTab::adjustContainerPanel () +{ + S32 width = getRect().getWidth(); + S32 height = getRect().getHeight(); + + LLRect child_rect; + child_rect.setLeftTopAndSize( + getPaddingLeft(), + height - getHeaderHeight() - getPaddingTop(), + width - getPaddingLeft() - getPaddingRight(), + height - getHeaderHeight() - getPaddingTop() - getPaddingBottom() ); + + adjustContainerPanel(child_rect); +} + +void LLAccordionCtrlTab::adjustContainerPanel(const LLRect& child_rect) +{ + if(!mContainerPanel) + return; + + if(!mFitPanel) + { + show_hide_scrollbar(child_rect); + updateLayout(child_rect); + } + else + { + mContainerPanel->reshape(child_rect.getWidth(),child_rect.getHeight()); + mContainerPanel->setRect(child_rect); + } +} + +S32 LLAccordionCtrlTab::getChildViewHeight() +{ + if(!mContainerPanel) + return 0; + return mContainerPanel->getRect().getHeight(); +} + +void LLAccordionCtrlTab::show_hide_scrollbar(const LLRect& child_rect) +{ + if(getChildViewHeight() > child_rect.getHeight() ) + showScrollbar(child_rect); + else + hideScrollbar(child_rect); +} +void LLAccordionCtrlTab::showScrollbar(const LLRect& child_rect) +{ + if(!mContainerPanel || !mScrollbar) + return; + bool was_visible = mScrollbar->getVisible(); + mScrollbar->setVisible(true); + + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + + { + ctrlSetLeftTopAndSize(mScrollbar,child_rect.getWidth()-scrollbar_size, + child_rect.getHeight()-PARENT_BORDER_MARGIN, + scrollbar_size, + child_rect.getHeight()-2*PARENT_BORDER_MARGIN); + } + + LLRect orig_rect = mContainerPanel->getRect(); + + mScrollbar->setPageSize(child_rect.getHeight()); + mScrollbar->setDocParams(orig_rect.getHeight(),mScrollbar->getDocPos()); + + if(was_visible) + { + S32 scroll_pos = llmin(mScrollbar->getDocPos(), orig_rect.getHeight() - child_rect.getHeight() - 1); + mScrollbar->setDocPos(scroll_pos); + } + else//shrink child panel + { + updateLayout(child_rect); + } + +} + +void LLAccordionCtrlTab::hideScrollbar( const LLRect& child_rect ) +{ + if(!mContainerPanel || !mScrollbar) + return; + + if(mScrollbar->getVisible() == false) + return; + mScrollbar->setVisible(false); + mScrollbar->setDocPos(0); + + //shrink child panel + updateLayout(child_rect); +} + +void LLAccordionCtrlTab::onScrollPosChangeCallback(S32, LLScrollbar*) +{ + LLRect child_rect; + + S32 width = getRect().getWidth(); + S32 height = getRect().getHeight(); + + child_rect.setLeftTopAndSize( + getPaddingLeft(), + height - getHeaderHeight() - getPaddingTop(), + width - getPaddingLeft() - getPaddingRight(), + height - getHeaderHeight() - getPaddingTop() - getPaddingBottom() ); + + updateLayout(child_rect); +} + +void LLAccordionCtrlTab::drawChild(const LLRect& root_rect,LLView* child) +{ + if (child && child->getVisible() && child->getRect().isValid()) + { + LLRect screen_rect; + localRectToScreen(child->getRect(),&screen_rect); + + if ( root_rect.overlaps(screen_rect) && LLUI::sDirtyRect.overlaps(screen_rect)) + { + glMatrixMode(GL_MODELVIEW); + LLUI::pushMatrix(); + { + LLUI::translate((F32)child->getRect().mLeft, (F32)child->getRect().mBottom, 0.f); + child->draw(); + + } + LLUI::popMatrix(); + } + } +} + +void LLAccordionCtrlTab::draw() +{ + if(mFitPanel) + LLUICtrl::draw(); + else + { + LLRect root_rect = getRootView()->getRect(); + drawChild(root_rect,mHeader); + drawChild(root_rect,mScrollbar ); + { + LLRect child_rect; + + S32 width = getRect().getWidth(); + S32 height = getRect().getHeight(); + + child_rect.setLeftTopAndSize( + getPaddingLeft(), + height - getHeaderHeight() - getPaddingTop(), + width - getPaddingLeft() - getPaddingRight(), + height - getHeaderHeight() - getPaddingTop() - getPaddingBottom() ); + + LLLocalClipRect clip(child_rect); + drawChild(root_rect,mContainerPanel); + } + } +} + +void LLAccordionCtrlTab::updateLayout ( const LLRect& child_rect ) +{ + LLView* child = getAccordionView(); + if(!mContainerPanel) + return; + + S32 panel_top = child_rect.getHeight(); + S32 panel_width = child_rect.getWidth(); + + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + if(mScrollbar->getVisible() != false) + { + panel_top+=mScrollbar->getDocPos(); + panel_width-=scrollbar_size; + } + + //set sizes for first panels and dragbars + LLRect panel_rect = child->getRect(); + ctrlSetLeftTopAndSize(mContainerPanel,child_rect.mLeft,panel_top,panel_width,panel_rect.getHeight()); +} +void LLAccordionCtrlTab::ctrlSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S32 width, S32 height) +{ + if(!panel) + return; + LLRect panel_rect = panel->getRect(); + panel_rect.setLeftTopAndSize( left, top, width, height); + panel->reshape( width, height, 1); + panel->setRect(panel_rect); +} +BOOL LLAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask) +{ + //header may be not the first child but we need to process it first + if(y >= (getRect().getHeight() - HEADER_HEIGHT - HEADER_HEIGHT/2) ) + { + //inside tab header + //fix for EXT-6619 + return TRUE; + } + return LLUICtrl::handleToolTip(x, y, mask); +} + + diff --git a/indra/llui/llaccordionctrltab.h b/indra/llui/llaccordionctrltab.h index b200d43438..fb19d17e99 100644 --- a/indra/llui/llaccordionctrltab.h +++ b/indra/llui/llaccordionctrltab.h @@ -35,12 +35,14 @@ #include <string> #include "llrect.h" +#include "lluictrl.h" +#include "lluicolor.h" -class LLUICtrl; class LLUICtrlFactory; class LLUIImage; class LLButton; class LLTextBox; +class LLScrollbar; @@ -84,6 +86,8 @@ public: Optional<bool> header_visible; + Optional<bool> fit_panel; + Optional<S32> padding_left; Optional<S32> padding_right; Optional<S32> padding_top; @@ -107,7 +111,7 @@ public: //set LLAccordionCtrlTab panel void setAccordionView(LLView* panel); - LLView* getAccordionView(); + LLView* getAccordionView() { return mContainerPanel; }; bool getCollapsible() {return mCollapsible;}; @@ -115,6 +119,7 @@ public: void changeOpenClose(bool is_open); void canOpenClose(bool can_open_close) { mCanOpenClose = can_open_close;}; + bool canOpenClose() const { return mCanOpenClose; }; virtual BOOL postBuild(); @@ -122,6 +127,8 @@ public: S32 notify(const LLSD& info); bool notifyChildren(const LLSD& info); + void draw(); + void storeOpenCloseState (); void restoreOpenCloseState (); @@ -141,6 +148,8 @@ public: virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleToolTip(S32 x, S32 y, MASK mask); + virtual bool addChild(LLView* child, S32 tab_group); bool isExpanded() { return mDisplayChildren; } @@ -163,9 +172,26 @@ public: void showAndFocusHeader(); -private: + void setFitPanel( bool fit ) { mFitPanel = true; } - +protected: + void adjustContainerPanel (const LLRect& child_rect); + void adjustContainerPanel (); + S32 getChildViewHeight (); + + void onScrollPosChangeCallback(S32, LLScrollbar*); + + void show_hide_scrollbar (const LLRect& child_rect); + void showScrollbar (const LLRect& child_rect); + void hideScrollbar (const LLRect& child_rect); + + void updateLayout ( const LLRect& child_rect ); + void ctrlSetLeftTopAndSize (LLView* panel, S32 left, S32 top, S32 width, S32 height); + + void drawChild(const LLRect& root_rect,LLView* child); + + LLView* findContainerView (); +private: class LLAccordionCtrlTabHeader; LLAccordionCtrlTabHeader* mHeader; //Header @@ -175,6 +201,7 @@ private: bool mHeaderVisible; bool mCanOpenClose; + bool mFitPanel; S32 mPaddingLeft; S32 mPaddingRight; @@ -184,6 +211,8 @@ private: bool mStoredOpenCloseState; bool mWasStateStored; + LLScrollbar* mScrollbar; + LLView* mContainerPanel; LLUIColor mDropdownBGColor; }; diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index e9f6288f44..0255061b12 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, @@ -819,7 +824,7 @@ void LLButton::draw() x = text_right; break; case LLFontGL::HCENTER: - x = getRect().getWidth() / 2; + x = text_left + (text_width / 2); break; case LLFontGL::LEFT: default: @@ -998,6 +1003,11 @@ void LLButton::setImageDisabledSelected(LLPointer<LLUIImage> image) mFadeWhenDisabled = TRUE; } +void LLButton::setImagePressed(LLPointer<LLUIImage> image) +{ + mImagePressed = image; +} + void LLButton::setImageHoverSelected(LLPointer<LLUIImage> image) { mImageHoverSelected = image; @@ -1022,6 +1032,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..a4d81ed6c3 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,8 +214,10 @@ 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; } - + LLFontGL::HAlign getImageOverlayHAlign() const { return mImageOverlayAlignment; } + void autoResize(); // resize with label of current btn state void resize(LLUIString label); // resize with label input void setLabel( const LLStringExplicit& label); @@ -230,6 +246,7 @@ public: void setImageHoverUnselected(LLPointer<LLUIImage> image); void setImageDisabled(LLPointer<LLUIImage> image); void setImageDisabledSelected(LLPointer<LLUIImage> image); + void setImagePressed(LLPointer<LLUIImage> image); void setCommitOnReturn(BOOL commit) { mCommitOnReturn = commit; } BOOL getCommitOnReturn() const { return mCommitOnReturn; } @@ -242,6 +259,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 +331,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/llcombobox.cpp b/indra/llui/llcombobox.cpp index f29e8785eb..cc107c972d 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 @@ -159,7 +160,7 @@ LLComboBox::LLComboBox(const LLComboBox::Params& p) createLineEditor(p); - setTopLostCallback(boost::bind(&LLComboBox::hideList, this)); + mTopLostSignalConnection = setTopLostCallback(boost::bind(&LLComboBox::hideList, this)); } void LLComboBox::initFromParams(const LLComboBox::Params& p) @@ -186,6 +187,9 @@ BOOL LLComboBox::postBuild() LLComboBox::~LLComboBox() { // children automatically deleted, including mMenu, mButton + + // explicitly disconect this signal, since base class destructor might fire top lost + mTopLostSignalConnection.disconnect(); } @@ -319,15 +323,19 @@ void LLComboBox::setValue(const LLSD& value) LLScrollListItem* item = mList->getFirstSelected(); if (item) { - setLabel( mList->getSelectedItemLabel() ); + setLabel(getSelectedItemLabel()); } mLastSelectedIndex = mList->getFirstSelectedIndex(); } + else + { + mLastSelectedIndex = -1; + } } const std::string LLComboBox::getSimple() const { - const std::string res = mList->getSelectedItemLabel(); + const std::string res = getSelectedItemLabel(); if (res.empty() && mAllowTextEntry) { return mTextEntry->getText(); @@ -406,7 +414,7 @@ BOOL LLComboBox::remove(S32 index) if (index < mList->getItemCount()) { mList->deleteSingleItem(index); - setLabel(mList->getSelectedItemLabel()); + setLabel(getSelectedItemLabel()); return TRUE; } return FALSE; @@ -447,7 +455,7 @@ BOOL LLComboBox::setCurrentByIndex( S32 index ) BOOL found = mList->selectNthItem( index ); if (found) { - setLabel(mList->getSelectedItemLabel()); + setLabel(getSelectedItemLabel()); mLastSelectedIndex = index; } return found; @@ -487,9 +495,9 @@ void LLComboBox::createLineEditor(const LLComboBox::Params& p) params.max_length_bytes(mMaxChars); params.commit_callback.function(boost::bind(&LLComboBox::onTextCommit, this, _2)); params.keystroke_callback(boost::bind(&LLComboBox::onTextEntry, this, _1)); - params.handle_edit_keys_directly(true); params.commit_on_focus_lost(false); params.follows.flags(FOLLOWS_ALL); + params.label(mLabel); mTextEntry = LLUICtrlFactory::create<LLLineEditor> (params); mTextEntry->setText(cur_label); mTextEntry->setIgnoreTab(TRUE); @@ -505,7 +513,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); @@ -609,16 +618,14 @@ void LLComboBox::showList() mList->setFocus(TRUE); - // register ourselves as a "top" control - // effectively putting us into a special draw layer - // and not affecting the bounding rectangle calculation - gFocusMgr.setTopCtrl(this); - // Show the list and push the button down mButton->setToggleState(TRUE); mList->setVisible(TRUE); + LLUI::addPopup(this); + setUseBoundingRect(TRUE); +// updateBoundingRect(); } void LLComboBox::hideList() @@ -633,7 +640,7 @@ void LLComboBox::hideList() if(mLastSelectedIndex >= 0) mList->selectNthItem(mLastSelectedIndex); } - else + else if(mLastSelectedIndex >= 0) mList->selectNthItem(mLastSelectedIndex); mButton->setToggleState(FALSE); @@ -641,10 +648,8 @@ void LLComboBox::hideList() mList->mouseOverHighlightNthItem(-1); setUseBoundingRect(FALSE); - if( gFocusMgr.getTopCtrl() == this ) - { - gFocusMgr.setTopCtrl(NULL); - } + LLUI::removePopup(this); +// updateBoundingRect(); } } @@ -700,13 +705,10 @@ void LLComboBox::onListMouseUp() void LLComboBox::onItemSelected(const LLSD& data) { - const std::string name = mList->getSelectedItemLabel(); - - S32 cur_id = getCurrentIndex(); - mLastSelectedIndex = cur_id; - if (cur_id != -1) + mLastSelectedIndex = getCurrentIndex(); + if (mLastSelectedIndex != -1) { - setLabel(name); + setLabel(getSelectedItemLabel()); if (mAllowTextEntry) { @@ -714,7 +716,6 @@ void LLComboBox::onItemSelected(const LLSD& data) mTextEntry->selectAll(); } } - // hiding the list reasserts the old value stored in the text editor/dropdown button hideList(); @@ -909,7 +910,7 @@ void LLComboBox::updateSelection() } else if (mList->selectItemByPrefix(left_wstring, FALSE)) { - LLWString selected_item = utf8str_to_wstring(mList->getSelectedItemLabel()); + LLWString selected_item = utf8str_to_wstring(getSelectedItemLabel()); LLWString wtext = left_wstring + selected_item.substr(left_wstring.size(), selected_item.size()); mTextEntry->setText(wstring_to_utf8str(wtext)); mTextEntry->setSelection(left_wstring.size(), mTextEntry->getWText().size()); @@ -1011,7 +1012,7 @@ BOOL LLComboBox::setCurrentByID(const LLUUID& id) if (found) { - setLabel(mList->getSelectedItemLabel()); + setLabel(getSelectedItemLabel()); mLastSelectedIndex = mList->getFirstSelectedIndex(); } @@ -1027,7 +1028,7 @@ BOOL LLComboBox::setSelectedByValue(const LLSD& value, BOOL selected) BOOL found = mList->setSelectedByValue(value, selected); if (found) { - setLabel(mList->getSelectedItemLabel()); + setLabel(getSelectedItemLabel()); } return found; } @@ -1066,3 +1067,24 @@ BOOL LLComboBox::selectItemRange( S32 first, S32 last ) { return mList->selectItemRange(first, last); } + + +static LLDefaultChildRegistry::Register<LLIconsComboBox> register_icons_combo_box("icons_combo_box"); + +LLIconsComboBox::Params::Params() +: icon_column("icon_column", ICON_COLUMN), + label_column("label_column", LABEL_COLUMN) +{} + +LLIconsComboBox::LLIconsComboBox(const LLIconsComboBox::Params& p) +: LLComboBox(p), + mIconColumnIndex(p.icon_column), + mLabelColumnIndex(p.label_column) +{} + +const std::string LLIconsComboBox::getSelectedItemLabel(S32 column) const +{ + mButton->setImageOverlay(LLComboBox::getSelectedItemLabel(mIconColumnIndex), mButton->getImageOverlayHAlign()); + + return LLComboBox::getSelectedItemLabel(mLabelColumnIndex); +} diff --git a/indra/llui/llcombobox.h b/indra/llui/llcombobox.h index 4f27588467..f0bd432f3a 100644 --- a/indra/llui/llcombobox.h +++ b/indra/llui/llcombobox.h @@ -150,7 +150,7 @@ public: // Get name of current item. Returns an empty string if not found. const std::string getSimple() const; // Get contents of column x of selected row - const std::string getSelectedItemLabel(S32 column = 0) const; + virtual const std::string getSelectedItemLabel(S32 column = 0) const; // Sets the label, which doesn't have to exist in the label. // This is probably a UI abuse. @@ -230,6 +230,38 @@ private: commit_callback_t mPrearrangeCallback; commit_callback_t mTextEntryCallback; commit_callback_t mSelectionCallback; - S32 mLastSelectedIndex; + boost::signals2::connection mTopLostSignalConnection; + S32 mLastSelectedIndex; }; + +// A combo box with icons for the list of items. +class LLIconsComboBox +: public LLComboBox +{ +public: + struct Params + : public LLInitParam::Block<Params, LLComboBox::Params> + { + Optional<S32> icon_column, + label_column; + Params(); + }; + + /*virtual*/ const std::string getSelectedItemLabel(S32 column = 0) const; + +private: + enum EColumnIndex + { + ICON_COLUMN = 0, + LABEL_COLUMN + }; + + friend class LLUICtrlFactory; + LLIconsComboBox(const Params&); + virtual ~LLIconsComboBox() {}; + + S32 mIconColumnIndex; + S32 mLabelColumnIndex; +}; + #endif diff --git a/indra/llui/llconsole.cpp b/indra/llui/llconsole.cpp index 59499f987b..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()) { @@ -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. @@ -381,21 +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(); - } + 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 74438b184a..3d8670fef2 100644 --- a/indra/llui/lldockablefloater.cpp +++ b/indra/llui/lldockablefloater.cpp @@ -95,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 @@ -146,7 +146,7 @@ void LLDockableFloater::setVisible(BOOL visible) if (visible) { - LLFloater::setFrontmost(TRUE); + LLFloater::setFrontmost(getAutoFocus()); } LLFloater::setVisible(visible); } @@ -202,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); } @@ -226,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/lldockcontrol.cpp b/indra/llui/lldockcontrol.cpp index 0d8e54aa48..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; + } } } @@ -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 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..9f83fcca35 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 ); } @@ -247,15 +248,14 @@ void LLDragHandleTop::reshapeTitleBox() return; } const LLFontGL* font = LLFontGL::getFontSansSerif(); - S32 title_width = font->getWidth( mTitleBox->getText() ) + TITLE_HPAD; - if (getMaxTitleWidth() > 0) - title_width = llmin(title_width, getMaxTitleWidth()); + S32 title_width = getRect().getWidth(); + title_width -= LEFT_PAD + 2 * BORDER_PAD + getButtonsRect().getWidth(); S32 title_height = llround(font->getLineHeight()); LLRect title_rect; title_rect.setLeftTopAndSize( LEFT_PAD, getRect().getHeight() - title_vpad, - getRect().getWidth() - LEFT_PAD - RIGHT_PAD, + title_width, title_height); // calls reshape on mTitleBox diff --git a/indra/llui/lldraghandle.h b/indra/llui/lldraghandle.h index dc5410787b..825bc9303e 100644 --- a/indra/llui/lldraghandle.h +++ b/indra/llui/lldraghandle.h @@ -71,6 +71,8 @@ public: BOOL getForeground() const { return mForeground; } void setMaxTitleWidth(S32 max_width) {mMaxTitleWidth = llmin(max_width, mMaxTitleWidth); } S32 getMaxTitleWidth() const { return mMaxTitleWidth; } + void setButtonsRect(const LLRect& rect){ mButtonsRect = rect; } + LLRect getButtonsRect() { return mButtonsRect; } void setTitleVisible(BOOL visible); virtual void setTitle( const std::string& title ) = 0; @@ -88,6 +90,7 @@ protected: LLTextBox* mTitleBox; private: + LLRect mButtonsRect; S32 mDragLastScreenX; S32 mDragLastScreenY; S32 mLastMouseScreenX; diff --git a/indra/llui/lleditmenuhandler.cpp b/indra/llui/lleditmenuhandler.cpp index 821afae8fd..245bce76f5 100644 --- a/indra/llui/lleditmenuhandler.cpp +++ b/indra/llui/lleditmenuhandler.cpp @@ -37,3 +37,10 @@ /* static */ LLEditMenuHandler* LLEditMenuHandler::gEditMenuHandler = NULL; +LLEditMenuHandler::~LLEditMenuHandler() +{ + if (gEditMenuHandler == this) + { + gEditMenuHandler = NULL; + } +} diff --git a/indra/llui/lleditmenuhandler.h b/indra/llui/lleditmenuhandler.h index 1de9c56afb..d72283cd99 100644 --- a/indra/llui/lleditmenuhandler.h +++ b/indra/llui/lleditmenuhandler.h @@ -38,7 +38,7 @@ class LLEditMenuHandler { public: // this is needed even though this is just an interface class. - virtual ~LLEditMenuHandler() {}; + virtual ~LLEditMenuHandler(); virtual void undo() {}; virtual BOOL canUndo() const { return FALSE; } diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index 3694ecd4f4..ec247b25c3 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -1,10 +1,10 @@ /** * @file llflatlistview.cpp - * @brief LLFlatListView base class + * @brief LLFlatListView base class and extension to support messages for several cases of an empty list. * * $LicenseInfo:firstyear=2009&license=viewergpl$ * - * Copyright (c) 2009, Linden Research, Inc. + * Copyright (c) 2009-2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -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 */) @@ -244,7 +243,7 @@ LLUUID LLFlatListView::getSelectedUUID() const } } -void LLFlatListView::getSelectedUUIDs(std::vector<LLUUID>& selected_uuids) const +void LLFlatListView::getSelectedUUIDs(uuid_vec_t& selected_uuids) const { if (mSelectedItemPairs.empty()) return; @@ -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,21 +484,88 @@ 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) { if (!item_pair) return; + if (!item_pair->first) + { + llwarning("Attempt to selet an item pair containing null panel item", 0); + return; + } + setFocus(TRUE); bool select_item = !isSelected(item_pair); //*TODO find a better place for that enforcing stuff if (mKeepOneItemSelected && numSelected() == 1 && !select_item) return; - + + if ( (mask & MASK_SHIFT) && !(mask & MASK_CONTROL) + && mMultipleSelection && !mSelectedItemPairs.empty() ) + { + item_pair_t* last_selected_pair = mSelectedItemPairs.back(); + + // If item_pair is already selected - do nothing + if (last_selected_pair == item_pair) + return; + + bool grab_items = false; + pairs_list_t pairs_to_select; + + // Pick out items from list between last selected and current clicked item_pair. + for (pairs_iterator_t + iter = mItemPairs.begin(), + iter_end = mItemPairs.end(); + iter != iter_end; ++iter) + { + item_pair_t* cur = *iter; + if (cur == last_selected_pair || cur == item_pair) + { + grab_items = !grab_items; + // Skip last selected and current clicked item pairs. + continue; + } + if (!cur->first->getVisible()) + { + // Skip invisible item pairs. + continue; + } + if (grab_items) + { + pairs_to_select.push_back(cur); + } + } + + if (select_item) + { + pairs_to_select.push_back(item_pair); + } + + for (pairs_iterator_t + iter = pairs_to_select.begin(), + iter_end = pairs_to_select.end(); + iter != iter_end; ++iter) + { + item_pair_t* pair_to_select = *iter; + selectItemPair(pair_to_select, true); + } + + if (!select_item) + { + // Item was already selected but there is a need to update last selected item and its border. + // Do it here to prevent extra mCommitOnSelectionChange in selectItemPair(). + mSelectedItemPairs.remove(item_pair); + mSelectedItemPairs.push_back(item_pair); + mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); + } + return; + } + if (!(mask & MASK_CONTROL) || !mMultipleSelection) resetSelection(); selectItemPair(item_pair, select_item); } @@ -548,15 +619,6 @@ BOOL LLFlatListView::handleKeyHere(KEY key, MASK mask) } break; } - case 'A': - { - if(MASK_CONTROL & mask) - { - selectAll(); - handled = TRUE; - } - break; - } default: break; } @@ -664,8 +726,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,31 +742,20 @@ 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 () { + // No items - no actions! + if (mItemPairs.empty()) return; + selectItemPair(mItemPairs.front(), true); ensureSelectedVisible(); } void LLFlatListView::selectLastItem () { + // No items - no actions! + if (mItemPairs.empty()) return; + selectItemPair(mItemPairs.back(), true); ensureSelectedVisible(); } @@ -798,10 +849,15 @@ bool LLFlatListView::selectNextItemPair(bool is_up_direction, bool reset_selecti return false; } -bool LLFlatListView::selectAll() +BOOL LLFlatListView::canSelectAll() const { - if (!mAllowSelection) - return false; + return !mItemPairs.empty() && mAllowSelection && mMultipleSelection; +} + +void LLFlatListView::selectAll() +{ + if (!mAllowSelection || !mMultipleSelection) + return; mSelectedItemPairs.clear(); @@ -819,10 +875,8 @@ bool LLFlatListView::selectAll() onCommit(); } - // Stretch selected items rect to ensure it won't be clipped - mSelectedItemsBorder->setRect(getSelectedItemsRect().stretch(-1)); - - return true; + // Stretch selected item rect to ensure it won't be clipped + mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1)); } bool LLFlatListView::isSelected(item_pair_t* item_pair) const @@ -878,7 +932,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(); @@ -909,6 +967,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(); @@ -920,6 +982,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); } @@ -951,11 +1014,17 @@ void LLFlatListView::getValues(std::vector<LLSD>& values) const void LLFlatListView::onFocusReceived() { mSelectedItemsBorder->setVisible(TRUE); + gEditMenuHandler = this; } // virtual void LLFlatListView::onFocusLost() { mSelectedItemsBorder->setVisible(FALSE); + // Route menu back to the default + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } } //virtual @@ -1059,4 +1128,43 @@ void LLFlatListView::detachItems(std::vector<LLPanel*>& detached_items) } } + +/************************************************************************/ +/* LLFlatListViewEx implementation */ +/************************************************************************/ +LLFlatListViewEx::Params::Params() +: no_items_msg("no_items_msg") +, no_filtered_items_msg("no_filtered_items_msg") +{ + +} + +LLFlatListViewEx::LLFlatListViewEx(const Params& p) +: LLFlatListView(p) +, mNoFilteredItemsMsg(p.no_filtered_items_msg) +, mNoItemsMsg(p.no_items_msg) +{ + +} + +void LLFlatListViewEx::updateNoItemsMessage(const std::string& filter_string) +{ + bool items_filtered = !filter_string.empty(); + if (items_filtered) + { + // items were filtered + LLStringUtil::format_map_t args; + args["[SEARCH_TERM]"] = LLURI::escape(filter_string); + std::string text = mNoFilteredItemsMsg; + LLStringUtil::format(text, args); + setNoItemsCommentText(text); + } + else + { + // list does not contain any items at all + setNoItemsCommentText(mNoItemsMsg); + } + +} + //EOF diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 5999e79f61..4f718ab0dc 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -1,10 +1,10 @@ /** * @file llflatlistview.h - * @brief LLFlatListView base class + * @brief LLFlatListView base class and extension to support messages for several cases of an empty list. * * $LicenseInfo:firstyear=2009&license=viewergpl$ * - * Copyright (c) 2009, Linden Research, Inc. + * Copyright (c) 2009-2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -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. @@ -58,7 +58,7 @@ class LLTextBox; * - Order of returned selected items are not guaranteed * - The control assumes that all items being added are unique. */ -class LLFlatListView : public LLScrollContainer +class LLFlatListView : public LLScrollContainer, public LLEditMenuHandler { public: @@ -108,11 +108,12 @@ public: /** padding between items */ Optional<U32> item_pad; + /** textbox with info message when list is empty*/ + Optional<LLTextBox::Params> no_items_text; + Params(); }; - virtual ~LLFlatListView() { clear(); }; - /** * Connects callback to signal called when Return key is pressed. */ @@ -221,7 +222,7 @@ public: * Get LLUUIDs associated with selected items * @param selected_uuids An std::vector being populated with LLUUIDs associated with selected items */ - virtual void getSelectedUUIDs(std::vector<LLUUID>& selected_uuids) const; + virtual void getSelectedUUIDs(uuid_vec_t& selected_uuids) const; /** Get the top selected item */ virtual LLPanel* getSelectedItem() const; @@ -341,7 +342,8 @@ protected: virtual bool selectNextItemPair(bool is_up_direction, bool reset_selection); - virtual bool selectAll(); + virtual BOOL canSelectAll() const; + virtual void selectAll(); virtual bool isSelected(item_pair_t* item_pair) const; @@ -368,8 +370,6 @@ protected: LLRect getLastSelectedItemRect(); - LLRect getSelectedItemsRect(); - void ensureSelectedVisible(); private: @@ -378,11 +378,14 @@ private: void setNoItemsCommentVisible(bool visible) const; -private: +protected: /** Comparator to use when sorting the list. */ const ItemComparator* mItemComparator; + +private: + LLPanel* mItemsPanel; S32 mItemsNoScrollWidth; @@ -427,4 +430,54 @@ private: commit_signal_t mOnReturnSignal; }; +/** + * Extends LLFlatListView functionality to show different messages when there are no items in the + * list depend on whether they are filtered or not. + * + * Class provides one message per case of empty list. + * It also provides protected updateNoItemsMessage() method to be called each time when derived list + * is changed to update base mNoItemsCommentTextbox value. + * + * It is implemented to avoid duplication of this functionality in concrete implementations of the + * lists. It is intended to be used as a base class for lists which should support two different + * messages for empty state. Can be improved to support more than two messages via state-to-message map. + */ +class LLFlatListViewEx : public LLFlatListView +{ +public: + struct Params : public LLInitParam::Block<Params, LLFlatListView::Params> + { + /** + * Contains a message for empty list when it does not contain any items at all. + */ + Optional<std::string> no_items_msg; + + /** + * Contains a message for empty list when its items are removed by filtering. + */ + Optional<std::string> no_filtered_items_msg; + Params(); + }; + + // *WORKAROUND: two methods to overload appropriate Params due to localization issue: + // no_items_msg & no_filtered_items_msg attributes are not defined as translatable in VLT. See EXT-5931 + void setNoItemsMsg(const std::string& msg) { mNoItemsMsg = msg; } + void setNoFilteredItemsMsg(const std::string& msg) { mNoFilteredItemsMsg = msg; } + +protected: + LLFlatListViewEx(const Params& p); + + /** + * Applies a message for empty list depend on passed argument. + * + * @param filter_string - if is not empty, message for filtered items will be set, otherwise for + * completely empty list. Value of filter string will be passed as search_term in SLURL. + */ + void updateNoItemsMessage(const std::string& filter_string); + +private: + std::string mNoFilteredItemsMsg; + std::string mNoItemsMsg; +}; + #endif diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 79d8f90fec..79c47a1136 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -346,7 +346,7 @@ void LLFloater::layoutDragHandle() rect = getLocalRect(); } mDragHandle->setRect(rect); - updateButtons(); + updateTitleButtons(); applyTitle(); } @@ -527,10 +527,7 @@ void LLFloater::setVisible( BOOL visible ) if( !visible ) { - if( gFocusMgr.childIsTopCtrl( this ) ) - { - gFocusMgr.setTopCtrl(NULL); - } + LLUI::removePopup(this); if( gFocusMgr.childHasMouseCapture( this ) ) { @@ -566,6 +563,7 @@ void LLFloater::handleVisibilityChange ( BOOL new_visibility ) void LLFloater::openFloater(const LLSD& key) { + llinfos << "Opening floater " << getName() << llendl; mKey = key; // in case we need to open ourselves again if (getSoundFlags() != SILENT @@ -606,6 +604,7 @@ void LLFloater::openFloater(const LLSD& key) void LLFloater::closeFloater(bool app_quitting) { + llinfos << "Closing floater " << getName() << llendl; if (app_quitting) { LLFloater::sQuitting = true; @@ -704,10 +703,7 @@ void LLFloater::reshape(S32 width, S32 height, BOOL called_from_parent) void LLFloater::releaseFocus() { - if( gFocusMgr.childIsTopCtrl( this ) ) - { - gFocusMgr.setTopCtrl(NULL); - } + LLUI::removePopup(this); setFocus(FALSE); @@ -1067,11 +1063,10 @@ void LLFloater::setMinimized(BOOL minimize) // Reshape *after* setting mMinimized reshape( mExpandedRect.getWidth(), mExpandedRect.getHeight(), TRUE ); } - - applyTitle (); make_ui_sound("UISndWindowClose"); - updateButtons(); + updateTitleButtons(); + applyTitle (); } void LLFloater::setFocus( BOOL b ) @@ -1127,6 +1122,7 @@ void LLFloater::setIsChrome(BOOL is_chrome) setFocus(FALSE); // can't Ctrl-Tab to "chrome" floaters setFocusRoot(FALSE); + mButtons[BUTTON_CLOSE]->setToolTip(LLStringExplicit(getButtonTooltip(Params(), BUTTON_CLOSE, is_chrome))); } // no titles displayed on "chrome" floaters @@ -1196,7 +1192,7 @@ void LLFloater::setHost(LLMultiFloater* host) mButtonScale = 1.f; //mButtonsEnabled[BUTTON_TEAR_OFF] = FALSE; } - updateButtons(); + updateTitleButtons(); if (host) { mHostHandle = host->getHandle(); @@ -1360,7 +1356,6 @@ void LLFloater::bringToFront( S32 x, S32 y ) // virtual void LLFloater::setVisibleAndFrontmost(BOOL take_focus) { - gFocusMgr.setTopCtrl(NULL); setVisible(TRUE); setFrontmost(take_focus); } @@ -1396,7 +1391,7 @@ void LLFloater::setCanDock(bool b) mButtonsEnabled[BUTTON_DOCK] = FALSE; } } - updateButtons(); + updateTitleButtons(); } void LLFloater::setDocked(bool docked, bool pop_on_undock) @@ -1405,7 +1400,7 @@ void LLFloater::setDocked(bool docked, bool pop_on_undock) { mDocked = docked; mButtonsEnabled[BUTTON_DOCK] = !mDocked; - updateButtons(); + updateTitleButtons(); storeDockStateControl(); } @@ -1458,7 +1453,7 @@ void LLFloater::onClickTearOff(LLFloater* self) } self->setTornOff(false); } - self->updateButtons(); + self->updateTitleButtons(); } // static @@ -1554,7 +1549,12 @@ void LLFloater::onClickClose( LLFloater* self ) { if (!self) return; - self->closeFloater(false); + self->onClickCloseBtn(); +} + +void LLFloater::onClickCloseBtn() +{ + closeFloater(false); } @@ -1565,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 { @@ -1650,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 @@ -1682,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, @@ -1695,7 +1693,7 @@ void LLFloater::setCanMinimize(BOOL can_minimize) mButtonsEnabled[BUTTON_MINIMIZE] = can_minimize && !isMinimized(); mButtonsEnabled[BUTTON_RESTORE] = can_minimize && isMinimized(); - updateButtons(); + updateTitleButtons(); } void LLFloater::setCanClose(BOOL can_close) @@ -1703,7 +1701,7 @@ void LLFloater::setCanClose(BOOL can_close) mCanClose = can_close; mButtonsEnabled[BUTTON_CLOSE] = can_close; - updateButtons(); + updateTitleButtons(); } void LLFloater::setCanTearOff(BOOL can_tear_off) @@ -1711,7 +1709,7 @@ void LLFloater::setCanTearOff(BOOL can_tear_off) mCanTearOff = can_tear_off; mButtonsEnabled[BUTTON_TEAR_OFF] = mCanTearOff && !mHostHandle.isDead(); - updateButtons(); + updateTitleButtons(); } @@ -1735,10 +1733,11 @@ void LLFloater::setCanDrag(BOOL can_drag) } } -void LLFloater::updateButtons() +void LLFloater::updateTitleButtons() { static LLUICachedControl<S32> floater_close_box_size ("UIFloaterCloseBoxSize", 0); static LLUICachedControl<S32> close_box_from_top ("UICloseBoxFromTop", 0); + LLRect buttons_rect; S32 button_count = 0; for (S32 i = 0; i < BUTTON_COUNT; i++) { @@ -1789,6 +1788,18 @@ void LLFloater::updateButtons() llround((F32)floater_close_box_size * mButtonScale)); } + // first time here, init 'buttons_rect' + if(1 == button_count) + { + buttons_rect = btn_rect; + } + else + { + // if mDragOnLeft=true then buttons are on top-left side vertically aligned + // title is not displayed in this case, calculating 'buttons_rect' for future use + mDragOnLeft ? buttons_rect.mBottom -= btn_rect.mBottom : + buttons_rect.mLeft = btn_rect.mLeft; + } mButtons[i]->setRect(btn_rect); mButtons[i]->setVisible(TRUE); // the restore button should have a tab stop so that it takes action when you Ctrl-Tab to a minimized floater @@ -1800,7 +1811,10 @@ void LLFloater::updateButtons() } } if (mDragHandle) - mDragHandle->setMaxTitleWidth(getRect().getWidth() - (button_count * (floater_close_box_size + 1))); + { + localRectToOtherView(buttons_rect, &buttons_rect, mDragHandle); + mDragHandle->setButtonsRect(buttons_rect); + } } void LLFloater::buildButtons(const Params& floater_params) @@ -1848,7 +1862,7 @@ void LLFloater::buildButtons(const Params& floater_params) p.click_callback.function(boost::bind(sButtonCallbacks[i], this)); p.tab_stop(false); p.follows.flags(FOLLOWS_TOP|FOLLOWS_RIGHT); - p.tool_tip = getButtonTooltip(floater_params, (EFloaterButton)i); + p.tool_tip = getButtonTooltip(floater_params, (EFloaterButton)i, getIsChrome()); p.scale_image(true); p.chrome(true); @@ -1857,7 +1871,7 @@ void LLFloater::buildButtons(const Params& floater_params) mButtons[i] = buttonp; } - updateButtons(); + updateTitleButtons(); } // static @@ -1903,8 +1917,15 @@ LLUIImage* LLFloater::getButtonPressedImage(const Params& p, EFloaterButton e) } // static -std::string LLFloater::getButtonTooltip(const Params& p, EFloaterButton e) +std::string LLFloater::getButtonTooltip(const Params& p, EFloaterButton e, bool is_chrome) { + // EXT-4081 (Lag Meter: Ctrl+W does not close floater) + // If floater is chrome set 'Close' text for close button's tooltip + if(is_chrome && BUTTON_CLOSE == e) + { + static std::string close_tooltip_chrome = LLTrans::getString("BUTTON_CLOSE_CHROME"); + return close_tooltip_chrome; + } // TODO: per-floater localizable tooltips set in XML return sButtonToolTips[e]; } @@ -1916,9 +1937,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) { } @@ -2391,10 +2413,17 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out LLRect new_rect; new_rect.setLeftTopAndSize(view_rect.mLeft,view_rect.mTop,new_width, new_height); - floater->reshape( new_width, new_height, TRUE ); - floater->setRect(new_rect); + floater->setShape(new_rect); - floater->translateIntoRect( getLocalRect(), false ); + if (floater->followsRight()) + { + floater->translate(old_width - new_width, 0); + } + + if (floater->followsTop()) + { + floater->translate(0, old_height - new_height); + } } } @@ -2497,10 +2526,7 @@ void LLFloaterView::syncFloaterTabOrder() if (modal_dialog) { // If we have a visible modal dialog, make sure that it has focus - if( gFocusMgr.getTopCtrl() != modal_dialog ) - { - gFocusMgr.setTopCtrl( modal_dialog ); - } + LLUI::addPopup(modal_dialog); if( !gFocusMgr.childHasKeyboardFocus( modal_dialog ) ) { @@ -2578,6 +2604,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) @@ -2595,6 +2623,8 @@ void LLFloaterView::popVisibleAll(const skip_list_t& skip_list) view->popVisible(); } } + + LLFloaterReg::blockShowFloaters(false); } void LLFloater::setInstanceName(const std::string& name) @@ -2805,3 +2835,15 @@ bool LLFloater::isShown(const LLFloater* floater) { return floater && floater->isShown(); } + +/* static */ +bool LLFloater::isMinimized(const LLFloater* floater) +{ + return floater && floater->isMinimized(); +} + +/* static */ +bool LLFloater::isVisible(const LLFloater* floater) +{ + return floater && floater->getVisible(); +} diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index f70495c0f0..444711de81 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -194,6 +194,8 @@ public: /// The static isShown() can accept a NULL pointer (which of course /// returns false). When non-NULL, it calls the non-static isShown(). static bool isShown(const LLFloater* floater); + static bool isVisible(const LLFloater* floater); + static bool isMinimized(const LLFloater* floater); BOOL isFirstLook() { return mFirstLook; } // EXT-2653: This function is necessary to prevent overlapping for secondary showed toasts BOOL isFrontmost(); BOOL isDependent() { return !mDependeeHandle.isDead(); } @@ -222,6 +224,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) {} @@ -301,23 +304,33 @@ 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() + virtual void onClickCloseBtn(); + + virtual void updateTitleButtons(); + private: void setForeground(BOOL b); // called only by floaterview void cleanupHandles(); // remove handles to dead floaters void createMinimizeButton(); - void updateButtons(); void buildButtons(const Params& p); // Images and tooltips are named in the XML, but we want to look them // up by index. static LLUIImage* getButtonImage(const Params& p, EFloaterButton e); static LLUIImage* getButtonPressedImage(const Params& p, EFloaterButton e); - static std::string getButtonTooltip(const Params& p, EFloaterButton e); + /** + * @params is_chrome - if floater is Chrome it means that floater will never get focus. + * Therefore it can't be closed with 'Ctrl+W'. So the tooltip text of close button( X ) + * should be 'Close' not 'Close(Ctrl+W)' as for usual floaters. + */ + static std::string getButtonTooltip(const Params& p, EFloaterButton e, bool is_chrome); + BOOL offerClickToButton(S32 x, S32 y, MASK mask, EFloaterButton index); void addResizeCtrls(); void layoutResizeCtrls(); @@ -344,6 +357,7 @@ protected: LLResizeBar* mResizeBar[4]; LLResizeHandle* mResizeHandle[4]; + LLButton* mButtons[BUTTON_COUNT]; private: LLRect mExpandedRect; @@ -377,7 +391,6 @@ private: handle_set_t mDependents; bool mButtonsEnabled[BUTTON_COUNT]; - LLButton* mButtons[BUTTON_COUNT]; F32 mButtonScale; BOOL mAutoFocus; LLHandle<LLFloater> mSnappedTo; @@ -419,11 +432,15 @@ private: class LLFloaterView : public LLUICtrl { +public: + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>{}; + protected: LLFloaterView (const Params& p); friend class LLUICtrlFactory; public: + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); void reshapeFloater(S32 width, S32 height, BOOL called_from_parent, BOOL adjust_vertical); @@ -468,9 +485,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..7588d8ab7a 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) { @@ -268,11 +272,11 @@ bool LLFloaterReg::toggleInstance(const std::string& name, const LLSD& key) } //static -// returns true if the instance exists and is visible +// returns true if the instance exists and is visible (doesnt matter minimized or not) bool LLFloaterReg::instanceVisible(const std::string& name, const LLSD& key) { LLFloater* instance = findInstance(name, key); - return LLFloater::isShown(instance); + return LLFloater::isVisible(instance); } //static @@ -459,3 +463,12 @@ bool LLFloaterReg::floaterInstanceVisible(const LLSD& sdname) return instanceVisible(name, key); } +//static +bool LLFloaterReg::floaterInstanceMinimized(const LLSD& sdname) +{ + LLSD key; + std::string name = sdname.asString(); + parse_name_key(name, key); + LLFloater* instance = findInstance(name, key); + return LLFloater::isShown(instance); +} diff --git a/indra/llui/llfloaterreg.h b/indra/llui/llfloaterreg.h index 634a235926..5cacf76771 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 @@ -133,6 +134,7 @@ public: static void hideFloaterInstance(const LLSD& sdname); static void toggleFloaterInstance(const LLSD& sdname); static bool floaterInstanceVisible(const LLSD& sdname); + static bool floaterInstanceMinimized(const LLSD& sdname); // Typed find / get / show template <class T> @@ -152,6 +154,8 @@ public: { return dynamic_cast<T*>(showInstance(name, key, focus)); } + + static void blockShowFloaters(bool value) { sBlockShowFloaters = value;} }; diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index 35fbc7b0a8..b3af258456 100644 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -38,8 +38,6 @@ const F32 FOCUS_FADE_TIME = 0.3f; -// NOTE: the LLFocusableElement implementation has been moved here from lluictrl.cpp. - LLFocusableElement::LLFocusableElement() : mFocusLostCallback(NULL), mFocusReceivedCallback(NULL), @@ -124,8 +122,7 @@ boost::signals2::connection LLFocusableElement::setTopLostCallback(const focus_s LLFocusMgr gFocusMgr; LLFocusMgr::LLFocusMgr() - : - mLockedView( NULL ), +: mLockedView( NULL ), mMouseCaptor( NULL ), mKeyboardFocus( NULL ), mLastKeyboardFocus( NULL ), @@ -133,16 +130,11 @@ LLFocusMgr::LLFocusMgr() mKeystrokesOnly(FALSE), mTopCtrl( NULL ), mAppHasFocus(TRUE) // Macs don't seem to notify us that we've gotten focus, so default to true - #ifdef _DEBUG - , mMouseCaptorName("none") - , mKeyboardFocusName("none") - , mTopCtrlName("none") - #endif { } -void LLFocusMgr::releaseFocusIfNeeded( const LLView* view ) +void LLFocusMgr::releaseFocusIfNeeded( LLView* view ) { if( childHasMouseCapture( view ) ) { @@ -162,10 +154,7 @@ void LLFocusMgr::releaseFocusIfNeeded( const LLView* view ) } } - if( childIsTopCtrl( view ) ) - { - setTopCtrl( NULL ); - } + LLUI::removePopup(view); } @@ -248,11 +237,6 @@ void LLFocusMgr::setKeyboardFocus(LLFocusableElement* new_focus, BOOL lock, BOOL return; } - #ifdef _DEBUG - LLUICtrl* focus_ctrl = dynamic_cast<LLUICtrl*>(new_focus); - mKeyboardFocusName = focus_ctrl ? focus_ctrl->getName() : std::string("none"); - #endif - // If we've got a default keyboard focus, and the caller is // releasing keyboard focus, move to the default. if (mDefaultKeyboardFocus != NULL && mKeyboardFocus == NULL) @@ -334,20 +318,12 @@ void LLFocusMgr::removeKeyboardFocusWithoutCallback( const LLFocusableElement* f if( mKeyboardFocus == focus ) { mKeyboardFocus = NULL; - #ifdef _DEBUG - mKeyboardFocusName = std::string("none"); - #endif } } void LLFocusMgr::setMouseCapture( LLMouseHandler* new_captor ) { - //if (mFocusLocked) - //{ - // return; - //} - if( new_captor != mMouseCaptor ) { LLMouseHandler* old_captor = mMouseCaptor; @@ -370,24 +346,14 @@ void LLFocusMgr::setMouseCapture( LLMouseHandler* new_captor ) old_captor->onMouseCaptureLost(); } - #ifdef _DEBUG - mMouseCaptorName = new_captor ? new_captor->getName() : std::string("none"); - #endif } } void LLFocusMgr::removeMouseCaptureWithoutCallback( const LLMouseHandler* captor ) { - //if (mFocusLocked) - //{ - // return; - //} if( mMouseCaptor == captor ) { mMouseCaptor = NULL; - #ifdef _DEBUG - mMouseCaptorName = std::string("none"); - #endif } } @@ -416,10 +382,6 @@ void LLFocusMgr::setTopCtrl( LLUICtrl* new_top ) { mTopCtrl = new_top; - #ifdef _DEBUG - mTopCtrlName = new_top ? new_top->getName() : std::string("none"); - #endif - if (old_top) { old_top->onTopLost(); @@ -432,9 +394,6 @@ void LLFocusMgr::removeTopCtrlWithoutCallback( const LLUICtrl* top_view ) if( mTopCtrl == top_view ) { mTopCtrl = NULL; - #ifdef _DEBUG - mTopCtrlName = std::string("none"); - #endif } } @@ -478,9 +437,9 @@ void LLFocusMgr::setAppHasFocus(BOOL focus) } // release focus from "top ctrl"s, which generally hides them - if (!focus && mTopCtrl) + if (!focus) { - setTopCtrl(NULL); + LLUI::clearPopups(); } mAppHasFocus = focus; } diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h index 83ecd1d301..86d3ccf111 100644 --- a/indra/llui/llfocusmgr.h +++ b/indra/llui/llfocusmgr.h @@ -65,10 +65,10 @@ public: virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); virtual BOOL handleUnicodeChar(llwchar uni_char, BOOL called_from_parent); + virtual void onTopLost(); // called when registered as top ctrl and user clicks elsewhere protected: virtual void onFocusReceived(); virtual void onFocusLost(); - virtual void onTopLost(); // called when registered as top ctrl and user clicks elsewhere focus_signal_t* mFocusLostCallback; focus_signal_t* mFocusReceivedCallback; focus_signal_t* mFocusChangedCallback; @@ -119,7 +119,7 @@ public: BOOL childIsTopCtrl( const LLView* parent ) const; // All Three - void releaseFocusIfNeeded( const LLView* top_view ); + void releaseFocusIfNeeded( LLView* top_view ); void lockFocus(); void unlockFocus(); BOOL focusLocked() const { return mLockedView != NULL; } @@ -149,12 +149,6 @@ private: typedef std::map<LLHandle<LLView>, LLHandle<LLView> > focus_history_map_t; focus_history_map_t mFocusHistory; - - #ifdef _DEBUG - std::string mMouseCaptorName; - std::string mKeyboardFocusName; - std::string mTopCtrlName; - #endif }; extern LLFocusMgr gFocusMgr; diff --git a/indra/llui/llhandle.h b/indra/llui/llhandle.h index 899f6b9326..8ade327044 100644 --- a/indra/llui/llhandle.h +++ b/indra/llui/llhandle.h @@ -67,6 +67,13 @@ public: return *this; } + template<typename Subclass> + LLHandle<T>& operator =(const LLHandle<Subclass>& other) + { + mTombStone = other.mTombStone; + return *this; + } + bool isDead() const { return mTombStone->getTarget() == NULL; diff --git a/indra/llui/lliconctrl.h b/indra/llui/lliconctrl.h index 66368f979b..7e37600409 100644 --- a/indra/llui/lliconctrl.h +++ b/indra/llui/lliconctrl.h @@ -73,6 +73,7 @@ public: std::string getImageName() const; void setColor(const LLColor4& color) { mColor = color; } + void setImage(LLPointer<LLUIImage> image) { mImagep = image; } private: void setIconImageDrawSize() ; 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 c4f10038f8..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,6 +89,14 @@ 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); diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 73e4d126f3..45f9de8e8d 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"), @@ -101,7 +91,6 @@ LLLineEditor::Params::Params() background_image_disabled("background_image_disabled"), background_image_focused("background_image_focused"), select_on_focus("select_on_focus", false), - handle_edit_keys_directly("handle_edit_keys_directly", false), revert_on_esc("revert_on_esc", true), commit_on_focus_lost("commit_on_focus_lost", true), ignore_tab("ignore_tab", true), @@ -146,7 +135,6 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p) mIgnoreArrowKeys( FALSE ), mIgnoreTab( p.ignore_tab ), mDrawAsterixes( FALSE ), - mHandleEditKeysDirectly(p.handle_edit_keys_directly), mSelectAllonFocusReceived( p.select_on_focus ), mPassDelete(FALSE), mReadOnly(FALSE), @@ -162,7 +150,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,18 +178,20 @@ 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() { mCommitOnFocusLost = FALSE; + // calls onCommit() while LLLineEditor still valid gFocusMgr.releaseFocusIfNeeded( this ); - - if( gEditMenuHandler == this ) - { - gEditMenuHandler = NULL; - } } @@ -401,7 +392,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 +411,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)); @@ -496,6 +491,7 @@ void LLLineEditor::selectAll() setCursor(mSelectionEnd); //mScrollHPos = 0; mIsSelecting = TRUE; + updatePrimary(); } @@ -661,6 +657,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; @@ -777,7 +783,7 @@ void LLLineEditor::removeChar() } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } } @@ -816,7 +822,7 @@ void LLLineEditor::addChar(const llwchar uni_char) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } getWindow()->hideCursorUntilMouseMove(); @@ -905,7 +911,7 @@ BOOL LLLineEditor::handleSelectionKey(KEY key, MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } break; @@ -921,7 +927,7 @@ BOOL LLLineEditor::handleSelectionKey(KEY key, MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } break; @@ -947,22 +953,6 @@ BOOL LLLineEditor::handleSelectionKey(KEY key, MASK mask) } } - if (!handled && mHandleEditKeysDirectly) - { - if( (MASK_CONTROL & mask) && ('A' == key) ) - { - if( canSelectAll() ) - { - selectAll(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - } - if(handled) { // take selection to 'primary' clipboard @@ -1009,7 +999,7 @@ void LLLineEditor::cut() if( need_to_rollback ) { rollback.doRollback( this ); - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } else if( mKeystrokeCallback ) @@ -1118,7 +1108,7 @@ void LLLineEditor::pasteHelper(bool is_primary) } // Truncate the clean string at the limit of what will fit clean_string = clean_string.substr(0, wchars_that_fit); - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } mText.insert(getCursor(), clean_string); @@ -1130,7 +1120,7 @@ void LLLineEditor::pasteHelper(bool is_primary) if( need_to_rollback ) { rollback.doRollback( this ); - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } else if( mKeystrokeCallback ) @@ -1195,7 +1185,7 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } } handled = TRUE; @@ -1244,7 +1234,7 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } handled = TRUE; } @@ -1271,7 +1261,7 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } handled = TRUE; } @@ -1288,7 +1278,7 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } handled = TRUE; } @@ -1305,7 +1295,7 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } handled = TRUE; } @@ -1328,64 +1318,6 @@ BOOL LLLineEditor::handleSpecialKey(KEY key, MASK mask) break; } - if( !handled && mHandleEditKeysDirectly ) - { - // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system. - if( KEY_DELETE == key ) - { - if( canDoDelete() ) - { - doDelete(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - else - if( MASK_CONTROL & mask ) - { - if( 'C' == key ) - { - if( canCopy() ) - { - copy(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - else - if( 'V' == key ) - { - if( canPaste() ) - { - paste(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - else - if( 'X' == key ) - { - if( canCut() ) - { - cut(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - } - } return handled; } @@ -1440,7 +1372,7 @@ BOOL LLLineEditor::handleKeyHere(KEY key, MASK mask ) { rollback.doRollback(this); - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } // Notify owner if requested @@ -1488,7 +1420,7 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char) { rollback.doRollback( this ); - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } // Notify owner if requested @@ -1508,7 +1440,7 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char) BOOL LLLineEditor::canDoDelete() const { - return ( !mReadOnly && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) ); + return ( !mReadOnly && mText.length() > 0 && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) ); } void LLLineEditor::doDelete() @@ -1533,7 +1465,7 @@ void LLLineEditor::doDelete() if( need_to_rollback ) { rollback.doRollback( this ); - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } else { @@ -1599,7 +1531,7 @@ void LLLineEditor::draw() std::string text; for (S32 i = 0; i < mText.length(); i++) { - text += '*'; + text += PASSWORD_ASTERISK; } mText = text; } @@ -1868,11 +1800,6 @@ S32 LLLineEditor::findPixelNearestPos(const S32 cursor_offset) const return result; } -void LLLineEditor::reportBadKeystroke() -{ - make_ui_sound("UISndBadKeystroke"); -} - //virtual void LLLineEditor::clear() { @@ -1960,51 +1887,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) { @@ -2064,223 +1952,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(); @@ -2558,3 +2229,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..9489e723e3 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; @@ -89,7 +81,6 @@ public: background_image_focused; Optional<bool> select_on_focus, - handle_edit_keys_directly, revert_on_esc, commit_on_focus_lost, ignore_tab; @@ -113,6 +104,7 @@ protected: LLLineEditor(const Params&); friend class LLUICtrlFactory; friend class LLFloaterEditUI; + void showContextMenu(S32 x, S32 y); public: virtual ~LLLineEditor(); @@ -122,6 +114,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 +197,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; } @@ -219,7 +214,6 @@ public: void extendSelection(S32 new_cursor_pos); void deleteSelection(); - void setHandleEditKeysDirectly( BOOL b ) { mHandleEditKeysDirectly = b; } void setSelectAllonFocusReceived(BOOL b); typedef boost::function<void (LLLineEditor* caller, void* user_data)> callback_t; @@ -231,17 +225,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 +233,9 @@ public: void updateHistory(); // stores current line in history void setReplaceNewlinesWithSpaces(BOOL replace); - + + void setContextMenu(LLContextMenu* new_context_menu); + private: // private helper methods @@ -259,7 +245,6 @@ private: void addChar(const llwchar c); void setCursorAtLocalPos(S32 local_mouse_x); S32 findPixelNearestPos(S32 cursor_offset = 0) const; - void reportBadKeystroke(); BOOL handleSpecialKey(KEY key, MASK mask); BOOL handleSelectionKey(KEY key, MASK mask); BOOL handleControlKey(KEY key, MASK mask); @@ -319,7 +304,7 @@ protected: S32 mLastSelectionStart; S32 mLastSelectionEnd; - LLLinePrevalidateFunc mPrevalidateFunc; + LLTextValidate::validate_func_t mPrevalidateFunc; LLFrameTimer mKeystrokeTimer; LLTimer mTripleClickTimer; @@ -337,7 +322,6 @@ protected: BOOL mIgnoreTab; BOOL mDrawAsterixes; - BOOL mHandleEditKeysDirectly; // If true, the standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system BOOL mSelectAllonFocusReceived; BOOL mPassDelete; @@ -348,6 +332,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/llloadingindicator.cpp b/indra/llui/llloadingindicator.cpp new file mode 100644 index 0000000000..f8b029e19c --- /dev/null +++ b/indra/llui/llloadingindicator.cpp @@ -0,0 +1,133 @@ +/** + * @file llloadingindicator.cpp + * @brief Perpetual loading indicator + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, 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 "llloadingindicator.h" + +// Linden library includes +#include "llsingleton.h" + +// Project includes +#include "lluictrlfactory.h" +#include "lluiimage.h" + +static LLDefaultChildRegistry::Register<LLLoadingIndicator> r("loading_indicator"); + +/////////////////////////////////////////////////////////////////////////////// +// LLLoadingIndicator::Data class +/////////////////////////////////////////////////////////////////////////////// + +/** + * Pre-loaded images shared by all instances of the widget + */ +class LLLoadingIndicator::Data: public LLSingleton<LLLoadingIndicator::Data> +{ +public: + /*virtual*/ void initSingleton(); // from LLSingleton + + LLPointer<LLUIImage> getNextImage(S8& idx) const; + U8 getImagesCount() const { return NIMAGES; } +private: + + static const U8 NIMAGES = 12; + LLPointer<LLUIImage> mImages[NIMAGES]; +}; + +// virtual +// Called right after the instance gets constructed. +void LLLoadingIndicator::Data::initSingleton() +{ + // Load images. + for (U8 i = 0; i < NIMAGES; ++i) + { + std::string img_name = llformat("Progress_%d", i+1); + mImages[i] = LLUI::getUIImage(img_name, 0); + llassert(mImages[i]); + } +} + +LLPointer<LLUIImage> LLLoadingIndicator::Data::getNextImage(S8& idx) const +{ + // Calculate next index, performing array bounds checking. + idx = (idx >= NIMAGES || idx < 0) ? 0 : (idx + 1) % NIMAGES; + return mImages[idx]; +} + +/////////////////////////////////////////////////////////////////////////////// +// LLLoadingIndicator class +/////////////////////////////////////////////////////////////////////////////// + +LLLoadingIndicator::LLLoadingIndicator(const Params& p) +: LLUICtrl(p) + , mRotationsPerSec(p.rotations_per_sec > 0 ? p.rotations_per_sec : 1.0f) + , mCurImageIdx(-1) +{ + // Select initial image. + mCurImagep = Data::instance().getNextImage(mCurImageIdx); + + // Start timer for switching images. + start(); +} + +void LLLoadingIndicator::draw() +{ + // Time to switch to the next image? + if (mImageSwitchTimer.getStarted() && mImageSwitchTimer.hasExpired()) + { + // Switch to the next image. + mCurImagep = Data::instance().getNextImage(mCurImageIdx); + + // Restart timer. + start(); + } + + // Draw current image. + if( mCurImagep.notNull() ) + { + mCurImagep->draw(getLocalRect(), LLColor4::white % getDrawContext().mAlpha); + } + + LLUICtrl::draw(); +} + +void LLLoadingIndicator::stop() +{ + mImageSwitchTimer.stop(); +} + +void LLLoadingIndicator::start() +{ + mImageSwitchTimer.start(); + F32 period = 1.0f / (Data::instance().getImagesCount() * mRotationsPerSec); + mImageSwitchTimer.setTimerExpirySec(period); +} diff --git a/indra/llui/llloadingindicator.h b/indra/llui/llloadingindicator.h new file mode 100644 index 0000000000..32dd1fead8 --- /dev/null +++ b/indra/llui/llloadingindicator.h @@ -0,0 +1,93 @@ +/** + * @file llloadingindicator.h + * @brief Perpetual loading indicator + * + * $LicenseInfo:firstyear=2010&license=viewergpl$ + * + * Copyright (c) 2010, 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_LLLOADINGINDICATOR_H +#define LL_LLLOADINGINDICATOR_H + +#include "lluictrl.h" + +/////////////////////////////////////////////////////////////////////////////// +// class LLLoadingIndicator +/////////////////////////////////////////////////////////////////////////////// + +/** + * Perpetual loading indicator (a la MacOSX or YouTube) + * + * Number of rotations per second can be overriden + * with the "roations_per_sec" parameter. + * + * Can start/stop spinning. + * + * @see start() + * @see stop() + */ +class LLLoadingIndicator +: public LLUICtrl +{ + LOG_CLASS(LLLoadingIndicator); +public: + struct Params : public LLInitParam::Block<Params, LLUICtrl::Params> + { + Optional<F32> rotations_per_sec; + Params() + : rotations_per_sec("rotations_per_sec", 1.0f) + {} + }; + + virtual ~LLLoadingIndicator() {} + + // llview overrides + virtual void draw(); + + /** + * Stop spinning. + */ + void stop(); + + /** + * Start spinning. + */ + void start(); + +private: + LLLoadingIndicator(const Params&); + friend class LLUICtrlFactory; + + class Data; + + F32 mRotationsPerSec; + S8 mCurImageIdx; + LLPointer<LLUIImage> mCurImagep; + LLFrameTimer mImageSwitchTimer; +}; + +#endif // LL_LLLOADINGINDICATOR_H diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index c172a2b714..e0e86ae228 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -657,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() { @@ -680,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) @@ -1651,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), @@ -1670,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) { @@ -1698,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 @@ -1710,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); } @@ -1875,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; @@ -2228,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); @@ -2809,7 +2838,7 @@ BOOL LLMenuGL::handleHover( S32 x, S32 y, MASK mask ) ((LLMenuItemGL*)viewp)->setHighlight(TRUE); LLMenuGL::setKeyboardMode(FALSE); } - mHasSelection = TRUE; + mHasSelection = true; } } } @@ -2888,7 +2917,7 @@ void LLMenuGL::setVisible(BOOL visible) } else { - mHasSelection = FALSE; + mHasSelection = true; mFadeTimer.stop(); } @@ -3316,7 +3345,7 @@ void LLMenuHolderGL::draw() LLView::draw(); // now draw last selected item as overlay LLMenuItemGL* selecteditem = (LLMenuItemGL*)sItemLastSelectedHandle.get(); - if (selecteditem && sItemActivationTimer.getStarted() && sItemActivationTimer.getElapsedTimeF32() < ACTIVATE_HIGHLIGHT_TIME) + if (selecteditem && selecteditem->getVisible() && sItemActivationTimer.getStarted() && sItemActivationTimer.getElapsedTimeF32() < ACTIVATE_HIGHLIGHT_TIME) { // make sure toggle items, for example, show the proper state when fading out selecteditem->buildDrawLabel(); @@ -3426,7 +3455,7 @@ LLView* const LLMenuHolderGL::getVisibleMenu() const for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) { LLView* viewp = *child_it; - if (viewp->getVisible() && dynamic_cast<LLMenuBarGL*>(viewp) == NULL) + if (viewp->getVisible() && dynamic_cast<LLMenuGL*>(viewp) != NULL) { return viewp; } @@ -3449,8 +3478,7 @@ BOOL LLMenuHolderGL::hideMenus() for ( child_list_const_iter_t child_it = getChildList()->begin(); child_it != getChildList()->end(); ++child_it) { LLView* viewp = *child_it; - // clicks off of menu do not hide menu bar - if (dynamic_cast<LLMenuBarGL*>(viewp) == NULL && viewp->getVisible()) + if (dynamic_cast<LLMenuGL*>(viewp) != NULL && viewp->getVisible()) { viewp->setVisible(FALSE); } @@ -3936,7 +3964,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..6f0f83d4b9 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -295,7 +295,7 @@ private: // class, by allowing another method to be specified which determines // if the menu item should consider itself checked as true or not. Be // careful that the provided callback is fast - it needs to be VERY -// FUCKING EFFICIENT, because it may need to be checked a lot. +// EFFICIENT because it may need to be checked a lot. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ class LLMenuItemCheckGL @@ -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/llmodaldialog.cpp b/indra/llui/llmodaldialog.cpp index 387af05935..6cff68c20b 100644 --- a/indra/llui/llmodaldialog.cpp +++ b/indra/llui/llmodaldialog.cpp @@ -111,7 +111,7 @@ void LLModalDialog::onOpen(const LLSD& key) // This is a modal dialog. It sucks up all mouse and keyboard operations. gFocusMgr.setMouseCapture( this ); - gFocusMgr.setTopCtrl( this ); + LLUI::addPopup(this); setFocus(TRUE); sModalStack.push_front( this ); @@ -153,7 +153,7 @@ void LLModalDialog::setVisible( BOOL visible ) gFocusMgr.setMouseCapture( this ); // The dialog view is a root view - gFocusMgr.setTopCtrl( this ); + LLUI::addPopup(this); setFocus( TRUE ); } else @@ -291,7 +291,7 @@ void LLModalDialog::onAppFocusGained() // This is a modal dialog. It sucks up all mouse and keyboard operations. gFocusMgr.setMouseCapture( instance ); instance->setFocus(TRUE); - gFocusMgr.setTopCtrl( instance ); + LLUI::addPopup(instance); instance->centerOnScreen(); } diff --git a/indra/llui/llmultifloater.cpp b/indra/llui/llmultifloater.cpp index 78738c826d..3aea648562 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(); } } @@ -353,13 +345,20 @@ void LLMultiFloater::setVisible(BOOL visible) BOOL LLMultiFloater::handleKeyHere(KEY key, MASK mask) { - if (key == 'W' && mask == MASK_CONTROL) + if (key == 'W' && mask == (MASK_CONTROL|MASK_SHIFT)) { LLFloater* floater = getActiveFloater(); // is user closeable and is system closeable if (floater && floater->canClose() && floater->isCloseable()) { floater->closeFloater(); + + // EXT-5695 (Tabbed IM window loses focus if close any tabs by Ctrl+W) + // bring back focus on tab container if there are any tab left + if(mTabContainer->getTabCount() > 0) + { + mTabContainer->setFocus(TRUE); + } } return TRUE; } 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..7b8f51ae3c 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -48,122 +48,38 @@ const std::string NOTIFICATION_PERSIST_VERSION = "0.93"; -// local channel for notification history -class LLNotificationHistoryChannel : public LLNotificationChannel +// Local channel for persistent notifications +// Stores only persistent notifications. +// Class users can use connectChanged() to process persistent notifications +// (see LLNotificationStorage for example). +class LLPersistentNotificationChannel : public LLNotificationChannel { - LOG_CLASS(LLNotificationHistoryChannel); + LOG_CLASS(LLPersistentNotificationChannel); public: - LLNotificationHistoryChannel(const std::string& filename) : - LLNotificationChannel("History", "Visible", &historyFilter, LLNotificationComparators::orderByUUID()), - mFileName(filename) + LLPersistentNotificationChannel() : + LLNotificationChannel("Persistent", "Visible", ¬ificationFilter, LLNotificationComparators::orderByUUID()) { - connectChanged(boost::bind(&LLNotificationHistoryChannel::historyHandler, this, _1)); - loadPersistentNotifications(); } private: - bool historyHandler(const LLSD& payload) - { - // we ignore "load" messages, but rewrite the persistence file on any other - std::string sigtype = payload["sigtype"]; - if (sigtype != "load") - { - savePersistentNotifications(); - } - return false; - } - // The history channel gets all notifications except those that have been cancelled - static bool historyFilter(LLNotificationPtr pNotification) + // The channel gets all persistent notifications except those that have been canceled + static bool notificationFilter(LLNotificationPtr pNotification) { - return !pNotification->isCancelled(); - } - - void savePersistentNotifications() - { - /* NOTE: As of 2009-11-09 the reload of notifications on startup does not - work, and has not worked for months. Skip saving notifications until the - read can be fixed, because this hits the disk once per notification and - causes log spam. James - - llinfos << "Saving open notifications to " << mFileName << llendl; + bool handle_notification = false; - llofstream notify_file(mFileName.c_str()); - if (!notify_file.is_open()) - { - llwarns << "Failed to open " << mFileName << llendl; - return; - } - - LLSD output; - output["version"] = NOTIFICATION_PERSIST_VERSION; - LLSD& data = output["data"]; - - for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it) - { - if (!LLNotifications::instance().templateExists((*it)->getName())) continue; + handle_notification = pNotification->isPersistent() + && !pNotification->isCancelled(); - // only store notifications flagged as persisting - LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate((*it)->getName()); - if (!templatep->mPersist) continue; - - data.append((*it)->asLLSD()); - } - - LLPointer<LLSDFormatter> formatter = new LLSDXMLFormatter(); - formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY); - */ + return handle_notification; } - void loadPersistentNotifications() - { - llinfos << "Loading open notifications from " << mFileName << llendl; - - llifstream notify_file(mFileName.c_str()); - if (!notify_file.is_open()) - { - llwarns << "Failed to open " << mFileName << llendl; - return; - } - - LLSD input; - LLPointer<LLSDParser> parser = new LLSDXMLParser(); - if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0) - { - llwarns << "Failed to parse open notifications" << llendl; - return; - } - - if (input.isUndefined()) return; - std::string version = input["version"]; - if (version != NOTIFICATION_PERSIST_VERSION) - { - llwarns << "Bad open notifications version: " << version << llendl; - return; - } - LLSD& data = input["data"]; - if (data.isUndefined()) return; - - LLNotifications& instance = LLNotifications::instance(); - for (LLSD::array_const_iterator notification_it = data.beginArray(); - notification_it != data.endArray(); - ++notification_it) - { - instance.add(LLNotificationPtr(new LLNotification(*notification_it))); - } - } - - //virtual void onDelete(LLNotificationPtr pNotification) { - // we want to keep deleted notifications in our log + // we want to keep deleted notifications in our log, otherwise some + // notifications will be lost on exit. mItems.insert(pNotification); - - return; } - -private: - std::string mFileName; }; bool filterIgnoredNotifications(LLNotificationPtr notification) @@ -283,6 +199,7 @@ LLNotificationForm::LLNotificationForm(const std::string& name, const LLXMLNodeP } LLNotificationForm::LLNotificationForm(const LLSD& sd) + : mIgnore(IGNORE_NO) { if (sd.isArray()) { @@ -384,7 +301,8 @@ LLNotificationTemplate::LLNotificationTemplate() : mExpireSeconds(0), mExpireOption(-1), mURLOption(-1), - mURLOpenExternally(-1), + mURLOpenExternally(-1), + mPersist(false), mUnique(false), mPriority(NOTIFICATION_PRIORITY_NORMAL) { @@ -400,7 +318,9 @@ LLNotification::LLNotification(const LLNotification::Params& p) : mRespondedTo(false), mPriority(p.priority), mCancelled(false), - mIgnored(false) + mIgnored(false), + mResponderObj(NULL), + mIsReusable(false) { if (p.functor.name.isChosen()) { @@ -413,6 +333,15 @@ LLNotification::LLNotification(const LLNotification::Params& p) : mTemporaryResponder = true; } + else if(p.functor.responder.isChosen()) + { + mResponder = p.functor.responder; + } + + if(p.responder.isProvided()) + { + mResponderObj = p.responder; + } mId.generate(); init(p.name, p.form_elements); @@ -423,7 +352,9 @@ LLNotification::LLNotification(const LLSD& sd) : mTemporaryResponder(false), mRespondedTo(false), mCancelled(false), - mIgnored(false) + mIgnored(false), + mResponderObj(NULL), + mIsReusable(false) { mId.generate(); mSubstitutions = sd["substitutions"]; @@ -450,6 +381,13 @@ LLSD LLNotification::asLLSD() output["expiry"] = mExpiresAt; output["priority"] = (S32)mPriority; output["responseFunctor"] = mResponseFunctorName; + output["reusable"] = mIsReusable; + + if(mResponder) + { + output["responder"] = mResponder->asLLSD(); + } + return output; } @@ -477,7 +415,9 @@ void LLNotification::updateFrom(LLNotificationPtr other) mForm = other->mForm; mResponseFunctorName = other->mResponseFunctorName; mRespondedTo = other->mRespondedTo; + mResponse = other->mResponse; mTemporaryResponder = other->mTemporaryResponder; + mIsReusable = other->isReusable(); update(); } @@ -554,14 +494,24 @@ std::string LLNotification::getSelectedOptionName(const LLSD& response) void LLNotification::respond(const LLSD& response) { + // *TODO may remove mRespondedTo and use mResponce.isDefined() in isRespondedTo() mRespondedTo = true; - // look up the functor - LLNotificationFunctorRegistry::ResponseFunctor functor = - LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName); - // and then call it - functor(asLLSD(), response); - - if (mTemporaryResponder) + mResponse = response; + + if(mResponder) + { + mResponder->handleRespond(asLLSD(), response); + } + else + { + // look up the functor + LLNotificationFunctorRegistry::ResponseFunctor functor = + LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName); + // and then call it + functor(asLLSD(), response); + } + + if (mTemporaryResponder && !isReusable()) { LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); mResponseFunctorName = ""; @@ -595,6 +545,21 @@ void LLNotification::setResponseFunctor(std::string const &responseFunctorName) mTemporaryResponder = false; } +void LLNotification::setResponseFunctor(const LLNotificationFunctorRegistry::ResponseFunctor& cb) +{ + if(mTemporaryResponder) + { + LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName); + } + + LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, cb); +} + +void LLNotification::setResponseFunctor(const LLNotificationResponderPtr& responder) +{ + mResponder = responder; +} + bool LLNotification::payloadContainsAll(const std::vector<std::string>& required_fields) const { for(std::vector<std::string>::const_iterator required_fields_it = required_fields.begin(); @@ -854,8 +819,12 @@ bool LLNotificationChannelBase::updateItem(const LLSD& payload, LLNotificationPt if (wasFound) { abortProcessing = mChanged(payload); - mItems.erase(pNotification); - onDelete(pNotification); + // do not delete the notification to make LLChatHistory::appendMessage add notification panel to IM window + if( ! pNotification->isReusable() ) + { + mItems.erase(pNotification); + onDelete(pNotification); + } } } return abortProcessing; @@ -1058,6 +1027,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; } @@ -1085,12 +1055,9 @@ void LLNotifications::createDefaultChannels() LLNotificationChannel::buildChannel("Visible", "Ignore", &LLNotificationFilters::includeEverything); - // create special history channel - //std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_PER_SL_ACCOUNT, "open_notifications.xml" ); - // use ^^^ when done debugging notifications serialization - std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_USER_SETTINGS, "open_notifications.xml" ); + // create special persistent notification channel // this isn't a leak, don't worry about the empty "new" - new LLNotificationHistoryChannel(notifications_log_file); + new LLPersistentNotificationChannel(); // connect action methods to these channels LLNotifications::instance().getChannel("Expiration")-> @@ -1521,3 +1488,11 @@ std::ostream& operator<<(std::ostream& s, const LLNotification& notification) return s; } +void LLPostponedNotification::onCachedNameReceived(const LLUUID& id, const std::string& first, + const std::string& last, bool is_group) +{ + gCacheName->getFullName(id, mName); + modifyNotificationParams(); + LLNotifications::instance().add(mParams); + cleanup(); +} diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index aeb4cebf1b..c942a32512 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -104,6 +104,7 @@ #include "llinitparam.h" #include "llnotificationslistener.h" #include "llnotificationptr.h" +#include "llcachename.h" typedef enum e_notification_priority @@ -115,8 +116,23 @@ typedef enum e_notification_priority NOTIFICATION_PRIORITY_CRITICAL } ENotificationPriority; +class LLNotificationResponderInterface +{ +public: + LLNotificationResponderInterface(){}; + virtual ~LLNotificationResponderInterface(){}; + + virtual void handleRespond(const LLSD& notification, const LLSD& response) = 0; + + virtual LLSD asLLSD() = 0; + + virtual void fromLLSD(const LLSD& params) = 0; +}; + typedef boost::function<void (const LLSD&, const LLSD&)> LLNotificationResponder; +typedef boost::shared_ptr<LLNotificationResponderInterface> LLNotificationResponderPtr; + typedef LLFunctorRegistry<LLNotificationResponder> LLNotificationFunctorRegistry; typedef LLFunctorRegistration<LLNotificationResponder> LLNotificationFunctorRegistration; @@ -296,15 +312,18 @@ public: Optional<LLSD> form_elements; Optional<LLDate> time_stamp; Optional<LLNotificationContext*> context; + Optional<void*> responder; struct Functor : public LLInitParam::Choice<Functor> { Alternative<std::string> name; Alternative<LLNotificationFunctorRegistry::ResponseFunctor> function; + Alternative<LLNotificationResponderPtr> responder; Functor() : name("functor_name"), - function("functor") + function("functor"), + responder("responder") {} }; Optional<Functor> functor; @@ -317,6 +336,7 @@ public: form_elements("form_elements") { time_stamp = LLDate::now(); + responder = NULL; } Params(const std::string& _name) @@ -329,6 +349,7 @@ public: functor.name = _name; name = _name; time_stamp = LLDate::now(); + responder = NULL; } }; @@ -341,13 +362,17 @@ private: LLDate mExpiresAt; bool mCancelled; bool mRespondedTo; // once the notification has been responded to, this becomes true + LLSD mResponse; bool mIgnored; ENotificationPriority mPriority; LLNotificationFormPtr mForm; - + void* mResponderObj; // TODO - refactor/remove this field + bool mIsReusable; + LLNotificationResponderPtr mResponder; + // a reference to the template LLNotificationTemplatePtr mTemplatep; - + /* We want to be able to store and reload notifications so that they can survive a shutdown/restart of the client. So we can't simply pass in callbacks; @@ -371,7 +396,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(); @@ -384,6 +409,10 @@ public: void setResponseFunctor(std::string const &responseFunctorName); + void setResponseFunctor(const LLNotificationFunctorRegistry::ResponseFunctor& cb); + + void setResponseFunctor(const LLNotificationResponderPtr& responder); + typedef enum e_response_template_type { WITHOUT_DEFAULT_BUTTON, @@ -423,6 +452,10 @@ public: void respond(const LLSD& sd); + void* getResponder() { return mResponderObj; } + + void setResponder(void* responder) { mResponderObj = responder; } + void setIgnored(bool ignore); bool isCancelled() const @@ -435,6 +468,8 @@ public: return mRespondedTo; } + const LLSD& getResponse() { return mResponse; } + bool isIgnored() const { return mIgnored; @@ -444,7 +479,12 @@ public: { return mTemplatep->mName; } - + + bool isPersistent() const + { + return mTemplatep->mPersist; + } + const LLUUID& id() const { return mId; @@ -504,6 +544,10 @@ public: { return mId; } + + bool isReusable() { return mIsReusable; } + + void setReusable(bool reusable) { mIsReusable = reusable; } // comparing two notifications normally means comparing them by UUID (so we can look them // up quickly this way) @@ -621,7 +665,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) @@ -931,6 +975,62 @@ private: boost::scoped_ptr<LLNotificationsListener> mListener; }; +/** + * Abstract class for postponed notifications. + * Provides possibility to add notification after specified by id avatar or group will be + * received from cache name. The object of this type automatically well be deleted + * by cleanup method after respond will be received from cache name. + * + * To add custom postponed notification to the notification system client should: + * 1 create class derived from LLPostponedNotification; + * 2 call LLPostponedNotification::add method; + */ +class LLPostponedNotification +{ +public: + /** + * Performs hooking cache name callback which will add notification to notifications system. + * Type of added notification should be specified by template parameter T + * and non-private derived from LLPostponedNotification class, + * otherwise compilation error will occur. + */ + template<class T> + static void add(const LLNotification::Params& params, + const LLUUID& id, bool is_group) + { + // upcast T to the base type to restrict T derivation from LLPostponedNotification + LLPostponedNotification* thiz = new T(); + + thiz->mParams = params; + + gCacheName->get(id, is_group, boost::bind( + &LLPostponedNotification::onCachedNameReceived, thiz, _1, _2, + _3, _4)); + } + +private: + void onCachedNameReceived(const LLUUID& id, const std::string& first, + const std::string& last, bool is_group); + + void cleanup() + { + delete this; + } + +protected: + LLPostponedNotification() {} + virtual ~LLPostponedNotification() {} + + /** + * Abstract method provides possibility to modify notification parameters and + * will be called after cache name retrieve information about avatar or group + * and before notification will be added to the notification system. + */ + virtual void modifyNotificationParams() = 0; + + LLNotification::Params mParams; + std::string mName; +}; #endif//LL_LLNOTIFICATIONS_H 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 7f23fe2671..0cd052eefa 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -80,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), @@ -103,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), @@ -199,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 { @@ -212,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 { @@ -397,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; // :( + } } } @@ -414,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(); } @@ -475,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"); @@ -936,7 +948,7 @@ LLPanel *LLPanel::childGetVisiblePanelWithHelp() return ::childGetVisiblePanelWithHelp(this); } -void LLPanel::childSetPrevalidate(const std::string& id, BOOL (*func)(const LLWString &) ) +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 6de83fe3a7..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; } @@ -226,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); @@ -262,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/llradiogroup.cpp b/indra/llui/llradiogroup.cpp index 4087b484aa..e27792dc1d 100644 --- a/indra/llui/llradiogroup.cpp +++ b/indra/llui/llradiogroup.cpp @@ -106,7 +106,6 @@ LLRadioGroup::LLRadioGroup(const LLRadioGroup::Params& p) void LLRadioGroup::initFromParams(const Params& p) { - LLUICtrl::initFromParams(p); for (LLInitParam::ParamIterator<ItemParams>::const_iterator it = p.items().begin(); it != p.items().end(); ++it) @@ -124,6 +123,9 @@ void LLRadioGroup::initFromParams(const Params& p) LLRadioCtrl* item = LLUICtrlFactory::create<LLRadioCtrl>(item_params, this); mRadioButtons.push_back(item); } + + // call this *after* setting up mRadioButtons so we can handle setValue() calls + LLUICtrl::initFromParams(p); } @@ -138,10 +140,6 @@ BOOL LLRadioGroup::postBuild() { mRadioButtons[0]->setTabStop(true); } - if (mControlVariable) - { - setSelectedIndex(mControlVariable->getValue().asInteger()); - } return TRUE; } diff --git a/indra/llui/llresizehandle.cpp b/indra/llui/llresizehandle.cpp index 3df09d124a..00214d451c 100644 --- a/indra/llui/llresizehandle.cpp +++ b/indra/llui/llresizehandle.cpp @@ -124,7 +124,7 @@ BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) { // Make sure the mouse in still over the application. We don't want to make the parent // so big that we can't see the resize handle any more. - + S32 screen_x; S32 screen_y; localPointToScreen(x, y, &screen_x, &screen_y); @@ -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 @@ -146,61 +147,68 @@ BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) LLRect scaled_rect = orig_rect; S32 delta_x = screen_x - mDragLastScreenX; S32 delta_y = screen_y - mDragLastScreenY; - - if(delta_x == 0 && delta_y == 0) - return FALSE; - LLCoordGL mouse_dir; // use hysteresis on mouse motion to preserve user intent when mouse stops moving mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; - mLastMouseScreenX = screen_x; mLastMouseScreenY = screen_y; mLastMouseDir = mouse_dir; - S32 new_width = orig_rect.getWidth(); - S32 new_height = orig_rect.getHeight(); + S32 x_multiple = 1; + S32 y_multiple = 1; + switch( mCorner ) + { + case LEFT_TOP: + x_multiple = -1; + y_multiple = 1; + break; + case LEFT_BOTTOM: + x_multiple = -1; + y_multiple = -1; + break; + case RIGHT_TOP: + x_multiple = 1; + y_multiple = 1; + break; + case RIGHT_BOTTOM: + x_multiple = 1; + y_multiple = -1; + break; + } - S32 new_pos_x = orig_rect.mLeft; - S32 new_pos_y = orig_rect.mTop; + S32 new_width = orig_rect.getWidth() + x_multiple * delta_x; + if( new_width < mMinWidth ) + { + new_width = mMinWidth; + delta_x = x_multiple * (mMinWidth - orig_rect.getWidth()); + } + + S32 new_height = orig_rect.getHeight() + y_multiple * delta_y; + if( new_height < mMinHeight ) + { + new_height = mMinHeight; + delta_y = y_multiple * (mMinHeight - orig_rect.getHeight()); + } switch( mCorner ) { - case LEFT_TOP: - new_width-=delta_x; - new_height+=delta_y; - new_pos_x+=delta_x; - new_pos_y+=delta_y; + case LEFT_TOP: + scaled_rect.translate(delta_x, 0); break; case LEFT_BOTTOM: - new_width-=delta_x; - new_height-=delta_y; - new_pos_x+=delta_x; + scaled_rect.translate(delta_x, delta_y); break; case RIGHT_TOP: - new_width+=delta_x; - new_height+=delta_y; - new_pos_y+=delta_y; break; case RIGHT_BOTTOM: - new_width+=delta_x; - new_height-=delta_y; + scaled_rect.translate(0, delta_y); break; } - new_width = llmax(new_width,mMinWidth); - new_height = llmax(new_height,mMinHeight); - - LLRect::tCoordType screen_width = resizing_view->getParent()->getSnapRect().getWidth(); - LLRect::tCoordType screen_height = resizing_view->getParent()->getSnapRect().getHeight(); - - new_width = llmin(new_width, screen_width); - new_height = llmin(new_height, screen_height); - // temporarily set new parent rect - scaled_rect.setLeftTopAndSize(new_pos_x,new_pos_y,new_width,new_height); - + scaled_rect.mRight = scaled_rect.mLeft + new_width; + scaled_rect.mTop = scaled_rect.mBottom + new_height; resizing_view->setRect(scaled_rect); LLView* snap_view = NULL; @@ -251,11 +259,7 @@ BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) resizing_view->setRect(orig_rect); // translate and scale to new shape - resizing_view->reshape(scaled_rect.getWidth(),scaled_rect.getHeight()); - resizing_view->setRect(scaled_rect); - //set shape to handle dependent floaters... - resizing_view->handleReshape(scaled_rect, false); - + resizing_view->setShape(scaled_rect, true); // update last valid mouse cursor position based on resized view's actual size LLRect new_rect = resizing_view->getRect(); diff --git a/indra/llui/llscrollingpanellist.cpp b/indra/llui/llscrollingpanellist.cpp index 4f55c0507c..b7840d1b59 100644 --- a/indra/llui/llscrollingpanellist.cpp +++ b/indra/llui/llscrollingpanellist.cpp @@ -47,7 +47,12 @@ void LLScrollingPanelList::clearPanels() { deleteAllChildren(); mPanelList.clear(); - reshape( 1, 1, FALSE ); + + LLRect rc = getRect(); + rc.setLeftTopAndSize(rc.mLeft, rc.mTop, 1, 1); + setRect(rc); + + notifySizeChanged(rc.getHeight()); } S32 LLScrollingPanelList::addPanel( LLScrollingPanel* panel ) @@ -67,7 +72,11 @@ S32 LLScrollingPanelList::addPanel( LLScrollingPanel* panel ) max_width = llmax( max_width, childp->getRect().getWidth() ); cur_gap = GAP_BETWEEN_PANELS; } - reshape( max_width, total_height, FALSE ); + LLRect rc = getRect(); + rc.setLeftTopAndSize(rc.mLeft, rc.mTop, max_width, total_height); + setRect(rc); + + notifySizeChanged(rc.getHeight()); // Reposition each of the child views S32 cur_y = total_height; @@ -131,7 +140,11 @@ void LLScrollingPanelList::removePanel( U32 panel_index ) max_width = llmax( max_width, childp->getRect().getWidth() ); cur_gap = GAP_BETWEEN_PANELS; } - reshape( max_width, total_height, FALSE ); + LLRect rc = getRect(); + rc.setLeftTopAndSize(rc.mLeft, rc.mTop, max_width, total_height); + setRect(rc); + + notifySizeChanged(rc.getHeight()); // Reposition each of the child views S32 cur_y = total_height; @@ -200,3 +213,12 @@ void LLScrollingPanelList::draw() LLUICtrl::draw(); } +void LLScrollingPanelList::notifySizeChanged(S32 height) +{ + LLSD info; + info["action"] = "size_changes"; + info["height"] = height; + notifyParent(info); +} + +// EOF diff --git a/indra/llui/llscrollingpanellist.h b/indra/llui/llscrollingpanellist.h index 3abfbcbbe7..5f1996159b 100644 --- a/indra/llui/llscrollingpanellist.h +++ b/indra/llui/llscrollingpanellist.h @@ -61,7 +61,6 @@ public: Params() { name = "scrolling_panel_list"; - follows.flags = FOLLOWS_LEFT | FOLLOWS_BOTTOM; } }; LLScrollingPanelList(const Params& p) @@ -86,6 +85,11 @@ public: private: void updatePanelVisiblilty(); + /** + * Notify parent about size change, makes sense when used inside accordion + */ + void notifySizeChanged(S32 height); + panel_list_t mPanelList; }; diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp index 3cc92baa8d..d17be8b94a 100644 --- a/indra/llui/llscrolllistcell.cpp +++ b/indra/llui/llscrolllistcell.cpp @@ -185,6 +185,8 @@ LLScrollListText::LLScrollListText(const LLScrollListCell::Params& p) { sCount++; + mTextWidth = getWidth(); + // initialize rounded rect image if (!mRoundedRectImage) { @@ -340,7 +342,7 @@ void LLScrollListText::draw(const LLColor4& color, const LLColor4& highlight_col 0, LLFontGL::NO_SHADOW, string_chars, - getWidth(), + getTextWidth(), &right_x, TRUE); } diff --git a/indra/llui/llscrolllistcell.h b/indra/llui/llscrolllistcell.h index 5fecf5aade..b1c8901fc4 100644 --- a/indra/llui/llscrolllistcell.h +++ b/indra/llui/llscrolllistcell.h @@ -151,11 +151,16 @@ public: /*virtual*/ const std::string & getToolTip() const; /*virtual*/ BOOL needsToolTip() const; + S32 getTextWidth() const { return mTextWidth;} + void setTextWidth(S32 value) { mTextWidth = value;} + virtual void setWidth(S32 width) { LLScrollListCell::setWidth(width); mTextWidth = width; } + void setText(const LLStringExplicit& text); void setFontStyle(const U8 font_style); private: LLUIString mText; + S32 mTextWidth; const LLFontGL* mFont; LLColor4 mColor; U8 mUseColor; diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 4e84013db0..94eade06ad 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; }; @@ -169,6 +180,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) mOnSortChangedCallback( NULL ), mHighlightedItem(-1), mBorder(NULL), + mSortCallback(NULL), mPopupMenu(NULL), mNumDynamicWidthColumns(0), mTotalStaticColumnWidth(0), @@ -270,6 +282,8 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) text_p.border_visible(false); text_p.rect(mItemListRect); text_p.follows.flags(FOLLOWS_ALL); + // word wrap was added accroding to the EXT-6841 + text_p.wrap(true); addChild(LLUICtrlFactory::create<LLTextBox>(text_p)); } @@ -309,12 +323,9 @@ bool LLScrollListCtrl::preProcessChildNode(LLXMLNodePtr child) LLScrollListCtrl::~LLScrollListCtrl() { - std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); + delete mSortCallback; - if( gEditMenuHandler == this ) - { - gEditMenuHandler = NULL; - } + std::for_each(mItemList.begin(), mItemList.end(), DeletePointer()); } @@ -498,7 +509,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(); @@ -540,7 +551,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 @@ -943,14 +954,14 @@ void LLScrollListCtrl::mouseOverHighlightNthItem(S32 target_index) } } -S32 LLScrollListCtrl::selectMultiple( std::vector<LLUUID> ids ) +S32 LLScrollListCtrl::selectMultiple( uuid_vec_t ids ) { item_list::iterator iter; S32 count = 0; for (iter = mItemList.begin(); iter != mItemList.end(); iter++) { LLScrollListItem* item = *iter; - std::vector<LLUUID>::iterator iditr; + uuid_vec_t::iterator iditr; for(iditr = ids.begin(); iditr != ids.end(); ++iditr) { if (item->getEnabled() && (item->getUUID() == (*iditr))) @@ -1388,6 +1399,8 @@ void LLScrollListCtrl::drawItems() LLGLSUIDefault gls_ui; + F32 alpha = getDrawContext().mAlpha; + { LLLocalClipRect clip(mItemListRect); @@ -1463,7 +1476,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; } @@ -1534,7 +1547,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; } @@ -2393,7 +2406,7 @@ void LLScrollListCtrl::updateSort() const std::stable_sort( mItemList.begin(), mItemList.end(), - SortScrollListItem(mSortColumns)); + SortScrollListItem(mSortColumns,mSortCallback)); mSorted = true; } @@ -2409,7 +2422,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() @@ -2760,9 +2773,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 907dc90bea..1f0ef585db 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -73,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> { @@ -148,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 ] @@ -194,7 +219,10 @@ public: void deselectAllItems(BOOL no_commit_on_change = FALSE); // by default, go ahead and commit on selection change void clearHighlightedItems(); - void mouseOverHighlightNthItem( S32 index ); + + virtual void mouseOverHighlightNthItem( S32 index ); + + S32 getHighlightedItemInx() const { return mHighlightedItem; } void setDoubleClickCallback( callback_t cb ) { mOnDoubleClickCallback = cb; } void setMaximumSelectCallback( callback_t cb) { mOnMaximumSelectCallback = cb; } @@ -351,7 +379,7 @@ public: BOOL getSortAscending() { return mSortColumns.empty() ? TRUE : mSortColumns.back().second; } BOOL hasSortOrder() const; - S32 selectMultiple( std::vector<LLUUID> ids ); + S32 selectMultiple( uuid_vec_t ids ); // conceptually const, but mutates mItemList void updateSort() const; // sorts a list without affecting the permanent sort order (so further list insertions can be unsorted, for example) @@ -361,6 +389,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 @@ -473,6 +508,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/llsearcheditor.h b/indra/llui/llsearcheditor.h index 714aca9337..785d0633dc 100644 --- a/indra/llui/llsearcheditor.h +++ b/indra/llui/llsearcheditor.h @@ -66,6 +66,8 @@ public: } }; + void setCommitOnFocusLost(BOOL b) { if (mSearchEditor) mSearchEditor->setCommitOnFocusLost(b); } + protected: LLSearchEditor(const Params&); friend class LLUICtrlFactory; diff --git a/indra/llui/llsliderctrl.cpp b/indra/llui/llsliderctrl.cpp index 01c274bb4e..04958075db 100644 --- a/indra/llui/llsliderctrl.cpp +++ b/indra/llui/llsliderctrl.cpp @@ -63,7 +63,8 @@ LLSliderCtrl::LLSliderCtrl(const LLSliderCtrl::Params& p) mCanEditText(p.can_edit_text), mPrecision(p.decimal_digits), mTextEnabledColor(p.text_color()), - mTextDisabledColor(p.text_disabled_color()) + mTextDisabledColor(p.text_disabled_color()), + mLabelWidth(p.label_width) { S32 top = getRect().getHeight(); S32 bottom = 0; @@ -86,6 +87,7 @@ LLSliderCtrl::LLSliderCtrl(const LLSliderCtrl::Params& p) params.initial_value(p.label()); mLabelBox = LLUICtrlFactory::create<LLTextBox> (params); addChild(mLabelBox); + mLabelFont = params.font(); } if (p.show_text && !p.text_width.isProvided()) @@ -141,7 +143,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 )); @@ -186,9 +188,9 @@ BOOL LLSliderCtrl::setLabelArg( const std::string& key, const LLStringExplicit& if (mLabelBox) { res = mLabelBox->setTextArg(key, text); - if (res && mLabelWidth == 0) + if (res && mLabelFont && mLabelWidth == 0) { - S32 label_width = mFont->getWidth(mLabelBox->getText()); + S32 label_width = mLabelFont->getWidth(mLabelBox->getText()); LLRect rect = mLabelBox->getRect(); S32 prev_right = rect.mRight; rect.mRight = rect.mLeft + label_width; diff --git a/indra/llui/llsliderctrl.h b/indra/llui/llsliderctrl.h index c425849782..482c81a0f4 100644 --- a/indra/llui/llsliderctrl.h +++ b/indra/llui/llsliderctrl.h @@ -141,6 +141,7 @@ private: void reportInvalidData(); const LLFontGL* mFont; + const LLFontGL* mLabelFont; BOOL mShowText; BOOL mCanEditText; @@ -158,3 +159,4 @@ private: }; #endif // LL_LLSLIDERCTRL_H + diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index 20a1ab7af3..b47f21ed8a 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) @@ -453,3 +457,8 @@ BOOL LLSpinCtrl::handleKeyHere(KEY key, MASK mask) return FALSE; } +BOOL LLSpinCtrl::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + // just treat a double click as a second click + return handleMouseDown(x, y, mask); +} diff --git a/indra/llui/llspinctrl.h b/indra/llui/llspinctrl.h index 0e610b7741..06201255d2 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(); @@ -94,6 +94,7 @@ public: virtual BOOL handleScrollWheel(S32 x,S32 y,S32 clicks); virtual BOOL handleKeyHere(KEY key, MASK mask); + virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); void onEditorCommit(const LLSD& data); static void onEditorGainFocus(LLFocusableElement* caller, void *userdata); @@ -103,6 +104,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 17aecaf32f..390ec234d3 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,8 +244,9 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) LLTextBase::~LLTextBase() { - delete mPopupMenu; - clearSegments(); + // Menu, like any other LLUICtrl, is deleted by its parent - gMenuHolder + + mSegments.clear(); } void LLTextBase::initFromParams(const LLTextBase::Params& p) @@ -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,6 +350,8 @@ void LLTextBase::drawSelectionBackground() S32 segment_line_start = segmentp->getStart() + segment_offset; S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd); + if (segment_line_start > segment_line_end) break; + S32 segment_width = 0; S32 segment_height = 0; @@ -357,8 +363,11 @@ void LLTextBase::drawSelectionBackground() selection_rect.mLeft += segment_width; } - // if selection spans end of current segment... - if (selection_right > segment_line_end) + // if selection_right == segment_line_end then that means we are the first character of the next segment + // or first character of the next line, in either case we want to add the length of the current segment + // to the selection rectangle and continue. + // if selection right > segment_line_end then selection spans end of current segment... + if (selection_right >= segment_line_end) { // extend selection slightly beyond end of line // to indicate selection of newline character (use "n" character to determine width) @@ -406,7 +415,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); @@ -492,7 +501,6 @@ void LLTextBase::drawCursor() void LLTextBase::drawText() { - LLWString text = getWText(); const S32 text_len = getLength(); if( text_len <= 0 ) { @@ -619,7 +627,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 @@ -656,7 +665,7 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s } onValueChange(pos, pos + insert_len); - needsReflow(); + needsReflow(pos); return insert_len; } @@ -716,7 +725,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 } @@ -732,7 +741,7 @@ S32 LLTextBase::overwriteCharNoUndo(S32 pos, llwchar wc) getViewModel()->setDisplay(text); onValueChange(pos, pos + 1); - needsReflow(); + needsReflow(pos); return 1; } @@ -743,7 +752,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); } @@ -757,15 +767,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(); @@ -773,7 +786,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 @@ -823,7 +837,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) @@ -948,7 +962,18 @@ void LLTextBase::reshape(S32 width, S32 height, BOOL called_from_parent) { if (width != getRect().getWidth() || height != getRect().getHeight()) { + //EXT-4288 + //to keep consistance scrolling behaviour + //when scrolling from top and from bottom... + bool is_scrolled_to_end = (mScroller!=NULL) && scrolledToEnd(); + LLUICtrl::reshape( width, height, called_from_parent ); + + if (is_scrolled_to_end) + { + deselect(); + endOfDoc(); + } // do this first after reshape, because other things depend on // up-to-date mVisibleTextRect @@ -1018,12 +1043,29 @@ 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()); } //virtual +BOOL LLTextBase::canDeselect() const +{ + return hasSelection(); +} + + +//virtual void LLTextBase::deselect() { mSelectionStart = 0; @@ -1068,15 +1110,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 @@ -1108,7 +1151,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 @@ -1118,6 +1160,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()); } @@ -1425,10 +1468,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()) { @@ -1464,6 +1507,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url)); registrar.add("Url.Execute", boost::bind(&LLUrlAction::executeSLURL, url)); registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url)); + registrar.add("Url.ShowProfile", boost::bind(&LLUrlAction::showProfile, url)); registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url)); registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url)); registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url)); @@ -1510,16 +1554,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) @@ -1536,13 +1571,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 @@ -1570,25 +1599,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()) + { + appendAndHighlightText(match.getLabel(), prepend_newline, part, style_params); + } + else { - segment_set_t::iterator it = getSegIterContaining(getLength()-1); - if (it != mSegments.end()) + 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()) @@ -1611,9 +1650,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) +{ + 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; + if (new_text.empty()) return; // Save old state S32 selection_start = mSelectionStart; @@ -1631,7 +1676,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++) @@ -1651,7 +1696,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); @@ -1675,7 +1721,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); } @@ -1719,7 +1766,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(); @@ -1756,7 +1803,7 @@ void LLTextBase::setWText(const LLWString& text) setText(wstring_to_utf8str(text)); } -LLWString LLTextBase::getWText() const +const LLWString& LLTextBase::getWText() const { return getViewModel()->getDisplay(); } @@ -1765,7 +1812,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(); @@ -1790,11 +1837,18 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round const LLTextSegmentPtr segmentp = *line_seg_iter; S32 segment_line_start = segmentp->getStart() + line_seg_offset; - S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd - 1) - segment_line_start; + S32 segment_line_length = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd) - segment_line_start; S32 text_width, text_height; - segmentp->getDimensions(line_seg_offset, segment_line_length, text_width, text_height); - if (local_x < start_x + text_width // cursor to left of right edge of text - || segmentp->getEnd() >= line_iter->mDocIndexEnd - 1) // or this segment wraps to next line + bool newline = segmentp->getDimensions(line_seg_offset, segment_line_length, text_width, text_height); + + // if we've reached a line of text *below* the mouse cursor, doc index is first character on that line + if (hit_past_end_of_line && local_y - mVisibleTextRect.mBottom + visible_region.mBottom > line_iter->mRect.mTop) + { + pos = segment_line_start; + break; + } + if (local_x < start_x + text_width // cursor to left of right edge of text + || newline) // or this line ends with a newline, set doc pos to newline char { // Figure out which character we're nearest to. S32 offset; @@ -1818,6 +1872,13 @@ S32 LLTextBase::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round pos = segment_line_start + offset; break; } + else if (hit_past_end_of_line && segmentp->getEnd() >= line_iter->mDocIndexEnd - 1) + { + // segment wraps to next line, so just set doc pos to start of next line (represented by mDocIndexEnd) + pos = llmin(getLength(), line_iter->mDocIndexEnd); + break; + } + start_x += text_width; } @@ -2212,9 +2273,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 ) {} @@ -2239,7 +2300,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), @@ -2250,7 +2311,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)); } } @@ -2276,14 +2337,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); @@ -2313,8 +2381,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 @@ -2374,8 +2440,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; } @@ -2384,8 +2454,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; } @@ -2394,8 +2468,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; @@ -2405,8 +2483,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; @@ -2451,7 +2533,7 @@ bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& widt if (num_chars > 0) { height = mFontHeight; - LLWString text = mEditor.getWText(); + 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') @@ -2478,7 +2560,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 +2570,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 diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 038b9eaa62..8ed0680df9 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,11 +126,13 @@ 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; // LLEditMenuHandler interface + /*virtual*/ BOOL canDeselect() const; /*virtual*/ void deselect(); // used by LLTextSegment layout code @@ -144,11 +151,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 +170,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 +191,6 @@ public: bool scrolledToEnd(); const LLFontGL* getDefaultFont() const { return mDefaultFont; } - LLStyle::Params getDefaultStyle(); public: // Fired when a URL link is clicked @@ -274,14 +280,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 +297,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 +367,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 +395,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 +433,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 +443,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 +462,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 f2c3879a6c..4fd62045e8 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -237,13 +237,16 @@ 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), @@ -254,11 +257,12 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : mShowLineNumbers ( p.show_line_numbers ), mCommitOnFocusLost( p.commit_on_focus_lost), mAllowEmbeddedItems( p.embedded_items ), - mHandleEditKeysDirectly( p.handle_edit_keys_directly ), mMouseDownX(0), mMouseDownY(0), mTabsToNextField(p.ignore_tab), - mContextMenu(NULL) + mPrevalidateFunc(p.prevalidate_callback()), + mContextMenu(NULL), + mShowContextMenu(p.show_context_menu) { mDefaultFont = p.font; @@ -299,12 +303,6 @@ LLTextEditor::~LLTextEditor() { gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() while LLTextEditor still valid - // Route menu back to the default - if( gEditMenuHandler == this ) - { - gEditMenuHandler = NULL; - } - // Scrollbar is deleted by LLView std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); @@ -318,6 +316,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(); @@ -490,21 +499,6 @@ void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out, } } -// virtual -BOOL LLTextEditor::canDeselect() const -{ - return hasSelection(); -} - - -void LLTextEditor::deselect() -{ - mSelectionStart = 0; - mSelectionEnd = 0; - mIsSelecting = FALSE; -} - - BOOL LLTextEditor::selectionContainsLineBreaks() { if (hasSelection()) @@ -651,6 +645,7 @@ void LLTextEditor::selectAll() mSelectionStart = getLength(); mSelectionEnd = 0; setCursorPos(mSelectionEnd); + updatePrimary(); } BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) @@ -718,9 +713,13 @@ BOOL LLTextEditor::handleRightMouseDown(S32 x, S32 y, MASK mask) { setFocus(TRUE); } - if (!LLTextBase::handleRightMouseDown(x, y, mask)) + // Prefer editor menu if it has selection. See EXT-6806. + if (hasSelection() || !LLTextBase::handleRightMouseDown(x, y, mask)) { - showContextMenu(x, y); + if(getShowContextMenu()) + { + showContextMenu(x, y); + } } return TRUE; } @@ -906,6 +905,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 { @@ -990,7 +1004,7 @@ void LLTextEditor::removeCharOrTab() } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } } @@ -1013,7 +1027,7 @@ void LLTextEditor::removeChar() } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } } @@ -1029,7 +1043,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 @@ -1149,22 +1177,6 @@ BOOL LLTextEditor::handleSelectionKey(const KEY key, const MASK mask) } } - if( !handled && mHandleEditKeysDirectly ) - { - if( (MASK_CONTROL & mask) && ('A' == key) ) - { - if( canSelectAll() ) - { - selectAll(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - } - if( handled ) { // take selection to 'primary' clipboard @@ -1198,6 +1210,7 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) case KEY_DOWN: changeLine( 1 ); + deselect(); break; case KEY_PAGE_DOWN: @@ -1211,7 +1224,7 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) case KEY_LEFT: if( hasSelection() ) { - setCursorPos(llmin( mCursorPos - 1, mSelectionStart, mSelectionEnd )); + setCursorPos(llmin( mSelectionStart, mSelectionEnd )); } else { @@ -1221,7 +1234,7 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } } break; @@ -1229,7 +1242,7 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) case KEY_RIGHT: if( hasSelection() ) { - setCursorPos(llmax( mCursorPos + 1, mSelectionStart, mSelectionEnd )); + setCursorPos(llmax( mSelectionStart, mSelectionEnd )); } else { @@ -1239,7 +1252,7 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } } break; @@ -1250,6 +1263,11 @@ BOOL LLTextEditor::handleNavigationKey(const KEY key, const MASK mask) } } + if (handled) + { + deselect(); + } + return handled; } @@ -1285,8 +1303,6 @@ void LLTextEditor::cut() gClipboard.copyFromSubstring( getWText(), left_pos, length, mSourceID ); deleteSelection( FALSE ); - needsReflow(); - onKeyStroke(); } @@ -1391,8 +1407,6 @@ void LLTextEditor::pasteHelper(bool is_primary) setCursorPos(mCursorPos + insert(mCursorPos, clean_string, FALSE, LLTextSegmentPtr())); deselect(); - needsReflow(); - onKeyStroke(); } @@ -1506,75 +1520,13 @@ BOOL LLTextEditor::handleControlKey(const KEY key, const MASK mask) return handled; } -BOOL LLTextEditor::handleEditKey(const KEY key, const MASK mask) -{ - BOOL handled = FALSE; - // Standard edit keys (Ctrl-X, Delete, etc,) are handled here instead of routed by the menu system. - if( KEY_DELETE == key ) +BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask) { - if( canDoDelete() ) - { - doDelete(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - else - if( MASK_CONTROL & mask ) - { - if( 'C' == key ) - { - if( canCopy() ) - { - copy(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - else - if( 'V' == key ) - { - if( canPaste() ) - { - paste(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - else - if( 'X' == key ) - { - if( canCut() ) - { - cut(); - } - else - { - reportBadKeystroke(); - } - handled = TRUE; - } - } - - return handled; -} - - -BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return_key_hit) -{ - *return_key_hit = FALSE; BOOL handled = TRUE; + if (mReadOnly) return FALSE; + switch( key ) { case KEY_INSERT: @@ -1596,7 +1548,7 @@ BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return } else { - reportBadKeystroke(); + LLUI::reportBadKeystroke(); } break; @@ -1649,6 +1601,10 @@ BOOL LLTextEditor::handleSpecialKey(const KEY key, const MASK mask, BOOL* return break; } + if (handled) + { + onKeyStroke(); + } return handled; } @@ -1669,9 +1625,6 @@ void LLTextEditor::unindentLineBeforeCloseBrace() BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) { BOOL handled = FALSE; - BOOL selection_modified = FALSE; - BOOL return_key_hit = FALSE; - BOOL text_may_have_changed = TRUE; // Special case for TAB. If want to move to next field, report // not handled and let the parent take care of field movement. @@ -1679,118 +1632,24 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) { return FALSE; } - /* - if (KEY_F10 == key) - { - LLComboBox::Params cp; - cp.name = "combo box"; - cp.label = "my combo"; - cp.rect.width = 100; - cp.rect.height = 20; - cp.items.add().label = "item 1"; - cp.items.add().label = "item 2"; - cp.items.add().label = "item 3"; - appendWidget(LLUICtrlFactory::create<LLComboBox>(cp), "combo", true, false); - } - if (KEY_F11 == key) + if (mReadOnly && mScroller) { - LLButton::Params bp; - bp.name = "text button"; - bp.label = "Click me"; - bp.rect.width = 100; - bp.rect.height = 20; - - appendWidget(LLUICtrlFactory::create<LLButton>(bp), "button", true, false); - } - */ - if (mReadOnly) - { - if(mScroller) - { - handled = mScroller->handleKeyHere( key, mask ); + handled = (mScroller && mScroller->handleKeyHere( key, mask )) + || handleSelectionKey(key, mask) + || handleControlKey(key, mask); } else { - handled = handleNavigationKey( key, mask ); - } - - } - else - { - // handle navigation keys ourself - handled = handleNavigationKey( key, mask ); - } - - - if( handled ) - { - text_may_have_changed = FALSE; - } - - if( !handled ) - { - handled = handleSelectionKey( key, mask ); - if( handled ) - { - selection_modified = TRUE; - } - } - - if( !handled ) - { - handled = handleControlKey( key, mask ); - if( handled ) - { - selection_modified = TRUE; - } - } - - if( !handled && mHandleEditKeysDirectly ) - { - handled = handleEditKey( key, mask ); - if( handled ) - { - selection_modified = TRUE; - text_may_have_changed = TRUE; - } - } - - // Handle most keys only if the text editor is writeable. - if( !mReadOnly ) - { - if( !handled ) - { - handled = handleSpecialKey( key, mask, &return_key_hit ); - if( handled ) - { - selection_modified = TRUE; - text_may_have_changed = TRUE; - } - } - + handled = handleNavigationKey( key, mask ) + || handleSelectionKey(key, mask) + || handleControlKey(key, mask) + || handleSpecialKey(key, mask); } if( handled ) { resetCursorBlink(); - - // Most keystrokes will make the selection box go away, but not all will. - if( !selection_modified && - KEY_SHIFT != key && - KEY_CONTROL != key && - KEY_ALT != key && - KEY_CAPSLOCK ) - { - deselect(); - } - - if(text_may_have_changed) - { - needsReflow(); - - onKeyStroke(); - } needsScroll(); } @@ -1831,8 +1690,6 @@ BOOL LLTextEditor::handleUnicodeCharHere(llwchar uni_char) // Most keystrokes will make the selection box go away, but not all will. deselect(); - needsReflow(); - onKeyStroke(); } @@ -1891,8 +1748,6 @@ void LLTextEditor::doDelete() } onKeyStroke(); - - needsReflow(); } //---------------------------------------------------------------------------- @@ -1935,8 +1790,6 @@ void LLTextEditor::undo() setCursorPos(pos); - needsReflow(); - onKeyStroke(); } @@ -1979,8 +1832,6 @@ void LLTextEditor::redo() setCursorPos(pos); - needsReflow(); - onKeyStroke(); } @@ -2040,6 +1891,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); @@ -2285,7 +2150,7 @@ void LLTextEditor::getCurrentLineAndColumn( S32* line, S32* col, BOOL include_wo void LLTextEditor::autoIndent() { // Count the number of spaces in the current line - S32 line = getLineNumFromDocIndex(mCursorPos); + S32 line = getLineNumFromDocIndex(mCursorPos, false); S32 line_start = getLineStart(line); S32 space_count = 0; S32 i; @@ -2325,8 +2190,6 @@ void LLTextEditor::insertText(const std::string &new_text) setCursorPos(mCursorPos + insert( mCursorPos, utf8str_to_wstring(new_text), FALSE, LLTextSegmentPtr() )); - needsReflow(); - setEnabled( enabled ); } @@ -2349,8 +2212,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 ) { @@ -2375,52 +2236,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; @@ -2432,7 +2247,6 @@ void LLTextEditor::removeTextFromEnd(S32 num_chars) mSelectionStart = llclamp(mSelectionStart, 0, len); mSelectionEnd = llclamp(mSelectionEnd, 0, len); - needsReflow(); needsScroll(); } @@ -2491,8 +2305,6 @@ BOOL LLTextEditor::tryToRevertToPristineState() i--; } } - - needsReflow(); } return isPristine(); // TRUE => success @@ -2528,9 +2340,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); @@ -2559,13 +2371,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); } } } @@ -2664,7 +2479,6 @@ BOOL LLTextEditor::importBuffer(const char* buffer, S32 length ) startOfDoc(); deselect(); - needsReflow(); return success; } @@ -2768,7 +2582,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..9b3ab9414c 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,13 @@ 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; @@ -143,13 +145,10 @@ public: virtual BOOL canDoDelete() const; virtual void selectAll(); virtual BOOL canSelectAll() const; - virtual void deselect(); - virtual BOOL canDeselect() const; 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 +200,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(); @@ -213,13 +215,10 @@ protected: S32 indentLine( S32 pos, S32 spaces ); void unindentLineBeforeCloseBrace(); - void reportBadKeystroke() { make_ui_sound("UISndBadKeystroke"); } - BOOL handleNavigationKey(const KEY key, const MASK mask); - BOOL handleSpecialKey(const KEY key, const MASK mask, BOOL* return_key_hit); + BOOL handleSpecialKey(const KEY key, const MASK mask); BOOL handleSelectionKey(const KEY key, const MASK mask); BOOL handleControlKey(const KEY key, const MASK mask); - BOOL handleEditKey(const KEY key, const MASK mask); BOOL selectionContainsLineBreaks(); void deleteSelection(BOOL transient_operation); @@ -320,16 +319,14 @@ private: BOOL mTakesFocus; BOOL mAllowEmbeddedItems; + bool mShowContextMenu; LLUUID mSourceID; - // If true, the standard edit keys (Ctrl-X, Delete, etc,) are handled here - //instead of routed by the menu system - BOOL mHandleEditKeysDirectly; - 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..bf12384a28 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" @@ -55,6 +56,7 @@ #include "llfloaterreg.h" #include "llmenugl.h" #include "llmenubutton.h" +#include "llloadingindicator.h" #include "llwindow.h" // for registration @@ -85,12 +87,18 @@ std::list<std::string> gUntranslated; /*static*/ LLHelp* LLUI::sHelpImpl = NULL; /*static*/ std::vector<std::string> LLUI::sXUIPaths; /*static*/ LLFrameTimer LLUI::sMouseIdleTimer; +/*static*/ LLUI::add_popup_t LLUI::sAddPopupFunc; +/*static*/ LLUI::remove_popup_t LLUI::sRemovePopupFunc; +/*static*/ LLUI::clear_popups_t LLUI::sClearPopupsFunc; // register filtereditor here static LLDefaultChildRegistry::Register<LLFilterEditor> register_filter_editor("filter_editor"); static LLDefaultChildRegistry::Register<LLFlyoutButton> register_flyout_button("flyout_button"); static LLDefaultChildRegistry::Register<LLSearchEditor> register_search_editor("search_editor"); + +// register other widgets which otherwise may not be linked in static LLDefaultChildRegistry::Register<LLMenuButton> register_menu_button("menu_button"); +static LLDefaultChildRegistry::Register<LLLoadingIndicator> register_loading_indicator("loading_indicator"); // @@ -180,19 +188,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(); } @@ -203,7 +211,7 @@ void gl_rect_2d(S32 left, S32 top, S32 right, S32 bottom, BOOL filled ) // Counterclockwise quad will face the viewer if( filled ) - { + { gGL.begin( LLRender::QUADS ); gGL.vertex2i(left, top); gGL.vertex2i(left, bottom); @@ -508,9 +516,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 +645,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 +668,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 +788,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 +821,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 +860,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 +887,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 +898,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 +1091,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 +1234,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 +1251,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 +1424,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, @@ -1573,6 +1614,13 @@ void LLUI::cleanupClass() sImageProvider->cleanUp(); } +void LLUI::setPopupFuncs(const add_popup_t& add_popup, const remove_popup_t& remove_popup, const clear_popups_t& clear_popups) +{ + sAddPopupFunc = add_popup; + sRemovePopupFunc = remove_popup; + sClearPopupsFunc = clear_popups; +} + //static void LLUI::dirtyRect(LLRect rect) { @@ -1591,7 +1639,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 +1648,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 +1663,7 @@ void LLUI::popMatrix() //static void LLUI::loadIdentity() { - glLoadIdentity(); + gGL.loadUIIdentity(); LLFontGL::sCurOrigin.mX = 0; LLFontGL::sCurOrigin.mY = 0; LLFontGL::sCurOrigin.mZ = 0; @@ -1843,6 +1891,39 @@ LLControlGroup& LLUI::getControlControlGroup (const std::string& controlname) return *sSettingGroups["config"]; // default group } +//static +void LLUI::addPopup(LLView* viewp) +{ + if (sAddPopupFunc) + { + sAddPopupFunc(viewp); + } +} + +//static +void LLUI::removePopup(LLView* viewp) +{ + if (sRemovePopupFunc) + { + sRemovePopupFunc(viewp); + } +} + +//static +void LLUI::clearPopups() +{ + if (sClearPopupsFunc) + { + sClearPopupsFunc(); + } +} + +//static +void LLUI::reportBadKeystroke() +{ + make_ui_sound("UISndBadKeystroke"); +} + //static // spawn_x and spawn_y are top left corner of view in screen GL coordinates void LLUI::positionViewNearMouse(LLView* view, S32 spawn_x, S32 spawn_y) @@ -1894,7 +1975,9 @@ namespace LLInitParam blue("blue"), alpha("alpha"), control("") - {} + { + setBlockFromValue(); + } void TypedParam<LLUIColor>::setValueFromBlock() const { @@ -1911,10 +1994,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 +2022,9 @@ namespace LLInitParam size("size"), style("style") { + setBlockFromValue(); addSynonym(name, ""); + setBlockFromValue(); } void TypedParam<const LLFontGL*>::setValueFromBlock() const @@ -1965,9 +2050,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 +2064,9 @@ namespace LLInitParam bottom("bottom"), width("width"), height("height") - {} + { + setBlockFromValue(); + } void TypedParam<LLRect>::setValueFromBlock() const { @@ -2064,6 +2151,7 @@ namespace LLInitParam x("x"), y("y") { + setBlockFromValue(); } void TypedParam<LLCoordGL>::setValueFromBlock() const @@ -2073,8 +2161,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..c18262ef76 100644 --- a/indra/llui/llui.h +++ b/indra/llui/llui.h @@ -160,12 +160,17 @@ public: // Methods // typedef std::map<std::string, LLControlGroup*> settings_map_t; + typedef boost::function<void(LLView*)> add_popup_t; + typedef boost::function<void(LLView*)> remove_popup_t; + typedef boost::function<void(void)> clear_popups_t; + static void initClass(const settings_map_t& settings, LLImageProviderInterface* image_provider, LLUIAudioCallback audio_callback = NULL, const LLVector2 *scale_factor = NULL, const std::string& language = LLStringUtil::null); static void cleanupClass(); + static void setPopupFuncs(const add_popup_t& add_popup, const remove_popup_t&, const clear_popups_t& ); static void pushMatrix(); static void popMatrix(); @@ -208,6 +213,12 @@ public: static void resetMouseIdleTimer() { sMouseIdleTimer.reset(); } static LLWindow* getWindow() { return sWindow; } + static void addPopup(LLView*); + static void removePopup(LLView*); + static void clearPopups(); + + static void reportBadKeystroke(); + // Ensures view does not overlap mouse cursor, but is inside // the view's parent rectangle. Used for tooltips, inspectors. // Optionally override the view's default X/Y, which are relative to the @@ -227,6 +238,9 @@ private: static LLImageProviderInterface* sImageProvider; static std::vector<std::string> sXUIPaths; static LLFrameTimer sMouseIdleTimer; + static add_popup_t sAddPopupFunc; + static remove_popup_t sRemovePopupFunc; + static clear_popups_t sClearPopupsFunc; }; @@ -426,8 +440,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/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index 27237800d4..930dadc377 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -435,7 +435,10 @@ void LLUICtrlFactory::registerWidget(const std::type_info* widget_type, const st std::string* existing_tag = LLWidgetNameRegistry::instance().getValue(param_block_type); if (existing_tag != NULL && *existing_tag != tag) { - llerrs << "Duplicate entry for T::Params, try creating empty param block in derived classes that inherit T::Params" << llendl; + std::cerr << "Duplicate entry for T::Params, try creating empty param block in derived classes that inherit T::Params" << std::endl; + // forcing crash here + char* foo = 0; + *foo = 1; } LLWidgetNameRegistry ::instance().defaultRegistrar().add(param_block_type, tag); // associate widget type with factory function diff --git a/indra/llui/lluifwd.h b/indra/llui/lluifwd.h index f99bb39fdd..d6047b943c 100644 --- a/indra/llui/lluifwd.h +++ b/indra/llui/lluifwd.h @@ -39,6 +39,7 @@ class LLComboBox; class LLDragHandle; class LLFloater; class LLIconCtrl; +class LLLoadingIndicator; class LLLineEditor; class LLMenuGL; class LLPanel; 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/llurlaction.cpp b/indra/llui/llurlaction.cpp index 679db5e39b..2f13a56b42 100644 --- a/indra/llui/llurlaction.cpp +++ b/indra/llui/llurlaction.cpp @@ -146,3 +146,20 @@ void LLUrlAction::copyLabelToClipboard(std::string url) LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(match.getLabel())); } } + +void LLUrlAction::showProfile(std::string url) +{ + // Get id from 'secondlife:///app/{cmd}/{id}/{action}' + // and show its profile + LLURI uri(url); + LLSD path_array = uri.pathArray(); + if (path_array.size() == 4) + { + std::string id_str = path_array.get(2).asString(); + if (LLUUID::validate(id_str)) + { + std::string cmd_str = path_array.get(1).asString(); + executeSLURL("secondlife:///app/" + cmd_str + "/" + id_str + "/about"); + } + } +} diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h index 4830cf27ef..b96faf1b3f 100644 --- a/indra/llui/llurlaction.h +++ b/indra/llui/llurlaction.h @@ -79,6 +79,9 @@ public: /// copy a Url to the clipboard static void copyURLToClipboard(std::string url); + /// if the Url specifies an SL command in the form like 'app/{cmd}/{id}/*', show its profile + static void showProfile(std::string url); + /// specify the callbacks to enable this class's functionality static void setOpenURLCallback(void (*cb) (const std::string& url)); static void setOpenURLInternalCallback(void (*cb) (const std::string& url)); diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 4927e57a52..3375c13c94 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -34,13 +34,16 @@ #include "linden_common.h" #include "llurlentry.h" #include "lluri.h" +#include "llurlmatch.h" +#include "llurlregistry.h" #include "llcachename.h" #include "lltrans.h" #include "lluicolortable.h" -LLUrlEntryBase::LLUrlEntryBase() -: mColor(LLUIColorTable::instance().getColor("HTMLLinkColor")) +LLUrlEntryBase::LLUrlEntryBase() : + mColor(LLUIColorTable::instance().getColor("HTMLLinkColor")), + mDisabledLink(false) { } @@ -48,7 +51,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 +91,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(); @@ -104,7 +107,7 @@ std::string LLUrlEntryBase::getLabelFromWikiLink(const std::string &url) 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 +194,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); } @@ -204,7 +207,7 @@ LLUrlEntryHTTPNoProtocol::LLUrlEntryHTTPNoProtocol() mPattern = boost::regex("(" "\\bwww\\.\\S+\\.\\S+" // i.e. www.FOO.BAR "|" // or - "(?<!@)\\b[^[:space:]:@/]+\\.(?:com|net|edu|org)([/:]\\S*)?\\b" // i.e. FOO.net + "(?<!@)\\b[^[:space:]:@/>]+\\.(?:com|net|edu|org)([/:][^[:space:]<]*)?\\b" // i.e. FOO.net ")", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_http.xml"; @@ -216,7 +219,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) { @@ -231,7 +234,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"); @@ -286,7 +289,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) { @@ -319,6 +322,38 @@ void LLUrlEntryAgent::onAgentNameReceived(const LLUUID& id, callObservers(id.asString(), first + " " + last); } +std::string LLUrlEntryAgent::getTooltip(const std::string &string) const +{ + // return a tooltip corresponding to the URL type instead of the generic one + std::string url = getUrl(string); + + if (LLStringUtil::endsWith(url, "/mute")) + { + return LLTrans::getString("TooltipAgentMute"); + } + if (LLStringUtil::endsWith(url, "/unmute")) + { + return LLTrans::getString("TooltipAgentUnmute"); + } + if (LLStringUtil::endsWith(url, "/im")) + { + return LLTrans::getString("TooltipAgentIM"); + } + if (LLStringUtil::endsWith(url, "/pay")) + { + return LLTrans::getString("TooltipAgentPay"); + } + if (LLStringUtil::endsWith(url, "/offerteleport")) + { + return LLTrans::getString("TooltipAgentOfferTeleport"); + } + if (LLStringUtil::endsWith(url, "/requestfriend")) + { + return LLTrans::getString("TooltipAgentRequestFriend"); + } + return LLTrans::getString("TooltipAgentUrl"); +} + std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) { if (!gCacheName) @@ -342,6 +377,31 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa } else if (gCacheName->getFullName(agent_id, full_name)) { + // customize label string based on agent SLapp suffix + if (LLStringUtil::endsWith(url, "/mute")) + { + return LLTrans::getString("SLappAgentMute") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/unmute")) + { + return LLTrans::getString("SLappAgentUnmute") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/im")) + { + return LLTrans::getString("SLappAgentIM") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/pay")) + { + return LLTrans::getString("SLappAgentPay") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/offerteleport")) + { + return LLTrans::getString("SLappAgentOfferTeleport") + " " + full_name; + } + if (LLStringUtil::endsWith(url, "/requestfriend")) + { + return LLTrans::getString("SLappAgentRequestFriend") + " " + full_name; + } return full_name; } else @@ -433,6 +493,35 @@ std::string LLUrlEntryInventory::getLabel(const std::string &url, const LLUrlLab return LLURI::unescape(label.empty() ? url : label); } +// +// LLUrlEntryObjectIM Describes a Second Life inspector for the object Url, e.g., +// secondlife:///app/objectim/7bcd7864-da6b-e43f-4486-91d28a28d95b?name=Object&owner=3de548e1-57be-cfea-2b78-83ae3ad95998&slurl=Danger!%20Danger!/200/200/30/&groupowned=1 +// +LLUrlEntryObjectIM::LLUrlEntryObjectIM() +{ + mPattern = boost::regex("secondlife:///app/objectim/[\\da-f-]+\?.*", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_objectim.xml"; +} + +std::string LLUrlEntryObjectIM::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + LLURI uri(url); + LLSD query_map = uri.queryMap(); + if (query_map.has("name")) + return query_map["name"]; + return unescapeUrl(url); +} + +std::string LLUrlEntryObjectIM::getLocation(const std::string &url) const +{ + LLURI uri(url); + LLSD query_map = uri.queryMap(); + if (query_map.has("slurl")) + return query_map["slurl"]; + return LLUrlEntryBase::getLocation(url); +} + /// /// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., /// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about @@ -596,11 +685,25 @@ 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); } +std::string LLUrlEntrySLLabel::getTooltip(const std::string &string) const +{ + // return a tooltip corresponding to the URL type instead of the generic one (EXT-4574) + std::string url = getUrl(string); + LLUrlMatch match; + if (LLUrlRegistry::instance().findUrl(url, match)) + { + return match.getTooltip(); + } + + // unrecognized URL? should not happen + return LLUrlEntryBase::getTooltip(string); +} + // // LLUrlEntryWorldMap Describes secondlife:///<location> URLs // @@ -629,7 +732,7 @@ std::string LLUrlEntryWorldMap::getLabel(const std::string &url, const LLUrlLabe } const std::string label = LLTrans::getString("SLurlLabelShowOnMap"); - std::string location = path_array[2]; + std::string location = unescapeUrl(path_array[2]); std::string x = (path_parts > 3) ? path_array[3] : "128"; std::string y = (path_parts > 4) ? path_array[4] : "128"; std::string z = (path_parts > 5) ? path_array[5] : "0"; @@ -641,3 +744,56 @@ 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); +} + +// +// LLUrlEntryIcon describes an icon with <icon>...</icon> tags +// +LLUrlEntryIcon::LLUrlEntryIcon() +{ + mPattern = boost::regex("<icon\\s*>\\s*([^<]*)?\\s*</icon\\s*>", + boost::regex::perl|boost::regex::icase); + mDisabledLink = true; +} + +std::string LLUrlEntryIcon::getUrl(const std::string &url) const +{ + return LLStringUtil::null; +} + +std::string LLUrlEntryIcon::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return LLStringUtil::null; +} + +std::string LLUrlEntryIcon::getIcon(const std::string &url) +{ + // Grep icon info between <icon>...</icon> tags + // matches[1] contains the icon name/path + boost::match_results<std::string::const_iterator> matches; + mIcon = (boost::regex_match(url, matches, mPattern) && matches[1].matched) + ? matches[1] + : LLStringUtil::null; + LLStringUtil::trim(mIcon); + return mIcon; +} diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index 4adffde99c..71f030677a 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -71,19 +71,19 @@ 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; } /// Return an icon that can be displayed next to Urls of this type - std::string getIcon() const { return mIcon; } + virtual std::string getIcon(const std::string &url) { return mIcon; } /// Return the color to render the displayed text LLUIColor getColor() const { return mColor; } /// Given a matched Url, return a tooltip string for the hyperlink - std::string getTooltip() const { return mTooltip; } + virtual std::string getTooltip(const std::string &string) const { return mTooltip; } /// Return the name of a XUI file containing the context menu items std::string getMenuName() const { return mMenuName; } @@ -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; }; /// @@ -165,6 +169,7 @@ class LLUrlEntryAgent : public LLUrlEntryBase public: LLUrlEntryAgent(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getTooltip(const std::string &string) const; private: void onAgentNameReceived(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group); @@ -196,6 +201,18 @@ public: private: }; +/// +/// LLUrlEntryObjectIM Describes a Second Life inspector for the object Url, e.g., +/// secondlife:///app/objectim/7bcd7864-da6b-e43f-4486-91d28a28d95b?name=Object&owner=3de548e1-57be-cfea-2b78-83ae3ad95998&slurl=Danger!%20Danger!/200/200/30/&groupowned=1 +/// +class LLUrlEntryObjectIM : public LLUrlEntryBase +{ +public: + LLUrlEntryObjectIM(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLocation(const std::string &url) const; +private: +}; /// /// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., @@ -252,7 +269,8 @@ 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; + /*virtual*/ std::string getTooltip(const std::string &string) const; }; /// @@ -267,4 +285,28 @@ 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; +}; + +/// +/// LLUrlEntryIcon describes an icon with <icon>...</icon> tags +/// +class LLUrlEntryIcon : public LLUrlEntryBase +{ +public: + LLUrlEntryIcon(); + /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getIcon(const std::string &url); +}; + + #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..4341286eb4 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -44,6 +44,8 @@ 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 LLUrlEntryIcon()); registerUrl(new LLUrlEntrySLURL()); registerUrl(new LLUrlEntryHTTP()); registerUrl(new LLUrlEntryHTTPLabel()); @@ -52,8 +54,10 @@ LLUrlRegistry::LLUrlRegistry() registerUrl(new LLUrlEntryParcel()); registerUrl(new LLUrlEntryTeleport()); registerUrl(new LLUrlEntryWorldMap()); + registerUrl(new LLUrlEntryObjectIM()); registerUrl(new LLUrlEntryPlace()); registerUrl(new LLUrlEntryInventory()); + registerUrl(new LLUrlEntryObjectIM()); //LLUrlEntrySL and LLUrlEntrySLLabel have more common pattern, //so it should be registered in the end of list registerUrl(new LLUrlEntrySL()); @@ -131,7 +135,9 @@ 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 || + text.find("<icon") != std::string::npos); } bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb) @@ -172,11 +178,12 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL match.setValues(match_start, match_end, match_entry->getUrl(url), match_entry->getLabel(url, cb), - match_entry->getTooltip(), - match_entry->getIcon(), + match_entry->getTooltip(url), + match_entry->getIcon(url), match_entry->getColor(), match_entry->getMenuName(), - match_entry->getLocation(url)); + match_entry->getLocation(url), + match_entry->isLinkDisabled()); return true; } @@ -204,9 +211,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..e67f0ec3fc 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -73,9 +73,9 @@ S32 LLView::sLastBottomXML = S32_MIN; std::vector<LLViewDrawContext*> LLViewDrawContext::sDrawContextStack; -#if LL_DEBUG +//#if LL_DEBUG BOOL LLView::sIsDrawing = FALSE; -#endif +//#endif // Compiler optimization, generate extern template template class LLView* LLView::getChild<class LLView>( @@ -150,6 +150,10 @@ LLView::~LLView() { dirtyRect(); //llinfos << "Deleting view " << mName << ":" << (void*) this << llendl; + if (LLView::sIsDrawing) + { + lldebugs << "Deleting view " << mName << " during UI draw() phase" << llendl; + } // llassert(LLView::sIsDrawing == FALSE); // llassert_always(sDepth == 0); // avoid deleting views while drawing! It can subtly break list iterators @@ -592,11 +596,6 @@ void LLView::setVisible(BOOL visible) { if ( mVisible != visible ) { - if( !visible && (gFocusMgr.getTopCtrl() == this) ) - { - gFocusMgr.setTopCtrl( NULL ); - } - mVisible = visible; // notify children of visibility change if root, or part of visible hierarchy @@ -1322,11 +1321,12 @@ void LLView::drawChildren() if (viewp->getVisible() && viewp->getRect().isValid()) { + // check for bad data + llassert_always(viewp->getVisible() == TRUE); // Only draw views that are within the root view localRectToScreen(viewp->getRect(),&screenRect); if ( rootRect.overlaps(screenRect) && LLUI::sDirtyRect.overlaps(screenRect)) { - glMatrixMode(GL_MODELVIEW); LLUI::pushMatrix(); { LLUI::translate((F32)viewp->getRect().mLeft, (F32)viewp->getRect().mBottom, 0.f); @@ -1350,8 +1350,6 @@ void LLView::drawChildren() } --sDepth; } - - gGL.getTexUnit(0)->disable(); } void LLView::dirtyRect() @@ -1720,6 +1718,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 +1730,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/llview.h b/indra/llui/llview.h index c4d7313743..efae00f0e5 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -171,9 +171,9 @@ private: // widgets in general are not copyable LLView(const LLView& other) {}; public: -#if LL_DEBUG +//#if LL_DEBUG static BOOL sIsDrawing; -#endif +//#endif enum ESoundFlags { SILENT = 0, 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 80be8fcbf7..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,35 +507,34 @@ 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<> @@ -553,62 +544,98 @@ namespace tut // test LLUrlEntryHTTPNoProtocol - general URLs without a protocol // LLUrlEntryHTTPNoProtocol url; - boost::regex r = url.getPattern(); - testRegex("naked .com URL", r, + testRegex("naked .com URL", url, "see google.com", - "google.com"); + "http://google.com"); - testRegex("naked .org URL", r, + testRegex("naked .org URL", url, "see en.wikipedia.org for details", - "en.wikipedia.org"); + "http://en.wikipedia.org"); - testRegex("naked .net URL", r, + testRegex("naked .net URL", url, "example.net", - "example.net"); + "http://example.net"); - testRegex("naked .edu URL (2 instances)", r, + testRegex("naked .edu URL (2 instances)", url, "MIT web site is at web.mit.edu and also www.mit.edu", - "web.mit.edu"); + "http://web.mit.edu"); - testRegex("don't match e-mail addresses", r, + testRegex("don't match e-mail addresses", url, "test@lindenlab.com", ""); - testRegex(".com URL with path", r, + testRegex(".com URL with path", url, "see secondlife.com/status for grid status", - "secondlife.com/status"); + "http://secondlife.com/status"); - testRegex(".com URL with port", r, + testRegex(".com URL with port", url, "secondlife.com:80", - "secondlife.com:80"); + "http://secondlife.com:80"); - testRegex(".com URL with port and path", r, + testRegex(".com URL with port and path", url, "see secondlife.com:80/status", - "secondlife.com:80/status"); + "http://secondlife.com:80/status"); - testRegex("www.*.com URL with port and path", r, + testRegex("www.*.com URL with port and path", url, "see www.secondlife.com:80/status", - "www.secondlife.com:80/status"); + "http://www.secondlife.com:80/status"); - testRegex("invalid .com URL [1]", r, + testRegex("invalid .com URL [1]", url, "..com", ""); - testRegex("invalid .com URL [2]", r, + testRegex("invalid .com URL [2]", url, "you.come", ""); - testRegex("invalid .com URL [3]", r, + testRegex("invalid .com URL [3]", url, "recommended", ""); - testRegex("invalid .edu URL", r, + testRegex("invalid .edu URL", url, "hi there scheduled maitenance has begun", ""); - testRegex("invalid .net URL", r, + 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()); } } |