diff options
Diffstat (limited to 'indra/llui')
-rwxr-xr-x | indra/llui/lldockablefloater.cpp | 2 | ||||
-rw-r--r-- | indra/llui/llfolderviewitem.cpp | 45 | ||||
-rwxr-xr-x | indra/llui/llfolderviewitem.h | 4 | ||||
-rwxr-xr-x | indra/llui/llfolderviewmodel.h | 2 | ||||
-rwxr-xr-x | indra/llui/llkeywords.cpp | 11 | ||||
-rwxr-xr-x | indra/llui/llmenugl.cpp | 58 | ||||
-rwxr-xr-x | indra/llui/llmenugl.h | 5 | ||||
-rwxr-xr-x | indra/llui/llsearcheditor.cpp | 13 | ||||
-rwxr-xr-x | indra/llui/llsearcheditor.h | 2 | ||||
-rwxr-xr-x | indra/llui/lltextbase.cpp | 53 | ||||
-rwxr-xr-x | indra/llui/lltextbase.h | 8 | ||||
-rwxr-xr-x | indra/llui/llurlentry.cpp | 88 | ||||
-rwxr-xr-x | indra/llui/llurlentry.h | 24 | ||||
-rwxr-xr-x | indra/llui/llurlregistry.cpp | 50 | ||||
-rwxr-xr-x | indra/llui/llurlregistry.h | 1 | ||||
-rwxr-xr-x | indra/llui/tests/llurlentry_test.cpp | 172 |
16 files changed, 355 insertions, 183 deletions
diff --git a/indra/llui/lldockablefloater.cpp b/indra/llui/lldockablefloater.cpp index 3396213f1c..c937d190c6 100755 --- a/indra/llui/lldockablefloater.cpp +++ b/indra/llui/lldockablefloater.cpp @@ -153,7 +153,7 @@ void LLDockableFloater::setVisible(BOOL visible) mDockControl.get()->repositionDockable(); } - if (visible) + if (visible && !isMinimized()) { LLFloater::setFrontmost(getAutoFocus()); } diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 81a0204bc5..3def0386e1 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -129,6 +129,7 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mSelectPending(FALSE), mLabelStyle( LLFontGL::NORMAL ), mHasVisibleChildren(FALSE), + mIsFolderComplete(true), mLocalIndentation(p.folder_indentation), mIndentation(0), mItemHeight(p.item_height), @@ -674,7 +675,7 @@ void LLFolderViewItem::drawOpenFolderArrow(const Params& default_params, const L // const S32 TOP_PAD = default_params.item_top_pad; - if (hasVisibleChildren()) + if (hasVisibleChildren() || !isFolderComplete()) { LLUIImage* arrow_image = default_params.folder_arrow_image; gl_draw_scaled_rotated_image( @@ -934,6 +935,8 @@ LLFolderViewFolder::LLFolderViewFolder( const LLFolderViewItem::Params& p ): mLastArrangeGeneration( -1 ), mLastCalculatedWidth(0) { + // folder might have children that are not loaded yet. Mark it as incomplete until chance to check it. + mIsFolderComplete = false; } void LLFolderViewFolder::updateLabelRotation() @@ -1016,6 +1019,12 @@ S32 LLFolderViewFolder::arrange( S32* width, S32* height ) mHasVisibleChildren = found; } + if (!mIsFolderComplete) + { + mIsFolderComplete = getFolderViewModel()->isFolderComplete(this); + } + + // calculate height as a single item (without any children), and reshapes rectangle to match LLFolderViewItem::arrange( width, height ); @@ -1463,31 +1472,37 @@ void LLFolderViewFolder::extendSelectionTo(LLFolderViewItem* new_selection) LLFolderView* root = getRoot(); - for (std::vector<LLFolderViewItem*>::iterator it = items_to_select_forward.begin(), end_it = items_to_select_forward.end(); + BOOL selection_reverse = new_selection->isSelected(); //indication that some elements are being deselected + + // array always go from 'will be selected' to ' will be unselected', iterate + // in opposite direction to simplify identification of 'point of origin' in + // case it is in the list we are working with + for (std::vector<LLFolderViewItem*>::reverse_iterator it = items_to_select_forward.rbegin(), end_it = items_to_select_forward.rend(); it != end_it; ++it) { LLFolderViewItem* item = *it; - if (item->isSelected()) + BOOL selected = item->isSelected(); + if (!selection_reverse && selected) { - root->removeFromSelectionList(item); + // it is our 'point of origin' where we shift/expand from + // don't deselect it + selection_reverse = TRUE; } else { - item->selectItem(); + root->changeSelection(item, !selected); } - root->addToSelectionList(item); } - if (new_selection->isSelected()) + if (selection_reverse) { - root->removeFromSelectionList(new_selection); + // at some point we reversed selection, first element should be deselected + root->changeSelection(last_selected_item_from_cur, FALSE); } - else - { - new_selection->selectItem(); - } - root->addToSelectionList(new_selection); + + // element we expand to should always be selected + root->changeSelection(new_selection, TRUE); } @@ -1677,7 +1692,9 @@ void LLFolderViewFolder::setOpenArrangeRecursively(BOOL openitem, ERecurseType r mIsOpen = openitem; if(!was_open && openitem) { - getViewModelItem()->openItem(); + getViewModelItem()->openItem(); + // openItem() will request content, it won't be incomplete + mIsFolderComplete = true; } else if(was_open && !openitem) { diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index ed4496cfaa..0322c8836d 100755 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -115,6 +115,7 @@ protected: F32 mControlLabelRotation; LLFolderView* mRoot; bool mHasVisibleChildren, + mIsFolderComplete, // indicates that some children were not loaded/added yet mIsCurSelection, mDragAndDropTarget, mIsMouseOverTitle, @@ -212,6 +213,9 @@ public: BOOL hasVisibleChildren() { return mHasVisibleChildren; } + // true if object can't have children + BOOL isFolderComplete() { return mIsFolderComplete; } + // Call through to the viewed object and return true if it can be // removed. Returns true if it's removed. //virtual BOOL removeRecursively(BOOL single_item); diff --git a/indra/llui/llfolderviewmodel.h b/indra/llui/llfolderviewmodel.h index d60e36183b..a395af537a 100755 --- a/indra/llui/llfolderviewmodel.h +++ b/indra/llui/llfolderviewmodel.h @@ -122,6 +122,7 @@ public: virtual void filter() = 0; virtual bool contentsReady() = 0; + virtual bool isFolderComplete(class LLFolderViewFolder*) = 0; virtual void setFolderView(LLFolderView* folder_view) = 0; virtual LLFolderViewFilter& getFilter() = 0; virtual const LLFolderViewFilter& getFilter() const = 0; @@ -444,6 +445,7 @@ public: // By default, we assume the content is available. If a network fetch mechanism is implemented for the model, // this method needs to be overloaded and return the relevant fetch status. virtual bool contentsReady() { return true; } + virtual bool isFolderComplete(LLFolderViewFolder* folder) { return true; } struct ViewModelCompare { diff --git a/indra/llui/llkeywords.cpp b/indra/llui/llkeywords.cpp index 6750ee482a..fc4a007d9e 100755 --- a/indra/llui/llkeywords.cpp +++ b/indra/llui/llkeywords.cpp @@ -296,7 +296,7 @@ void LLKeywords::processTokensGroup(const LLSD& tokens, const std::string& group } color_group = getColorGroup(group); - LL_INFOS("SyntaxLSL") << "Group: '" << group << "', using color: '" << color_group << "'" << LL_ENDL; + LL_DEBUGS("SyntaxLSL") << "Group: '" << group << "', using color: '" << color_group << "'" << LL_ENDL; if (tokens.isMap()) { @@ -331,7 +331,14 @@ void LLKeywords::processTokensGroup(const LLSD& tokens, const std::string& group switch (token_type) { case LLKeywordToken::TT_CONSTANT: - color_group = getColorGroup(group + "-" + getAttribute("type")); + if (getAttribute("type").length() > 0) + { + color_group = getColorGroup(group + "-" + getAttribute("type")); + } + else + { + color_group = getColorGroup(group); + } tooltip = "Type: " + getAttribute("type") + ", Value: " + getAttribute("value"); break; case LLKeywordToken::TT_EVENT: diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 7cdbcb0621..848367f8a8 100755 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -225,7 +225,6 @@ BOOL LLMenuItemGL::handleAcceleratorKey(KEY key, MASK mask) BOOL LLMenuItemGL::handleHover(S32 x, S32 y, MASK mask) { - setHover(TRUE); getWindow()->setCursor(UI_CURSOR_ARROW); return TRUE; } @@ -236,6 +235,18 @@ BOOL LLMenuItemGL::handleRightMouseDown(S32 x, S32 y, MASK mask) return LLUICtrl::handleRightMouseDown(x,y,mask); } +void LLMenuItemGL::onMouseEnter(S32 x, S32 y, MASK mask) +{ + setHover(TRUE); + LLUICtrl::onMouseEnter(x,y,mask); +} + +void LLMenuItemGL::onMouseLeave(S32 x, S32 y, MASK mask) +{ + setHover(FALSE); + LLUICtrl::onMouseLeave(x,y,mask); +} + //virtual BOOL LLMenuItemGL::handleRightMouseUp(S32 x, S32 y, MASK mask) { @@ -533,9 +544,6 @@ void LLMenuItemGL::draw( void ) gl_line_2d(x_begin, (MENU_ITEM_PADDING / 2) + 1, x_end, (MENU_ITEM_PADDING / 2) + 1); } } - - // clear got hover every frame - setHover(FALSE); } BOOL LLMenuItemGL::setLabelArg( const std::string& key, const LLStringExplicit& text ) @@ -1043,7 +1051,7 @@ void LLMenuItemBranchGL::onCommit( void ) // keyboard navigation automatically propagates highlight to sub-menu // to facilitate fast menu control via jump keys - if (LLMenuGL::getKeyboardMode() && getBranch()&& !getBranch()->getHighlightedItem()) + if (LLMenuGL::getKeyboardMode() && getBranch() && !getBranch()->getHighlightedItem()) { getBranch()->highlightNextItem(NULL); } @@ -1456,7 +1464,16 @@ BOOL LLMenuItemBranchDownGL::handleMouseDown( S32 x, S32 y, MASK mask ) { // switch to mouse control mode LLMenuGL::setKeyboardMode(FALSE); - onCommit(); + + if (getVisible() && isOpen()) + { + LLMenuGL::sMenuContainer->hideMenus(); + } + else + { + onCommit(); + } + make_ui_sound("UISndClick"); return TRUE; } @@ -1588,10 +1605,6 @@ void LLMenuItemBranchDownGL::draw( void ) gl_line_2d(x_begin, LABEL_BOTTOM_PAD_PIXELS, x_end, LABEL_BOTTOM_PAD_PIXELS); } } - - // reset every frame so that we only show highlight - // when we get hover events on that frame - setHover(FALSE); } @@ -3381,6 +3394,11 @@ BOOL LLMenuBarGL::handleMouseDown(S32 x, S32 y, MASK mask) return LLMenuGL::handleMouseDown(x, y, mask); } +BOOL LLMenuBarGL::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + return LLMenuGL::handleMouseDown(x, y, mask); +} + void LLMenuBarGL::draw() { LLMenuItemGL* itemp = getHighlightedItem(); @@ -3625,8 +3643,24 @@ BOOL LLMenuHolderGL::handleMouseDown( S32 x, S32 y, MASK mask ) BOOL handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; if (!handled) { - // clicked off of menu, hide them all - hideMenus(); + LLMenuGL* visible_menu = (LLMenuGL*)getVisibleMenu(); + LLMenuItemGL* parent_menu = visible_menu ? visible_menu->getParentMenuItem() : NULL; + if (parent_menu && parent_menu->getVisible()) + { + // don't hide menu if parent was hit + LLRect parent_rect; + parent_menu->localRectToOtherView(parent_menu->getLocalRect(), &parent_rect, this); + if (!parent_rect.pointInRect(x, y)) + { + // clicked off of menu and parent, hide them all + hideMenus(); + } + } + else + { + // no visible parent, clicked off of menu, hide them all + hideMenus(); + } } return handled; } diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index ae9b169691..628dedb906 100755 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -158,6 +158,10 @@ public: virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask ); virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ); virtual BOOL handleScrollWheel( S32 x, S32 y, S32 clicks ); + + virtual void onMouseEnter(S32 x, S32 y, MASK mask); + virtual void onMouseLeave(S32 x, S32 y, MASK mask); + virtual void draw( void ); BOOL getHover() const { return mGotHover; } @@ -753,6 +757,7 @@ public: /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask); /*virtual*/ BOOL handleJumpKey(KEY key); /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); /*virtual*/ void draw(); /*virtual*/ BOOL jumpKeysActive(); diff --git a/indra/llui/llsearcheditor.cpp b/indra/llui/llsearcheditor.cpp index ea96fc573d..1fdd05a11c 100755 --- a/indra/llui/llsearcheditor.cpp +++ b/indra/llui/llsearcheditor.cpp @@ -29,6 +29,7 @@ #include "linden_common.h" #include "llsearcheditor.h" +#include "llkeyboard.h" LLSearchEditor::LLSearchEditor(const LLSearchEditor::Params& p) : LLUICtrl(p), @@ -166,4 +167,16 @@ void LLSearchEditor::handleKeystroke() { mKeystrokeCallback(this, getValue()); } + + KEY key = gKeyboard->currentKey(); + if (key == KEY_LEFT || + key == KEY_RIGHT) + { + return; + } + + if (mTextChangedCallback) + { + mTextChangedCallback(this, getValue()); + } } diff --git a/indra/llui/llsearcheditor.h b/indra/llui/llsearcheditor.h index c2d7916938..3b12868225 100755 --- a/indra/llui/llsearcheditor.h +++ b/indra/llui/llsearcheditor.h @@ -82,12 +82,14 @@ public: virtual void setFocus( BOOL b ); void setKeystrokeCallback( commit_callback_t cb ) { mKeystrokeCallback = cb; } + void setTextChangedCallback( commit_callback_t cb ) { mTextChangedCallback = cb; } protected: void onClearButtonClick(const LLSD& data); virtual void handleKeystroke(); commit_callback_t mKeystrokeCallback; + commit_callback_t mTextChangedCallback; LLLineEditor* mSearchEditor; LLButton* mSearchButton; LLButton* mClearButton; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 602a703450..bf660849c4 100755 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -37,6 +37,7 @@ #include "lltextparser.h" #include "lltextutil.h" #include "lltooltip.h" +#include "lltrans.h" #include "lluictrl.h" #include "llurlaction.h" #include "llurlregistry.h" @@ -2060,27 +2061,33 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para // add icon before url if need LLTextUtil::processUrlMatch(&match, this, isContentTrusted() || match.isTrusted()); + if ((isContentTrusted() || match.isTrusted()) && !match.getIcon().empty() ) + { + setLastSegmentToolTip(LLTrans::getString("TooltipSLIcon")); + } // output the styled Url appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly()); + bool tooltip_required = !match.getTooltip().empty(); + + // set the tooltip for the Url label + if (tooltip_required) + { + setLastSegmentToolTip(match.getTooltip()); + } - // show query part of url with gray color only for LLUrlEntryHTTP and LLUrlEntryHTTPNoProtocol url entries + // show query part of url with gray color only for LLUrlEntryHTTP url entries std::string label = match.getQuery(); if (label.size()) { link_params.color = LLColor4::grey; link_params.readonly_color = LLColor4::grey; appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly()); - } - - // set the tooltip for the Url label - if (! match.getTooltip().empty()) - { - segment_set_t::iterator it = getSegIterContaining(getLength()-1); - if (it != mSegments.end()) + + // set the tooltip for the query part of url + if (tooltip_required) { - LLTextSegmentPtr segment = *it; - segment->setToolTip(match.getTooltip()); + setLastSegmentToolTip(match.getTooltip()); } } @@ -2107,6 +2114,16 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para } } +void LLTextBase::setLastSegmentToolTip(const std::string &tooltip) +{ + segment_set_t::iterator it = getSegIterContaining(getLength()-1); + if (it != mSegments.end()) + { + LLTextSegmentPtr segment = *it; + segment->setToolTip(tooltip); + } +} + static LLTrace::BlockTimerStatHandle FTM_APPEND_TEXT("Append Text"); void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, const LLStyle::Params& input_params) @@ -3557,6 +3574,22 @@ S32 LLImageTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin return 0; } +BOOL LLImageTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (!mTooltip.empty()) + { + LLToolTipMgr::instance().show(mTooltip); + return TRUE; + } + + return FALSE; +} + +void LLImageTextSegment::setToolTip(const std::string& tooltip) +{ + mTooltip = tooltip; +} + F32 LLImageTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) { if ( (start >= 0) && (end <= mEnd - mStart)) diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index dfc10923f3..87809aa8fb 100755 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -241,9 +241,15 @@ public: S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const; F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect); + /*virtual*/ BOOL handleToolTip(S32 x, S32 y, MASK mask); + /*virtual*/ void setToolTip(const std::string& tooltip); + private: class LLTextBase& mEditor; LLStyleConstSP mStyle; + +protected: + std::string mTooltip; }; typedef LLPointer<LLTextSegment> LLTextSegmentPtr; @@ -392,6 +398,8 @@ public: const std::string& getLabel() { return mLabel.getString(); } const LLWString& getWlabel() { return mLabel.getWString();} + void setLastSegmentToolTip(const std::string &tooltip); + /** * If label is set, draws text label (which is LLLabelTextSegment) * that is visible when no user text provided diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 6db0d88998..7f6cc22e90 100755 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -203,10 +203,10 @@ std::string LLUrlEntryBase::urlToGreyQuery(const std::string &url) const { LLUriParser up(unescapeUrl(url)); - std::string query; + std::string label; up.extractParts(); - up.glueSecond(query); - + up.glueFirst(label); + std::string query = url.substr(label.size()); return query; } @@ -229,7 +229,7 @@ static std::string getStringAfterToken(const std::string str, const std::string LLUrlEntryHTTP::LLUrlEntryHTTP() : LLUrlEntryBase() { - mPattern = boost::regex("https?://([-\\w\\.]+)+(:\\d+)?(:\\w+)?(@\\d+)?(@\\w+)?/?\\S*", + mPattern = boost::regex("https?://([-\\w\\.]+)+(:\\d+)?(:\\w+)?(@\\d+)?(@\\w+)?\\.[a-z](:\\d+)?(:\\w+)?(@\\d+)?(@\\w+)?/?\\S*", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_http.xml"; mTooltip = LLTrans::getString("TooltipHttpUrl"); @@ -287,46 +287,6 @@ std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string) const return getUrlFromWikiLink(string); } -// -// LLUrlEntryHTTPNoProtocol Describes generic Urls like www.google.com -// -LLUrlEntryHTTPNoProtocol::LLUrlEntryHTTPNoProtocol() - : LLUrlEntryBase() -{ - mPattern = boost::regex("(" - "\\bwww\\.\\S+\\.\\S+" // i.e. www.FOO.BAR - "|" // or - "(?<!@)\\b[^[:space:]:@/>]+\\.(?:com|net|edu|org)([/:][^[:space:]<]*)?\\b" // i.e. FOO.net - ")", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_http.xml"; - mTooltip = LLTrans::getString("TooltipHttpUrl"); -} - -std::string LLUrlEntryHTTPNoProtocol::getLabel(const std::string &url, const LLUrlLabelCallback &cb) -{ - return urlToLabelWithGreyQuery(url); -} - -std::string LLUrlEntryHTTPNoProtocol::getQuery(const std::string &url) const -{ - return urlToGreyQuery(url); -} - -std::string LLUrlEntryHTTPNoProtocol::getUrl(const std::string &string) const -{ - if (string.find("://") == std::string::npos) - { - return "http://" + escapeUrl(string); - } - return escapeUrl(string); -} - -std::string LLUrlEntryHTTPNoProtocol::getTooltip(const std::string &url) const -{ - return unescapeUrl(url); -} - LLUrlEntryInvalidSLURL::LLUrlEntryInvalidSLURL() : LLUrlEntryBase() { @@ -489,7 +449,7 @@ std::string LLUrlEntrySLURL::getLocation(const std::string &url) const // LLUrlEntrySecondlifeURL::LLUrlEntrySecondlifeURL() { - mPattern = boost::regex("(https?://)?([-\\w\\.]*\\.)?(secondlife|lindenlab)\\.com(:\\d{1,5})?\\/\\S*", + mPattern = boost::regex("https?://([-\\w\\.]*\\.)?(secondlife|lindenlab)\\.com(:\\d{1,5})?\\/\\S*", boost::regex::perl|boost::regex::icase); mIcon = "Hand"; @@ -527,7 +487,7 @@ std::string LLUrlEntrySecondlifeURL::getTooltip(const std::string &url) const // LLUrlEntrySimpleSecondlifeURL::LLUrlEntrySimpleSecondlifeURL() { - mPattern = boost::regex("(https?://)?([-\\w\\.]*\\.)?(secondlife|lindenlab)\\.com(?!\\S)", + mPattern = boost::regex("https?://([-\\w\\.]*\\.)?(secondlife|lindenlab)\\.com(?!\\S)", boost::regex::perl|boost::regex::icase); mIcon = "Hand"; @@ -1401,9 +1361,43 @@ std::string LLUrlEntryIcon::getIcon(const std::string &url) return mIcon; } +// +// LLUrlEntryEmail Describes a generic mailto: Urls +// +LLUrlEntryEmail::LLUrlEntryEmail() + : LLUrlEntryBase() +{ + mPattern = boost::regex("(mailto:)?[\\w\\.\\-]+@[\\w\\.\\-]+\\.[a-z]{2,6}", + boost::regex::perl | boost::regex::icase); + mMenuName = "menu_url_email.xml"; + mTooltip = LLTrans::getString("TooltipEmail"); +} + +std::string LLUrlEntryEmail::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + int pos = url.find("mailto:"); + + if (pos == std::string::npos) + { + return escapeUrl(url); + } + + std::string ret = escapeUrl(url.substr(pos + 7, url.length() - pos + 8)); + return ret; +} + +std::string LLUrlEntryEmail::getUrl(const std::string &string) const +{ + if (string.find("mailto:") == std::string::npos) + { + return "mailto:" + escapeUrl(string); + } + return escapeUrl(string); +} + LLUrlEntryExperienceProfile::LLUrlEntryExperienceProfile() { - mPattern = boost::regex(APP_HEADER_REGEX "/experience/[\\da-f-]+/\\w+\\S*", + mPattern = boost::regex(APP_HEADER_REGEX "/experience/[\\da-f-]+/profile", boost::regex::perl|boost::regex::icase); mIcon = "Generic_Experience"; mMenuName = "menu_url_experience.xml"; diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index dd1f257a3d..413c20a657 100755 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -157,19 +157,6 @@ public: /*virtual*/ std::string getUrl(const std::string &string) const; }; -/// -/// LLUrlEntryHTTPNoProtocol Describes generic Urls like www.google.com -/// -class LLUrlEntryHTTPNoProtocol : public LLUrlEntryBase -{ -public: - LLUrlEntryHTTPNoProtocol(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getQuery(const std::string &url) const; - /*virtual*/ std::string getUrl(const std::string &string) const; - /*virtual*/ std::string getTooltip(const std::string &url) const; -}; - class LLUrlEntryInvalidSLURL : public LLUrlEntryBase { public: @@ -506,5 +493,16 @@ public: /*virtual*/ std::string getIcon(const std::string &url); }; +/// +/// LLUrlEntryEmail Describes a generic mailto: Urls +/// +class LLUrlEntryEmail : public LLUrlEntryBase +{ +public: + LLUrlEntryEmail(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getUrl(const std::string &string) const; +}; + #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 2085505947..4f389a0c12 100755 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -41,7 +41,8 @@ LLUrlRegistry::LLUrlRegistry() mUrlEntry.reserve(20); // Urls are matched in the order that they were registered - registerUrl(new LLUrlEntryNoLink()); + mUrlEntryNoLink = new LLUrlEntryNoLink(); + registerUrl(mUrlEntryNoLink); mUrlEntryIcon = new LLUrlEntryIcon(); registerUrl(mUrlEntryIcon); mLLUrlEntryInvalidSLURL = new LLUrlEntryInvalidSLURL(); @@ -76,9 +77,7 @@ LLUrlRegistry::LLUrlRegistry() registerUrl(new LLUrlEntrySL()); mUrlEntrySLLabel = new LLUrlEntrySLLabel(); registerUrl(mUrlEntrySLLabel); - // most common pattern is a URL without any protocol, - // e.g., "secondlife.com" - registerUrl(new LLUrlEntryHTTPNoProtocol()); + registerUrl(new LLUrlEntryEmail()); } LLUrlRegistry::~LLUrlRegistry() @@ -155,11 +154,9 @@ static bool stringHasUrl(const std::string &text) return (text.find("://") != std::string::npos || text.find("www.") != std::string::npos || text.find(".com") != std::string::npos || - text.find(".net") != std::string::npos || - text.find(".edu") != std::string::npos || - text.find(".org") != std::string::npos || text.find("<nolink>") != std::string::npos || - text.find("<icon") != std::string::npos); + text.find("<icon") != std::string::npos || + text.find("@") != std::string::npos); } bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted) @@ -218,9 +215,40 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL // did we find a match? if so, return its details in the match object if (match_entry) { + + // Skip if link is an email with an empty username (starting with @). See MAINT-5371. + if (match_start > 0 && text.substr(match_start - 1, 1) == "@") + return false; + // fill in the LLUrlMatch object and return it std::string url = text.substr(match_start, match_end - match_start + 1); + LLUrlEntryBase *stripped_entry = NULL; + if(match_entry != mUrlEntryNoLink && LLStringUtil::containsNonprintable(url)) + { + LLStringUtil::stripNonprintable(url); + + std::vector<LLUrlEntryBase *>::iterator iter; + for (iter = mUrlEntry.begin(); iter != mUrlEntry.end(); ++iter) + { + LLUrlEntryBase *url_entry = *iter; + U32 start = 0, end = 0; + if (matchRegex(url.c_str(), url_entry->getPattern(), start, end)) + { + if (mLLUrlEntryInvalidSLURL == *iter) + { + if(url_entry && url_entry->isSLURLvalid(url)) + { + continue; + } + } + stripped_entry = url_entry; + break; + } + } + } + + if (match_entry == mUrlEntryTrusted) { LLUriParser up(url); @@ -228,10 +256,12 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL url = up.normalizedUri(); } + std::string url_label = stripped_entry? stripped_entry->getLabel(url, cb) : match_entry->getLabel(url, cb); + std::string url_query = stripped_entry? stripped_entry->getQuery(url) : match_entry->getQuery(url); match.setValues(match_start, match_end, match_entry->getUrl(url), - match_entry->getLabel(url, cb), - match_entry->getQuery(url), + url_label, + url_query, match_entry->getTooltip(url), match_entry->getIcon(url), match_entry->getStyle(), diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h index 5ce4048d5d..24c3a2b513 100755 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -98,6 +98,7 @@ private: LLUrlEntryBase* mLLUrlEntryInvalidSLURL; LLUrlEntryBase* mUrlEntryHTTPLabel; LLUrlEntryBase* mUrlEntrySLLabel; + LLUrlEntryBase* mUrlEntryNoLink; }; #endif diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index 15f2354552..96e94c0f80 100755 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -653,79 +653,6 @@ namespace tut void object::test<11>() { // - // test LLUrlEntryHTTPNoProtocol - general URLs without a protocol - // - LLUrlEntryHTTPNoProtocol url; - - testRegex("naked .com URL", url, - "see google.com", - "http://google.com"); - - testRegex("naked .org URL", url, - "see en.wikipedia.org for details", - "http://en.wikipedia.org"); - - testRegex("naked .net URL", url, - "example.net", - "http://example.net"); - - testRegex("naked .edu URL (2 instances)", url, - "MIT web site is at web.mit.edu and also www.mit.edu", - "http://web.mit.edu"); - - testRegex("don't match e-mail addresses", url, - "test@lindenlab.com", - ""); - - testRegex(".com URL with path", url, - "see secondlife.com/status for grid status", - "http://secondlife.com/status"); - - testRegex(".com URL with port", url, - "secondlife.com:80", - "http://secondlife.com:80"); - - testRegex(".com URL with port and path", url, - "see secondlife.com:80/status", - "http://secondlife.com:80/status"); - - testRegex("www.*.com URL with port and path", url, - "see www.secondlife.com:80/status", - "http://www.secondlife.com:80/status"); - - testRegex("invalid .com URL [1]", url, - "..com", - ""); - - testRegex("invalid .com URL [2]", url, - "you.come", - ""); - - testRegex("invalid .com URL [3]", url, - "recommended", - ""); - - testRegex("invalid .edu URL", url, - "hi there scheduled maitenance has begun", - ""); - - testRegex("invalid .net URL", url, - "foo.netty", - ""); - - testRegex("XML tags around URL [1]", url, - "<foo>secondlife.com</foo>", - "http://secondlife.com"); - - testRegex("XML tags around URL [2]", url, - "<foo>secondlife.com/status?bar=1</foo>", - "http://secondlife.com/status?bar=1"); - } - - template<> template<> - void object::test<12>() - { - // // test LLUrlEntryNoLink - turn off hyperlinking // LLUrlEntryNoLink url; @@ -752,7 +679,7 @@ namespace tut } template<> template<> - void object::test<13>() + void object::test<12>() { // // test LLUrlEntryRegion - secondlife:///app/region/<location> URLs @@ -860,4 +787,101 @@ namespace tut "secondlife:///app/region/Product%20Engine", "Product Engine"); } + + template<> template<> + void object::test<13>() + { + // + // test LLUrlEntryemail - general emails + // + LLUrlEntryEmail url; + + // Regex tests. + testRegex("match e-mail addresses", url, + "test@lindenlab.com", + "mailto:test@lindenlab.com"); + + testRegex("match e-mail addresses with mailto: prefix", url, + "mailto:test@lindenlab.com", + "mailto:test@lindenlab.com"); + + testRegex("match e-mail addresses with different domains", url, + "test@foo.org.us", + "mailto:test@foo.org.us"); + + testRegex("match e-mail addresses with different domains", url, + "test@foo.bar", + "mailto:test@foo.bar"); + + testRegex("don't match incorrect e-mail addresses", url, + "test @foo.com", + ""); + + testRegex("don't match incorrect e-mail addresses", url, + "test@ foo.com", + ""); + } + + template<> template<> + void object::test<14>() + { + // + // test LLUrlEntrySimpleSecondlifeURL - http://*.secondlife.com/* and http://*lindenlab.com/* urls + // + LLUrlEntrySecondlifeURL url; + + testRegex("match urls with protocol", url, + "this url should match http://lindenlab.com/products/second-life", + "http://lindenlab.com/products/second-life"); + + testRegex("match urls with protocol", url, + "search something https://marketplace.secondlife.com/products/search on marketplace and test the https", + "https://marketplace.secondlife.com/products/search"); + + testRegex("match urls with port", url, + "let's specify some port http://secondlife.com:888/status", + "http://secondlife.com:888/status"); + + testRegex("don't match urls w/o protocol", url, + "looks like an url something www.marketplace.secondlife.com/products but no https prefix", + ""); + + testRegex("but with a protocol www is fine", url, + "so let's add a protocol http://www.marketplace.secondlife.com:8888/products", + "http://www.marketplace.secondlife.com:8888/products"); + + testRegex("don't match urls w/o protocol", url, + "and even no www something secondlife.com/status", + ""); + } + + template<> template<> + void object::test<15>() + { + // + // test LLUrlEntrySimpleSecondlifeURL - http://*.secondlife.com and http://*lindenlab.com urls + // + + LLUrlEntrySimpleSecondlifeURL url; + + testRegex("match urls with a protocol", url, + "this url should match http://lindenlab.com", + "http://lindenlab.com"); + + testRegex("match urls with a protocol", url, + "search something https://marketplace.secondlife.com on marketplace and test the https", + "https://marketplace.secondlife.com"); + + testRegex("don't match urls w/o protocol", url, + "looks like an url something www.marketplace.secondlife.com but no https prefix", + ""); + + testRegex("but with a protocol www is fine", url, + "so let's add a protocol http://www.marketplace.secondlife.com", + "http://www.marketplace.secondlife.com"); + + testRegex("don't match urls w/o protocol", url, + "and even no www something lindenlab.com", + ""); + } } |