summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
authorOz Linden <oz@lindenlab.com>2015-10-13 08:43:03 -0400
committerOz Linden <oz@lindenlab.com>2015-10-13 08:43:03 -0400
commit69ff937e47248eca0a4f876efebe97ee57bd4da9 (patch)
tree4fde61ed0ed439b0bf033b15f6703d72bee6f561 /indra/llui
parent9367a3787929dfff8b46763d9a7b4872c469e85a (diff)
parent4312629e7c5749b86add9d42e6e550602f34dbf5 (diff)
merge changes for 3.8.5-release
Diffstat (limited to 'indra/llui')
-rwxr-xr-xindra/llui/lldockablefloater.cpp2
-rw-r--r--indra/llui/llfolderviewitem.cpp45
-rwxr-xr-xindra/llui/llfolderviewitem.h4
-rwxr-xr-xindra/llui/llfolderviewmodel.h2
-rwxr-xr-xindra/llui/llmenugl.cpp58
-rwxr-xr-xindra/llui/llmenugl.h5
-rwxr-xr-xindra/llui/llsearcheditor.cpp13
-rwxr-xr-xindra/llui/llsearcheditor.h2
-rwxr-xr-xindra/llui/lltextbase.cpp53
-rwxr-xr-xindra/llui/lltextbase.h8
-rwxr-xr-xindra/llui/llurlentry.cpp88
-rwxr-xr-xindra/llui/llurlentry.h24
-rwxr-xr-xindra/llui/llurlregistry.cpp47
-rwxr-xr-xindra/llui/tests/llurlentry_test.cpp172
14 files changed, 343 insertions, 180 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/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 20ec243ec5..4a3780e2ef 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"
@@ -2061,27 +2062,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());
}
}
@@ -2108,6 +2115,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)
@@ -3558,6 +3575,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 0698e9b53c..e64cdf2ee0 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;
@@ -393,6 +399,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..69eefa736c 100755
--- a/indra/llui/llurlregistry.cpp
+++ b/indra/llui/llurlregistry.cpp
@@ -76,9 +76,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 +153,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 +214,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(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 +255,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/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",
+ "");
+ }
}