summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/integration_tests/llui_libtest/CMakeLists.txt2
-rw-r--r--indra/llui/CMakeLists.txt37
-rw-r--r--indra/llui/llscrolllistctrl.cpp75
-rw-r--r--indra/llui/llscrolllistctrl.h13
-rw-r--r--indra/llui/llstyle.cpp8
-rw-r--r--indra/llui/llstyle.h8
-rw-r--r--indra/llui/lltextbase.cpp451
-rw-r--r--indra/llui/lltextbase.h198
-rw-r--r--indra/llui/lltextbox.cpp498
-rw-r--r--indra/llui/lltextbox.h49
-rw-r--r--indra/llui/lltexteditor.cpp677
-rw-r--r--indra/llui/lltexteditor.h129
-rw-r--r--indra/llui/llurlaction.cpp137
-rw-r--r--indra/llui/llurlaction.h93
-rw-r--r--indra/llui/llurlentry.cpp546
-rw-r--r--indra/llui/llurlentry.h252
-rw-r--r--indra/llui/llurlmatch.cpp61
-rw-r--r--indra/llui/llurlmatch.h98
-rw-r--r--indra/llui/llurlregistry.cpp165
-rw-r--r--indra/llui/llurlregistry.h87
-rw-r--r--indra/llui/tests/llurlentry_stub.cpp64
-rw-r--r--indra/llui/tests/llurlentry_test.cpp535
-rw-r--r--indra/llui/tests/llurlmatch_test.cpp177
-rw-r--r--indra/newview/llappviewer.cpp12
-rw-r--r--indra/newview/llavatarlist.cpp3
-rw-r--r--indra/newview/llchatmsgbox.cpp372
-rw-r--r--indra/newview/llchatmsgbox.h129
-rw-r--r--indra/newview/llfloaterabout.cpp52
-rw-r--r--indra/newview/llfloaterfriends.cpp1
-rw-r--r--indra/newview/llfloatergroups.cpp19
-rw-r--r--indra/newview/llfloaterland.cpp7
-rw-r--r--indra/newview/llgrouplist.cpp2
-rw-r--r--indra/newview/llimpanel.cpp1
-rw-r--r--indra/newview/llnamelistctrl.cpp4
-rw-r--r--indra/newview/llpanelavatar.cpp19
-rw-r--r--indra/newview/llpanelavatar.h3
-rw-r--r--indra/newview/llpanelgroupgeneral.cpp1
-rw-r--r--indra/newview/llpanelgrouproles.cpp4
-rw-r--r--indra/newview/llpreviewnotecard.cpp3
-rw-r--r--indra/newview/llprogressview.cpp6
-rw-r--r--indra/newview/llprogressview.h1
-rw-r--r--indra/newview/llviewermessage.cpp8
-rw-r--r--indra/newview/llviewertexteditor.cpp45
-rw-r--r--indra/newview/llviewertexteditor.h4
-rw-r--r--indra/newview/llweb.cpp10
-rw-r--r--indra/newview/llweb.h20
-rw-r--r--indra/newview/skins/default/xui/en/menu_url_agent.xml28
-rw-r--r--indra/newview/skins/default/xui/en/menu_url_group.xml28
-rw-r--r--indra/newview/skins/default/xui/en/menu_url_http.xml37
-rw-r--r--indra/newview/skins/default/xui/en/menu_url_objectim.xml37
-rw-r--r--indra/newview/skins/default/xui/en/menu_url_parcel.xml21
-rw-r--r--indra/newview/skins/default/xui/en/menu_url_slapp.xml21
-rw-r--r--indra/newview/skins/default/xui/en/menu_url_slurl.xml30
-rw-r--r--indra/newview/skins/default/xui/en/menu_url_teleport.xml21
-rw-r--r--indra/newview/skins/default/xui/en/panel_edit_profile.xml12
-rw-r--r--indra/newview/skins/default/xui/en/panel_profile.xml13
-rw-r--r--indra/newview/skins/default/xui/en/panel_progress.xml4
-rw-r--r--indra/newview/skins/default/xui/en/strings.xml13
58 files changed, 4043 insertions, 1308 deletions
diff --git a/indra/integration_tests/llui_libtest/CMakeLists.txt b/indra/integration_tests/llui_libtest/CMakeLists.txt
index 1ccdb0f20b..84e3477ce6 100644
--- a/indra/integration_tests/llui_libtest/CMakeLists.txt
+++ b/indra/integration_tests/llui_libtest/CMakeLists.txt
@@ -11,6 +11,7 @@ include(LLCommon)
include(LLImage)
include(LLImageJ2COJ) # ugh, needed for images
include(LLMath)
+include(LLMessage)
include(LLRender)
include(LLWindow)
include(LLUI)
@@ -67,6 +68,7 @@ endif (DARWIN)
# Sort by high-level to low-level
target_link_libraries(llui_libtest
llui
+ llmessage
${OS_LIBRARIES}
${GOOGLE_PERFTOOLS_LIBRARIES}
)
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index a7f899ce41..790f2d5729 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -47,7 +47,6 @@ set(llui_SOURCE_FILES
llkeywords.cpp
lllayoutstack.cpp
lllineeditor.cpp
- lllink.cpp
lllistctrl.cpp
llmenugl.cpp
llmodaldialog.cpp
@@ -79,6 +78,7 @@ set(llui_SOURCE_FILES
llstatview.cpp
llstyle.cpp
lltabcontainer.cpp
+ lltextbase.cpp
lltextbox.cpp
lltexteditor.cpp
lltextparser.cpp
@@ -90,6 +90,10 @@ set(llui_SOURCE_FILES
lluiimage.cpp
lluistring.cpp
llundo.cpp
+ llurlaction.cpp
+ llurlentry.cpp
+ llurlmatch.cpp
+ llurlregistry.cpp
llviewborder.cpp
llviewmodel.cpp
llview.cpp
@@ -124,7 +128,6 @@ set(llui_HEADER_FILES
lllayoutstack.h
lllazyvalue.h
lllineeditor.h
- lllink.h
lllistctrl.h
llmenugl.h
llmodaldialog.h
@@ -156,6 +159,7 @@ set(llui_HEADER_FILES
llstatview.h
llstyle.h
lltabcontainer.h
+ lltextbase.h
lltextbox.h
lltexteditor.h
lltextparser.h
@@ -169,6 +173,10 @@ set(llui_HEADER_FILES
lluiimage.h
lluistring.h
llundo.h
+ llurlaction.h
+ llurlentry.h
+ llurlmatch.h
+ llurlregistry.h
llviewborder.h
llviewmodel.h
llview.h
@@ -184,12 +192,21 @@ add_library (llui ${llui_SOURCE_FILES})
# Libraries on which this library depends, needed for Linux builds
# Sort by high-level to low-level
target_link_libraries(llui
- llrender
- llwindow
- llimage
- llvfs # ugh, just for LLDir
- llxuixml
- llxml
- llcommon # must be after llimage, llwindow, llrender
- llmath
+ ${LLMESSAGE_LIBRARIES}
+ ${LLRENDER_LIBRARIES}
+ ${LLWINDOW_LIBRARIES}
+ ${LLIMAGE_LIBRARIES}
+ ${LLVFS_LIBRARIES} # ugh, just for LLDir
+ ${LLXUIXML_LIBRARIES}
+ ${LLXML_LIBRARIES}
+ ${LLMATH_LIBRARIES}
+ ${LLCOMMON_LIBRARIES} # must be after llimage, llwindow, llrender
)
+
+# Add tests
+include(LLAddBuildTest)
+SET(llui_TEST_SOURCE_FILES
+ llurlmatch.cpp
+ llurlentry.cpp
+ )
+LL_ADD_PROJECT_UNIT_TESTS(llui "${llui_TEST_SOURCE_FILES}")
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp
index 637642cdcd..b9a253aac8 100644
--- a/indra/llui/llscrolllistctrl.cpp
+++ b/indra/llui/llscrolllistctrl.cpp
@@ -57,6 +57,11 @@
#include "llviewborder.h"
#include "lltextbox.h"
#include "llsdparam.h"
+#include "llcachename.h"
+#include "llmenugl.h"
+#include "llurlaction.h"
+
+#include <boost/bind.hpp>
static LLDefaultChildRegistry::Register<LLScrollListCtrl> r("scroll_list");
@@ -157,6 +162,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
mOnSortChangedCallback( NULL ),
mHighlightedItem(-1),
mBorder(NULL),
+ mPopupMenu(NULL),
mNumDynamicWidthColumns(0),
mTotalStaticColumnWidth(0),
mTotalColumnPadding(0),
@@ -179,7 +185,8 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
mHighlightedColor(p.highlighted_color()),
mHoveredColor(p.hovered_color()),
mSearchColumn(p.search_column),
- mColumnPadding(p.column_padding)
+ mColumnPadding(p.column_padding),
+ mContextMenuType(MENU_NONE)
{
mItemListRect.setOriginAndSize(
mBorderThickness,
@@ -1692,6 +1699,72 @@ BOOL LLScrollListCtrl::handleMouseUp(S32 x, S32 y, MASK mask)
return LLUICtrl::handleMouseUp(x, y, mask);
}
+// virtual
+BOOL LLScrollListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask)
+{
+ LLScrollListItem *item = hitItem(x, y);
+ if (item)
+ {
+ // check to see if we have a UUID for this row
+ std::string id = item->getValue().asString();
+ LLUUID uuid(id);
+ if (! uuid.isNull() && mContextMenuType != MENU_NONE)
+ {
+ // set up the callbacks for all of the avatar/group menu items
+ // (N.B. callbacks don't take const refs as id is local scope)
+ bool is_group = (mContextMenuType == MENU_GROUP);
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+ registrar.add("Url.Execute", boost::bind(&LLScrollListCtrl::showNameDetails, id, is_group));
+ registrar.add("Url.CopyLabel", boost::bind(&LLScrollListCtrl::copyNameToClipboard, id, is_group));
+ registrar.add("Url.CopyUrl", boost::bind(&LLScrollListCtrl::copySLURLToClipboard, id, is_group));
+
+ // create the context menu from the XUI file and display it
+ std::string menu_name = is_group ? "menu_url_group.xml" : "menu_url_agent.xml";
+ delete mPopupMenu;
+ mPopupMenu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(
+ menu_name, LLMenuGL::sMenuContainer, LLMenuHolderGL::child_registry_t::instance());
+ if (mPopupMenu)
+ {
+ mPopupMenu->show(x, y);
+ LLMenuGL::showPopup(this, mPopupMenu, x, y);
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+void LLScrollListCtrl::showNameDetails(std::string id, bool is_group)
+{
+ // show the resident's profile or the group profile
+ std::string sltype = is_group ? "group" : "agent";
+ std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
+ LLUrlAction::clickAction(slurl);
+}
+
+void LLScrollListCtrl::copyNameToClipboard(std::string id, bool is_group)
+{
+ // copy the name of the avatar or group to the clipboard
+ std::string name;
+ if (is_group)
+ {
+ gCacheName->getGroupName(LLUUID(id), name);
+ }
+ else
+ {
+ gCacheName->getFullName(LLUUID(id), name);
+ }
+ LLUrlAction::copyURLToClipboard(name);
+}
+
+void LLScrollListCtrl::copySLURLToClipboard(std::string id, bool is_group)
+{
+ // copy a SLURL for the avatar or group to the clipboard
+ std::string sltype = is_group ? "group" : "agent";
+ std::string slurl = "secondlife:///app/" + sltype + "/" + id + "/about";
+ LLUrlAction::copyURLToClipboard(slurl);
+}
+
BOOL LLScrollListCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
{
//BOOL handled = FALSE;
diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h
index 253a58ab73..7a7e5be0be 100644
--- a/indra/llui/llscrolllistctrl.h
+++ b/indra/llui/llscrolllistctrl.h
@@ -54,6 +54,7 @@
class LLScrollListCell;
class LLTextBox;
+class LLContextMenu;
class LLScrollListCtrl : public LLUICtrl, public LLEditMenuHandler,
public LLCtrlListInterface, public LLCtrlScrollInterface
@@ -270,10 +271,15 @@ public:
void clearSearchString() { mSearchString.clear(); }
+ // support right-click context menus for avatar/group lists
+ enum ContextMenuType { MENU_NONE, MENU_AVATAR, MENU_GROUP };
+ void setContextMenu(const ContextMenuType &menu) { mContextMenuType = menu; }
+
// Overridden from LLView
/*virtual*/ void draw();
/*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask);
/*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask);
+ /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
/*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask);
/*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask);
/*virtual*/ BOOL handleKeyHere(KEY key, MASK mask);
@@ -375,6 +381,10 @@ private:
void commitIfChanged();
BOOL setSort(S32 column, BOOL ascending);
+ static void showNameDetails(std::string id, bool is_group);
+ static void copyNameToClipboard(std::string id, bool is_group);
+ static void copySLURLToClipboard(std::string id, bool is_group);
+
S32 mLineHeight; // the max height of a single line
S32 mScrollLines; // how many lines we've scrolled down
S32 mPageLines; // max number of lines is it possible to see on the screen given mRect and mLineHeight
@@ -421,6 +431,7 @@ private:
S32 mHighlightedItem;
class LLViewBorder* mBorder;
+ LLContextMenu *mPopupMenu;
LLWString mSearchString;
LLFrameTimer mSearchTimer;
@@ -438,6 +449,8 @@ private:
BOOL mDirty;
S32 mOriginalSelection;
+ ContextMenuType mContextMenuType;
+
typedef std::vector<LLScrollListColumn*> ordered_columns_t;
ordered_columns_t mColumnsIndexed;
diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp
index 929a809d88..c16ac08014 100644
--- a/indra/llui/llstyle.cpp
+++ b/indra/llui/llstyle.cpp
@@ -54,8 +54,6 @@ LLStyle::LLStyle(const LLStyle::Params& p)
mFont(p.font()),
mLink(p.link_href),
mDropShadow(p.drop_shadow),
- mImageHeight(0),
- mImageWidth(0),
mImagep(p.image())
{}
@@ -100,9 +98,7 @@ void LLStyle::setImage(const LLUUID& src)
mImagep = LLUI::getUIImageByID(src);
}
-
-void LLStyle::setImageSize(S32 width, S32 height)
+void LLStyle::setImage(const std::string& name)
{
- mImageWidth = width;
- mImageHeight = height;
+ mImagep = LLUI::getUIImage(name);
}
diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h
index dcf274a651..5e8883afd7 100644
--- a/indra/llui/llstyle.h
+++ b/indra/llui/llstyle.h
@@ -69,9 +69,9 @@ public:
LLUIImagePtr getImage() const;
void setImage(const LLUUID& src);
+ void setImage(const std::string& name);
- BOOL isImage() const { return ((mImageWidth != 0) && (mImageHeight != 0)); }
- void setImageSize(S32 width, S32 height);
+ BOOL isImage() const { return mImagep.notNull(); }
// inlined here to make it easier to compare to member data below. -MG
bool operator==(const LLStyle &rhs) const
@@ -82,8 +82,6 @@ public:
&& mFont == rhs.mFont
&& mLink == rhs.mLink
&& mImagep == rhs.mImagep
- && mImageHeight == rhs.mImageHeight
- && mImageWidth == rhs.mImageWidth
&& mItalic == rhs.mItalic
&& mBold == rhs.mBold
&& mUnderline == rhs.mUnderline
@@ -97,8 +95,6 @@ public:
BOOL mBold;
BOOL mUnderline;
BOOL mDropShadow;
- S32 mImageWidth;
- S32 mImageHeight;
protected:
~LLStyle() { }
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
new file mode 100644
index 0000000000..038ea2188f
--- /dev/null
+++ b/indra/llui/lltextbase.cpp
@@ -0,0 +1,451 @@
+/**
+ * @file lltextbase.cpp
+ * @author Martin Reddy
+ * @brief The base class of text box/editor, providing Url handling support
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "lltextbase.h"
+#include "llstl.h"
+#include "llview.h"
+#include "llwindow.h"
+#include "llmenugl.h"
+#include "lluictrl.h"
+#include "llurlaction.h"
+#include "llurlregistry.h"
+
+#include <boost/bind.hpp>
+
+// global state for all text fields
+LLUIColor LLTextBase::mLinkColor = LLColor4::blue;
+
+bool LLTextBase::compare_segment_end::operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const
+{
+ return a->getEnd() < b->getEnd();
+}
+
+//
+// LLTextSegment
+//
+
+LLTextSegment::~LLTextSegment()
+{}
+
+S32 LLTextSegment::getWidth(S32 first_char, S32 num_chars) const { return 0; }
+S32 LLTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { return 0; }
+S32 LLTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { return 0; }
+void LLTextSegment::updateLayout(const LLTextBase& editor) {}
+F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) { return draw_rect.mLeft; }
+S32 LLTextSegment::getMaxHeight() const { return 0; }
+bool LLTextSegment::canEdit() const { return false; }
+void LLTextSegment::unlinkFromDocument(LLTextBase*) {}
+void LLTextSegment::linkToDocument(LLTextBase*) {}
+void LLTextSegment::setHasMouseHover(bool hover) {}
+const LLColor4& LLTextSegment::getColor() const { return LLColor4::white; }
+void LLTextSegment::setColor(const LLColor4 &color) {}
+const LLStyleSP LLTextSegment::getStyle() const {static LLStyleSP sp(new LLStyle()); return sp; }
+void LLTextSegment::setStyle(const LLStyleSP &style) {}
+void LLTextSegment::setToken( LLKeywordToken* token ) {}
+LLKeywordToken* LLTextSegment::getToken() const { return NULL; }
+BOOL LLTextSegment::getToolTip( std::string& msg ) const { return FALSE; }
+void LLTextSegment::setToolTip( const std::string &msg ) {}
+void LLTextSegment::dump() const {}
+
+
+//
+// LLNormalTextSegment
+//
+
+LLNormalTextSegment::LLNormalTextSegment( const LLStyleSP& style, S32 start, S32 end, LLTextBase& editor )
+: LLTextSegment(start, end),
+ mStyle( style ),
+ mToken(NULL),
+ mHasMouseHover(false),
+ mEditor(editor)
+{
+ mMaxHeight = llceil(mStyle->getFont()->getLineHeight());
+}
+
+LLNormalTextSegment::LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible)
+: LLTextSegment(start, end),
+ mToken(NULL),
+ mHasMouseHover(false),
+ mEditor(editor)
+{
+ mStyle = new LLStyle(LLStyle::Params().visible(is_visible).color(color));
+
+ mMaxHeight = llceil(mStyle->getFont()->getLineHeight());
+}
+
+F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect)
+{
+ if( end - start > 0 )
+ {
+ if ( mStyle->isImage() && (start >= 0) && (end <= mEnd - mStart))
+ {
+ LLUIImagePtr image = mStyle->getImage();
+ S32 style_image_height = image->getHeight();
+ S32 style_image_width = image->getWidth();
+ image->draw(draw_rect.mLeft, draw_rect.mTop-style_image_height,
+ style_image_width, style_image_height);
+ }
+
+ return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect.mLeft, draw_rect.mBottom);
+ }
+ return draw_rect.mLeft;
+}
+
+// Draws a single text segment, reversing the color for selection if needed.
+F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, F32 x, F32 y)
+{
+ const LLWString &text = mEditor.getWText();
+
+ F32 right_x = x;
+ if (!mStyle->isVisible())
+ {
+ return right_x;
+ }
+
+ const LLFontGL* font = mStyle->getFont();
+
+ LLColor4 color = mStyle->getColor();
+
+ font = mStyle->getFont();
+
+ if( selection_start > seg_start )
+ {
+ // Draw normally
+ S32 start = seg_start;
+ S32 end = llmin( selection_start, seg_end );
+ S32 length = end - start;
+ font->render(text, start, x, y, color, LLFontGL::LEFT, LLFontGL::BOTTOM, 0, LLFontGL::NO_SHADOW, length, S32_MAX, &right_x, mEditor.allowsEmbeddedItems());
+ }
+ x = right_x;
+
+ if( (selection_start < seg_end) && (selection_end > seg_start) )
+ {
+ // Draw reversed
+ S32 start = llmax( selection_start, seg_start );
+ S32 end = llmin( selection_end, seg_end );
+ S32 length = end - start;
+
+ font->render(text, start, x, y,
+ LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ),
+ LLFontGL::LEFT, LLFontGL::BOTTOM, 0, LLFontGL::NO_SHADOW, length, S32_MAX, &right_x, mEditor.allowsEmbeddedItems());
+ }
+ x = right_x;
+ if( selection_end < seg_end )
+ {
+ // Draw normally
+ S32 start = llmax( selection_end, seg_start );
+ S32 end = seg_end;
+ S32 length = end - start;
+ font->render(text, start, x, y, color, LLFontGL::LEFT, LLFontGL::BOTTOM, 0, LLFontGL::NO_SHADOW, length, S32_MAX, &right_x, mEditor.allowsEmbeddedItems());
+ }
+ return right_x;
+}
+
+S32 LLNormalTextSegment::getMaxHeight() const
+{
+ return mMaxHeight;
+}
+
+BOOL LLNormalTextSegment::getToolTip(std::string& msg) const
+{
+ // do we have a tooltip for a loaded keyword (for script editor)?
+ if (mToken && !mToken->getToolTip().empty())
+ {
+ const LLWString& wmsg = mToken->getToolTip();
+ msg = wstring_to_utf8str(wmsg);
+ return TRUE;
+ }
+ // or do we have an explicitly set tooltip (e.g., for Urls)
+ if (! mTooltip.empty())
+ {
+ msg = mTooltip;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void LLNormalTextSegment::setToolTip(const std::string& tooltip)
+{
+ // we cannot replace a keyword tooltip that's loaded from a file
+ if (mToken)
+ {
+ llwarns << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << llendl;
+ return;
+ }
+ mTooltip = tooltip;
+}
+
+S32 LLNormalTextSegment::getWidth(S32 first_char, S32 num_chars) const
+{
+ LLWString text = mEditor.getWText();
+ return mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars);
+}
+
+S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const
+{
+ LLWString text = mEditor.getWText();
+ return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset,
+ (F32)segment_local_x_coord,
+ F32_MAX,
+ num_chars,
+ round);
+}
+
+S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const
+{
+ LLWString text = mEditor.getWText();
+ S32 num_chars = mStyle->getFont()->maxDrawableChars(text.c_str() + segment_offset + mStart,
+ (F32)num_pixels,
+ max_chars,
+ mEditor.getWordWrap());
+
+ if (num_chars == 0
+ && line_offset == 0
+ && max_chars > 0)
+ {
+ // If at the beginning of a line, and a single character won't fit, draw it anyway
+ num_chars = 1;
+ }
+ if (mStart + segment_offset + num_chars == mEditor.getLength())
+ {
+ // include terminating NULL
+ num_chars++;
+ }
+ return num_chars;
+}
+
+void LLNormalTextSegment::dump() const
+{
+ llinfos << "Segment [" <<
+// mColor.mV[VX] << ", " <<
+// mColor.mV[VY] << ", " <<
+// mColor.mV[VZ] << "]\t[" <<
+ mStart << ", " <<
+ getEnd() << "]" <<
+ llendl;
+}
+
+//////////////////////////////////////////////////////////////////////////
+//
+// LLTextBase
+//
+
+LLTextBase::LLTextBase(const LLUICtrl::Params &p) :
+ mHoverSegment(NULL),
+ mDefaultFont(p.font),
+ mParseHTML(TRUE),
+ mPopupMenu(NULL)
+{
+}
+
+LLTextBase::~LLTextBase()
+{
+ clearSegments();
+}
+
+void LLTextBase::clearSegments()
+{
+ setHoverSegment(NULL);
+ mSegments.clear();
+}
+
+void LLTextBase::setHoverSegment(LLTextSegmentPtr segment)
+{
+ if (mHoverSegment)
+ {
+ mHoverSegment->setHasMouseHover(false);
+ }
+ if (segment)
+ {
+ segment->setHasMouseHover(true);
+ }
+ mHoverSegment = segment;
+}
+
+void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const
+{
+ *seg_iter = getSegIterContaining(startpos);
+ if (*seg_iter == mSegments.end())
+ {
+ *offsetp = 0;
+ }
+ else
+ {
+ *offsetp = startpos - (**seg_iter)->getStart();
+ }
+}
+
+void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp )
+{
+ *seg_iter = getSegIterContaining(startpos);
+ if (*seg_iter == mSegments.end())
+ {
+ *offsetp = 0;
+ }
+ else
+ {
+ *offsetp = startpos - (**seg_iter)->getStart();
+ }
+}
+
+LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index)
+{
+ segment_set_t::iterator it = mSegments.upper_bound(new LLIndexSegment(index));
+ return it;
+}
+
+LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 index) const
+{
+ LLTextBase::segment_set_t::const_iterator it = mSegments.upper_bound(new LLIndexSegment(index));
+ return it;
+}
+
+// Finds the text segment (if any) at the give local screen position
+LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y )
+{
+ // Find the cursor position at the requested local screen position
+ S32 offset = getDocIndexFromLocalCoord( x, y, FALSE );
+ segment_set_t::iterator seg_iter = getSegIterContaining(offset);
+ if (seg_iter != mSegments.end())
+ {
+ return *seg_iter;
+ }
+ else
+ {
+ return LLTextSegmentPtr();
+ }
+}
+
+BOOL LLTextBase::handleHoverOverUrl(S32 x, S32 y)
+{
+ setHoverSegment(NULL);
+
+ // Check to see if we're over an HTML-style link
+ LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y );
+ if (cur_segment)
+ {
+ setHoverSegment(cur_segment);
+
+ LLStyleSP style = cur_segment->getStyle();
+ if (style && style->isLink())
+ {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL LLTextBase::handleMouseUpOverUrl(S32 x, S32 y)
+{
+ if (mParseHTML && mHoverSegment)
+ {
+ LLStyleSP style = mHoverSegment->getStyle();
+ if (style && style->isLink())
+ {
+ LLUrlAction::clickAction(style->getLinkHREF());
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL LLTextBase::handleRightMouseDownOverUrl(LLView *view, S32 x, S32 y)
+{
+ // pop up a context menu for any Url under the cursor
+ const LLTextSegment* cur_segment = getSegmentAtLocalPos(x, y);
+ if (cur_segment && cur_segment->getStyle() && cur_segment->getStyle()->isLink())
+ {
+ delete mPopupMenu;
+ mPopupMenu = createUrlContextMenu(cur_segment->getStyle()->getLinkHREF());
+ if (mPopupMenu)
+ {
+ mPopupMenu->show(x, y);
+ LLMenuGL::showPopup(view, mPopupMenu, x, y);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+BOOL LLTextBase::handleToolTipForUrl(LLView *view, S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen)
+{
+ const LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y );
+ if (cur_segment && cur_segment->getToolTip( msg ) && view)
+ {
+ // Use a slop area around the cursor
+ const S32 SLOP = 8;
+ // Convert rect local to screen coordinates
+ view->localPointToScreen(x - SLOP, y - SLOP, &(sticky_rect_screen->mLeft),
+ &(sticky_rect_screen->mBottom));
+ sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP;
+ sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP;
+ }
+ return TRUE;
+}
+
+LLContextMenu *LLTextBase::createUrlContextMenu(const std::string &in_url)
+{
+ // work out the XUI menu file to use for this url
+ LLUrlMatch match;
+ std::string url = in_url;
+ if (! LLUrlRegistry::instance().findUrl(url, match))
+ {
+ return NULL;
+ }
+
+ std::string xui_file = match.getMenuName();
+ if (xui_file.empty())
+ {
+ return NULL;
+ }
+
+ // set up the callbacks for all of the potential menu items, N.B. we
+ // don't use const ref strings in callbacks in case url goes out of scope
+ LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar;
+ registrar.add("Url.Open", boost::bind(&LLUrlAction::openURL, url));
+ registrar.add("Url.OpenInternal", boost::bind(&LLUrlAction::openURLInternal, url));
+ registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url));
+ registrar.add("Url.Execute", boost::bind(&LLUrlAction::executeSLURL, url));
+ registrar.add("Url.Teleport", boost::bind(&LLUrlAction::teleportToLocation, url));
+ registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url));
+ registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url));
+
+ // create and return the context menu from the XUI file
+ return LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>(xui_file, LLMenuGL::sMenuContainer,
+ LLMenuHolderGL::child_registry_t::instance());
+}
diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h
new file mode 100644
index 0000000000..27b88761a8
--- /dev/null
+++ b/indra/llui/lltextbase.h
@@ -0,0 +1,198 @@
+/**
+ * @file lltextbase.h
+ * @author Martin Reddy
+ * @brief The base class of text box/editor, providing Url handling support
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLTEXTBASE_H
+#define LL_LLTEXTBASE_H
+
+#include "v4color.h"
+#include "llstyle.h"
+#include "llkeywords.h"
+#include "lluictrl.h"
+
+#include <string>
+#include <set>
+
+class LLContextMenu;
+class LLTextSegment;
+
+typedef LLPointer<LLTextSegment> LLTextSegmentPtr;
+
+///
+/// The LLTextBase class provides a base class for all text fields, such
+/// as LLTextEditor and LLTextBox. It implements shared functionality
+/// such as Url highlighting and opening.
+///
+class LLTextBase
+{
+public:
+ LLTextBase(const LLUICtrl::Params &p);
+ virtual ~LLTextBase();
+
+ /// specify the color to display Url hyperlinks in the text
+ static void setLinkColor(LLColor4 color) { mLinkColor = color; }
+
+ /// enable/disable the automatic hyperlinking of Urls in the text
+ void setParseHTML(BOOL parsing) { mParseHTML=parsing; }
+
+ // public text editing virtual methods
+ virtual LLWString getWText() const = 0;
+ virtual BOOL allowsEmbeddedItems() const { return FALSE; }
+ virtual BOOL getWordWrap() { return mWordWrap; }
+ virtual S32 getLength() const = 0;
+
+protected:
+ struct compare_segment_end
+ {
+ bool operator()(const LLTextSegmentPtr& a, const LLTextSegmentPtr& b) const;
+ };
+ typedef std::multiset<LLTextSegmentPtr, compare_segment_end> segment_set_t;
+
+ // routines to manage segments
+ void getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const;
+ void getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp );
+ LLTextSegmentPtr getSegmentAtLocalPos( S32 x, S32 y );
+ segment_set_t::iterator getSegIterContaining(S32 index);
+ segment_set_t::const_iterator getSegIterContaining(S32 index) const;
+ void clearSegments();
+ void setHoverSegment(LLTextSegmentPtr segment);
+
+ // event handling for Urls within the text field
+ BOOL handleHoverOverUrl(S32 x, S32 y);
+ BOOL handleMouseUpOverUrl(S32 x, S32 y);
+ BOOL handleRightMouseDownOverUrl(LLView *view, S32 x, S32 y);
+ BOOL handleToolTipForUrl(LLView *view, S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen);
+
+ // pure virtuals that have to be implemented by any subclasses
+ virtual S32 getLineCount() const = 0;
+ virtual S32 getLineStart( S32 line ) const = 0;
+ virtual S32 getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const = 0;
+
+ // protected member variables
+ static LLUIColor mLinkColor;
+ const LLFontGL *mDefaultFont;
+ segment_set_t mSegments;
+ LLTextSegmentPtr mHoverSegment;
+ BOOL mParseHTML;
+ BOOL mWordWrap;
+
+private:
+ // create a popup context menu for the given Url
+ static LLContextMenu *createUrlContextMenu(const std::string &url);
+
+ LLContextMenu *mPopupMenu;
+};
+
+///
+/// A text segment is used to specify a subsection of a text string
+/// that should be formatted differently, such as a hyperlink. It
+/// includes a start/end offset from the start of the string, a
+/// style to render with, an optional tooltip, etc.
+///
+class LLTextSegment : public LLRefCount
+{
+public:
+ LLTextSegment(S32 start, S32 end) : mStart(start), mEnd(end){};
+ virtual ~LLTextSegment();
+
+ virtual S32 getWidth(S32 first_char, S32 num_chars) const;
+ virtual S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const;
+ virtual S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const;
+ virtual void updateLayout(const class LLTextBase& editor);
+ virtual F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect);
+ virtual S32 getMaxHeight() const;
+ virtual bool canEdit() const;
+ virtual void unlinkFromDocument(class LLTextBase* editor);
+ virtual void linkToDocument(class LLTextBase* editor);
+
+ virtual void setHasMouseHover(bool hover);
+ virtual const LLColor4& getColor() const;
+ virtual void setColor(const LLColor4 &color);
+ virtual const LLStyleSP getStyle() const;
+ virtual void setStyle(const LLStyleSP &style);
+ virtual void setToken( LLKeywordToken* token );
+ virtual LLKeywordToken* getToken() const;
+ virtual BOOL getToolTip( std::string& msg ) const;
+ virtual void setToolTip(const std::string& tooltip);
+ virtual void dump() const;
+
+ S32 getStart() const { return mStart; }
+ void setStart(S32 start) { mStart = start; }
+ S32 getEnd() const { return mEnd; }
+ void setEnd( S32 end ) { mEnd = end; }
+
+protected:
+ S32 mStart;
+ S32 mEnd;
+};
+
+class LLNormalTextSegment : public LLTextSegment
+{
+public:
+ LLNormalTextSegment( const LLStyleSP& style, S32 start, S32 end, LLTextBase& editor );
+ LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible = TRUE);
+
+ /*virtual*/ S32 getWidth(S32 first_char, S32 num_chars) const;
+ /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const;
+ /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const;
+ /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect);
+ /*virtual*/ S32 getMaxHeight() const;
+ /*virtual*/ bool canEdit() const { return true; }
+ /*virtual*/ void setHasMouseHover(bool hover) { mHasMouseHover = hover; }
+ /*virtual*/ const LLColor4& getColor() const { return mStyle->getColor(); }
+ /*virtual*/ void setColor(const LLColor4 &color) { mStyle->setColor(color); }
+ /*virtual*/ const LLStyleSP getStyle() const { return mStyle; }
+ /*virtual*/ void setStyle(const LLStyleSP &style) { mStyle = style; }
+ /*virtual*/ void setToken( LLKeywordToken* token ) { mToken = token; }
+ /*virtual*/ LLKeywordToken* getToken() const { return mToken; }
+ /*virtual*/ BOOL getToolTip( std::string& msg ) const;
+ /*virtual*/ void setToolTip(const std::string& tooltip);
+ /*virtual*/ void dump() const;
+
+protected:
+ F32 drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, F32 x, F32 y);
+
+ class LLTextBase& mEditor;
+ LLStyleSP mStyle;
+ S32 mMaxHeight;
+ LLKeywordToken* mToken;
+ bool mHasMouseHover;
+ std::string mTooltip;
+};
+
+class LLIndexSegment : public LLTextSegment
+{
+public:
+ LLIndexSegment(S32 pos) : LLTextSegment(pos, pos) {}
+};
+
+#endif
diff --git a/indra/llui/lltextbox.cpp b/indra/llui/lltextbox.cpp
index 96e72487b8..7a92bfb74c 100644
--- a/indra/llui/lltextbox.cpp
+++ b/indra/llui/lltextbox.cpp
@@ -32,30 +32,24 @@
#include "linden_common.h"
#include "lltextbox.h"
-#include "lllink.h"
#include "lluictrlfactory.h"
#include "llfocusmgr.h"
#include "llwindow.h"
+#include "llurlregistry.h"
+#include "llstyle.h"
static LLDefaultChildRegistry::Register<LLTextBox> r("text");
-//*NOTE
-// LLLink is not used in code for now, therefor Visual Studio doesn't build it.
-// "link" is registered here to force Visual Studio to build LLLink class.
-static LLDefaultChildRegistry::Register<LLLink> register_link("link");
-
LLTextBox::Params::Params()
: text_color("text_color"),
length("length"),
type("type"),
- highlight_on_hover("hover", false),
border_visible("border_visible", false),
border_drop_shadow_visible("border_drop_shadow_visible", false),
bg_visible("bg_visible", false),
use_ellipses("use_ellipses"),
word_wrap("word_wrap", false),
drop_shadow_visible("drop_shadow_visible"),
- hover_color("hover_color"),
disabled_color("disabled_color"),
background_color("background_color"),
border_color("border_color"),
@@ -68,9 +62,7 @@ LLTextBox::Params::Params()
LLTextBox::LLTextBox(const LLTextBox::Params& p)
: LLUICtrl(p),
- mFontGL(p.font),
- mHoverActive( p.highlight_on_hover ),
- mHasHover( FALSE ),
+ LLTextBase(p),
mBackgroundVisible( p.bg_visible ),
mBorderVisible( p.border_visible ),
mShadowType( p.font_shadow ),
@@ -84,12 +76,11 @@ LLTextBox::LLTextBox(const LLTextBox::Params& p)
mDisabledColor(p.disabled_color()),
mBackgroundColor(p.background_color()),
mBorderColor(p.border_color()),
- mHoverColor(p.hover_color()),
mHAlign(p.font_halign),
mLineSpacing(p.line_spacing),
- mWordWrap( p.word_wrap ),
mDidWordWrap(FALSE)
{
+ mWordWrap = p.word_wrap;
setText( p.text() );
}
@@ -97,9 +88,9 @@ BOOL LLTextBox::handleMouseDown(S32 x, S32 y, MASK mask)
{
BOOL handled = FALSE;
- // HACK: Only do this if there actually is a click callback, so that
+ // HACK: Only do this if there actually is something to click, so that
// overly large text boxes in the older UI won't start eating clicks.
- if (mClickedCallback)
+ if (isClickable())
{
handled = TRUE;
@@ -121,10 +112,9 @@ BOOL LLTextBox::handleMouseUp(S32 x, S32 y, MASK mask)
// We only handle the click if the click both started and ended within us
- // HACK: Only do this if there actually is a click callback, so that
+ // HACK: Only do this if there actually is something to click, so that
// overly large text boxes in the older UI won't start eating clicks.
- if (mClickedCallback
- && hasMouseCapture())
+ if (isClickable() && hasMouseCapture())
{
handled = TRUE;
@@ -136,27 +126,44 @@ BOOL LLTextBox::handleMouseUp(S32 x, S32 y, MASK mask)
make_ui_sound("UISndClickRelease");
}
- // DO THIS AT THE VERY END to allow the button to be destroyed as a result of being clicked.
- // If mouseup in the widget, it's been clicked
- if (mClickedCallback)
+ // handle clicks on Urls in the textbox first
+ if (! handleMouseUpOverUrl(x, y))
{
- mClickedCallback();
+ // DO THIS AT THE VERY END to allow the button to be destroyed
+ // as a result of being clicked. If mouseup in the widget,
+ // it's been clicked
+ if (mClickedCallback && ! handled)
+ {
+ mClickedCallback();
+ }
}
}
return handled;
}
+BOOL LLTextBox::handleRightMouseDown(S32 x, S32 y, MASK mask)
+{
+ // pop up a context menu for any Url under the cursor
+ return handleRightMouseDownOverUrl(this, x, y);
+}
+
BOOL LLTextBox::handleHover(S32 x, S32 y, MASK mask)
{
- BOOL handled = LLView::handleHover(x,y,mask);
- if(mHoverActive)
+ // Check to see if we're over an HTML-style link
+ if (handleHoverOverUrl(x, y))
{
- mHasHover = TRUE; // This should be set every frame during a hover.
- getWindow()->setCursor(UI_CURSOR_ARROW);
+ lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl;
+ getWindow()->setCursor(UI_CURSOR_HAND);
+ return TRUE;
}
- return (handled || mHasHover);
+ return LLView::handleHover(x,y,mask);
+}
+
+BOOL LLTextBox::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen)
+{
+ return handleToolTipForUrl(this, x, y, msg, sticky_rect_screen);
}
void LLTextBox::setText(const LLStringExplicit& text)
@@ -168,7 +175,7 @@ void LLTextBox::setText(const LLStringExplicit& text)
else
{
mText.assign(text);
- setLineLengths();
+ updateDisplayTextAndSegments();
}
}
@@ -177,11 +184,11 @@ void LLTextBox::setLineLengths()
mLineLengthList.clear();
std::string::size_type cur = 0;
- std::string::size_type len = mText.getWString().size();
+ std::string::size_type len = mDisplayText.size();
while (cur < len)
{
- std::string::size_type end = mText.getWString().find('\n', cur);
+ std::string::size_type end = mDisplayText.find('\n', cur);
std::string::size_type runLen;
if (end == std::string::npos)
@@ -199,20 +206,12 @@ void LLTextBox::setLineLengths()
}
}
-void LLTextBox::setWrappedText(const LLStringExplicit& in_text, F32 max_width)
+LLWString LLTextBox::wrapText(const LLWString &wtext, S32 &hoffset, S32 &line_num, F32 max_width)
{
- if (max_width < 0.0f)
- {
- max_width = (F32)getRect().getWidth();
- }
-
- LLWString wtext = utf8str_to_wstring(in_text);
LLWString final_wtext;
- LLWString::size_type cur = 0;;
- LLWString::size_type len = wtext.size();
- F32 line_height = mFontGL->getLineHeight();
- S32 line_num = 1;
+ LLWString::size_type cur = 0;
+ LLWString::size_type len = wtext.size();
while (cur < len)
{
LLWString::size_type end = wtext.find('\n', cur);
@@ -221,41 +220,121 @@ void LLTextBox::setWrappedText(const LLStringExplicit& in_text, F32 max_width)
end = len;
}
+ bool charsRemaining = true;
LLWString::size_type runLen = end - cur;
if (runLen > 0)
{
+ // work out how many chars can fit onto the current line
LLWString run(wtext, cur, runLen);
LLWString::size_type useLen =
- mFontGL->maxDrawableChars(run.c_str(), max_width, runLen, TRUE);
+ mDefaultFont->maxDrawableChars(run.c_str(), max_width-hoffset, runLen, TRUE);
+ charsRemaining = (cur + useLen < len);
+ // try to break lines on word boundaries
+ if (useLen < run.size())
+ {
+ LLWString::size_type prev_use_len = useLen;
+ while (useLen > 0 && ! isspace(run[useLen-1]) && ! ispunct(run[useLen-1]))
+ {
+ --useLen;
+ }
+ if (useLen == 0)
+ {
+ useLen = prev_use_len;
+ }
+ }
+
+ // add the chars that could fit onto one line to our result
final_wtext.append(wtext, cur, useLen);
cur += useLen;
- // not enough room to add any more characters
- if (useLen == 0) break;
+ hoffset += mDefaultFont->getWidth(run.substr(0, useLen).c_str());
+
+ // abort if not enough room to add any more characters
+ if (useLen == 0)
+ {
+ break;
+ }
}
- if (cur < len)
+ if (charsRemaining)
{
if (wtext[cur] == '\n')
{
cur += 1;
}
- line_num +=1;
- // Don't wrap the last line if the text is going to spill off
- // the bottom of the rectangle. Assume we prefer to run off
- // the right edge.
- // *TODO: Is this the right behavior?
- if((line_num-1)*line_height <= (F32)getRect().getHeight())
+ final_wtext += '\n';
+ hoffset = 0;
+ line_num += 1;
+ }
+ }
+
+ return final_wtext;
+}
+
+void LLTextBox::setWrappedText(const LLStringExplicit& in_text, F32 max_width)
+{
+ mDidWordWrap = TRUE;
+ setText(wstring_to_utf8str(getWrappedText(in_text, max_width)));
+}
+
+LLWString LLTextBox::getWrappedText(const LLStringExplicit& in_text, F32 max_width)
+{
+ //
+ // we don't want to wrap Urls otherwise we won't be able to detect their
+ // presence for hyperlinking. So we look for all Urls, and then word wrap
+ // the text before and after, but never break a Url in the middle. We
+ // also need to consider that the Url will be displayed as a label (not
+ // necessary the actual Url string).
+ //
+
+ if (max_width < 0.0f)
+ {
+ max_width = (F32)getRect().getWidth();
+ }
+
+ LLWString wtext = utf8str_to_wstring(in_text);
+ LLWString final_wtext;
+ S32 line_num = 1;
+ S32 hoffset = 0;
+
+ // find the next Url in the text string
+ LLUrlMatch match;
+ while ( LLUrlRegistry::instance().findUrl(wstring_to_utf8str(wtext), match))
+ {
+ S32 start = match.getStart();
+ S32 end = match.getEnd() + 1;
+
+ // perform word wrap on the text before the Url
+ final_wtext += wrapText(wtext.substr(0, start), hoffset, line_num, max_width);
+
+ // add the Url (but compute width based on its label)
+ S32 label_width = mDefaultFont->getWidth(match.getLabel());
+ if (hoffset > 0 && hoffset + label_width > max_width)
+ {
+ final_wtext += '\n';
+ line_num++;
+ hoffset = 0;
+ }
+ final_wtext += wtext.substr(start, end-start);
+ hoffset += label_width;
+ if (hoffset > max_width)
+ {
+ final_wtext += '\n';
+ line_num++;
+ hoffset = 0;
+ // eat any leading whitespace on the next line
+ while (isspace(wtext[end]) && end < (S32)wtext.size())
{
- final_wtext += '\n';
+ end++;
}
}
+
+ // move on to the rest of the text after the Url
+ wtext = wtext.substr(end, wtext.size() - end + 1);
}
-
- mDidWordWrap = TRUE;
- std::string final_text = wstring_to_utf8str(final_wtext);
- setText(final_text);
+ final_wtext += wrapText(wtext, hoffset, line_num, max_width);
+ return final_wtext;
}
S32 LLTextBox::getTextPixelWidth()
@@ -268,7 +347,7 @@ S32 LLTextBox::getTextPixelWidth()
iter != mLineLengthList.end(); ++iter)
{
S32 line_length = *iter;
- S32 line_width = mFontGL->getWidth( mText.getWString().c_str(), cur_pos, line_length );
+ S32 line_width = mDefaultFont->getWidth( mDisplayText.c_str(), cur_pos, line_length );
if( line_width > max_line_width )
{
max_line_width = line_width;
@@ -278,7 +357,7 @@ S32 LLTextBox::getTextPixelWidth()
}
else
{
- max_line_width = mFontGL->getWidth(mText.getWString().c_str());
+ max_line_width = mDefaultFont->getWidth(mDisplayText.c_str());
}
return max_line_width;
}
@@ -290,7 +369,7 @@ S32 LLTextBox::getTextPixelHeight()
{
num_lines = 1;
}
- return (S32)(num_lines * mFontGL->getLineHeight());
+ return (S32)(num_lines * mDefaultFont->getLineHeight());
}
void LLTextBox::setValue(const LLSD& value )
@@ -302,7 +381,7 @@ void LLTextBox::setValue(const LLSD& value )
BOOL LLTextBox::setTextArg( const std::string& key, const LLStringExplicit& text )
{
mText.setArg(key, text);
- setLineLengths();
+ updateDisplayTextAndSegments();
return TRUE;
}
@@ -345,18 +424,11 @@ void LLTextBox::draw()
if ( getEnabled() )
{
- if(mHasHover)
- {
- drawText( text_x, text_y, mHoverColor.get() );
- }
- else
- {
- drawText( text_x, text_y, mTextColor.get() );
- }
+ drawText( text_x, text_y, mDisplayText, mTextColor.get() );
}
else
{
- drawText( text_x, text_y, mDisabledColor.get() );
+ drawText( text_x, text_y, mDisplayText, mDisabledColor.get() );
}
if (sDebugRects)
@@ -370,41 +442,46 @@ void LLTextBox::draw()
//{
// drawDebugRect();
//}
-
- mHasHover = FALSE; // This is reset every frame.
}
void LLTextBox::reshape(S32 width, S32 height, BOOL called_from_parent)
{
- // reparse line lengths
+ // reparse line lengths (don't need to recalculate the display text)
setLineLengths();
LLView::reshape(width, height, called_from_parent);
}
-void LLTextBox::drawText( S32 x, S32 y, const LLColor4& color )
+void LLTextBox::drawText( S32 x, S32 y, const LLWString &text, const LLColor4& color )
{
- if( mLineLengthList.empty() )
+ if (mSegments.size() > 1)
{
- mFontGL->render(mText.getWString(), 0, (F32)x, (F32)y, color,
- mHAlign, mVAlign,
- 0,
- mShadowType,
- S32_MAX, getRect().getWidth(), NULL, mUseEllipses);
+ // we have Urls (or other multi-styled segments)
+ drawTextSegments(x, y, text);
+ }
+ else if( mLineLengthList.empty() )
+ {
+ // simple case of 1 line of text in one style
+ mDefaultFont->render(text, 0, (F32)x, (F32)y, color,
+ mHAlign, mVAlign,
+ 0,
+ mShadowType,
+ S32_MAX, getRect().getWidth(), NULL, mUseEllipses);
}
else
{
+ // simple case of multiple lines of text, all in the same style
S32 cur_pos = 0;
for (std::vector<S32>::iterator iter = mLineLengthList.begin();
iter != mLineLengthList.end(); ++iter)
{
S32 line_length = *iter;
- mFontGL->render(mText.getWString(), cur_pos, (F32)x, (F32)y, color,
- mHAlign, mVAlign,
- 0,
- mShadowType,
- line_length, getRect().getWidth(), NULL, mUseEllipses );
+ mDefaultFont->render(text, cur_pos, (F32)x, (F32)y, color,
+ mHAlign, mVAlign,
+ 0,
+ mShadowType,
+ line_length, getRect().getWidth(), NULL, mUseEllipses );
cur_pos += line_length + 1;
- y -= llfloor(mFontGL->getLineHeight()) + mLineSpacing;
+ y -= llfloor(mDefaultFont->getLineHeight()) + mLineSpacing;
}
}
}
@@ -415,3 +492,254 @@ void LLTextBox::reshapeToFitText()
S32 height = getTextPixelHeight();
reshape( width + 2 * mHPad, height + 2 * mVPad );
}
+
+S32 LLTextBox::getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const
+{
+ // Returns the character offset for the character under the local (x, y) coordinate.
+ // When round is true, if the position is on the right half of a character, the cursor
+ // will be put to its right. If round is false, the cursor will always be put to the
+ // character's left.
+
+ LLRect rect = getLocalRect();
+ rect.mLeft += mHPad;
+ rect.mRight -= mHPad;
+ rect.mTop += mVPad;
+ rect.mBottom -= mVPad;
+
+ // Figure out which line we're nearest to.
+ S32 total_lines = getLineCount();
+ S32 line_height = llround( mDefaultFont->getLineHeight() ) + mLineSpacing;
+ S32 line = (rect.mTop - 1 - local_y) / line_height;
+ if (line >= total_lines)
+ {
+ return getLength(); // past the end
+ }
+
+ line = llclamp( line, 0, total_lines );
+ S32 line_start = getLineStart(line);
+ S32 next_start = getLineStart(line+1);
+ S32 line_end = (next_start != line_start) ? next_start - 1 : getLength();
+ if (line_start == -1)
+ {
+ return 0;
+ }
+
+ S32 line_len = line_end - line_start;
+ S32 pos = mDefaultFont->charFromPixelOffset(mDisplayText.c_str(), line_start,
+ (F32)(local_x - rect.mLeft),
+ (F32)rect.getWidth(),
+ line_len, round);
+
+ return line_start + pos;
+}
+
+S32 LLTextBox::getLineStart( S32 line ) const
+{
+ line = llclamp(line, 0, getLineCount()-1);
+
+ S32 result = 0;
+ for (int i = 0; i < line; i++)
+ {
+ result += mLineLengthList[i] + 1 /* add newline */;
+ }
+
+ return result;
+}
+
+void LLTextBox::updateDisplayTextAndSegments()
+{
+ // remove any previous segment list
+ clearSegments();
+
+ // if URL parsing is turned off, then not much to bo
+ if (! mParseHTML)
+ {
+ mDisplayText = mText.getWString();
+ setLineLengths();
+ return;
+ }
+
+ // create unique text segments for Urls
+ mDisplayText.clear();
+ S32 end = 0;
+ LLUrlMatch match;
+ LLWString text = mText.getWString();
+
+ // find the next Url in the text string
+ while ( LLUrlRegistry::instance().findUrl(wstring_to_utf8str(text), match,
+ boost::bind(&LLTextBox::onUrlLabelUpdated, this, _1, _2)) )
+ {
+ // work out the char offset for the start/end of the url
+ S32 seg_start = mDisplayText.size();
+ S32 start = seg_start + match.getStart();
+ end = start + match.getLabel().size();
+
+ // create a segment for the text before the Url
+ mSegments.insert(new LLNormalTextSegment(new LLStyle(), seg_start, start, *this));
+ mDisplayText += text.substr(0, match.getStart());
+
+ // create a segment for the Url text
+ LLStyleSP html(new LLStyle);
+ html->setVisible(true);
+ html->setColor(mLinkColor);
+ html->mUnderline = TRUE;
+ html->setLinkHREF(match.getUrl());
+
+ LLNormalTextSegment *html_seg = new LLNormalTextSegment(html, start, end, *this);
+ html_seg->setToolTip(match.getTooltip());
+
+ mSegments.insert(html_seg);
+ mDisplayText += utf8str_to_wstring(match.getLabel());
+
+ // move on to the rest of the text after the Url
+ text = text.substr(match.getEnd()+1, text.size() - match.getEnd());
+ }
+
+ // output a segment for the remaining text
+ if (text.size() > 0)
+ {
+ mSegments.insert(new LLNormalTextSegment(new LLStyle(), end, end + text.size(), *this));
+ mDisplayText += text;
+ }
+
+ // strip whitespace from the end of the text
+ while (mDisplayText.size() > 0 && isspace(mDisplayText[mDisplayText.size()-1]))
+ {
+ mDisplayText = mDisplayText.substr(0, mDisplayText.size() - 1);
+
+ segment_set_t::iterator it = getSegIterContaining(mDisplayText.size());
+ if (it != mSegments.end())
+ {
+ LLTextSegmentPtr seg = *it;
+ seg->setEnd(seg->getEnd()-1);
+ }
+ }
+
+ // we may have changed the line lengths, so recalculate them
+ setLineLengths();
+}
+
+void LLTextBox::onUrlLabelUpdated(const std::string &url, const std::string &label)
+{
+ if (mDidWordWrap)
+ {
+ // re-word wrap as the url label lengths may have changed
+ setWrappedText(mText.getString());
+ }
+ else
+ {
+ // or just update the display text with the latest Url labels
+ updateDisplayTextAndSegments();
+ }
+}
+
+bool LLTextBox::isClickable() const
+{
+ // return true if we have been given a click callback
+ if (mClickedCallback)
+ {
+ return true;
+ }
+
+ // also return true if we have a clickable Url in the text
+ segment_set_t::const_iterator it;
+ for (it = mSegments.begin(); it != mSegments.end(); ++it)
+ {
+ LLTextSegmentPtr segmentp = *it;
+ if (segmentp)
+ {
+ const LLStyleSP style = segmentp->getStyle();
+ if (style && style->isLink())
+ {
+ return true;
+ }
+ }
+ }
+
+ // otherwise there is nothing clickable here
+ return false;
+}
+
+void LLTextBox::drawTextSegments(S32 init_x, S32 init_y, const LLWString &text)
+{
+ const S32 text_len = text.length();
+ if (text_len <= 0)
+ {
+ return;
+ }
+
+ S32 cur_line = 0;
+ S32 num_lines = getLineCount();
+ S32 line_start = getLineStart(cur_line);
+ S32 line_height = llround( mDefaultFont->getLineHeight() ) + mLineSpacing;
+ F32 text_y = (F32) init_y;
+ segment_set_t::iterator cur_seg = mSegments.begin();
+
+ // render a line of text at a time
+ const LLRect textRect = getLocalRect();
+ while((textRect.mBottom <= text_y) && (cur_line < num_lines))
+ {
+ S32 next_start = -1;
+ S32 line_end = text_len;
+
+ if ((cur_line + 1) < num_lines)
+ {
+ next_start = getLineStart(cur_line + 1);
+ line_end = next_start;
+ }
+ if ( text[line_end-1] == '\n' )
+ {
+ --line_end;
+ }
+
+ // render all segments on this line
+ F32 text_x = init_x;
+ S32 seg_start = line_start;
+ while (seg_start < line_end && cur_seg != mSegments.end())
+ {
+ // move to the next segment (or continue the previous one)
+ LLTextSegment *cur_segment = *cur_seg;
+ while (cur_segment->getEnd() <= seg_start)
+ {
+ if (++cur_seg == mSegments.end())
+ {
+ return;
+ }
+ cur_segment = *cur_seg;
+ }
+
+ // Draw a segment within the line
+ S32 clipped_end = llmin( line_end, cur_segment->getEnd() );
+ S32 clipped_len = clipped_end - seg_start;
+ if( clipped_len > 0 )
+ {
+ LLStyleSP style = cur_segment->getStyle();
+ if (style && style->isVisible())
+ {
+ // work out the color for the segment
+ LLColor4 color ;
+ if (getEnabled())
+ {
+ color = style->isLink() ? mLinkColor.get() : mTextColor.get();
+ }
+ else
+ {
+ color = mDisabledColor.get();
+ }
+
+ // render a single line worth for this segment
+ mDefaultFont->render(text, seg_start, text_x, text_y, color,
+ mHAlign, mVAlign, 0, mShadowType, clipped_len,
+ textRect.getWidth(), &text_x, mUseEllipses);
+ }
+
+ seg_start += clipped_len;
+ }
+ }
+
+ // move down one line
+ text_y -= (F32)line_height;
+ line_start = next_start;
+ cur_line++;
+ }
+}
diff --git a/indra/llui/lltextbox.h b/indra/llui/lltextbox.h
index d807fe7639..940b820004 100644
--- a/indra/llui/lltextbox.h
+++ b/indra/llui/lltextbox.h
@@ -37,10 +37,11 @@
#include "v4color.h"
#include "llstring.h"
#include "lluistring.h"
+#include "lltextbase.h"
-
-class LLTextBox
-: public LLUICtrl
+class LLTextBox :
+ public LLTextBase,
+ public LLUICtrl
{
public:
@@ -51,8 +52,7 @@ public:
{
Optional<std::string> text;
- Optional<bool> highlight_on_hover,
- border_visible,
+ Optional<bool> border_visible,
border_drop_shadow_visible,
bg_visible,
use_ellipses,
@@ -65,7 +65,6 @@ public:
length;
Optional<LLUIColor> text_color,
- hover_color,
disabled_color,
background_color,
border_color;
@@ -90,15 +89,14 @@ public:
virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask);
virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask);
virtual BOOL handleHover(S32 x, S32 y, MASK mask);
+ virtual BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
+ virtual BOOL handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen);
void setColor( const LLColor4& c ) { mTextColor = c; }
void setDisabledColor( const LLColor4& c) { mDisabledColor = c; }
void setBackgroundColor( const LLColor4& c) { mBackgroundColor = c; }
void setBorderColor( const LLColor4& c) { mBorderColor = c; }
- void setHoverColor( const LLColor4& c ) { mHoverColor = c; }
- void setHoverActive( BOOL active ) { mHoverActive = active; }
-
void setText( const LLStringExplicit& text );
void setWrappedText(const LLStringExplicit& text, F32 max_width = -1.f); // -1 means use existing control width
void setUseEllipses( BOOL use_ellipses ) { mUseEllipses = use_ellipses; }
@@ -112,35 +110,42 @@ public:
void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; }
void setClickedCallback( boost::function<void (void*)> cb, void* userdata = NULL ){ mClickedCallback = boost::bind(cb, userdata); } // mouse down and up within button
- const LLFontGL* getFont() const { return mFontGL; }
+ const LLFontGL* getFont() const { return mDefaultFont; }
void reshapeToFitText();
const std::string& getText() const { return mText.getString(); }
+ LLWString getWText() const { return mDisplayText; }
S32 getTextPixelWidth();
S32 getTextPixelHeight();
+ S32 getLength() const { return mDisplayText.length(); }
virtual void setValue(const LLSD& value );
virtual LLSD getValue() const { return LLSD(getText()); }
virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text );
-private:
+protected:
+ S32 getLineCount() const { return mLineLengthList.size(); }
+ S32 getLineStart( S32 line ) const;
+ S32 getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const;
+ LLWString getWrappedText(const LLStringExplicit& in_text, F32 max_width = -1.f);
void setLineLengths();
- void drawText(S32 x, S32 y, const LLColor4& color );
+ void updateDisplayTextAndSegments();
+ virtual void drawText(S32 x, S32 y, const LLWString &text, const LLColor4& color );
+ void onUrlLabelUpdated(const std::string &url, const std::string &label);
+ bool isClickable() const;
+ LLWString wrapText(const LLWString &wtext, S32 &hoffset, S32 &line_num, F32 max_width);
+ void drawTextSegments(S32 x, S32 y, const LLWString &text);
LLUIString mText;
- const LLFontGL* mFontGL;
- LLUIColor mTextColor;
- LLUIColor mDisabledColor;
- LLUIColor mBackgroundColor;
- LLUIColor mBorderColor;
- LLUIColor mHoverColor;
-
- BOOL mHoverActive;
- BOOL mHasHover;
+ LLWString mDisplayText;
+ LLUIColor mTextColor;
+ LLUIColor mDisabledColor;
+ LLUIColor mBackgroundColor;
+ LLUIColor mBorderColor;
+
BOOL mBackgroundVisible;
BOOL mBorderVisible;
- BOOL mWordWrap;
BOOL mDidWordWrap;
LLFontGL::ShadowType mShadowType;
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index 921041d17f..296ccea0e4 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -59,6 +59,7 @@
#include "lltextparser.h"
#include "llscrollcontainer.h"
#include "llpanel.h"
+#include "llurlregistry.h"
#include <queue>
#include "llcombobox.h"
@@ -78,10 +79,6 @@ const S32 CURSOR_THICKNESS = 2;
const S32 SPACES_PER_TAB = 4;
-void (* LLTextEditor::sURLcallback)(const std::string&) = NULL;
-bool (* LLTextEditor::sSecondlifeURLcallback)(const std::string&) = NULL;
-bool (* LLTextEditor::sSecondlifeURLcallbackRightClick)(const std::string&) = NULL;
-
// helper functors
struct LLTextEditor::compare_bottom
{
@@ -331,8 +328,9 @@ LLTextEditor::Params::Params()
is_unicode("is_unicode")// ignored
{}
-LLTextEditor::LLTextEditor(const LLTextEditor::Params& p)
- : LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)),
+LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) :
+ LLUICtrl(p, LLTextViewModelPtr(new LLTextViewModel)),
+ LLTextBase(p),
mMaxTextByteLength( p.max_text_length ),
mBaseDocIsPristine(TRUE),
mPristineCmd( NULL ),
@@ -351,7 +349,6 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p)
mFocusBgColor( p.bg_focus_color() ),
mLinkColor( p.link_color() ),
mReadOnly(p.read_only),
- mWordWrap( p.word_wrap ),
mShowLineNumbers ( p.show_line_numbers ),
mCommitOnFocusLost( p.commit_on_focus_lost),
mTrackBottom( p.track_bottom ),
@@ -363,14 +360,16 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p)
mReflowNeeded(FALSE),
mScrollNeeded(FALSE),
mLastSelectionY(-1),
- mParseHTML(FALSE),
mParseHighlights(FALSE),
mTabsToNextField(p.ignore_tab),
- mDefaultFont(p.font),
mScrollIndex(-1)
{
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
+ mWordWrap = p.word_wrap;
+ mDefaultFont = p.font;
+ mParseHTML = FALSE;
+
mSourceID.generate();
// reset desired x cursor position
@@ -413,7 +412,6 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p)
appendText(p.default_text, FALSE, FALSE);
- mHTML.clear();
}
void LLTextEditor::initFromParams( const LLTextEditor::Params& p)
@@ -451,7 +449,6 @@ LLTextEditor::~LLTextEditor()
}
// Scrollbar is deleted by LLView
- mHoverSegment = NULL;
std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer());
}
@@ -666,18 +663,12 @@ BOOL LLTextEditor::truncate()
return did_truncate;
}
-void LLTextEditor::clearSegments()
-{
- mHoverSegment = NULL;
- mSegments.clear();
-}
-
void LLTextEditor::setText(const LLStringExplicit &utf8str)
{
+ // clear out the existing text and segments
clearSegments();
- // LLStringUtil::removeCRLF(utf8str);
- getViewModel()->setValue(utf8str_removeCRLF(utf8str));
+ getViewModel()->setValue("");
truncate();
blockUndo();
@@ -687,6 +678,11 @@ void LLTextEditor::setText(const LLStringExplicit &utf8str)
startOfDoc();
deselect();
+ // append the new text (supports Url linking)
+ std::string text(utf8str);
+ LLStringUtil::removeCRLF(text);
+ appendStyledText(text, false, false, LLStyle::Params());
+
needsReflow();
resetDirty();
@@ -696,9 +692,10 @@ void LLTextEditor::setText(const LLStringExplicit &utf8str)
void LLTextEditor::setWText(const LLWString &wtext)
{
+ // clear out the existing text and segments
clearSegments();
- getViewModel()->setDisplay(wtext);
+ getViewModel()->setDisplay(LLWString());
truncate();
blockUndo();
@@ -708,6 +705,9 @@ void LLTextEditor::setWText(const LLWString &wtext)
startOfDoc();
deselect();
+ // append the new text (supports Url linking)
+ appendStyledText(wstring_to_utf8str(wtext), false, false, LLStyle::Params());
+
needsReflow();
resetDirty();
@@ -913,32 +913,6 @@ void LLTextEditor::getLineAndOffset( S32 startpos, S32* linep, S32* offsetp, boo
}
}
-void LLTextEditor::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const
-{
- *seg_iter = getSegIterContaining(startpos);
- if (*seg_iter == mSegments.end())
- {
- *offsetp = 0;
- }
- else
- {
- *offsetp = startpos - (**seg_iter)->getStart();
- }
-}
-
-void LLTextEditor::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp )
-{
- *seg_iter = getSegIterContaining(startpos);
- if (*seg_iter == mSegments.end())
- {
- *offsetp = 0;
- }
- else
- {
- *offsetp = startpos - (**seg_iter)->getStart();
- }
-}
-
const LLTextSegmentPtr LLTextEditor::getPreviousSegment() const
{
// find segment index at character to left of cursor (or rightmost edge of selection)
@@ -1154,6 +1128,10 @@ S32 LLTextEditor::getEditableIndex(S32 index, bool increasing_direction)
segment_set_t::iterator segment_iter;
S32 offset;
getSegmentAndOffset(index, &segment_iter, &offset);
+ if (segment_iter == mSegments.end())
+ {
+ return 0;
+ }
LLTextSegmentPtr segmentp = *segment_iter;
@@ -1377,25 +1355,7 @@ BOOL LLTextEditor::handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_
}
}
- const LLTextSegmentPtr cur_segment = getSegmentAtLocalPos( x, y );
- if( cur_segment )
- {
- BOOL has_tool_tip = FALSE;
- has_tool_tip = cur_segment->getToolTip( msg );
-
- if( has_tool_tip )
- {
- // Just use a slop area around the cursor
- // Convert rect local to screen coordinates
- S32 SLOP = 8;
- localPointToScreen(
- x - SLOP, y - SLOP,
- &(sticky_rect_screen->mLeft), &(sticky_rect_screen->mBottom) );
- sticky_rect_screen->mRight = sticky_rect_screen->mLeft + 2 * SLOP;
- sticky_rect_screen->mTop = sticky_rect_screen->mBottom + 2 * SLOP;
- }
- }
- return TRUE;
+ return handleToolTipForUrl(this, x, y, msg, sticky_rect_screen);
}
BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask)
@@ -1480,12 +1440,6 @@ BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask)
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
BOOL handled = FALSE;
- if (mHoverSegment)
- {
- mHoverSegment->setHasMouseHover(false);
- }
- mHoverSegment = NULL;
-
if(hasMouseCapture() )
{
if( mIsSelecting )
@@ -1525,30 +1479,11 @@ BOOL LLTextEditor::handleHover(S32 x, S32 y, MASK mask)
if( !handled )
{
// Check to see if we're over an HTML-style link
- LLTextSegmentPtr cur_segment = getSegmentAtLocalPos( x, y );
- if( cur_segment )
+ handled = handleHoverOverUrl(x, y);
+ if( handled )
{
- if(cur_segment->getStyle()->isLink())
- {
- lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over link, inactive)" << llendl;
- getWindow()->setCursor(UI_CURSOR_HAND);
- handled = TRUE;
- }
- //else
- //if(cur_segment->getStyle()->getIsEmbeddedItem())
- //{
- // lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << " (over embedded item, inactive)" << llendl;
- // getWindow()->setCursor(UI_CURSOR_HAND);
- // //getWindow()->setCursor(UI_CURSOR_ARROW);
- // handled = TRUE;
- //}
- if (mHoverSegment)
- {
- mHoverSegment->setHasMouseHover(false);
- }
- cur_segment->setHasMouseHover(true);
- mHoverSegment = cur_segment;
- mHTML = mHoverSegment->getStyle()->getLinkHREF();
+ lldebugst(LLERR_USER_INPUT) << "hover handled by " << getName() << llendl;
+ getWindow()->setCursor(UI_CURSOR_HAND);
}
if( !handled )
@@ -1581,9 +1516,9 @@ BOOL LLTextEditor::handleMouseUp(S32 x, S32 y, MASK mask)
endSelection();
}
- if( !hasSelection() )
+ if( !hasSelection() && hasMouseCapture() )
{
- handleMouseUpOverSegment( x, y, mask );
+ handleMouseUpOverUrl(x, y);
}
// take selection to 'primary' clipboard
@@ -3596,14 +3531,20 @@ void LLTextEditor::appendStyledText(const std::string &new_text,
{
S32 start=0,end=0;
+ LLUrlMatch match;
std::string text = new_text;
- while ( findHTML(text, &start, &end) )
+ while ( LLUrlRegistry::instance().findUrl(text, match,
+ boost::bind(&LLTextEditor::onUrlLabelUpdated, this, _1, _2)) )
{
+ start = match.getStart();
+ end = match.getEnd()+1;
+
LLStyle::Params link_params = style_params;
link_params.color = mLinkColor;
link_params.font.style = "UNDERLINE";
- link_params.link_href = text.substr(start,end-start);
+ link_params.link_href = match.getUrl();
+ // output the text before the Url
if (start > 0)
{
if (part == (S32)LLTextParser::WHOLE ||
@@ -3617,9 +3558,38 @@ void LLTextEditor::appendStyledText(const std::string &new_text,
}
std::string subtext=text.substr(0,start);
appendHighlightedText(subtext,allow_undo, prepend_newline, part, style_params);
+ prepend_newline = false;
}
-
- appendText(text.substr(start, end-start),allow_undo, prepend_newline, link_params);
+
+ // output the styled Url
+ appendText(match.getLabel(),allow_undo, prepend_newline, link_params);
+ prepend_newline = false;
+
+ // set the tooltip for the Url label
+ if (! match.getTooltip().empty())
+ {
+ segment_set_t::iterator it = getSegIterContaining(getLength()-1);
+ if (it != mSegments.end())
+ {
+ LLTextSegmentPtr segment = *it;
+ segment->setToolTip(match.getTooltip());
+ }
+ }
+
+ // output an optional icon after the Url
+ if (! match.getIcon().empty())
+ {
+ LLUIImagePtr image = LLUI::getUIImage(match.getIcon());
+ if (image)
+ {
+ LLStyle::Params icon;
+ icon.image = image;
+ // TODO: fix spacing of images and remove the fixed char spacing
+ appendText(" ", allow_undo, prepend_newline, icon);
+ }
+ }
+
+ // move on to the rest of the text after the Url
if (end < (S32)text.length())
{
text = text.substr(end,text.length() - end);
@@ -3711,7 +3681,7 @@ void LLTextEditor::appendText(const std::string &new_text, bool allow_undo, bool
}
append(wide_text, TRUE, segmentp);
-
+
needsReflow();
// Set the cursor and scroll position
@@ -3795,6 +3765,58 @@ void LLTextEditor::appendWidget(LLView* widget, const std::string &widget_text,
}
}
+void LLTextEditor::onUrlLabelUpdated(const std::string &url,
+ const std::string &label)
+{
+ // LLUrlRegistry has given us a new label for one of our Urls
+ replaceUrlLabel(url, label);
+}
+
+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;
@@ -4097,7 +4119,7 @@ void LLTextEditor::updateSegments()
segment_vec_t segment_list;
mKeywords.findSegments(&segment_list, getWText(), mDefaultColor.get(), *this);
- mSegments.clear();
+ clearSegments();
segment_set_t::iterator insert_it = mSegments.begin();
for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it)
{
@@ -4106,7 +4128,29 @@ void LLTextEditor::updateSegments()
}
createDefaultSegment();
+}
+void LLTextEditor::updateLinkSegments()
+{
+ // update any segments that contain a link
+ for (segment_set_t::iterator it = mSegments.begin(); it != mSegments.end(); ++it)
+ {
+ LLTextSegment *segment = *it;
+ if (segment && segment->getStyle() && segment->getStyle()->isLink())
+ {
+ // 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.
+ LLUrlMatch match;
+ LLStyleSP style = static_cast<LLStyleSP>(segment->getStyle());
+ std::string url_label = getText().substr(segment->getStart(), segment->getEnd()-segment->getStart());
+ if (LLUrlRegistry::instance().findUrl(url_label, match))
+ {
+ LLStringUtil::trim(url_label);
+ style->setLinkHREF(url_label);
+ }
+ }
+ }
}
void LLTextEditor::insertSegment(LLTextSegmentPtr segment_to_insert)
@@ -4170,57 +4214,6 @@ void LLTextEditor::insertSegment(LLTextSegmentPtr segment_to_insert)
}
}
-BOOL LLTextEditor::handleMouseUpOverSegment(S32 x, S32 y, MASK mask)
-{
- if ( hasMouseCapture() )
- {
- // This mouse up was part of a click.
- // Regardless of where the cursor is, see if we recently touched a link
- // and launch it if we did.
- if (mParseHTML && mHTML.length() > 0)
- {
- //Special handling for slurls
- if ( (sSecondlifeURLcallback!=NULL) && !(*sSecondlifeURLcallback)(mHTML) )
- {
- if (sURLcallback!=NULL) (*sURLcallback)(mHTML);
- }
- mHTML.clear();
- }
- }
-
- return FALSE;
-}
-
-
-// Finds the text segment (if any) at the give local screen position
-LLTextSegmentPtr LLTextEditor::getSegmentAtLocalPos( S32 x, S32 y )
-{
- // Find the cursor position at the requested local screen position
- S32 offset = getDocIndexFromLocalCoord( x, y, FALSE );
- segment_set_t::iterator seg_iter = getSegIterContaining(offset);
- if (seg_iter != mSegments.end())
- {
- return *seg_iter;
- }
- else
- {
- return LLTextSegmentPtr();
- }
-}
-
-LLTextEditor::segment_set_t::iterator LLTextEditor::getSegIterContaining(S32 index)
-{
- segment_set_t::iterator it = mSegments.upper_bound(new LLIndexSegment(index));
- return it;
-}
-
-LLTextEditor::segment_set_t::const_iterator LLTextEditor::getSegIterContaining(S32 index) const
-{
- LLTextEditor::segment_set_t::const_iterator it = mSegments.upper_bound(new LLIndexSegment(index));
- return it;
-}
-
-
void LLTextEditor::onMouseCaptureLost()
{
endSelection();
@@ -4330,169 +4323,6 @@ BOOL LLTextEditor::exportBuffer(std::string &buffer )
return TRUE;
}
-///////////////////////////////////////////////////////////////////
-// Refactoring note: We may eventually want to replace this with boost::regex or
-// boost::tokenizer capabilities since we've already fixed at least two JIRAs
-// concerning logic issues associated with this function.
-S32 LLTextEditor::findHTMLToken(const std::string &line, S32 pos, BOOL reverse) const
-{
- std::string openers=" \t\n('\"[{<>";
- std::string closers=" \t\n)'\"]}><;";
-
- if (reverse)
- {
- for (int index=pos; index >= 0; index--)
- {
- char c = line[index];
- S32 m2 = openers.find(c);
- if (m2 >= 0)
- {
- return index+1;
- }
- }
- return 0; // index is -1, don't want to return that.
- }
- else
- {
- // adjust the search slightly, to allow matching parenthesis inside the URL
- S32 paren_count = 0;
- for (int index=pos; index<(S32)line.length(); index++)
- {
- char c = line[index];
-
- if (c == '(')
- {
- paren_count++;
- }
- else if (c == ')')
- {
- if (paren_count <= 0)
- {
- return index;
- }
- else
- {
- paren_count--;
- }
- }
- else
- {
- S32 m2 = closers.find(c);
- if (m2 >= 0)
- {
- return index;
- }
- }
- }
- return line.length();
- }
-}
-
-BOOL LLTextEditor::findHTML(const std::string &line, S32 *begin, S32 *end) const
-{
-
- S32 m1,m2,m3;
- BOOL matched = FALSE;
-
- m1=line.find("://",*end);
-
- if (m1 >= 0) //Easy match.
- {
- *begin = findHTMLToken(line, m1, TRUE);
- *end = findHTMLToken(line, m1, FALSE);
-
- //Load_url only handles http and https so don't hilite ftp, smb, etc.
- m2 = line.substr(*begin,(m1 - *begin)).find("http");
- m3 = line.substr(*begin,(m1 - *begin)).find("secondlife");
-
- std::string badneighbors=".,<>?';\"][}{=-+_)(*&^%$#@!~`\t\r\n\\";
-
- if (m2 >= 0 || m3>=0)
- {
- S32 bn = badneighbors.find(line.substr(m1+3,1));
-
- if (bn < 0)
- {
- matched = TRUE;
- }
- }
- }
-/* matches things like secondlife.com (no http://) needs a whitelist to really be effective.
- else //Harder match.
- {
- m1 = line.find(".",*end);
-
- if (m1 >= 0)
- {
- *end = findHTMLToken(line, m1, FALSE);
- *begin = findHTMLToken(line, m1, TRUE);
-
- m1 = line.rfind(".",*end);
-
- if ( ( *end - m1 ) > 2 && m1 > *begin)
- {
- std::string badneighbors=".,<>/?';\"][}{=-+_)(*&^%$#@!~`";
- m2 = badneighbors.find(line.substr(m1+1,1));
- m3 = badneighbors.find(line.substr(m1-1,1));
- if (m3<0 && m2<0)
- {
- matched = TRUE;
- }
- }
- }
- }
- */
-
- if (matched)
- {
- S32 strpos, strpos2;
-
- std::string url = line.substr(*begin,*end - *begin);
- std::string slurlID = "slurl.com/secondlife/";
- strpos = url.find(slurlID);
-
- if (strpos < 0)
- {
- slurlID="secondlife://";
- strpos = url.find(slurlID);
- }
-
- if (strpos < 0)
- {
- slurlID="sl://";
- strpos = url.find(slurlID);
- }
-
- if (strpos >= 0)
- {
- strpos+=slurlID.length();
-
- while ( ( strpos2=url.find("/",strpos) ) == -1 )
- {
- if ((*end+2) >= (S32)line.length() || line.substr(*end,1) != " " )
- {
- matched=FALSE;
- break;
- }
-
- strpos = (*end + 1) - *begin;
-
- *end = findHTMLToken(line,(*begin + strpos),FALSE);
- url = line.substr(*begin,*end - *begin);
- }
- }
-
- }
-
- if (!matched)
- {
- *begin=*end=0;
- }
- return matched;
-}
-
-
-
void LLTextEditor::updateAllowingLanguageInput()
{
LLWindow* window = getWindow();
@@ -4754,193 +4584,6 @@ void LLTextEditor::onValueChange(S32 start, S32 end)
}
//
-// LLTextSegment
-//
-
-LLTextSegment::~LLTextSegment()
-{}
-
-S32 LLTextSegment::getWidth(S32 first_char, S32 num_chars) const { return 0; }
-S32 LLTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { return 0; }
-S32 LLTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const { return 0; }
-void LLTextSegment::updateLayout(const LLTextEditor& editor) {}
-F32 LLTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect) { return draw_rect.mLeft; }
-S32 LLTextSegment::getMaxHeight() const { return 0; }
-bool LLTextSegment::canEdit() const { return false; }
-void LLTextSegment::unlinkFromDocument(LLTextEditor*) {}
-void LLTextSegment::linkToDocument(LLTextEditor*) {}
-void LLTextSegment::setHasMouseHover(bool hover) {}
-const LLColor4& LLTextSegment::getColor() const { return LLColor4::white; }
-void LLTextSegment::setColor(const LLColor4 &color) {}
-const LLStyleSP LLTextSegment::getStyle() const {static LLStyleSP sp(new LLStyle()); return sp; }
-void LLTextSegment::setStyle(const LLStyleSP &style) {}
-void LLTextSegment::setToken( LLKeywordToken* token ) {}
-LLKeywordToken* LLTextSegment::getToken() const { return NULL; }
-BOOL LLTextSegment::getToolTip( std::string& msg ) const { return FALSE; }
-void LLTextSegment::dump() const {}
-
-
-//
-// LLNormalTextSegment
-//
-
-LLNormalTextSegment::LLNormalTextSegment( const LLStyleSP& style, S32 start, S32 end, LLTextEditor& editor )
-: LLTextSegment(start, end),
- mStyle( style ),
- mToken(NULL),
- mHasMouseHover(false),
- mEditor(editor)
-{
- mMaxHeight = llceil(mStyle->getFont()->getLineHeight());
-}
-
-LLNormalTextSegment::LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextEditor& editor, BOOL is_visible)
-: LLTextSegment(start, end),
- mToken(NULL),
- mHasMouseHover(false),
- mEditor(editor)
-{
- mStyle = new LLStyle(LLStyle::Params().visible(is_visible).color(color));
-
- mMaxHeight = llceil(mStyle->getFont()->getLineHeight());
-}
-
-F32 LLNormalTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect)
-{
- if( end - start > 0 )
- {
- if ( mStyle->isImage() && (start >= 0) && (end <= mEnd - mStart))
- {
- S32 style_image_height = mStyle->mImageHeight;
- S32 style_image_width = mStyle->mImageWidth;
- LLUIImagePtr image = mStyle->getImage();
- image->draw(draw_rect.mLeft, draw_rect.mTop-style_image_height,
- style_image_width, style_image_height);
- }
-
- return drawClippedSegment( getStart() + start, getStart() + end, selection_start, selection_end, draw_rect.mLeft, draw_rect.mBottom);
- }
- return draw_rect.mLeft;
-}
-
-// Draws a single text segment, reversing the color for selection if needed.
-F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, F32 x, F32 y)
-{
- const LLWString &text = mEditor.getWText();
-
- F32 right_x = x;
- if (!mStyle->isVisible())
- {
- return right_x;
- }
-
- const LLFontGL* font = mStyle->getFont();
-
- LLColor4 color = mStyle->getColor();
-
- font = mStyle->getFont();
-
- if( selection_start > seg_start )
- {
- // Draw normally
- S32 start = seg_start;
- S32 end = llmin( selection_start, seg_end );
- S32 length = end - start;
- font->render(text, start, x, y, color, LLFontGL::LEFT, LLFontGL::BOTTOM, 0, LLFontGL::NO_SHADOW, length, S32_MAX, &right_x, mEditor.allowsEmbeddedItems());
- }
- x = right_x;
-
- if( (selection_start < seg_end) && (selection_end > seg_start) )
- {
- // Draw reversed
- S32 start = llmax( selection_start, seg_start );
- S32 end = llmin( selection_end, seg_end );
- S32 length = end - start;
-
- font->render(text, start, x, y,
- LLColor4( 1.f - color.mV[0], 1.f - color.mV[1], 1.f - color.mV[2], 1.f ),
- LLFontGL::LEFT, LLFontGL::BOTTOM, 0, LLFontGL::NO_SHADOW, length, S32_MAX, &right_x, mEditor.allowsEmbeddedItems());
- }
- x = right_x;
- if( selection_end < seg_end )
- {
- // Draw normally
- S32 start = llmax( selection_end, seg_start );
- S32 end = seg_end;
- S32 length = end - start;
- font->render(text, start, x, y, color, LLFontGL::LEFT, LLFontGL::BOTTOM, 0, LLFontGL::NO_SHADOW, length, S32_MAX, &right_x, mEditor.allowsEmbeddedItems());
- }
- return right_x;
-}
-
-S32 LLNormalTextSegment::getMaxHeight() const
-{
- return mMaxHeight;
-}
-
-BOOL LLNormalTextSegment::getToolTip(std::string& msg) const
-{
- if (mToken && !mToken->getToolTip().empty())
- {
- const LLWString& wmsg = mToken->getToolTip();
- msg = wstring_to_utf8str(wmsg);
- return TRUE;
- }
- return FALSE;
-}
-
-
-S32 LLNormalTextSegment::getWidth(S32 first_char, S32 num_chars) const
-{
- LLWString text = mEditor.getWText();
- return mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars);
-}
-
-S32 LLNormalTextSegment::getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const
-{
- LLWString text = mEditor.getWText();
- return mStyle->getFont()->charFromPixelOffset(text.c_str(), mStart + start_offset,
- (F32)segment_local_x_coord,
- F32_MAX,
- num_chars,
- round);
-}
-
-S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const
-{
- LLWString text = mEditor.getWText();
- S32 num_chars = mStyle->getFont()->maxDrawableChars(text.c_str() + segment_offset + mStart,
- (F32)num_pixels,
- max_chars,
- mEditor.getWordWrap());
-
- if (num_chars == 0
- && line_offset == 0
- && max_chars > 0)
- {
- // If at the beginning of a line, and a single character won't fit, draw it anyway
- num_chars = 1;
- }
- if (mStart + segment_offset + num_chars == mEditor.getLength())
- {
- // include terminating NULL
- num_chars++;
- }
- return num_chars;
-}
-
-void LLNormalTextSegment::dump() const
-{
- llinfos << "Segment [" <<
-// mColor.mV[VX] << ", " <<
-// mColor.mV[VY] << ", " <<
-// mColor.mV[VZ] << "]\t[" <<
- mStart << ", " <<
- getEnd() << "]" <<
- llendl;
-}
-
-//
// LLInlineViewSegment
//
@@ -4979,11 +4622,15 @@ S32 LLInlineViewSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin
}
}
-void LLInlineViewSegment::updateLayout(const LLTextEditor& editor)
+void LLInlineViewSegment::updateLayout(const LLTextBase& editor)
{
- LLRect start_rect = editor.getLocalRectFromDocIndex(mStart);
- LLRect doc_rect = editor.getDocumentPanel()->getRect();
- mView->setOrigin(doc_rect.mLeft + start_rect.mLeft, doc_rect.mBottom + start_rect.mBottom);
+ const LLTextEditor *ed = dynamic_cast<const LLTextEditor *>(&editor);
+ if (ed)
+ {
+ LLRect start_rect = ed->getLocalRectFromDocIndex(mStart);
+ LLRect doc_rect = ed->getDocumentPanel()->getRect();
+ mView->setOrigin(doc_rect.mLeft + start_rect.mLeft, doc_rect.mBottom + start_rect.mBottom);
+ }
}
F32 LLInlineViewSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect)
@@ -4996,12 +4643,20 @@ S32 LLInlineViewSegment::getMaxHeight() const
return mView->getRect().getHeight();
}
-void LLInlineViewSegment::unlinkFromDocument(LLTextEditor* editor)
+void LLInlineViewSegment::unlinkFromDocument(LLTextBase* editor)
{
- editor->removeDocumentChild(mView);
+ LLTextEditor *ed = dynamic_cast<LLTextEditor *>(editor);
+ if (ed)
+ {
+ ed->removeDocumentChild(mView);
+ }
}
-void LLInlineViewSegment::linkToDocument(LLTextEditor* editor)
+void LLInlineViewSegment::linkToDocument(LLTextBase* editor)
{
- editor->addDocumentChild(mView);
+ LLTextEditor *ed = dynamic_cast<LLTextEditor *>(editor);
+ if (ed)
+ {
+ ed->addDocumentChild(mView);
+ }
}
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index 67c67d0f67..d537751130 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -44,6 +44,7 @@
#include "lleditmenuhandler.h"
#include "lldarray.h"
#include "llviewborder.h" // for params
+#include "lltextbase.h"
#include "llpreeditor.h"
#include "llcontrol.h"
@@ -55,76 +56,6 @@ class LLTextCmd;
class LLUICtrlFactory;
class LLScrollContainer;
-class LLTextSegment : public LLRefCount
-{
-public:
- LLTextSegment(S32 start, S32 end) : mStart(start), mEnd(end){};
- virtual ~LLTextSegment();
-
- virtual S32 getWidth(S32 first_char, S32 num_chars) const;
- virtual S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const;
- virtual S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const;
- virtual void updateLayout(const class LLTextEditor& editor);
- virtual F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect);
- virtual S32 getMaxHeight() const;
- virtual bool canEdit() const;
- virtual void unlinkFromDocument(class LLTextEditor* editor);
- virtual void linkToDocument(class LLTextEditor* editor);
-
- virtual void setHasMouseHover(bool hover);
- virtual const LLColor4& getColor() const;
- virtual void setColor(const LLColor4 &color);
- virtual const LLStyleSP getStyle() const;
- virtual void setStyle(const LLStyleSP &style);
- virtual void setToken( LLKeywordToken* token );
- virtual LLKeywordToken* getToken() const;
- virtual BOOL getToolTip( std::string& msg ) const;
- virtual void dump() const;
-
- S32 getStart() const { return mStart; }
- void setStart(S32 start) { mStart = start; }
- S32 getEnd() const { return mEnd; }
- void setEnd( S32 end ) { mEnd = end; }
-
-protected:
- S32 mStart;
- S32 mEnd;
-};
-
-class LLNormalTextSegment : public LLTextSegment
-{
-public:
- LLNormalTextSegment( const LLStyleSP& style, S32 start, S32 end, LLTextEditor& editor );
- LLNormalTextSegment( const LLColor4& color, S32 start, S32 end, LLTextEditor& editor, BOOL is_visible = TRUE);
-
- /*virtual*/ S32 getWidth(S32 first_char, S32 num_chars) const;
- /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const;
- /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const;
- /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect);
- /*virtual*/ S32 getMaxHeight() const;
- /*virtual*/ bool canEdit() const { return true; }
- /*virtual*/ void setHasMouseHover(bool hover) { mHasMouseHover = hover; }
- /*virtual*/ const LLColor4& getColor() const { return mStyle->getColor(); }
- /*virtual*/ void setColor(const LLColor4 &color) { mStyle->setColor(color); }
- /*virtual*/ const LLStyleSP getStyle() const { return mStyle; }
- /*virtual*/ void setStyle(const LLStyleSP &style) { mStyle = style; }
- /*virtual*/ void setToken( LLKeywordToken* token ) { mToken = token; }
- /*virtual*/ LLKeywordToken* getToken() const { return mToken; }
- /*virtual*/ BOOL getToolTip( std::string& msg ) const;
- /*virtual*/ void dump() const;
-
-protected:
- F32 drawClippedSegment(S32 seg_start, S32 seg_end, S32 selection_start, S32 selection_end, F32 x, F32 y);
-
- class LLTextEditor& mEditor;
- LLStyleSP mStyle;
- S32 mMaxHeight;
- LLKeywordToken* mToken;
- bool mHasMouseHover;
-};
-
-typedef LLPointer<LLTextSegment> LLTextSegmentPtr;
-
class LLInlineViewSegment : public LLTextSegment
{
public:
@@ -132,24 +63,22 @@ public:
~LLInlineViewSegment();
/*virtual*/ S32 getWidth(S32 first_char, S32 num_chars) const;
/*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars) const;
- /*virtual*/ void updateLayout(const class LLTextEditor& editor);
+ /*virtual*/ void updateLayout(const class LLTextBase& editor);
/*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRect& draw_rect);
/*virtuaL*/ S32 getMaxHeight() const;
/*virtual*/ bool canEdit() const { return false; }
- /*virtual*/ void unlinkFromDocument(class LLTextEditor* editor);
- /*virtual*/ void linkToDocument(class LLTextEditor* editor);
+ /*virtual*/ void unlinkFromDocument(class LLTextBase* editor);
+ /*virtual*/ void linkToDocument(class LLTextBase* editor);
private:
LLView* mView;
};
-class LLIndexSegment : public LLTextSegment
-{
-public:
- LLIndexSegment(S32 pos) : LLTextSegment(pos, pos) {}
-};
-
-class LLTextEditor : public LLUICtrl, LLEditMenuHandler, protected LLPreeditor
+class LLTextEditor :
+ public LLTextBase,
+ public LLUICtrl,
+ private LLEditMenuHandler,
+ protected LLPreeditor
{
public:
struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>
@@ -208,11 +137,8 @@ public:
}
};
- typedef std::multiset<LLTextSegmentPtr, compare_segment_end> segment_set_t;
-
virtual ~LLTextEditor();
- void setParseHTML(BOOL parsing) {mParseHTML=parsing;}
void setParseHighlights(BOOL parsing) {mParseHighlights=parsing;}
// mousehandler overrides
@@ -277,6 +203,7 @@ public:
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);
BOOL hasSelection() const { return (mSelectionStart !=mSelectionEnd); }
+ void replaceUrlLabel(const std::string &url, const std::string &label);
// Undo/redo stack
void blockUndo();
@@ -285,7 +212,6 @@ public:
virtual void makePristine();
BOOL isPristine() const;
BOOL allowsEmbeddedItems() const { return mAllowEmbeddedItems; }
- BOOL getWordWrap() { return mWordWrap; }
S32 getLength() const { return getWText().length(); }
void setReadOnly(bool read_only) { mReadOnly = read_only; }
bool getReadOnly() { return mReadOnly; }
@@ -352,13 +278,11 @@ public:
const LLUUID& getSourceID() const { return mSourceID; }
// Callbacks
- static void setURLCallbacks(void (*callback1) (const std::string& url),
- bool (*callback2) (const std::string& url),
- bool (*callback3) (const std::string& url) )
- { sURLcallback = callback1; sSecondlifeURLcallback = callback2; sSecondlifeURLcallbackRightClick = callback3;}
-
std::string getText() const;
+ // Callback for when a Url has been resolved by the server
+ void onUrlLabelUpdated(const std::string &url, const std::string &label);
+
// Getters
LLWString getWText() const;
llwchar getWChar(S32 pos) const { return getWText()[pos]; }
@@ -382,8 +306,6 @@ protected:
void startOfDoc();
void endOfDoc();
- 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 ) ;
void drawPreeditMarker();
void needsReflow() { mReflowNeeded = TRUE; }
@@ -399,16 +321,12 @@ protected:
void removeCharOrTab();
void setCursorAtLocalPos(S32 x, S32 y, bool round, bool keep_cursor_offset = false);
- S32 getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const;
+ /*virtual*/ S32 getDocIndexFromLocalCoord( S32 local_x, S32 local_y, BOOL round ) const;
void indentSelectedLines( S32 spaces );
S32 indentLine( S32 pos, S32 spaces );
void unindentLineBeforeCloseBrace();
- LLTextSegmentPtr getSegmentAtLocalPos(S32 x, S32 y);
- segment_set_t::iterator getSegIterContaining(S32 index);
- segment_set_t::const_iterator getSegIterContaining(S32 index) const;
-
void reportBadKeystroke() { make_ui_sound("UISndBadKeystroke"); }
BOOL handleNavigationKey(const KEY key, const MASK mask);
@@ -438,15 +356,9 @@ protected:
void findEmbeddedItemSegments(S32 start, S32 end);
void insertSegment(LLTextSegmentPtr segment_to_insert);
-
- virtual BOOL handleMouseUpOverSegment(S32 x, S32 y, MASK mask);
-
virtual llwchar pasteEmbeddedItem(llwchar ext_char) { return ext_char; }
- S32 findHTMLToken(const std::string &line, S32 pos, BOOL reverse) const;
- BOOL findHTML(const std::string &line, S32 *begin, S32 *end) const;
-
// Abstract inner base class representing an undoable editor command.
// Concrete sub-classes can be defined for operations such as insert, remove, etc.
// Used as arguments to the execute() method below.
@@ -538,13 +450,8 @@ protected:
S32 mLastSelectionX;
S32 mLastSelectionY;
- BOOL mParseHTML;
BOOL mParseHighlights;
- std::string mHTML;
- segment_set_t mSegments;
- LLTextSegmentPtr mHoverSegment;
-
// Scrollbar data
class DocumentPanel* mDocumentPanel;
LLScrollContainer* mScroller;
@@ -569,10 +476,10 @@ protected:
LLUIColor mLinkColor;
BOOL mReadOnly;
- BOOL mWordWrap;
BOOL mShowLineNumbers;
void updateSegments();
+ void updateLinkSegments();
private:
@@ -584,7 +491,6 @@ private:
virtual LLTextViewModel* getViewModel() const;
void reflow(S32 startpos = 0);
- void clearSegments();
void createDefaultSegment();
LLStyleSP getDefaultStyle();
S32 getEditableIndex(S32 index, bool increasing_direction);
@@ -601,9 +507,6 @@ private:
// Data
//
LLKeywords mKeywords;
- static void (*sURLcallback) (const std::string& url);
- static bool (*sSecondlifeURLcallback) (const std::string& url);
- static bool (*sSecondlifeURLcallbackRightClick) (const std::string& url);
// Concrete LLTextCmd sub-classes used by the LLTextEditor base class
class LLTextCmdInsert;
@@ -613,8 +516,6 @@ private:
S32 mMaxTextByteLength; // Maximum length mText is allowed to be in bytes
- const LLFontGL* mDefaultFont;
-
class LLViewBorder* mBorder;
BOOL mBaseDocIsPristine;
diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp
new file mode 100644
index 0000000000..3b689b93c0
--- /dev/null
+++ b/indra/llui/llurlaction.cpp
@@ -0,0 +1,137 @@
+/**
+ * @file llurlaction.cpp
+ * @author Martin Reddy
+ * @brief A set of actions that can performed on Urls
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "llurlaction.h"
+#include "llview.h"
+#include "llwindow.h"
+#include "llurlregistry.h"
+
+// global state for the callback functions
+void (*LLUrlAction::sOpenURLCallback) (const std::string& url) = NULL;
+void (*LLUrlAction::sOpenURLInternalCallback) (const std::string& url) = NULL;
+void (*LLUrlAction::sOpenURLExternalCallback) (const std::string& url) = NULL;
+bool (*LLUrlAction::sExecuteSLURLCallback) (const std::string& url) = NULL;
+
+
+void LLUrlAction::setOpenURLCallback(void (*cb) (const std::string& url))
+{
+ sOpenURLCallback = cb;
+}
+
+void LLUrlAction::setOpenURLInternalCallback(void (*cb) (const std::string& url))
+{
+ sOpenURLInternalCallback = cb;
+}
+
+void LLUrlAction::setOpenURLExternalCallback(void (*cb) (const std::string& url))
+{
+ sOpenURLExternalCallback = cb;
+}
+
+void LLUrlAction::setExecuteSLURLCallback(bool (*cb) (const std::string& url))
+{
+ sExecuteSLURLCallback = cb;
+}
+
+void LLUrlAction::openURL(std::string url)
+{
+ if (sOpenURLCallback)
+ {
+ (*sOpenURLCallback)(url);
+ }
+}
+
+void LLUrlAction::openURLInternal(std::string url)
+{
+ if (sOpenURLInternalCallback)
+ {
+ (*sOpenURLInternalCallback)(url);
+ }
+}
+
+void LLUrlAction::openURLExternal(std::string url)
+{
+ if (sOpenURLExternalCallback)
+ {
+ (*sOpenURLExternalCallback)(url);
+ }
+}
+
+void LLUrlAction::executeSLURL(std::string url)
+{
+ if (sExecuteSLURLCallback)
+ {
+ (*sExecuteSLURLCallback)(url);
+ }
+}
+
+void LLUrlAction::clickAction(std::string url)
+{
+ // Try to handle as SLURL first, then http Url
+ if ( (sExecuteSLURLCallback) && !(*sExecuteSLURLCallback)(url) )
+ {
+ if (sOpenURLCallback)
+ {
+ (*sOpenURLCallback)(url);
+ }
+ }
+}
+
+void LLUrlAction::teleportToLocation(std::string url)
+{
+ LLUrlMatch match;
+ if (LLUrlRegistry::instance().findUrl(url, match))
+ {
+ if (! match.getLocation().empty())
+ {
+ executeSLURL("secondlife:///app/teleport/" + match.getLocation());
+ }
+ }
+}
+
+void LLUrlAction::copyURLToClipboard(std::string url)
+{
+ LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(url));
+}
+
+void LLUrlAction::copyLabelToClipboard(std::string url)
+{
+ LLUrlMatch match;
+ if (LLUrlRegistry::instance().findUrl(url, match))
+ {
+ LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(match.getLabel()));
+ }
+}
+
diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h
new file mode 100644
index 0000000000..6b9d565b44
--- /dev/null
+++ b/indra/llui/llurlaction.h
@@ -0,0 +1,93 @@
+/**
+ * @file llurlaction.h
+ * @author Martin Reddy
+ * @brief A set of actions that can performed on Urls
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLURLACTION_H
+#define LL_LLURLACTION_H
+
+#include <string>
+
+///
+/// The LLUrlAction class provides a number of static functions that
+/// let you open Urls in web browsers, execute SLURLs, and copy Urls
+/// to the clipboard. Many of these functions are not available at
+/// the llui level, and must be supplied via a set of callbacks.
+///
+/// N.B. The action functions specifically do not use const ref
+/// strings so that a url parameter can be used into a boost::bind()
+/// call under situations when that input string is deallocated before
+/// the callback is executed.
+///
+class LLUrlAction
+{
+public:
+ LLUrlAction();
+
+ /// load a Url in the user's preferred web browser
+ static void openURL(std::string url);
+
+ /// load a Url in the internal Second Life web browser
+ static void openURLInternal(std::string url);
+
+ /// load a Url in the operating system's default web browser
+ static void openURLExternal(std::string url);
+
+ /// execute the given secondlife: SLURL
+ static void executeSLURL(std::string url);
+
+ /// if the Url specifies an SL location, teleport there
+ static void teleportToLocation(std::string url);
+
+ /// perform the appropriate action for left-clicking on a Url
+ static void clickAction(std::string url);
+
+ /// copy the label for a Url to the clipboard
+ static void copyLabelToClipboard(std::string url);
+
+ /// copy a Url to the clipboard
+ static void copyURLToClipboard(std::string url);
+
+ /// specify the callbacks to enable this class's functionality
+ static void setOpenURLCallback(void (*cb) (const std::string& url));
+ static void setOpenURLInternalCallback(void (*cb) (const std::string& url));
+ static void setOpenURLExternalCallback(void (*cb) (const std::string& url));
+ static void setExecuteSLURLCallback(bool (*cb) (const std::string& url));
+
+private:
+ // callbacks for operations we can perform on Urls
+ static void (*sOpenURLCallback) (const std::string& url);
+ static void (*sOpenURLInternalCallback) (const std::string& url);
+ static void (*sOpenURLExternalCallback) (const std::string& url);
+ static bool (*sExecuteSLURLCallback) (const std::string& url);
+};
+
+#endif
diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp
new file mode 100644
index 0000000000..85f9064115
--- /dev/null
+++ b/indra/llui/llurlentry.cpp
@@ -0,0 +1,546 @@
+/**
+ * @file llurlentry.cpp
+ * @author Martin Reddy
+ * @brief Describes the Url types that can be registered in LLUrlRegistry
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+#include "llurlentry.h"
+#include "lluri.h"
+#include "llcachename.h"
+#include "lltrans.h"
+
+LLUrlEntryBase::LLUrlEntryBase()
+{
+}
+
+LLUrlEntryBase::~LLUrlEntryBase()
+{
+}
+
+std::string LLUrlEntryBase::getUrl(const std::string &string)
+{
+ return escapeUrl(string);
+}
+
+std::string LLUrlEntryBase::getIDStringFromUrl(const std::string &url) const
+{
+ // return the id from a SLURL in the format /app/{cmd}/{id}/about
+ LLURI uri(url);
+ LLSD path_array = uri.pathArray();
+ if (path_array.size() == 4)
+ {
+ return path_array.get(2).asString();
+ }
+ return "";
+}
+
+std::string LLUrlEntryBase::unescapeUrl(const std::string &url) const
+{
+ return LLURI::unescape(url);
+}
+
+std::string LLUrlEntryBase::escapeUrl(const std::string &url) const
+{
+ static std::string no_escape_chars;
+ static bool initialized = false;
+ if (!initialized)
+ {
+ no_escape_chars =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-._~!$?&()*+,@:;=/%";
+
+ std::sort(no_escape_chars.begin(), no_escape_chars.end());
+ initialized = true;
+ }
+ return LLURI::escape(url, no_escape_chars, true);
+}
+
+std::string LLUrlEntryBase::getLabelFromWikiLink(const std::string &url)
+{
+ // return the label part from [http://www.example.org Label]
+ const char *text = url.c_str();
+ S32 start = 0;
+ while (! isspace(text[start]))
+ {
+ start++;
+ }
+ while (text[start] == ' ' || text[start] == '\t')
+ {
+ start++;
+ }
+ return url.substr(start, url.size()-start-1);
+}
+
+std::string LLUrlEntryBase::getUrlFromWikiLink(const std::string &string)
+{
+ // return the url part from [http://www.example.org Label]
+ const char *text = string.c_str();
+ S32 end = 0;
+ while (! isspace(text[end]))
+ {
+ end++;
+ }
+ return escapeUrl(string.substr(1, end-1));
+}
+
+void LLUrlEntryBase::addObserver(const std::string &id,
+ const std::string &url,
+ const LLUrlLabelCallback &cb)
+{
+ // add a callback to be notified when we have a label for the uuid
+ LLUrlEntryObserver observer;
+ observer.url = url;
+ observer.signal = new LLUrlLabelSignal();
+ if (observer.signal)
+ {
+ observer.signal->connect(cb);
+ mObservers.insert(std::pair<std::string, LLUrlEntryObserver>(id, observer));
+ }
+}
+
+void LLUrlEntryBase::callObservers(const std::string &id, const std::string &label)
+{
+ // notify all callbacks waiting on the given uuid
+ std::multimap<std::string, LLUrlEntryObserver>::iterator it;
+ for (it = mObservers.find(id); it != mObservers.end();)
+ {
+ // call the callback - give it the new label
+ LLUrlEntryObserver &observer = it->second;
+ (*observer.signal)(it->second.url, label);
+ // then remove the signal - we only need to call it once
+ delete observer.signal;
+ mObservers.erase(it++);
+ }
+}
+
+//
+// LLUrlEntryHTTP Describes generic http: and https: Urls
+//
+LLUrlEntryHTTP::LLUrlEntryHTTP()
+{
+ mPattern = boost::regex("https?://([-\\w\\.]+)+(:\\d+)?(:\\w+)?(@\\d+)?(@\\w+)?/?\\S*",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_http.xml";
+ mTooltip = LLTrans::getString("TooltipHttpUrl");
+ //mIcon = "gear.tga";
+}
+
+std::string LLUrlEntryHTTP::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ return unescapeUrl(url);
+}
+
+//
+// LLUrlEntryHTTP Describes generic http: and https: Urls with custom label
+// We use the wikipedia syntax of [http://www.example.org Text]
+//
+LLUrlEntryHTTPLabel::LLUrlEntryHTTPLabel()
+{
+ mPattern = boost::regex("\\[https?://\\S+[ \t]+[^\\]]+\\]",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_http.xml";
+ mTooltip = LLTrans::getString("TooltipHttpUrl");
+}
+
+std::string LLUrlEntryHTTPLabel::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ return getLabelFromWikiLink(url);
+}
+
+std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string)
+{
+ return getUrlFromWikiLink(string);
+}
+
+//
+// LLUrlEntrySLURL Describes generic http: and https: Urls
+//
+LLUrlEntrySLURL::LLUrlEntrySLURL()
+{
+ // see http://slurl.com/about.php for details on the SLURL format
+ mPattern = boost::regex("http://slurl.com/secondlife/\\S+/?(\\d+)?/?(\\d+)?/?(\\d+)?/?\\S*",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_slurl.xml";
+ mTooltip = LLTrans::getString("TooltipSLURL");
+}
+
+std::string LLUrlEntrySLURL::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ //
+ // we handle SLURLs in the following formats:
+ // - http://slurl.com/secondlife/Place/X/Y/Z
+ // - http://slurl.com/secondlife/Place/X/Y
+ // - http://slurl.com/secondlife/Place/X
+ // - http://slurl.com/secondlife/Place
+ //
+ LLURI uri(url);
+ LLSD path_array = uri.pathArray();
+ S32 path_parts = path_array.size();
+ if (path_parts == 5)
+ {
+ // handle slurl with (X,Y,Z) coordinates
+ std::string location = unescapeUrl(path_array[path_parts-4]);
+ std::string x = path_array[path_parts-3];
+ std::string y = path_array[path_parts-2];
+ std::string z = path_array[path_parts-1];
+ return location + " (" + x + "," + y + "," + z + ")";
+ }
+ else if (path_parts == 4)
+ {
+ // handle slurl with (X,Y) coordinates
+ std::string location = unescapeUrl(path_array[path_parts-3]);
+ std::string x = path_array[path_parts-2];
+ std::string y = path_array[path_parts-1];
+ return location + " (" + x + "," + y + ")";
+ }
+ else if (path_parts == 3)
+ {
+ // handle slurl with (X) coordinate
+ std::string location = unescapeUrl(path_array[path_parts-2]);
+ std::string x = path_array[path_parts-1];
+ return location + " (" + x + ")";
+ }
+ else if (path_parts == 2)
+ {
+ // handle slurl with no coordinates
+ std::string location = unescapeUrl(path_array[path_parts-1]);
+ return location;
+ }
+
+ return url;
+}
+
+std::string LLUrlEntrySLURL::getLocation(const std::string &url) const
+{
+ // return the part of the Url after slurl.com/secondlife/
+ const std::string search_string = "secondlife";
+ size_t pos = url.find(search_string);
+ if (pos == std::string::npos)
+ {
+ return "";
+ }
+
+ pos += search_string.size() + 1;
+ return url.substr(pos, url.size() - pos);
+}
+
+//
+// LLUrlEntryAgent Describes a Second Life agent Url, e.g.,
+// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about
+//
+LLUrlEntryAgent::LLUrlEntryAgent()
+{
+ mPattern = boost::regex("secondlife:///app/agent/[\\da-f-]+/about",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_agent.xml";
+ mTooltip = LLTrans::getString("TooltipAgentUrl");
+}
+
+void LLUrlEntryAgent::onAgentNameReceived(const LLUUID& id,
+ const std::string& first,
+ const std::string& last,
+ BOOL is_group)
+{
+ // received the agent name from the server - tell our observers
+ callObservers(id.asString(), first + " " + last);
+}
+
+std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ std::string id = getIDStringFromUrl(url);
+ if (gCacheName && ! id.empty())
+ {
+ LLUUID uuid(id);
+ std::string full_name;
+ if (gCacheName->getFullName(uuid, full_name))
+ {
+ return full_name;
+ }
+ else
+ {
+ gCacheName->get(uuid, FALSE, boost::bind(&LLUrlEntryAgent::onAgentNameReceived, this, _1, _2, _3, _4));
+ addObserver(id, url, cb);
+ }
+ }
+
+ return unescapeUrl(url);
+}
+
+//
+// LLUrlEntryGroup Describes a Second Life group Url, e.g.,
+// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about
+//
+LLUrlEntryGroup::LLUrlEntryGroup()
+{
+ mPattern = boost::regex("secondlife:///app/group/[\\da-f-]+/about",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_group.xml";
+ mTooltip = LLTrans::getString("TooltipGroupUrl");
+}
+
+void LLUrlEntryGroup::onGroupNameReceived(const LLUUID& id,
+ const std::string& first,
+ const std::string& last,
+ BOOL is_group)
+{
+ // received the group name from the server - tell our observers
+ callObservers(id.asString(), first);
+}
+
+std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ std::string id = getIDStringFromUrl(url);
+ if (gCacheName && ! id.empty())
+ {
+ LLUUID uuid(id);
+ std::string group_name;
+ if (gCacheName->getGroupName(uuid, group_name))
+ {
+ return group_name;
+ }
+ else
+ {
+ gCacheName->get(uuid, TRUE, boost::bind(&LLUrlEntryGroup::onGroupNameReceived, this, _1, _2, _3, _4));
+ addObserver(id, url, cb);
+ }
+ }
+
+ return unescapeUrl(url);
+}
+
+///
+/// LLUrlEntryEvent Describes a Second Life event Url, e.g.,
+/// secondlife:///app/event/700727/about
+///
+LLUrlEntryEvent::LLUrlEntryEvent()
+{
+ mPattern = boost::regex("secondlife:///app/event/[\\da-f-]+/about",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_event.xml";
+ mTooltip = LLTrans::getString("TooltipEventUrl");
+}
+
+std::string LLUrlEntryEvent::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ return unescapeUrl(url);
+}
+
+///
+/// LLUrlEntryClassified Describes a Second Life classified Url, e.g.,
+/// secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about
+///
+LLUrlEntryClassified::LLUrlEntryClassified()
+{
+ mPattern = boost::regex("secondlife:///app/classified/[\\da-f-]+/about",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_classified.xml";
+ mTooltip = LLTrans::getString("TooltipClassifiedUrl");
+}
+
+std::string LLUrlEntryClassified::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ return unescapeUrl(url);
+}
+
+///
+/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g.,
+/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about
+///
+LLUrlEntryParcel::LLUrlEntryParcel()
+{
+ mPattern = boost::regex("secondlife:///app/parcel/[\\da-f-]+/about",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_parcel.xml";
+ mTooltip = LLTrans::getString("TooltipParcelUrl");
+}
+
+std::string LLUrlEntryParcel::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ return unescapeUrl(url);
+}
+
+//
+// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g.,
+// secondlife:///app/teleport/Ahern/50/50/50/
+//
+LLUrlEntryTeleport::LLUrlEntryTeleport()
+{
+ mPattern = boost::regex("secondlife:///app/teleport/\\S+(/\\d+)?(/\\d+)?(/\\d+)?/?\\S*",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_teleport.xml";
+ mTooltip = LLTrans::getString("TooltipTeleportUrl");
+}
+
+std::string LLUrlEntryTeleport::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ //
+ // we handle teleport SLURLs in the following formats:
+ // - secondlife:///app/teleport/Place/X/Y/Z
+ // - secondlife:///app/teleport/Place/X/Y
+ // - secondlife:///app/teleport/Place/X
+ // - secondlife:///app/teleport/Place
+ //
+ LLURI uri(url);
+ LLSD path_array = uri.pathArray();
+ S32 path_parts = path_array.size();
+ if (path_parts == 6)
+ {
+ // handle teleport url with (X,Y,Z) coordinates
+ std::string location = unescapeUrl(path_array[path_parts-4]);
+ std::string x = path_array[path_parts-3];
+ std::string y = path_array[path_parts-2];
+ std::string z = path_array[path_parts-1];
+ return "Teleport to " + location + " (" + x + "," + y + "," + z + ")";
+ }
+ else if (path_parts == 5)
+ {
+ // handle teleport url with (X,Y) coordinates
+ std::string location = unescapeUrl(path_array[path_parts-3]);
+ std::string x = path_array[path_parts-2];
+ std::string y = path_array[path_parts-1];
+ return "Teleport to " + location + " (" + x + "," + y + ")";
+ }
+ else if (path_parts == 4)
+ {
+ // handle teleport url with (X) coordinate only
+ std::string location = unescapeUrl(path_array[path_parts-2]);
+ std::string x = path_array[path_parts-1];
+ return "Teleport to " + location + " (" + x + ")";
+ }
+ else if (path_parts == 3)
+ {
+ // handle teleport url with no coordinates
+ std::string location = unescapeUrl(path_array[path_parts-1]);
+ return "Teleport to " + location;
+ }
+
+ return url;
+}
+
+std::string LLUrlEntryTeleport::getLocation(const std::string &url) const
+{
+ // return the part of the Url after ///app/teleport
+ const std::string search_string = "teleport";
+ size_t pos = url.find(search_string);
+ if (pos == std::string::npos)
+ {
+ return "";
+ }
+
+ pos += search_string.size() + 1;
+ return url.substr(pos, url.size() - pos);
+}
+
+///
+/// LLUrlEntryObjectIM Describes a Second Life object instant msg Url, e.g.,
+/// secondlife:///app/objectim/<sessionid>
+///
+LLUrlEntryObjectIM::LLUrlEntryObjectIM()
+{
+ mPattern = boost::regex("secondlife:///app/objectim/[\\da-f-]+\\??\\S*",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_objectim.xml";
+ mTooltip = LLTrans::getString("TooltipObjectIMUrl");
+}
+
+std::string LLUrlEntryObjectIM::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ LLURI uri(url);
+ LLSD params = uri.queryMap();
+ if (params.has("name"))
+ {
+ // look for a ?name=<obj-name> param in the url
+ // and use that as the label if present.
+ std::string name = params.get("name");
+ LLStringUtil::trim(name);
+ if (name.empty())
+ {
+ name = LLTrans::getString("Unnamed");
+ }
+ return name;
+ }
+
+ return unescapeUrl(url);
+}
+
+std::string LLUrlEntryObjectIM::getLocation(const std::string &url) const
+{
+ LLURI uri(url);
+ LLSD params = uri.queryMap();
+ if (params.has("slurl"))
+ {
+ return params.get("slurl");
+ }
+
+ return "";
+}
+
+//
+// LLUrlEntrySL Describes a generic SLURL, e.g., a Url that starts
+// with secondlife:// (used as a catch-all for cases not matched above)
+//
+LLUrlEntrySL::LLUrlEntrySL()
+{
+ mPattern = boost::regex("secondlife://(\\w+)?(:\\d+)?/\\S+",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_slapp.xml";
+ mTooltip = LLTrans::getString("TooltipSLAPP");
+}
+
+std::string LLUrlEntrySL::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ return unescapeUrl(url);
+}
+
+//
+// LLUrlEntrySLLabel Describes a generic SLURL, e.g., a Url that starts
+/// with secondlife:// with the ability to specify a custom label.
+//
+LLUrlEntrySLLabel::LLUrlEntrySLLabel()
+{
+ mPattern = boost::regex("\\[secondlife://\\S+[ \t]+[^\\]]+\\]",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_slapp.xml";
+ mTooltip = LLTrans::getString("TooltipSLAPP");
+}
+
+std::string LLUrlEntrySLLabel::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ return getLabelFromWikiLink(url);
+}
+
+std::string LLUrlEntrySLLabel::getUrl(const std::string &string)
+{
+ return getUrlFromWikiLink(string);
+}
+
diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h
new file mode 100644
index 0000000000..f3e76dbec0
--- /dev/null
+++ b/indra/llui/llurlentry.h
@@ -0,0 +1,252 @@
+/**
+ * @file llurlentry.h
+ * @author Martin Reddy
+ * @brief Describes the Url types that can be registered in LLUrlRegistry
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLURLENTRY_H
+#define LL_LLURLENTRY_H
+
+#include "lluuid.h"
+
+#include <boost/signals2.hpp>
+#include <boost/regex.hpp>
+#include <string>
+#include <map>
+
+typedef boost::signals2::signal<void (const std::string& url,
+ const std::string& label)> LLUrlLabelSignal;
+typedef LLUrlLabelSignal::slot_type LLUrlLabelCallback;
+
+///
+/// LLUrlEntryBase is the base class of all Url types registered in the
+/// LLUrlRegistry. Each derived classes provides a regular expression
+/// to match the Url type (e.g., http://... or secondlife://...) along
+/// with an optional icon to display next to instances of the Url in
+/// a text display and a XUI file to use for any context menu popup.
+/// Functions are also provided to compute an appropriate label and
+/// tooltip/status bar text for the Url.
+///
+/// Some derived classes of LLUrlEntryBase may wish to compute an
+/// appropriate label for a Url by asking the server for information.
+/// You must therefore provide a callback method, so that you can be
+/// notified when an updated label has been received from the server.
+/// This label should then be used to replace any previous label
+/// that you received from getLabel() for the Url in question.
+///
+class LLUrlEntryBase
+{
+public:
+ LLUrlEntryBase();
+ virtual ~LLUrlEntryBase();
+
+ /// Return the regex pattern that matches this Url
+ boost::regex getPattern() const { return mPattern; }
+
+ /// Return the url from a string that matched the regex
+ virtual std::string getUrl(const std::string &string);
+
+ /// Given a matched Url, return a label for the Url
+ virtual std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) { return url; }
+
+ /// Return an icon that can be displayed next to Urls of this type
+ const std::string &getIcon() const { return mIcon; }
+
+ /// Given a matched Url, return a tooltip string for the hyperlink
+ std::string getTooltip() const { return mTooltip; }
+
+ /// Return the name of a XUI file containing the context menu items
+ const std::string getMenuName() const { return mMenuName; }
+
+ /// Return the name of a SL location described by this Url, if any
+ virtual std::string getLocation(const std::string &url) const { return ""; }
+
+protected:
+ std::string getIDStringFromUrl(const std::string &url) const;
+ std::string escapeUrl(const std::string &url) const;
+ std::string unescapeUrl(const std::string &url) const;
+ std::string getLabelFromWikiLink(const std::string &url);
+ std::string getUrlFromWikiLink(const std::string &string);
+ void addObserver(const std::string &id, const std::string &url, const LLUrlLabelCallback &cb);
+ void callObservers(const std::string &id, const std::string &label);
+
+ typedef struct {
+ std::string url;
+ LLUrlLabelSignal *signal;
+ } LLUrlEntryObserver;
+
+ boost::regex mPattern;
+ std::string mIcon;
+ std::string mMenuName;
+ std::string mTooltip;
+ std::multimap<std::string, LLUrlEntryObserver> mObservers;
+};
+
+///
+/// LLUrlEntryHTTP Describes generic http: and https: Urls
+///
+class LLUrlEntryHTTP : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryHTTP();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+};
+
+///
+/// LLUrlEntryHTTPLabel Describes generic http: and https: Urls with custom labels
+///
+class LLUrlEntryHTTPLabel : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryHTTPLabel();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+ /*virtual*/ std::string getUrl(const std::string &string);
+};
+
+///
+/// LLUrlEntrySLURL Describes http://slurl.com/... Urls
+///
+class LLUrlEntrySLURL : public LLUrlEntryBase
+{
+public:
+ LLUrlEntrySLURL();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+ /*virtual*/ std::string getLocation(const std::string &url) const;
+};
+
+///
+/// LLUrlEntryAgent Describes a Second Life agent Url, e.g.,
+/// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about
+///
+class LLUrlEntryAgent : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryAgent();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+private:
+ void onAgentNameReceived(const LLUUID& id, const std::string& first,
+ const std::string& last, BOOL is_group);
+};
+
+///
+/// LLUrlEntryGroup Describes a Second Life group Url, e.g.,
+/// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about
+///
+class LLUrlEntryGroup : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryGroup();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+private:
+ void onGroupNameReceived(const LLUUID& id, const std::string& first,
+ const std::string& last, BOOL is_group);
+};
+
+///
+/// LLUrlEntryEvent Describes a Second Life event Url, e.g.,
+/// secondlife:///app/event/700727/about
+///
+class LLUrlEntryEvent : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryEvent();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+};
+
+///
+/// LLUrlEntryClassified Describes a Second Life classified Url, e.g.,
+/// secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about
+///
+class LLUrlEntryClassified : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryClassified();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+};
+
+///
+/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g.,
+/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about
+///
+class LLUrlEntryParcel : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryParcel();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+};
+
+///
+/// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g.,
+/// secondlife:///app/teleport/Ahern/50/50/50/
+///
+class LLUrlEntryTeleport : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryTeleport();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+ /*virtual*/ std::string getLocation(const std::string &url) const;
+};
+
+///
+/// LLUrlEntryObjectIM Describes a Second Life object instant msg Url, e.g.,
+/// secondlife:///app/objectim/<sessionid>?name=Foo
+///
+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;
+};
+
+///
+/// LLUrlEntrySL Describes a generic SLURL, e.g., a Url that starts
+/// with secondlife:// (used as a catch-all for cases not matched above)
+///
+class LLUrlEntrySL : public LLUrlEntryBase
+{
+public:
+ LLUrlEntrySL();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+};
+
+///
+/// LLUrlEntrySLLabel Describes a generic SLURL, e.g., a Url that starts
+/// with secondlife:// with the ability to specify a custom label.
+///
+class LLUrlEntrySLLabel : public LLUrlEntryBase
+{
+public:
+ LLUrlEntrySLLabel();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+ /*virtual*/ std::string getUrl(const std::string &string);
+};
+
+#endif
diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp
new file mode 100644
index 0000000000..7eec4c4a65
--- /dev/null
+++ b/indra/llui/llurlmatch.cpp
@@ -0,0 +1,61 @@
+/**
+ * @file llurlmatch.cpp
+ * @author Martin Reddy
+ * @brief Specifies a matched Url in a string, as returned by LLUrlRegistry
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+#include "llurlmatch.h"
+
+LLUrlMatch::LLUrlMatch() :
+ mStart(0),
+ mEnd(0),
+ mUrl(""),
+ mLabel(""),
+ mTooltip(""),
+ mIcon(""),
+ mMenuName("")
+{
+}
+
+void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url,
+ const std::string &label, const std::string &tooltip,
+ const std::string &icon, const std::string &menu,
+ const std::string &location)
+{
+ mStart = start;
+ mEnd = end;
+ mUrl = url;
+ mLabel = label;
+ mTooltip = tooltip;
+ mIcon = icon;
+ mMenuName = menu;
+ mLocation = location;
+}
diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h
new file mode 100644
index 0000000000..0711e41443
--- /dev/null
+++ b/indra/llui/llurlmatch.h
@@ -0,0 +1,98 @@
+/**
+ * @file llurlmatch.h
+ * @author Martin Reddy
+ * @brief Specifies a matched Url in a string, as returned by LLUrlRegistry
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLURLMATCH_H
+#define LL_LLURLMATCH_H
+
+#include "linden_common.h"
+
+#include <string>
+#include <vector>
+
+///
+/// LLUrlMatch describes a single Url that was matched within a string by
+/// the LLUrlRegistry::findUrl() method. It includes the actual Url that
+/// was matched along with its first/last character offset in the string.
+/// An alternate label is also provided for creating a hyperlink, as well
+/// as tooltip/status text, an icon, and a XUI file for a context menu
+/// that can be used in a popup for a Url (e.g., Open, Copy URL, etc.)
+///
+class LLUrlMatch
+{
+public:
+ LLUrlMatch();
+
+ /// return true if this object does not contain a valid Url match yet
+ bool empty() const { return mUrl.empty(); }
+
+ /// return the offset in the string for the first character of the Url
+ U32 getStart() const { return mStart; }
+
+ /// return the offset in the string for the last character of the Url
+ U32 getEnd() const { return mEnd; }
+
+ /// return the Url that has been matched in the input string
+ const std::string &getUrl() const { return mUrl; }
+
+ /// return a label that can be used for the display of this Url
+ const std::string &getLabel() const { return mLabel; }
+
+ /// return a message that could be displayed in a tooltip or status bar
+ const std::string &getTooltip() const { return mTooltip; }
+
+ /// return the filename for an icon that can be displayed next to this Url
+ const std::string &getIcon() const { return mIcon; }
+
+ /// Return the name of a XUI file containing the context menu items
+ const std::string getMenuName() const { return mMenuName; }
+
+ /// return the SL location that this Url describes, or "" if none.
+ const std::string &getLocation() const { return mLocation; }
+
+ /// Change the contents of this match object (used by LLUrlRegistry)
+ void setValues(U32 start, U32 end, const std::string &url, const std::string &label,
+ const std::string &tooltip, const std::string &icon,
+ const std::string &menu, const std::string &location);
+
+private:
+ U32 mStart;
+ U32 mEnd;
+ std::string mUrl;
+ std::string mLabel;
+ std::string mTooltip;
+ std::string mIcon;
+ std::string mMenuName;
+ std::string mLocation;
+};
+
+#endif
diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp
new file mode 100644
index 0000000000..938375ad13
--- /dev/null
+++ b/indra/llui/llurlregistry.cpp
@@ -0,0 +1,165 @@
+/**
+ * @file llurlregistry.cpp
+ * @author Martin Reddy
+ * @brief Contains a set of Url types that can be matched in a string
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+#include "llurlregistry.h"
+
+#include <boost/regex.hpp>
+
+// default dummy callback that ignores any label updates from the server
+void LLUrlRegistryNullCallback(const std::string &url, const std::string &label)
+{
+}
+
+LLUrlRegistry::LLUrlRegistry()
+{
+ // Urls are matched in the order that they were registered
+ registerUrl(new LLUrlEntrySLURL());
+ registerUrl(new LLUrlEntryHTTP());
+ registerUrl(new LLUrlEntryHTTPLabel());
+ registerUrl(new LLUrlEntryAgent());
+ registerUrl(new LLUrlEntryGroup());
+ registerUrl(new LLUrlEntryEvent());
+ registerUrl(new LLUrlEntryClassified());
+ registerUrl(new LLUrlEntryParcel());
+ registerUrl(new LLUrlEntryTeleport());
+ registerUrl(new LLUrlEntryObjectIM());
+ registerUrl(new LLUrlEntrySL());
+ registerUrl(new LLUrlEntrySLLabel());
+}
+
+LLUrlRegistry::~LLUrlRegistry()
+{
+ // free all of the LLUrlEntryBase objects we are holding
+ std::vector<LLUrlEntryBase *>::iterator it;
+ for (it = mUrlEntry.begin(); it != mUrlEntry.end(); ++it)
+ {
+ delete *it;
+ }
+}
+
+void LLUrlRegistry::registerUrl(LLUrlEntryBase *url)
+{
+ if (url)
+ {
+ mUrlEntry.push_back(url);
+ }
+}
+
+static bool matchRegex(const char *text, boost::regex regex, U32 &start, U32 &end)
+{
+ boost::cmatch result;
+ bool found;
+
+ // regex_search can potentially throw an exception, so check for it
+ try
+ {
+ found = boost::regex_search(text, result, regex);
+ }
+ catch (std::runtime_error &)
+ {
+ return false;
+ }
+
+ if (! found)
+ {
+ return false;
+ }
+
+ // return the first/last character offset for the matched substring
+ start = static_cast<U32>(result[0].first - text);
+ end = static_cast<U32>(result[0].second - text) - 1;
+
+ // we allow certain punctuation to terminate a Url but not match it,
+ // e.g., "http://foo.com/." should just match "http://foo.com/"
+ if (text[end] == '.' || text[end] == ',')
+ {
+ end--;
+ }
+ // ignore a terminating ')' when Url contains no matching '('
+ // see DEV-19842 for details
+ else if (text[end] == ')' && std::string(text+start, end-start).find('(') == std::string::npos)
+ {
+ end--;
+ }
+
+ return true;
+}
+
+bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb)
+{
+ // test for the trivial case of no text and get out fast
+ if (text.empty())
+ {
+ return false;
+ }
+
+ // find the first matching regex from all url entries in the registry
+ U32 match_start = 0, match_end = 0;
+ LLUrlEntryBase *match_entry = NULL;
+
+ std::vector<LLUrlEntryBase *>::iterator it;
+ for (it = mUrlEntry.begin(); it != mUrlEntry.end(); ++it)
+ {
+ LLUrlEntryBase *url_entry = *it;
+
+ U32 start = 0, end = 0;
+ if (matchRegex(text.c_str(), url_entry->getPattern(), start, end))
+ {
+ // does this match occur in the string before any other match
+ if (start < match_start || match_entry == NULL)
+ {
+ match_start = start;
+ match_end = end;
+ match_entry = url_entry;
+ }
+ }
+ }
+
+ // did we find a match? if so, return its details in the match object
+ if (match_entry)
+ {
+ // fill in the LLUrlMatch object and return it
+ std::string url = text.substr(match_start, match_end - match_start + 1);
+ match.setValues(match_start, match_end,
+ match_entry->getUrl(url),
+ match_entry->getLabel(url, cb),
+ match_entry->getTooltip(),
+ match_entry->getIcon(),
+ match_entry->getMenuName(),
+ match_entry->getLocation(url));
+ return true;
+ }
+
+ return false;
+}
diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h
new file mode 100644
index 0000000000..84b033036c
--- /dev/null
+++ b/indra/llui/llurlregistry.h
@@ -0,0 +1,87 @@
+/**
+ * @file llurlregistry.h
+ * @author Martin Reddy
+ * @brief Contains a set of Url types that can be matched in a string
+ *
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * Second Life Viewer Source Code
+ * The source code in this file ("Source Code") is provided by Linden Lab
+ * to you under the terms of the GNU General Public License, version 2.0
+ * ("GPL"), unless you have obtained a separate licensing agreement
+ * ("Other License"), formally executed by you and Linden Lab. Terms of
+ * the GPL can be found in doc/GPL-license.txt in this distribution, or
+ * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
+ *
+ * There are special exceptions to the terms and conditions of the GPL as
+ * it is applied to this Source Code. View the full text of the exception
+ * in the file doc/FLOSS-exception.txt in this software distribution, or
+ * online at
+ * http://secondlifegrid.net/programs/open_source/licensing/flossexception
+ *
+ * By copying, modifying or distributing this software, you acknowledge
+ * that you have read and understood your obligations described above,
+ * and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLURLREGISTRY_H
+#define LL_LLURLREGISTRY_H
+
+#include "llurlentry.h"
+#include "llurlmatch.h"
+#include "llsingleton.h"
+
+#include <string>
+#include <vector>
+#include <map>
+
+/// This default callback for findUrl() simply ignores any label updates
+void LLUrlRegistryNullCallback(const std::string &url, const std::string &label);
+
+///
+/// LLUrlRegistry is a singleton that contains a set of Url types that
+/// can be matched in string. E.g., http:// or secondlife:// Urls.
+///
+/// Clients call the findUrl() method on a string to locate the first
+/// occurence of a supported Urls in that string. If findUrl() returns
+/// true, the LLUrlMatch object will be updated to describe the Url
+/// that was matched, including a label that can be used to hyperlink
+/// the Url, an icon to display next to the Url, and a XUI menu that
+/// can be used as a popup context menu for that Url.
+///
+/// New Url types can be added to the registry with the registerUrl
+/// method. E.g., to add support for a new secondlife:///app/ Url.
+///
+/// Computing the label for a Url could involve a roundtrip request
+/// to the server (e.g., to find the actual agent or group name).
+/// As such, you can provide a callback method that will get invoked
+/// when a new label is available for one of your matched Urls.
+///
+class LLUrlRegistry : public LLSingleton<LLUrlRegistry>
+{
+public:
+ ~LLUrlRegistry();
+
+ /// add a new Url handler to the registry (will be freed on destruction)
+ void registerUrl(LLUrlEntryBase *url);
+
+ /// get the next Url in an input string, starting at a given character offset
+ /// your callback is invoked if the matched Url's label changes in the future
+ bool findUrl(const std::string &text, LLUrlMatch &match,
+ const LLUrlLabelCallback &cb = &LLUrlRegistryNullCallback);
+
+private:
+ LLUrlRegistry();
+ friend class LLSingleton<LLUrlRegistry>;
+
+ std::vector<LLUrlEntryBase *> mUrlEntry;
+};
+
+#endif
diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp
new file mode 100644
index 0000000000..05bd5d8bb3
--- /dev/null
+++ b/indra/llui/tests/llurlentry_stub.cpp
@@ -0,0 +1,64 @@
+/**
+ * @file llurlentry_stub.cpp
+ * @author Martin Reddy
+ * @brief Stub implementations for LLUrlEntry unit test dependencies
+ *
+ * $LicenseInfo:firstyear=2009&license=internal$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+ * this source code is governed by the Linden Lab Source Code Disclosure
+ * Agreement ("Agreement") previously entered between you and Linden
+ * Lab. By accessing, using, copying, modifying or distributing this
+ * software, you acknowledge that you have been informed of your
+ * obligations under the Agreement and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "llstring.h"
+#include "llfile.h"
+#include "llcachename.h"
+#include "lluuid.h"
+
+#include <string>
+
+//
+// Stub implementation for LLCacheName
+//
+BOOL LLCacheName::getFullName(const LLUUID& id, std::string& fullname)
+{
+ fullname = "Lynx Linden";
+ return TRUE;
+}
+
+BOOL LLCacheName::getGroupName(const LLUUID& id, std::string& group)
+{
+ group = "My Group";
+ return TRUE;
+}
+
+boost::signals2::connection LLCacheName::get(const LLUUID& id, BOOL is_group, const LLCacheNameCallback& callback)
+{
+ return boost::signals2::connection();
+}
+
+LLCacheName* gCacheName = NULL;
+
+//
+// Stub implementation for LLTrans
+//
+class LLTrans
+{
+public:
+ static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args);
+};
+
+std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args)
+{
+ return std::string();
+}
diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp
new file mode 100644
index 0000000000..f8e6aa65d0
--- /dev/null
+++ b/indra/llui/tests/llurlentry_test.cpp
@@ -0,0 +1,535 @@
+/**
+ * @file llurlentry_test.cpp
+ * @author Martin Reddy
+ * @brief Unit tests for LLUrlEntry objects
+ *
+ * $LicenseInfo:firstyear=2009&license=internal$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+ * this source code is governed by the Linden Lab Source Code Disclosure
+ * Agreement ("Agreement") previously entered between you and Linden
+ * Lab. By accessing, using, copying, modifying or distributing this
+ * software, you acknowledge that you have been informed of your
+ * obligations under the Agreement and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+#include "../llurlentry.h"
+#include "llurlentry_stub.cpp"
+#include "lltut.h"
+
+#include <boost/regex.hpp>
+
+namespace tut
+{
+ struct LLUrlEntryData
+ {
+ };
+
+ typedef test_group<LLUrlEntryData> factory;
+ typedef factory::object object;
+}
+
+namespace
+{
+ tut::factory tf("LLUrlEntry");
+}
+
+namespace tut
+{
+ void testRegex(const std::string &testname, boost::regex regex,
+ const char *text, const std::string &expected)
+ {
+ std::string url = "";
+ boost::cmatch result;
+ bool found = boost::regex_search(text, result, regex);
+ if (found)
+ {
+ S32 start = static_cast<U32>(result[0].first - text);
+ S32 end = static_cast<U32>(result[0].second - text);
+ url = std::string(text+start, end-start);
+ }
+ ensure_equals(testname, url, expected);
+ }
+
+ template<> template<>
+ void object::test<1>()
+ {
+ //
+ // test LLUrlEntryHTTP - standard http Urls
+ //
+ LLUrlEntryHTTP url;
+ boost::regex r = url.getPattern();
+
+ testRegex("no valid url", r,
+ "htp://slurl.com/",
+ "");
+
+ testRegex("simple http (1)", r,
+ "http://slurl.com/",
+ "http://slurl.com/");
+
+ testRegex("simple http (2)", r,
+ "http://slurl.com",
+ "http://slurl.com");
+
+ testRegex("simple http (3)", r,
+ "http://slurl.com/about.php",
+ "http://slurl.com/about.php");
+
+ testRegex("simple https", r,
+ "https://slurl.com/about.php",
+ "https://slurl.com/about.php");
+
+ testRegex("http in text (1)", r,
+ "XX http://slurl.com/ XX",
+ "http://slurl.com/");
+
+ testRegex("http in text (2)", r,
+ "XX http://slurl.com/about.php XX",
+ "http://slurl.com/about.php");
+
+ testRegex("https in text", r,
+ "XX https://slurl.com/about.php XX",
+ "https://slurl.com/about.php");
+
+ testRegex("two http urls", r,
+ "XX http://slurl.com/about.php http://secondlife.com/ XX",
+ "http://slurl.com/about.php");
+
+ testRegex("http url with port and username", r,
+ "XX http://nobody@slurl.com:80/about.php http://secondlife.com/ XX",
+ "http://nobody@slurl.com:80/about.php");
+
+ testRegex("http url with port, username, and query string", r,
+ "XX http://nobody@slurl.com:80/about.php?title=hi%20there http://secondlife.com/ XX",
+ "http://nobody@slurl.com:80/about.php?title=hi%20there");
+
+ // note: terminating commas will be removed by LLUrlRegistry:findUrl()
+ testRegex("http url with commas in middle and terminating", r,
+ "XX http://slurl.com/?title=Hi,There, XX",
+ "http://slurl.com/?title=Hi,There,");
+
+ // note: terminating periods will be removed by LLUrlRegistry:findUrl()
+ testRegex("http url with periods in middle and terminating", r,
+ "XX http://slurl.com/index.php. XX",
+ "http://slurl.com/index.php.");
+
+ // DEV-19842: Closing parenthesis ")" breaks urls
+ testRegex("http url with brackets (1)", r,
+ "XX http://en.wikipedia.org/wiki/JIRA_(software) XX",
+ "http://en.wikipedia.org/wiki/JIRA_(software)");
+
+ // DEV-19842: Closing parenthesis ")" breaks urls
+ testRegex("http url with brackets (2)", r,
+ "XX http://jira.secondlife.com/secure/attachment/17990/eggy+avs+in+1.21.0+(93713)+public+nightly.jpg XX",
+ "http://jira.secondlife.com/secure/attachment/17990/eggy+avs+in+1.21.0+(93713)+public+nightly.jpg");
+
+ // DEV-10353: URLs in chat log terminated incorrectly when newline in chat
+ testRegex("http url with newlines", r,
+ "XX\nhttp://www.secondlife.com/\nXX",
+ "http://www.secondlife.com/");
+ }
+
+ template<> template<>
+ void object::test<2>()
+ {
+ //
+ // test LLUrlEntryHTTPLabel - wiki-style http Urls with labels
+ //
+ LLUrlEntryHTTPLabel url;
+ boost::regex r = url.getPattern();
+
+ testRegex("invalid wiki url [1]", r,
+ "[http://www.example.org]",
+ "");
+
+ testRegex("invalid wiki url [2]", r,
+ "[http://www.example.org",
+ "");
+
+ testRegex("invalid wiki url [3]", r,
+ "[http://www.example.org Label",
+ "");
+
+ testRegex("example.org with label (spaces)", r,
+ "[http://www.example.org Text]",
+ "[http://www.example.org Text]");
+
+ testRegex("example.org with label (tabs)", r,
+ "[http://www.example.org\t Text]",
+ "[http://www.example.org\t Text]");
+
+ testRegex("SL http URL with label", r,
+ "[http://www.secondlife.com/ Second Life]",
+ "[http://www.secondlife.com/ Second Life]");
+
+ testRegex("SL https URL with label", r,
+ "XXX [https://www.secondlife.com/ Second Life] YYY",
+ "[https://www.secondlife.com/ Second Life]");
+
+ testRegex("SL http URL with label", r,
+ "[http://www.secondlife.com/?test=Hi%20There Second Life]",
+ "[http://www.secondlife.com/?test=Hi%20There Second Life]");
+ }
+
+ template<> template<>
+ void object::test<3>()
+ {
+ //
+ // test LLUrlEntrySLURL - second life URLs
+ //
+ LLUrlEntrySLURL url;
+ boost::regex r = url.getPattern();
+
+ testRegex("no valid slurl [1]", r,
+ "htp://slurl.com/secondlife/Ahern/50/50/50/",
+ "");
+
+ testRegex("no valid slurl [2]", r,
+ "http://slurl.com/secondlife/",
+ "");
+
+ testRegex("no valid slurl [3]", r,
+ "hhtp://slurl.com/secondlife/Ahern/50/FOO/50/",
+ "");
+
+ testRegex("Ahern (50,50,50) [1]", r,
+ "http://slurl.com/secondlife/Ahern/50/50/50/",
+ "http://slurl.com/secondlife/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50,50) [2]", r,
+ "XXX http://slurl.com/secondlife/Ahern/50/50/50/ XXX",
+ "http://slurl.com/secondlife/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50,50) [3]", r,
+ "XXX http://slurl.com/secondlife/Ahern/50/50/50 XXX",
+ "http://slurl.com/secondlife/Ahern/50/50/50");
+
+ testRegex("Ahern (50,50,50) multicase", r,
+ "XXX http://SLUrl.com/SecondLife/Ahern/50/50/50/ XXX",
+ "http://SLUrl.com/SecondLife/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50) [1]", r,
+ "XXX http://slurl.com/secondlife/Ahern/50/50/ XXX",
+ "http://slurl.com/secondlife/Ahern/50/50/");
+
+ testRegex("Ahern (50,50) [2]", r,
+ "XXX http://slurl.com/secondlife/Ahern/50/50 XXX",
+ "http://slurl.com/secondlife/Ahern/50/50");
+
+ testRegex("Ahern (50)", r,
+ "XXX http://slurl.com/secondlife/Ahern/50 XXX",
+ "http://slurl.com/secondlife/Ahern/50");
+
+ testRegex("Ahern", r,
+ "XXX http://slurl.com/secondlife/Ahern/ XXX",
+ "http://slurl.com/secondlife/Ahern/");
+
+ testRegex("Ahern SLURL with title", r,
+ "XXX http://slurl.com/secondlife/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE! XXX",
+ "http://slurl.com/secondlife/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE!");
+
+ testRegex("Ahern SLURL with msg", r,
+ "XXX http://slurl.com/secondlife/Ahern/50/50/50/?msg=Your%20text%20here. XXX",
+ "http://slurl.com/secondlife/Ahern/50/50/50/?msg=Your%20text%20here.");
+
+ // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat
+ testRegex("SLURL with brackets", r,
+ "XXX http://slurl.com/secondlife/Burning%20Life%20(Hyper)/27/210/30 XXX",
+ "http://slurl.com/secondlife/Burning%20Life%20(Hyper)/27/210/30");
+
+ // DEV-35459: SLURLs and teleport Links not parsed properly
+ testRegex("SLURL with quote", r,
+ "XXX http://slurl.com/secondlife/A'ksha%20Oasis/41/166/701 XXX",
+ "http://slurl.com/secondlife/A'ksha%20Oasis/41/166/701");
+ }
+
+ template<> template<>
+ void object::test<4>()
+ {
+ //
+ // test LLUrlEntryAgent - secondlife://app/agent Urls
+ //
+ LLUrlEntryAgent url;
+ boost::regex r = url.getPattern();
+
+ testRegex("Invalid Agent Url", r,
+ "secondlife:///app/agent/0e346d8b-4433-4d66-XXXX-fd37083abc4c/about",
+ "");
+
+ testRegex("Agent Url ", r,
+ "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about",
+ "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about");
+
+ testRegex("Agent Url in text", r,
+ "XXX secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about XXX",
+ "secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about");
+
+ testRegex("Agent Url multicase", r,
+ "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About XXX",
+ "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/About");
+ }
+
+ template<> template<>
+ void object::test<5>()
+ {
+ //
+ // test LLUrlEntryGroup - secondlife://app/group Urls
+ //
+ LLUrlEntryGroup url;
+ boost::regex r = url.getPattern();
+
+ testRegex("Invalid Group Url", r,
+ "secondlife:///app/group/00005ff3-4044-c79f-XXXX-fb28ae0df991/about",
+ "");
+
+ testRegex("Group Url ", r,
+ "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about",
+ "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about");
+
+ testRegex("Group Url in text", r,
+ "XXX secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about XXX",
+ "secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about");
+
+ testRegex("Group Url multicase", r,
+ "XXX secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About XXX",
+ "secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About");
+ }
+
+ template<> template<>
+ void object::test<6>()
+ {
+ //
+ // test LLUrlEntryEvent - secondlife://app/event Urls
+ //
+ LLUrlEntryEvent url;
+ boost::regex r = url.getPattern();
+
+ testRegex("Invalid Event Url", r,
+ "secondlife:///app/event/FOO/about",
+ "");
+
+ testRegex("Event Url ", r,
+ "secondlife:///app/event/700727/about",
+ "secondlife:///app/event/700727/about");
+
+ testRegex("Event Url in text", r,
+ "XXX secondlife:///app/event/700727/about XXX",
+ "secondlife:///app/event/700727/about");
+
+ testRegex("Event Url multicase", r,
+ "XXX secondlife:///APP/Event/700727/about XXX",
+ "secondlife:///APP/Event/700727/about");
+ }
+
+ template<> template<>
+ void object::test<7>()
+ {
+ //
+ // test LLUrlEntryClassified - secondlife://app/classified Urls
+ //
+ LLUrlEntryClassified url;
+ boost::regex r = url.getPattern();
+
+ testRegex("Invalid Classified Url", r,
+ "secondlife:///app/classified/00128854-XXXX-5649-7ca6-5dfaa7514ab2/about",
+ "");
+
+ testRegex("Classified Url ", r,
+ "secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about",
+ "secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about");
+
+ testRegex("Classified Url in text", r,
+ "XXX secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about XXX",
+ "secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about");
+
+ testRegex("Classified Url multicase", r,
+ "XXX secondlife:///APP/Classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/About XXX",
+ "secondlife:///APP/Classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/About");
+ }
+
+ template<> template<>
+ void object::test<8>()
+ {
+ //
+ // test LLUrlEntryParcel - secondlife://app/parcel Urls
+ //
+ LLUrlEntryParcel url;
+ boost::regex r = url.getPattern();
+
+ testRegex("Invalid Classified Url", r,
+ "secondlife:///app/parcel/0000060e-4b39-e00b-XXXX-d98b1934e3a8/about",
+ "");
+
+ testRegex("Classified Url ", r,
+ "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about",
+ "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about");
+
+ testRegex("Classified Url in text", r,
+ "XXX secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about XXX",
+ "secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about");
+
+ testRegex("Classified Url multicase", r,
+ "XXX secondlife:///APP/Parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/About XXX",
+ "secondlife:///APP/Parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/About");
+ }
+ template<> template<>
+ void object::test<9>()
+ {
+ //
+ // test LLUrlEntryTeleport - secondlife://app/teleport URLs
+ //
+ LLUrlEntryTeleport url;
+ boost::regex r = url.getPattern();
+
+ testRegex("no valid teleport [1]", r,
+ "http://slurl.com/secondlife/Ahern/50/50/50/",
+ "");
+
+ testRegex("no valid teleport [2]", r,
+ "secondlife:///app/teleport/",
+ "");
+
+ testRegex("no valid teleport [3]", r,
+ "second-life:///app/teleport/Ahern/50/50/50/",
+ "");
+
+ testRegex("no valid teleport [3]", r,
+ "hhtp://slurl.com/secondlife/Ahern/50/FOO/50/",
+ "");
+
+ testRegex("Ahern (50,50,50) [1]", r,
+ "secondlife:///app/teleport/Ahern/50/50/50/",
+ "secondlife:///app/teleport/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50,50) [2]", r,
+ "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX",
+ "secondlife:///app/teleport/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50,50) [3]", r,
+ "XXX secondlife:///app/teleport/Ahern/50/50/50 XXX",
+ "secondlife:///app/teleport/Ahern/50/50/50");
+
+ testRegex("Ahern (50,50,50) multicase", r,
+ "XXX secondlife:///app/teleport/Ahern/50/50/50/ XXX",
+ "secondlife:///app/teleport/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50) [1]", r,
+ "XXX secondlife:///app/teleport/Ahern/50/50/ XXX",
+ "secondlife:///app/teleport/Ahern/50/50/");
+
+ testRegex("Ahern (50,50) [2]", r,
+ "XXX secondlife:///app/teleport/Ahern/50/50 XXX",
+ "secondlife:///app/teleport/Ahern/50/50");
+
+ testRegex("Ahern (50)", r,
+ "XXX secondlife:///app/teleport/Ahern/50 XXX",
+ "secondlife:///app/teleport/Ahern/50");
+
+ testRegex("Ahern", r,
+ "XXX secondlife:///app/teleport/Ahern/ XXX",
+ "secondlife:///app/teleport/Ahern/");
+
+ testRegex("Ahern teleport with title", r,
+ "XXX secondlife:///app/teleport/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE! XXX",
+ "secondlife:///app/teleport/Ahern/50/50/50/?title=YOUR%20TITLE%20HERE!");
+
+ testRegex("Ahern teleport with msg", r,
+ "XXX secondlife:///app/teleport/Ahern/50/50/50/?msg=Your%20text%20here. XXX",
+ "secondlife:///app/teleport/Ahern/50/50/50/?msg=Your%20text%20here.");
+
+ // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat
+ testRegex("Teleport with brackets", r,
+ "XXX secondlife:///app/teleport/Burning%20Life%20(Hyper)/27/210/30 XXX",
+ "secondlife:///app/teleport/Burning%20Life%20(Hyper)/27/210/30");
+
+ // DEV-35459: SLURLs and teleport Links not parsed properly
+ testRegex("Teleport url with quote", r,
+ "XXX secondlife:///app/teleport/A'ksha%20Oasis/41/166/701 XXX",
+ "secondlife:///app/teleport/A'ksha%20Oasis/41/166/701");
+ }
+
+ template<> template<>
+ void object::test<10>()
+ {
+ //
+ // test LLUrlEntrySL - general secondlife:// URLs
+ //
+ LLUrlEntrySL url;
+ boost::regex r = url.getPattern();
+
+ testRegex("no valid slapp [1]", r,
+ "http:///app/",
+ "");
+
+ testRegex("valid slapp [1]", r,
+ "secondlife:///app/",
+ "secondlife:///app/");
+
+ testRegex("valid slapp [2]", r,
+ "secondlife:///app/teleport/Ahern/50/50/50/",
+ "secondlife:///app/teleport/Ahern/50/50/50/");
+
+ testRegex("valid slapp [3]", r,
+ "secondlife:///app/foo",
+ "secondlife:///app/foo");
+
+ testRegex("valid slapp [4]", r,
+ "secondlife:///APP/foo?title=Hi%20There",
+ "secondlife:///APP/foo?title=Hi%20There");
+
+ testRegex("valid slapp [5]", r,
+ "secondlife://host/app/",
+ "secondlife://host/app/");
+
+ testRegex("valid slapp [6]", r,
+ "secondlife://host:8080/foo/bar",
+ "secondlife://host:8080/foo/bar");
+ }
+
+ template<> template<>
+ void object::test<11>()
+ {
+ //
+ // test LLUrlEntrySLLabel - general secondlife:// URLs with labels
+ //
+ LLUrlEntrySLLabel url;
+ boost::regex r = url.getPattern();
+
+ testRegex("invalid wiki url [1]", r,
+ "[secondlife:///app/]",
+ "");
+
+ testRegex("invalid wiki url [2]", r,
+ "[secondlife:///app/",
+ "");
+
+ testRegex("invalid wiki url [3]", r,
+ "[secondlife:///app/ Label",
+ "");
+
+ testRegex("agent slurl with label (spaces)", r,
+ "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about Text]",
+ "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about Text]");
+
+ testRegex("agent slurl with label (tabs)", r,
+ "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about\t Text]",
+ "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about\t Text]");
+
+ testRegex("agent slurl with label", r,
+ "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about FirstName LastName]",
+ "[secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about FirstName LastName]");
+
+ testRegex("teleport slurl with label", r,
+ "XXX [secondlife:///app/teleport/Ahern/50/50/50/ Teleport to Ahern] YYY",
+ "[secondlife:///app/teleport/Ahern/50/50/50/ Teleport to Ahern]");
+ }
+}
diff --git a/indra/llui/tests/llurlmatch_test.cpp b/indra/llui/tests/llurlmatch_test.cpp
new file mode 100644
index 0000000000..fcf8f4d62f
--- /dev/null
+++ b/indra/llui/tests/llurlmatch_test.cpp
@@ -0,0 +1,177 @@
+/**
+ * @file llurlmatch_test.cpp
+ * @author Martin Reddy
+ * @brief Unit tests for LLUrlMatch
+ *
+ * $LicenseInfo:firstyear=2009&license=internal$
+ *
+ * Copyright (c) 2009, Linden Research, Inc.
+ *
+ * The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+ * this source code is governed by the Linden Lab Source Code Disclosure
+ * Agreement ("Agreement") previously entered between you and Linden
+ * Lab. By accessing, using, copying, modifying or distributing this
+ * software, you acknowledge that you have been informed of your
+ * obligations under the Agreement and agree to abide by those obligations.
+ *
+ * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+ * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+ * COMPLETENESS OR PERFORMANCE.
+ * $/LicenseInfo$
+ */
+
+#include "../llurlmatch.h"
+#include "lltut.h"
+
+namespace tut
+{
+ struct LLUrlMatchData
+ {
+ };
+
+ typedef test_group<LLUrlMatchData> factory;
+ typedef factory::object object;
+}
+
+namespace
+{
+ tut::factory tf("LLUrlMatch");
+}
+
+namespace tut
+{
+ template<> template<>
+ void object::test<1>()
+ {
+ //
+ // test the empty() method
+ //
+ LLUrlMatch match;
+ ensure("empty()", match.empty());
+
+ match.setValues(0, 1, "http://secondlife.com", "Second Life", "", "", "", "");
+ ensure("! empty()", ! match.empty());
+ }
+
+ template<> template<>
+ void object::test<2>()
+ {
+ //
+ // test the getStart() method
+ //
+ LLUrlMatch match;
+ ensure_equals("getStart() == 0", match.getStart(), 0);
+
+ match.setValues(10, 20, "", "", "", "", "", "");
+ ensure_equals("getStart() == 10", match.getStart(), 10);
+ }
+
+ template<> template<>
+ void object::test<3>()
+ {
+ //
+ // test the getEnd() method
+ //
+ LLUrlMatch match;
+ ensure_equals("getEnd() == 0", match.getEnd(), 0);
+
+ match.setValues(10, 20, "", "", "", "", "", "");
+ ensure_equals("getEnd() == 20", match.getEnd(), 20);
+ }
+
+ template<> template<>
+ void object::test<4>()
+ {
+ //
+ // test the getUrl() method
+ //
+ LLUrlMatch match;
+ ensure_equals("getUrl() == ''", match.getUrl(), "");
+
+ match.setValues(10, 20, "http://slurl.com/", "", "", "", "", "");
+ ensure_equals("getUrl() == 'http://slurl.com/'", match.getUrl(), "http://slurl.com/");
+
+ match.setValues(10, 20, "", "", "", "", "", "");
+ ensure_equals("getUrl() == '' (2)", match.getUrl(), "");
+ }
+
+ template<> template<>
+ void object::test<5>()
+ {
+ //
+ // test the getLabel() method
+ //
+ LLUrlMatch match;
+ ensure_equals("getLabel() == ''", match.getLabel(), "");
+
+ match.setValues(10, 20, "", "Label", "", "", "", "");
+ ensure_equals("getLabel() == 'Label'", match.getLabel(), "Label");
+
+ match.setValues(10, 20, "", "", "", "", "", "");
+ ensure_equals("getLabel() == '' (2)", match.getLabel(), "");
+ }
+
+ template<> template<>
+ void object::test<6>()
+ {
+ //
+ // test the getTooltip() method
+ //
+ LLUrlMatch match;
+ ensure_equals("getTooltip() == ''", match.getTooltip(), "");
+
+ match.setValues(10, 20, "", "", "Info", "", "", "");
+ ensure_equals("getTooltip() == 'Info'", match.getTooltip(), "Info");
+
+ match.setValues(10, 20, "", "", "", "", "", "");
+ ensure_equals("getTooltip() == '' (2)", match.getTooltip(), "");
+ }
+
+ template<> template<>
+ void object::test<7>()
+ {
+ //
+ // test the getIcon() method
+ //
+ LLUrlMatch match;
+ ensure_equals("getIcon() == ''", match.getIcon(), "");
+
+ match.setValues(10, 20, "", "", "", "Icon", "", "");
+ ensure_equals("getIcon() == 'Icon'", match.getIcon(), "Icon");
+
+ match.setValues(10, 20, "", "", "", "", "", "");
+ ensure_equals("getIcon() == '' (2)", match.getIcon(), "");
+ }
+
+ template<> template<>
+ void object::test<8>()
+ {
+ //
+ // test the getMenuName() method
+ //
+ LLUrlMatch match;
+ ensure("getMenuName() empty", match.getMenuName().empty());
+
+ match.setValues(10, 20, "", "", "", "Icon", "xui_file.xml", "");
+ ensure_equals("getMenuName() == \"xui_file.xml\"", match.getMenuName(), "xui_file.xml");
+
+ match.setValues(10, 20, "", "", "", "", "", "");
+ ensure("getMenuName() empty (2)", match.getMenuName().empty());
+ }
+
+ template<> template<>
+ void object::test<9>()
+ {
+ //
+ // test the getLocation() method
+ //
+ LLUrlMatch match;
+ ensure("getLocation() empty", match.getLocation().empty());
+
+ match.setValues(10, 20, "", "", "", "Icon", "xui_file.xml", "Paris");
+ ensure_equals("getLocation() == \"Paris\"", match.getLocation(), "Paris");
+
+ match.setValues(10, 20, "", "", "", "", "", "");
+ ensure("getLocation() empty (2)", match.getLocation().empty());
+ }
+}
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index de4f7ab091..3d7465f2cb 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -80,6 +80,7 @@
// Linden library includes
#include "llmemory.h"
+#include "llurlaction.h"
// Third party library includes
#include <boost/bind.hpp>
@@ -685,9 +686,14 @@ bool LLAppViewer::init()
LLTransUtil::parseLanguageStrings("language_settings.xml");
LLWeb::initClass(); // do this after LLUI
- LLTextEditor::setURLCallbacks(&LLWeb::loadURL,
- &LLURLDispatcher::dispatchFromTextEditor,
- &LLURLDispatcher::dispatchFromTextEditor);
+ // Provide the text fields with callbacks for opening Urls
+ LLUrlAction::setOpenURLCallback(&LLWeb::loadURL);
+ LLUrlAction::setOpenURLInternalCallback(&LLWeb::loadURLInternal);
+ LLUrlAction::setOpenURLExternalCallback(&LLWeb::loadURLExternal);
+ LLUrlAction::setExecuteSLURLCallback(&LLURLDispatcher::dispatchFromTextEditor);
+
+ // Set the link color for any Urls in text fields
+ LLTextBase::setLinkColor( LLUIColorTable::instance().getColor("HTMLLinkColor") );
// Load translations for tooltips
LLFloater::initClass();
diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp
index 080d540f4a..e0322e26b9 100644
--- a/indra/newview/llavatarlist.cpp
+++ b/indra/newview/llavatarlist.cpp
@@ -62,6 +62,9 @@ LLAvatarList::LLAvatarList(const Params& p)
{
setCommitOnSelectionChange(TRUE); // there's no such param in LLScrollListCtrl::Params
+ // display a context menu appropriate for a list of avatar names
+ setContextMenu(LLScrollListCtrl::MENU_AVATAR);
+
// "volume" column
{
LLScrollListColumn::Params col_params;
diff --git a/indra/newview/llchatmsgbox.cpp b/indra/newview/llchatmsgbox.cpp
index fb5ab8ec5a..e6398dd47a 100644
--- a/indra/newview/llchatmsgbox.cpp
+++ b/indra/newview/llchatmsgbox.cpp
@@ -1,10 +1,11 @@
/**
* @file llchatmsgbox.cpp
+ * @author Martin Reddy
* @brief chat history text box, able to show array of strings with separator
*
- * $LicenseInfo:firstyear=2004&license=viewergpl$
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
*
- * Copyright (c) 2004-2009, Linden Research, Inc.
+ * Copyright (c) 2009, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -30,361 +31,96 @@
* $/LicenseInfo$
*/
-
#include "llviewerprecompiledheaders.h"
#include "llchatmsgbox.h"
#include "llwindow.h"
-#include "llfocusmgr.h"
-
-static LLDefaultChildRegistry::Register<LLChatMsgBox> r("text_chat");
-LLChatMsgBox::Params::Params()
-: text_color("text_color"),
- highlight_on_hover("hover", false),
- border_visible("border_visible", false),
- border_drop_shadow_visible("border_drop_shadow_visible", false),
- bg_visible("bg_visible", false),
- use_ellipses("use_ellipses"),
- word_wrap("word_wrap", false),
- hover_color("hover_color"),
- disabled_color("disabled_color"),
- background_color("background_color"),
- border_color("border_color"),
- line_spacing("line_spacing", 4),
- block_spacing("block_spacing",10),
- text("text"),
- font_shadow("font_shadow", LLFontGL::NO_SHADOW)
-{}
-LLChatMsgBox::LLChatMsgBox(const LLChatMsgBox::Params& p)
-: LLUICtrl(p),
- mFontGL(p.font),
- mHoverActive( p.highlight_on_hover ),
- mHasHover( FALSE ),
- mBackgroundVisible( p.bg_visible ),
- mBorderVisible( p.border_visible ),
- mShadowType( p.font_shadow ),
- mBorderDropShadowVisible( p.border_drop_shadow_visible ),
- mUseEllipses( p.use_ellipses ),
- mVAlign( LLFontGL::TOP ),
- mClickedCallback(NULL),
- mTextColor(p.text_color()),
- mDisabledColor(p.disabled_color()),
- mBackgroundColor(p.background_color()),
- mBorderColor(p.border_color()),
- mHoverColor(p.hover_color()),
- mHAlign(p.font_halign),
- mLineSpacing(p.line_spacing),
- mBlockSpasing(p.block_spacing),
- mWordWrap( p.word_wrap ),
- mFontStyle(LLFontGL::getStyleFromString(p.font.style))
-{
- setText( p.text() );
-}
+static LLDefaultChildRegistry::Register<LLChatMsgBox> r("text_chat");
-BOOL LLChatMsgBox::handleMouseDown(S32 x, S32 y, MASK mask)
+LLChatMsgBox::Params::Params() :
+ block_spacing("block_spacing", 10)
{
- BOOL handled = FALSE;
-
- // HACK: Only do this if there actually is a click callback, so that
- // overly large text boxes in the older UI won't start eating clicks.
- if (mClickedCallback)
- {
- handled = TRUE;
-
- // Route future Mouse messages here preemptively. (Release on mouse up.)
- gFocusMgr.setMouseCapture( this );
-
- if (getSoundFlags() & MOUSE_DOWN)
- {
- make_ui_sound("UISndClick");
- }
- }
-
- return handled;
+ line_spacing = 4;
}
-BOOL LLChatMsgBox::handleMouseUp(S32 x, S32 y, MASK mask)
-{
- BOOL handled = FALSE;
-
- // We only handle the click if the click both started and ended within us
-
- // HACK: Only do this if there actually is a click callback, so that
- // overly large text boxes in the older UI won't start eating clicks.
- if (mClickedCallback
- && hasMouseCapture())
- {
- handled = TRUE;
-
- // Release the mouse
- gFocusMgr.setMouseCapture( NULL );
-
- if (getSoundFlags() & MOUSE_UP)
- {
- make_ui_sound("UISndClickRelease");
- }
-
- // DO THIS AT THE VERY END to allow the button to be destroyed as a result of being clicked.
- // If mouseup in the widget, it's been clicked
- if (mClickedCallback)
- {
- mClickedCallback();
- }
- }
-
- return handled;
-}
+LLChatMsgBox::LLChatMsgBox(const Params& p) :
+ LLTextBox(p),
+ mBlockSpacing(p.block_spacing)
+{}
-BOOL LLChatMsgBox::handleHover(S32 x, S32 y, MASK mask)
+void LLChatMsgBox::addText( const LLStringExplicit& text )
{
- BOOL handled = LLView::handleHover(x,y,mask);
- if(mHoverActive)
+ LLWString t = mText.getWString();
+ if (! t.empty())
{
- mHasHover = TRUE; // This should be set every frame during a hover.
- getWindow()->setCursor(UI_CURSOR_ARROW);
+ t += '\n';
}
-
- return (handled || mHasHover);
-}
-
-void LLChatMsgBox::addText( const LLStringExplicit& text )
-{
- boost::shared_ptr<text_block> t(new text_block());
- t->text = wrapText(text);
- setLineLengths(*t);
- mTextStrings.push_back(t);
+ t += getWrappedText(text);
+ LLTextBox::setText(wstring_to_utf8str(t));
+ mSeparatorOffset.push_back(getLength());
}
void LLChatMsgBox::setText(const LLStringExplicit& text)
{
- mTextStrings.clear();
-
+ mSeparatorOffset.clear();
+ mText.clear();
addText(text);
-
-}
-
-void LLChatMsgBox::resetLineLengths()
-{
- for(std::vector< boost::shared_ptr<text_block> >::iterator it = mTextStrings.begin();
- it!=mTextStrings.end();++it)
- {
- boost::shared_ptr<text_block> tblock = *it;
- setLineLengths(*tblock);
- }
-}
-
-void LLChatMsgBox::setLineLengths(text_block& t)
-{
- t.lines.clear();
-
- std::string::size_type cur = 0;
- std::string::size_type len = t.text.length();
-
- while (cur < len)
- {
- std::string::size_type end = t.text.getWString().find('\n', cur);
- std::string::size_type runLen;
-
- if (end == std::string::npos)
- {
- runLen = len - cur;
- cur = len;
- }
- else
- {
- runLen = end - cur;
- cur = end + 1; // skip the new line character
- }
-
- t.lines.push_back( (S32)runLen );
- }
-}
-
-std::string LLChatMsgBox::wrapText(const LLStringExplicit& in_text, F32 max_width)
-{
- if (max_width < 0.0f)
- {
- max_width = (F32)getRect().getWidth();
- }
-
- LLWString wtext = utf8str_to_wstring(in_text);
- LLWString final_wtext;
-
- LLWString::size_type cur = 0;;
- LLWString::size_type len = wtext.size();
- while (cur < len)
- {
- LLWString::size_type end = wtext.find('\n', cur);
- if (end == LLWString::npos)
- {
- end = len;
- }
-
- LLWString::size_type runLen = end - cur;
- if (runLen > 0)
- {
- LLWString run(wtext, cur, runLen);
- LLWString::size_type useLen =
- mFontGL->maxDrawableChars(run.c_str(), max_width, runLen, TRUE);
-
- final_wtext.append(wtext, cur, useLen);
- cur += useLen;
- // not enough room to add any more characters
- if (useLen == 0) break;
- }
-
- if (cur < len)
- {
- if (wtext[cur] == '\n')
- cur += 1;
-
- // There is no need to to cut line ending symbols found in origin string, see EXT-702.
- final_wtext += '\n';
- }
- }
-
- std::string final_text = wstring_to_utf8str(final_wtext);
- return final_text;
}
-S32 LLChatMsgBox::getTextLinesNum()
-{
- S32 num_lines = 0;
- for(std::vector< boost::shared_ptr<text_block> >::iterator it = mTextStrings.begin();
- it!=mTextStrings.end();++it)
- {
- boost::shared_ptr<text_block> tblock = *it;
- num_lines+=tblock->lines.size();
- }
-
- if( num_lines < 1 )
- {
- num_lines = 1;
- }
-
- return num_lines;
+void LLChatMsgBox::setValue(const LLSD& value )
+{
+ setText(value.asString());
}
S32 LLChatMsgBox::getTextPixelHeight()
{
+ S32 num_blocks = mSeparatorOffset.size();
S32 num_lines = getTextLinesNum();
- return (S32)(num_lines * mFontGL->getLineHeight() + (num_lines-1)*mLineSpacing + mBlockSpasing*(mTextStrings.size()-1) + 2*mLineSpacing);//some extra space
-}
-
-void LLChatMsgBox::setValue(const LLSD& value )
-{
- setText(value.asString());
+ return (S32)(num_lines * mDefaultFont->getLineHeight() + \
+ (num_lines-1) * mLineSpacing + \
+ (num_blocks-1) * mBlockSpacing + \
+ 2 * mLineSpacing);
}
-
-void LLChatMsgBox::draw()
+S32 LLChatMsgBox::getTextLinesNum()
{
- if (mBorderVisible)
- {
- gl_rect_2d_offset_local(getLocalRect(), 2, FALSE);
- }
-
- if( mBorderDropShadowVisible )
- {
- static LLUICachedControl<LLColor4> color_drop_shadow ("ColorDropShadow", *(new LLColor4));
- static LLUICachedControl<S32> drop_shadow_tooltip ("DropShadowTooltip", 0);
- gl_drop_shadow(0, getRect().getHeight(), getRect().getWidth(), 0,
- color_drop_shadow, drop_shadow_tooltip);
- }
-
- if (mBackgroundVisible)
- {
- LLRect r( 0, getRect().getHeight(), getRect().getWidth(), 0 );
- gl_rect_2d( r, mBackgroundColor.get() );
- }
-
- S32 text_x = 0;
- switch( mHAlign )
- {
- case LLFontGL::LEFT:
- break;
- case LLFontGL::HCENTER:
- text_x = getRect().getWidth() / 2;
- break;
- case LLFontGL::RIGHT:
- text_x = getRect().getWidth() ;
- break;
- }
-
- S32 text_y = getRect().getHeight() ;
-
- if ( getEnabled() )
- {
- if(mHasHover)
- {
- drawText( text_x, text_y, mHoverColor.get() );
- }
- else
- {
- drawText( text_x, text_y, mTextColor.get() );
- }
- }
- else
- {
- drawText( text_x, text_y, mDisabledColor.get() );
- }
-
- if (sDebugRects)
+ S32 num_lines = getLineCount();
+ if (num_lines < 1)
{
- drawDebugRect();
+ num_lines = 1;
}
-
- //// *HACK: also draw debug rectangles around currently-being-edited LLView, and any elements that are being highlighted by GUI preview code (see LLFloaterUIPreview)
- //std::set<LLView*>::iterator iter = std::find(sPreviewHighlightedElements.begin(), sPreviewHighlightedElements.end(), this);
- //if ((sEditingUI && this == sEditingUIView) || (iter != sPreviewHighlightedElements.end() && sDrawPreviewHighlights))
- //{
- // drawDebugRect();
- //}
-
- mHasHover = FALSE; // This is reset every frame.
-}
-
-void LLChatMsgBox::reshape(S32 width, S32 height, BOOL called_from_parent)
-{
- // reparse line lengths
- LLView::reshape(width, height, called_from_parent);
- resetLineLengths();
+
+ return num_lines;
}
-void LLChatMsgBox::drawText( S32 x, S32 y, const LLColor4& color )
+void LLChatMsgBox::drawText(S32 x, S32 y, const LLWString &text, const LLColor4 &color)
{
+ S32 start = 0;
S32 width = getRect().getWidth()-10;
-
- for(std::vector< boost::shared_ptr<text_block> >::iterator it = mTextStrings.begin();
- it!=mTextStrings.end();++it)
+ // iterate through each block of text that has been added
+ y -= mLineSpacing;
+ for (std::vector<S32>::iterator it = mSeparatorOffset.begin(); true ;)
{
- boost::shared_ptr<text_block> tblock = *it;
+ // display the text for this block
+ S32 num_chars = *it - start;
+ LLWString text = mDisplayText.substr(start, num_chars);
+ LLTextBox::drawText(x, y, text, color);
- S32 cur_pos = 0;
- for (std::vector<S32>::iterator iter = tblock->lines.begin();
- iter != tblock->lines.end(); ++iter)
+ // exit the loop if this is the last text block
+ start += num_chars + 1; // skip the newline
+ if (++it == mSeparatorOffset.end())
{
- S32 line_length = *iter;
- mFontGL->render(tblock->text, cur_pos, (F32)x, (F32)y, color,
- mHAlign, mVAlign,
- mFontStyle,
- mShadowType,
- line_length, getRect().getWidth(), NULL, mUseEllipses );
- cur_pos += line_length + 1;
- y -= llfloor(mFontGL->getLineHeight()) + mLineSpacing;
-
- }
- std::vector< boost::shared_ptr<text_block> >::iterator next = it;
- ++next;
- if(next == mTextStrings.end())
break;
- //separator
- gl_line_2d(5,y-mBlockSpasing/2,width,y-mBlockSpasing/2,LLColor4::grey);
- y-=mBlockSpasing;
- }
+ }
+ // output a separator line between blocks
+ S32 num_lines = std::count(text.begin(), text.end(), '\n') + 1;
+ y -= num_lines * (llfloor(mDefaultFont->getLineHeight()) + mLineSpacing);
+ S32 sep_y = y - mBlockSpacing/2 + mLineSpacing/2;
+ gl_line_2d(5, sep_y, width, sep_y, LLColor4::grey);
+ y -= mBlockSpacing;
+ }
}
-
diff --git a/indra/newview/llchatmsgbox.h b/indra/newview/llchatmsgbox.h
index 61035499c7..b81b740bdc 100644
--- a/indra/newview/llchatmsgbox.h
+++ b/indra/newview/llchatmsgbox.h
@@ -1,10 +1,11 @@
/**
* @file llchatmsgbox.h
+ * @author Martin Reddy
* @brief chat history text box, able to show array of strings with separator
*
- * $LicenseInfo:firstyear=2004&license=viewergpl$
+ * $LicenseInfo:firstyear=2009&license=viewergpl$
*
- * Copyright (c) 2004-2009, Linden Research, Inc.
+ * Copyright (c) 2009, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -33,127 +34,45 @@
#ifndef LL_LLCHATMSGBOX_H
#define LL_LLCHATMSGBOX_H
-
+#include "lltextbox.h"
#include "lluictrl.h"
#include "v4color.h"
#include "llstring.h"
-#include "lluistring.h"
-
-class LLChatMsgBox
-: public LLUICtrl
+///
+/// LLChatMsgBox provides a text box with support for multiple blocks
+/// of text that can be added incrementally. Each block of text is
+/// visual separated from the previous block (e.g., with a horizontal
+/// line).
+///
+class LLChatMsgBox :
+ public LLTextBox
{
-protected:
- struct text_block
- {
- LLUIString text;
- std::vector<S32> lines;
- };
public:
- typedef boost::function<void (void)> callback_t;
-
- struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>
+ struct Params : public LLInitParam::Block<Params, LLTextBox::Params>
{
- Optional<std::string> text;
-
- Optional<bool> highlight_on_hover,
- border_visible,
- border_drop_shadow_visible,
- bg_visible,
- use_ellipses,
- word_wrap;
-
- Optional<LLFontGL::ShadowType> font_shadow;
-
- Optional<LLUIColor> text_color,
- hover_color,
- disabled_color,
- background_color,
- border_color;
-
- Optional<S32> line_spacing;
-
- Optional<S32> block_spacing;
+ Optional<S32> block_spacing;
Params();
};
+
protected:
LLChatMsgBox(const Params&);
friend class LLUICtrlFactory;
-public:
- virtual void draw();
- virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
- virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask);
- virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask);
- virtual BOOL handleHover(S32 x, S32 y, MASK mask);
-
- void setColor( const LLColor4& c ) { mTextColor = c; }
- void setDisabledColor( const LLColor4& c) { mDisabledColor = c; }
- void setBackgroundColor( const LLColor4& c) { mBackgroundColor = c; }
- void setBorderColor( const LLColor4& c) { mBorderColor = c; }
-
- void setHoverColor( const LLColor4& c ) { mHoverColor = c; }
- void setHoverActive( BOOL active ) { mHoverActive = active; }
-
- void setText( const LLStringExplicit& text );
- void addText( const LLStringExplicit& text );
-
- void setUseEllipses( BOOL use_ellipses ) { mUseEllipses = use_ellipses; }
+public:
+ void setText(const LLStringExplicit &text);
+ void addText(const LLStringExplicit &text);
- void setBackgroundVisible(BOOL visible) { mBackgroundVisible = visible; }
- void setBorderVisible(BOOL visible) { mBorderVisible = visible; }
- void setBorderDropshadowVisible(BOOL visible){ mBorderDropShadowVisible = visible; }
- void setRightAlign() { mHAlign = LLFontGL::RIGHT; }
- void setHAlign( LLFontGL::HAlign align ) { mHAlign = align; }
- void setClickedCallback( boost::function<void (void*)> cb, void* userdata = NULL ){ mClickedCallback = boost::bind(cb, userdata); } // mouse down and up within button
-
- const LLFontGL* getFont() const { return mFontGL; }
-
- S32 getTextPixelHeight();
- S32 getTextLinesNum();
-
- virtual void setValue(const LLSD& value );
-
+ S32 getTextPixelHeight();
+ S32 getTextLinesNum();
+ /*virtual*/ void setValue(const LLSD &value);
+ /*virtual*/ void drawText(S32 x, S32 y, const LLWString &text, const LLColor4 &color);
private:
- std::string wrapText (const LLStringExplicit& in_text, F32 max_width = -1.0);
-
- void setLineLengths (text_block& t);
- void resetLineLengths ();
- void drawText (S32 x, S32 y, const LLColor4& color );
-
- const LLFontGL* mFontGL;
- LLUIColor mTextColor;
- LLUIColor mDisabledColor;
- LLUIColor mBackgroundColor;
- LLUIColor mBorderColor;
- LLUIColor mHoverColor;
-
- BOOL mHoverActive;
- BOOL mHasHover;
- BOOL mBackgroundVisible;
- BOOL mBorderVisible;
- BOOL mWordWrap;
-
- U8 mFontStyle; // style bit flags for font
- LLFontGL::ShadowType mShadowType;
- BOOL mBorderDropShadowVisible;
- BOOL mUseEllipses;
-
- S32 mLineSpacing;
- S32 mBlockSpasing;
-
- LLFontGL::HAlign mHAlign;
- LLFontGL::VAlign mVAlign;
-
- callback_t mClickedCallback;
-
-
- //same as mLineLengthList and mText in LLTextBox
- std::vector< boost::shared_ptr<text_block> > mTextStrings;
-
+ S32 mBlockSpacing;
+ std::vector<S32> mSeparatorOffset;
};
#endif
diff --git a/indra/newview/llfloaterabout.cpp b/indra/newview/llfloaterabout.cpp
index 56c5eaa70e..caa10e9452 100644
--- a/indra/newview/llfloaterabout.cpp
+++ b/indra/newview/llfloaterabout.cpp
@@ -99,24 +99,20 @@ BOOL LLFloaterAbout::postBuild()
LLViewerTextEditor *credits_widget =
getChild<LLViewerTextEditor>("credits_editor", true);
- // For some reason, adding style doesn't work unless this is true.
+ // make sure that we handle hyperlinks in the About text
support_widget->setParseHTML(TRUE);
- // Text styles for release notes hyperlinks
- LLStyle::Params link_style_params;
- link_style_params.color.control = "HTMLLinkColor";
- link_style_params.link_href = get_viewer_release_notes_url();
-
// Version string
std::string version = LLTrans::getString("APP_NAME")
+ llformat(" %d.%d.%d (%d) %s %s (%s)\n",
LL_VERSION_MAJOR, LL_VERSION_MINOR, LL_VERSION_PATCH, LL_VIEWER_BUILD,
__DATE__, __TIME__,
gSavedSettings.getString("VersionChannelName").c_str());
- support_widget->appendColoredText(version, FALSE, FALSE, LLUIColorTable::instance().getColor("TextFgReadOnlyColor"));
- support_widget->appendStyledText(LLTrans::getString("ReleaseNotes"), false, false, link_style_params);
std::string support;
+ support.append(version);
+ support.append("[" + get_viewer_release_notes_url() + " " +
+ LLTrans::getString("ReleaseNotes") + "]");
support.append("\n\n");
#if LL_MSVC
@@ -131,10 +127,6 @@ BOOL LLFloaterAbout::postBuild()
LLViewerRegion* region = gAgent.getRegion();
if (region)
{
- LLStyle::Params server_link_style_params;
- server_link_style_params.color.control = "HTMLLinkColor";
- server_link_style_params.link_href = region->getCapability("ServerReleaseNotes");
-
const LLVector3d &pos = gAgent.getPositionGlobal();
LLUIString pos_text = getString("you_are_at");
pos_text.setArg("[POSITION]",
@@ -154,11 +146,9 @@ BOOL LLFloaterAbout::postBuild()
support.append(")\n");
support.append(gLastVersionChannel);
support.append("\n");
-
- support_widget->appendColoredText(support, FALSE, FALSE, LLUIColorTable::instance().getColor("TextFgReadOnlyColor"));
- support_widget->appendStyledText(LLTrans::getString("ReleaseNotes"), false, false, server_link_style_params);
-
- support = "\n\n";
+ support.append("[" + LLWeb::escapeURL(region->getCapability("ServerReleaseNotes")) +
+ " " + LLTrans::getString("ReleaseNotes") + "]");
+ support.append("\n\n");
}
// *NOTE: Do not translate text like GPU, Graphics Card, etc -
@@ -248,20 +238,20 @@ BOOL LLFloaterAbout::postBuild()
}
- static std::string get_viewer_release_notes_url()
- {
- std::ostringstream version;
- version << LL_VERSION_MAJOR << "."
- << LL_VERSION_MINOR << "."
- << LL_VERSION_PATCH << "."
- << LL_VERSION_BUILD;
+static std::string get_viewer_release_notes_url()
+{
+ std::ostringstream version;
+ version << LL_VERSION_MAJOR << "."
+ << LL_VERSION_MINOR << "."
+ << LL_VERSION_PATCH << "."
+ << LL_VERSION_BUILD;
- LLSD query;
- query["channel"] = gSavedSettings.getString("VersionChannelName");
- query["version"] = version.str();
+ LLSD query;
+ query["channel"] = gSavedSettings.getString("VersionChannelName");
+ query["version"] = version.str();
- std::ostringstream url;
- url << LLTrans::getString("RELEASE_NOTES_BASE_URL") << LLURI::mapToQueryString(query);
+ std::ostringstream url;
+ url << LLTrans::getString("RELEASE_NOTES_BASE_URL") << LLURI::mapToQueryString(query);
- return url.str();
- }
+ return LLWeb::escapeURL(url.str());
+}
diff --git a/indra/newview/llfloaterfriends.cpp b/indra/newview/llfloaterfriends.cpp
index eb73bd6d8f..0c77d88efb 100644
--- a/indra/newview/llfloaterfriends.cpp
+++ b/indra/newview/llfloaterfriends.cpp
@@ -194,6 +194,7 @@ BOOL LLPanelFriends::postBuild()
mFriendsList->setMaxSelectable(MAX_FRIEND_SELECT);
mFriendsList->setMaximumSelectCallback(boost::bind(&LLPanelFriends::onMaximumSelect));
mFriendsList->setCommitOnSelectionChange(TRUE);
+ mFriendsList->setContextMenu(LLScrollListCtrl::MENU_AVATAR);
childSetCommitCallback("friend_list", onSelectName, this);
getChild<LLScrollListCtrl>("friend_list")->setDoubleClickCallback(onClickIM, this);
diff --git a/indra/newview/llfloatergroups.cpp b/indra/newview/llfloatergroups.cpp
index 7a88612f1a..b1f40d9d1d 100644
--- a/indra/newview/llfloatergroups.cpp
+++ b/indra/newview/llfloatergroups.cpp
@@ -82,7 +82,12 @@ void LLFloaterGroupPicker::setPowersMask(U64 powers_mask)
BOOL LLFloaterGroupPicker::postBuild()
{
LLScrollListCtrl* list_ctrl = getChild<LLScrollListCtrl>("group list");
- init_group_list(list_ctrl, gAgent.getGroupID(), mPowersMask);
+ if (list_ctrl)
+ {
+ init_group_list(list_ctrl, gAgent.getGroupID(), mPowersMask);
+ list_ctrl->setDoubleClickCallback(onBtnOK, this);
+ list_ctrl->setContextMenu(LLScrollListCtrl::MENU_GROUP);
+ }
// Remove group "none" from list. Group "none" is added in init_group_list().
// Some UI elements use group "none", we need to manually delete it here.
@@ -100,8 +105,6 @@ BOOL LLFloaterGroupPicker::postBuild()
setDefaultBtn("OK");
- getChild<LLScrollListCtrl>("group list")->setDoubleClickCallback(onBtnOK, this);
-
childEnable("OK");
return TRUE;
@@ -183,7 +186,13 @@ BOOL LLPanelGroups::postBuild()
childSetTextArg("groupcount", "[COUNT]", llformat("%d",gAgent.mGroups.count()));
childSetTextArg("groupcount", "[MAX]", llformat("%d",MAX_AGENT_GROUPS));
- init_group_list(getChild<LLScrollListCtrl>("group list"), gAgent.getGroupID());
+ LLScrollListCtrl *list = getChild<LLScrollListCtrl>("group list");
+ if (list)
+ {
+ init_group_list(list, gAgent.getGroupID());
+ list->setDoubleClickCallback(onBtnIM, this);
+ list->setContextMenu(LLScrollListCtrl::MENU_GROUP);
+ }
childSetAction("Activate", onBtnActivate, this);
@@ -199,8 +208,6 @@ BOOL LLPanelGroups::postBuild()
setDefaultBtn("IM");
- getChild<LLScrollListCtrl>("group list")->setDoubleClickCallback(onBtnIM, this);
-
reset();
return TRUE;
diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp
index 4cd09faaaf..e5f5e8eedb 100644
--- a/indra/newview/llfloaterland.cpp
+++ b/indra/newview/llfloaterland.cpp
@@ -1061,6 +1061,7 @@ BOOL LLPanelLandObjects::postBuild()
mOwnerList->sortByColumnIndex(3, FALSE);
childSetCommitCallback("owner list", onCommitList, this);
mOwnerList->setDoubleClickCallback(onDoubleClickOwner, this);
+ mOwnerList->setContextMenu(LLScrollListCtrl::MENU_AVATAR);
return TRUE;
}
@@ -2297,11 +2298,17 @@ BOOL LLPanelLandAccess::postBuild()
mListAccess = getChild<LLNameListCtrl>("AccessList");
if (mListAccess)
+ {
mListAccess->sortByColumnIndex(0, TRUE); // ascending
+ mListAccess->setContextMenu(LLScrollListCtrl::MENU_AVATAR);
+ }
mListBanned = getChild<LLNameListCtrl>("BannedList");
if (mListBanned)
+ {
mListBanned->sortByColumnIndex(0, TRUE); // ascending
+ mListBanned->setContextMenu(LLScrollListCtrl::MENU_AVATAR);
+ }
return TRUE;
}
diff --git a/indra/newview/llgrouplist.cpp b/indra/newview/llgrouplist.cpp
index 278fd5b9f6..73d3a60701 100644
--- a/indra/newview/llgrouplist.cpp
+++ b/indra/newview/llgrouplist.cpp
@@ -51,6 +51,8 @@ LLGroupList::Params::Params()
LLGroupList::LLGroupList(const Params& p)
: LLAvatarList(p)
{
+ // display a context menu appropriate for a list of group names
+ setContextMenu(LLScrollListCtrl::MENU_GROUP);
}
static bool findInsensitive(std::string haystack, const std::string& needle_upper)
diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp
index 9cf3e57e22..674fff4040 100644
--- a/indra/newview/llimpanel.cpp
+++ b/indra/newview/llimpanel.cpp
@@ -2125,6 +2125,7 @@ BOOL LLIMFloater::postBuild()
childSetCommitCallback("chat_editor", onSendMsg, this);
mHistoryEditor = getChild<LLViewerTextEditor>("im_text");
+ mHistoryEditor->setParseHTML(TRUE);
setTitle(LLIMModel::instance().getName(mSessionID));
setDocked(true);
diff --git a/indra/newview/llnamelistctrl.cpp b/indra/newview/llnamelistctrl.cpp
index 1b82c2dc18..8ef6b25c50 100644
--- a/indra/newview/llnamelistctrl.cpp
+++ b/indra/newview/llnamelistctrl.cpp
@@ -61,9 +61,9 @@ LLNameListCtrl::Params::Params()
LLNameListCtrl::LLNameListCtrl(const LLNameListCtrl::Params& p)
: LLScrollListCtrl(p),
- mAllowCallingCardDrop(p.allow_calling_card_drop),
+ mNameColumnIndex(p.name_column.column_index),
mNameColumn(p.name_column.column_name),
- mNameColumnIndex(p.name_column.column_index)
+ mAllowCallingCardDrop(p.allow_calling_card_drop)
{}
// public
diff --git a/indra/newview/llpanelavatar.cpp b/indra/newview/llpanelavatar.cpp
index 6e94b087a6..b2d606ab4d 100644
--- a/indra/newview/llpanelavatar.cpp
+++ b/indra/newview/llpanelavatar.cpp
@@ -595,9 +595,8 @@ BOOL LLPanelAvatarMeProfile::postBuild()
childSetCommitCallback("status_combo", boost::bind(&LLPanelAvatarMeProfile::onStatusChanged, this), NULL);
childSetCommitCallback("status_me_message_text", boost::bind(&LLPanelAvatarMeProfile::onStatusMessageChanged, this), NULL);
- childSetActionTextbox("payment_update_link", boost::bind(&LLPanelAvatarMeProfile::onUpdateAccountTextboxClicked, this));
- childSetActionTextbox("my_account_link", boost::bind(&LLPanelAvatarMeProfile::onMyAccountTextboxClicked, this));
- childSetActionTextbox("partner_edit_link", boost::bind(&LLPanelAvatarMeProfile::onPartnerEditTextboxClicked, this));
+
+ childSetTextArg("partner_edit_link", "[URL]", getString("partner_edit_link_url"));
resetControls();
resetData();
@@ -677,17 +676,3 @@ void LLPanelAvatarMeProfile::onStatusMessageChanged()
updateData();
}
-void LLPanelAvatarMeProfile::onUpdateAccountTextboxClicked()
-{
- onUrlTextboxClicked(getString("payment_update_link_url"));
-}
-
-void LLPanelAvatarMeProfile::onMyAccountTextboxClicked()
-{
- onUrlTextboxClicked(getString("my_account_link_url"));
-}
-
-void LLPanelAvatarMeProfile::onPartnerEditTextboxClicked()
-{
- onUrlTextboxClicked(getString("partner_edit_link_url"));
-}
diff --git a/indra/newview/llpanelavatar.h b/indra/newview/llpanelavatar.h
index 51bd619901..4ee4cb6e87 100644
--- a/indra/newview/llpanelavatar.h
+++ b/indra/newview/llpanelavatar.h
@@ -203,9 +203,6 @@ protected:
void onStatusChanged();
void onStatusMessageChanged();
- void onUpdateAccountTextboxClicked();
- void onMyAccountTextboxClicked();
- void onPartnerEditTextboxClicked();
private:
diff --git a/indra/newview/llpanelgroupgeneral.cpp b/indra/newview/llpanelgroupgeneral.cpp
index 4b2a1a4e48..f3893a104c 100644
--- a/indra/newview/llpanelgroupgeneral.cpp
+++ b/indra/newview/llpanelgroupgeneral.cpp
@@ -113,6 +113,7 @@ BOOL LLPanelGroupGeneral::postBuild()
if (mListVisibleMembers)
{
mListVisibleMembers->setDoubleClickCallback(openProfile, this);
+ mListVisibleMembers->setContextMenu(LLScrollListCtrl::MENU_AVATAR);
}
// Options
diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp
index 59f7319b2b..48c9c16780 100644
--- a/indra/newview/llpanelgrouproles.cpp
+++ b/indra/newview/llpanelgrouproles.cpp
@@ -843,6 +843,7 @@ BOOL LLPanelGroupMembersSubTab::postBuildSubTab(LLView* root)
mMembersList->setCommitCallback(onMemberSelect, this);
// Show the member's profile on double click.
mMembersList->setDoubleClickCallback(onMemberDoubleClick, this);
+ mMembersList->setContextMenu(LLScrollListCtrl::MENU_AVATAR);
LLButton* button = parent->getChild<LLButton>("member_invite", recurse);
if ( button )
@@ -1737,6 +1738,8 @@ BOOL LLPanelGroupRolesSubTab::postBuildSubTab(LLView* root)
mRolesList->setCommitOnSelectionChange(TRUE);
mRolesList->setCommitCallback(onRoleSelect, this);
+ mAssignedMembersList->setContextMenu(LLScrollListCtrl::MENU_AVATAR);
+
mMemberVisibleCheck->setCommitCallback(onMemberVisibilityChange, this);
mAllowedActionsList->setCommitOnSelectionChange(TRUE);
@@ -2403,6 +2406,7 @@ BOOL LLPanelGroupActionsSubTab::postBuildSubTab(LLView* root)
mActionList->setCommitOnSelectionChange(TRUE);
mActionList->setCommitCallback(boost::bind(&LLPanelGroupActionsSubTab::handleActionSelect, this));
+ mActionList->setContextMenu(LLScrollListCtrl::MENU_AVATAR);
update(GC_ALL);
diff --git a/indra/newview/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp
index cadab71ba8..29320522d9 100644
--- a/indra/newview/llpreviewnotecard.cpp
+++ b/indra/newview/llpreviewnotecard.cpp
@@ -87,6 +87,7 @@ BOOL LLPreviewNotecard::postBuild()
LLViewerTextEditor *ed = getChild<LLViewerTextEditor>("Notecard Editor");
if (ed)
{
+ ed->setParseHTML(TRUE);
ed->setNotecardInfo(mItemUUID, mObjectID, getKey());
ed->makePristine();
}
@@ -126,7 +127,7 @@ void LLPreviewNotecard::draw()
{
LLViewerTextEditor* editor = getChild<LLViewerTextEditor>("Notecard Editor");
BOOL changed = !editor->isPristine();
-
+
childSetEnabled("Save", changed && getEnabled());
LLPreview::draw();
diff --git a/indra/newview/llprogressview.cpp b/indra/newview/llprogressview.cpp
index f70cfc59ec..aa603c417f 100644
--- a/indra/newview/llprogressview.cpp
+++ b/indra/newview/llprogressview.cpp
@@ -72,7 +72,6 @@ const S32 ANIMATION_FRAMES = 1; //13;
LLProgressView::LLProgressView(const LLRect &rect)
: LLPanel(),
mPercentDone( 0.f ),
- mURLInMessage(false),
mMouseDownInActiveArea( false )
{
LLUICtrlFactory::getInstance()->buildPanel(this, "panel_progress.xml");
@@ -207,12 +206,7 @@ void LLProgressView::setPercent(const F32 percent)
void LLProgressView::setMessage(const std::string& msg)
{
mMessage = msg;
- mURLInMessage = (mMessage.find( "https://" ) != std::string::npos ||
- mMessage.find( "http://" ) != std::string::npos ||
- mMessage.find( "ftp://" ) != std::string::npos);
-
getChild<LLTextBox>("message_text")->setWrappedText(LLStringExplicit(mMessage));
- getChild<LLTextBox>("message_text")->setHoverActive(mURLInMessage);
}
void LLProgressView::setCancelButtonVisible(BOOL b, const std::string& label)
diff --git a/indra/newview/llprogressview.h b/indra/newview/llprogressview.h
index 83574ff52a..865646c85d 100644
--- a/indra/newview/llprogressview.h
+++ b/indra/newview/llprogressview.h
@@ -74,7 +74,6 @@ protected:
LLFrameTimer mProgressTimer;
LLRect mOutlineRect;
bool mMouseDownInActiveArea;
- bool mURLInMessage;
static LLProgressView* sInstance;
};
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 18fab3ec2e..dfb1c330e5 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -1,4 +1,3 @@
-
/**
* @file llviewermessage.cpp
* @brief Dumping ground for viewer-side message system callbacks.
@@ -1474,6 +1473,13 @@ void process_improved_im(LLMessageSystem *msg, void **user_data)
binary_bucket_size = msg->getSizeFast(_PREHASH_MessageBlock, _PREHASH_BinaryBucket);
EInstantMessage dialog = (EInstantMessage)d;
+ // make sure that we don't have an empty or all-whitespace name
+ LLStringUtil::trim(name);
+ if (name.empty())
+ {
+ name = LLTrans::getString("Unnamed");
+ }
+
BOOL is_busy = gAgent.getBusy();
BOOL is_muted = LLMuteList::getInstance()->isMuted(from_id, name, LLMute::flagTextChat);
BOOL is_linden = LLMuteList::getInstance()->isLinden(name);
diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp
index de01e79803..5bb0c9a120 100644
--- a/indra/newview/llviewertexteditor.cpp
+++ b/indra/newview/llviewertexteditor.cpp
@@ -814,38 +814,18 @@ BOOL LLViewerTextEditor::handleMouseUp(S32 x, S32 y, MASK mask)
BOOL LLViewerTextEditor::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
- BOOL handled = childrenHandleRightMouseDown(x, y, mask) != NULL;
-
- // *TODO: Add right click menus for SLURLs
-// if(! handled)
-// {
-// const LLTextSegment* cur_segment = getSegmentAtLocalPos( x, y );
-// if( cur_segment )
-// {
-// if(cur_segment->getStyle()->isLink())
-// {
-// handled = TRUE;
-// mHTML = cur_segment->getStyle()->getLinkHREF();
-// }
-// }
-// }
-// LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get();
-// if(handled && menu && mParseHTML && mHTML.length() > 0)
-// {
-// menu->setVisible(TRUE);
-// menu->arrange();
-// menu->updateParent(LLMenuGL::sMenuContainer);
-// LLMenuGL::showPopup(this, menu, x, y);
-// mHTML = "";
-// }
-// else
-// {
-// if(menu && menu->getVisible())
-// {
-// menu->setVisible(FALSE);
-// }
-// }
- return handled;
+ // pop up a context menu for any Url under the cursor
+ if (handleRightMouseDownOverUrl(this, x, y))
+ {
+ return TRUE;
+ }
+
+ if (childrenHandleRightMouseDown(x, y, mask) != NULL)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
}
BOOL LLViewerTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask)
@@ -1087,6 +1067,7 @@ llwchar LLViewerTextEditor::pasteEmbeddedItem(llwchar ext_char)
void LLViewerTextEditor::onValueChange(S32 start, S32 end)
{
updateSegments();
+ updateLinkSegments();
findEmbeddedItemSegments(start, end);
}
diff --git a/indra/newview/llviewertexteditor.h b/indra/newview/llviewertexteditor.h
index 9567bfbc48..2dfea4a589 100644
--- a/indra/newview/llviewertexteditor.h
+++ b/indra/newview/llviewertexteditor.h
@@ -35,7 +35,6 @@
#include "lltexteditor.h"
-
//
// Classes
//
@@ -137,9 +136,6 @@ private:
LLPointer<class LLEmbeddedNotecardOpener> mInventoryCallback;
- // *TODO: Add right click menus for SLURLs
- //LLViewHandle mPopupMenuHandle;
-
//
// Inner classes
//
diff --git a/indra/newview/llweb.cpp b/indra/newview/llweb.cpp
index 300a5db7c3..3204c2d264 100644
--- a/indra/newview/llweb.cpp
+++ b/indra/newview/llweb.cpp
@@ -67,6 +67,7 @@ void LLWeb::initClass()
LLAlertDialog::setURLLoader(&sAlertURLLoader);
}
+
// static
void LLWeb::loadURL(const std::string& url)
{
@@ -76,12 +77,19 @@ void LLWeb::loadURL(const std::string& url)
}
else
{
- LLFloaterReg::showInstance("media_browser",url);
+ loadURLInternal(url);
}
}
// static
+void LLWeb::loadURLInternal(const std::string &url)
+{
+ LLFloaterReg::showInstance("media_browser", url);
+}
+
+
+// static
void LLWeb::loadURLExternal(const std::string& url)
{
std::string escaped_url = escapeURL(url);
diff --git a/indra/newview/llweb.h b/indra/newview/llweb.h
index 71cc236621..96a53db2ca 100644
--- a/indra/newview/llweb.h
+++ b/indra/newview/llweb.h
@@ -36,23 +36,29 @@
#include <string>
+///
+/// The LLWeb class provides various static methods to display the
+/// contents of a Url in a web browser. Variations are provided to
+/// let you specifically use the Second Life internal browser, the
+/// operating system's default browser, or to respect the user's
+/// setting for which of these two they prefer to use with SL.
+///
class LLWeb
{
public:
static void initClass();
- // Loads unescaped url in either internal web browser or external
- // browser, depending on user settings.
+ /// Load the given url in the user's preferred web browser
static void loadURL(const std::string& url);
-
+ /// Load the given url in the user's preferred web browser
static void loadURL(const char* url) { loadURL( ll_safe_string(url) ); }
-
- // Loads unescaped url in external browser.
+ /// Load the given url in the Second Life internal web browser
+ static void loadURLInternal(const std::string &url);
+ /// Load the given url in the operating system's web browser
static void loadURLExternal(const std::string& url);
- // Returns escaped (eg, " " to "%20") url
+ // Returns escaped url (eg, " " to "%20") - used by all loadURL methods
static std::string escapeURL(const std::string& url);
-
};
#endif
diff --git a/indra/newview/skins/default/xui/en/menu_url_agent.xml b/indra/newview/skins/default/xui/en/menu_url_agent.xml
new file mode 100644
index 0000000000..463a9fccb6
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/menu_url_agent.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<context_menu
+ layout="topleft"
+ name="Url Popup">
+ <menu_item_call
+ label="Show Resident Profile"
+ layout="topleft"
+ name="show_agent">
+ <menu_item_call.on_click
+ function="Url.Execute" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Copy Name To Clipboard"
+ layout="topleft"
+ name="url_copy_label">
+ <menu_item_call.on_click
+ function="Url.CopyLabel" />
+ </menu_item_call>
+ <menu_item_call
+ label="Copy SLURL To Clipboard"
+ layout="topleft"
+ name="url_copy">
+ <menu_item_call.on_click
+ function="Url.CopyUrl" />
+ </menu_item_call>
+</context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_url_group.xml b/indra/newview/skins/default/xui/en/menu_url_group.xml
new file mode 100644
index 0000000000..cec0aa421e
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/menu_url_group.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<context_menu
+ layout="topleft"
+ name="Url Popup">
+ <menu_item_call
+ label="Show Group Information"
+ layout="topleft"
+ name="show_group">
+ <menu_item_call.on_click
+ function="Url.Execute" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Copy Group To Clipboard"
+ layout="topleft"
+ name="url_copy_label">
+ <menu_item_call.on_click
+ function="Url.CopyLabel" />
+ </menu_item_call>
+ <menu_item_call
+ label="Copy SLURL To Clipboard"
+ layout="topleft"
+ name="url_copy">
+ <menu_item_call.on_click
+ function="Url.CopyUrl" />
+ </menu_item_call>
+</context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_url_http.xml b/indra/newview/skins/default/xui/en/menu_url_http.xml
new file mode 100644
index 0000000000..2503b4a2a3
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/menu_url_http.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<context_menu
+ layout="topleft"
+ name="Url Popup">
+ <menu_item_call
+ label="Open Web Page"
+ layout="topleft"
+ name="url_open">
+ <menu_item_call.on_click
+ function="Url.Open" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Open in Internal Browser"
+ layout="topleft"
+ name="url_open_internal">
+ <menu_item_call.on_click
+ function="Url.OpenInternal" />
+ </menu_item_call>
+ <menu_item_call
+ label="Open in External Browser"
+ layout="topleft"
+ name="url_open_external">
+ <menu_item_call.on_click
+ function="Url.OpenExternal" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Copy URL To Clipboard"
+ layout="topleft"
+ name="url_copy">
+ <menu_item_call.on_click
+ function="Url.CopyUrl" />
+ </menu_item_call>
+</context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_url_objectim.xml b/indra/newview/skins/default/xui/en/menu_url_objectim.xml
new file mode 100644
index 0000000000..7d09403b15
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/menu_url_objectim.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<context_menu
+ layout="topleft"
+ name="Url Popup">
+ <menu_item_call
+ label="Show Object Information"
+ layout="topleft"
+ name="show_object">
+ <menu_item_call.on_click
+ function="Url.Execute" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Teleport to Object Location"
+ layout="topleft"
+ name="teleport_to_object">
+ <menu_item_call.on_click
+ function="Url.Teleport" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Copy Object Name To Clipboard"
+ layout="topleft"
+ name="url_copy_label">
+ <menu_item_call.on_click
+ function="Url.CopyLabel" />
+ </menu_item_call>
+ <menu_item_call
+ label="Copy SLURL To Clipboard"
+ layout="topleft"
+ name="url_copy">
+ <menu_item_call.on_click
+ function="Url.CopyUrl" />
+ </menu_item_call>
+</context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_url_parcel.xml b/indra/newview/skins/default/xui/en/menu_url_parcel.xml
new file mode 100644
index 0000000000..bbd63c6d8c
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/menu_url_parcel.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<context_menu
+ layout="topleft"
+ name="Url Popup">
+ <menu_item_call
+ label="Show Parcel Information"
+ layout="topleft"
+ name="show_parcel">
+ <menu_item_call.on_click
+ function="Url.Execute" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Copy SLURL To Clipboard"
+ layout="topleft"
+ name="url_copy">
+ <menu_item_call.on_click
+ function="Url.CopyUrl" />
+ </menu_item_call>
+</context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_url_slapp.xml b/indra/newview/skins/default/xui/en/menu_url_slapp.xml
new file mode 100644
index 0000000000..19df721b2b
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/menu_url_slapp.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<context_menu
+ layout="topleft"
+ name="Url Popup">
+ <menu_item_call
+ label="Run This Command"
+ layout="topleft"
+ name="run_slapp">
+ <menu_item_call.on_click
+ function="Url.Execute" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Copy SLURL To Clipboard"
+ layout="topleft"
+ name="url_copy">
+ <menu_item_call.on_click
+ function="Url.CopyUrl" />
+ </menu_item_call>
+</context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_url_slurl.xml b/indra/newview/skins/default/xui/en/menu_url_slurl.xml
new file mode 100644
index 0000000000..0e9525fa4b
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/menu_url_slurl.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<context_menu
+ layout="topleft"
+ name="Url Popup">
+ <menu_item_call
+ label="Show Place Information"
+ layout="topleft"
+ name="show_place">
+ <menu_item_call.on_click
+ function="Url.Execute" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Teleport to Location"
+ layout="topleft"
+ name="teleport_to_location">
+ <menu_item_call.on_click
+ function="Url.Teleport" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Copy SLURL To Clipboard"
+ layout="topleft"
+ name="url_copy">
+ <menu_item_call.on_click
+ function="Url.CopyUrl" />
+ </menu_item_call>
+</context_menu>
diff --git a/indra/newview/skins/default/xui/en/menu_url_teleport.xml b/indra/newview/skins/default/xui/en/menu_url_teleport.xml
new file mode 100644
index 0000000000..22cc035e49
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/menu_url_teleport.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<context_menu
+ layout="topleft"
+ name="Url Popup">
+ <menu_item_call
+ label="Teleport To This Location"
+ layout="topleft"
+ name="teleport">
+ <menu_item_call.on_click
+ function="Url.Execute" />
+ </menu_item_call>
+ <menu_item_separator
+ layout="topleft" />
+ <menu_item_call
+ label="Copy SLURL To Clipboard"
+ layout="topleft"
+ name="url_copy">
+ <menu_item_call.on_click
+ function="Url.CopyUrl" />
+ </menu_item_call>
+</context_menu>
diff --git a/indra/newview/skins/default/xui/en/panel_edit_profile.xml b/indra/newview/skins/default/xui/en/panel_edit_profile.xml
index c0366437db..fa02cdb4b2 100644
--- a/indra/newview/skins/default/xui/en/panel_edit_profile.xml
+++ b/indra/newview/skins/default/xui/en/panel_edit_profile.xml
@@ -12,6 +12,10 @@
name="edit_profile_panel"
top="10"
width="255">
+ <string
+ name="partner_edit_link_url">
+ http://www.secondlife.com/account/partners.php?lang=en
+ </string>
<scroll_container
color="DkGray2"
follows="left|top|right|bottom"
@@ -243,11 +247,10 @@
top_pad="15"
value="Account Status:"
width="100" />
- <link
+ <text
type="string"
follows="left|top"
font="SansSerifSmall"
- font.style="UNDERLINE"
height="15"
layout="topleft"
left_pad="10"
@@ -277,15 +280,14 @@
top_pad="15"
value="Partner:"
width="100" />
- <link
+ <text
follows="left|top"
height="15"
- font.style="UNDERLINE"
layout="topleft"
left_pad="10"
name="partner_edit_link"
top_delta="0"
- value="Edit"
+ value="[[URL] Edit]"
width="100" />
<panel
follows="left|top|right"
diff --git a/indra/newview/skins/default/xui/en/panel_profile.xml b/indra/newview/skins/default/xui/en/panel_profile.xml
index d90be9ea25..135dcb167b 100644
--- a/indra/newview/skins/default/xui/en/panel_profile.xml
+++ b/indra/newview/skins/default/xui/en/panel_profile.xml
@@ -167,11 +167,10 @@
width="255">
Homepage:
</text>
- <link
+ <text
follows="left|top|right"
height="15"
layout="topleft"
- font.style="UNDERLINE"
left="10"
name="homepage_edit"
top_pad="5"
@@ -212,11 +211,10 @@
top_pad="15"
value="Account Status:"
width="100" />
- <!-- <link
+ <!-- <text
type="string"
follows="left|top"
font="SansSerifSmall"
- font.style="UNDERLINE"
height="15"
layout="topleft"
left_pad="10"
@@ -246,16 +244,15 @@
top_pad="15"
value="Partner:"
width="100" />
- <!--<link
+ <text
follows="left|top"
height="15"
- font.style="UNDERLINE"
layout="topleft"
left_pad="10"
name="partner_edit_link"
top_delta="0"
- value="Edit"
- width="100" /> -->
+ value="[[URL] Edit]"
+ width="100" />
<panel
follows="left|top|right"
height="15"
diff --git a/indra/newview/skins/default/xui/en/panel_progress.xml b/indra/newview/skins/default/xui/en/panel_progress.xml
index 4f23c4d26d..9b2461db7c 100644
--- a/indra/newview/skins/default/xui/en/panel_progress.xml
+++ b/indra/newview/skins/default/xui/en/panel_progress.xml
@@ -99,12 +99,12 @@
halign="left"
height="100"
layout="topleft"
- left="30"
+ left="45"
line_spacing="2"
name="message_text"
text_color="LoginProgressBoxTextColor"
top="145"
- width="610" />
+ width="550" />
</layout_panel>
<layout_panel
height="200"
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 323c08ec4c..b8152a4956 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -74,6 +74,18 @@
<string name="TooltipMustSingleDrop">Only a single item can be dragged here</string>
<string name="TooltipAltLeft">Alt-Left arrow for previous tab</string>
<string name="TooltipAltRight">Alt-Right arrow for next tab</string>
+
+ <!-- tooltips for Urls -->
+ <string name="TooltipHttpUrl">Click to view this web page</string>
+ <string name="TooltipSLURL">Click to view this location's information</string>
+ <string name="TooltipAgentUrl">Click to view this resident's profile</string>
+ <string name="TooltipGroupUrl">Click to view this group's description</string>
+ <string name="TooltipEventUrl">Click to view this event's description</string>
+ <string name="TooltipClassifiedUrl">Click to view this classified</string>
+ <string name="TooltipParcelUrl">Click to view this parcel's description</string>
+ <string name="TooltipTeleportUrl">Click to teleport to this location</string>
+ <string name="TooltipObjectIMUrl">Click to view this object's description</string>
+ <string name="TooltipSLAPP">Click to run the secondlife:// command</string>
<!-- ButtonToolTips, llfloater.cpp -->
<string name="BUTTON_CLOSE_DARWIN">Close (&#8984;-W)</string>
@@ -259,6 +271,7 @@
<!-- IM -->
<string name="IM_logging_string">-- Instant message logging enabled --</string>
+ <string name="Unnamed">(Unnamed)</string>
<!-- Sim Access labels -->
<string name="SIM_ACCESS_PG">PG</string>