summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/CMakeLists.txt43
-rw-r--r--indra/llui/llbutton.cpp19
-rw-r--r--indra/llui/lldockablefloater.cpp86
-rw-r--r--indra/llui/lldockablefloater.h65
-rw-r--r--indra/llui/lldockcontrol.cpp126
-rw-r--r--indra/llui/lldockcontrol.h81
-rw-r--r--indra/llui/llscrollcontainer.cpp3
-rw-r--r--indra/llui/llscrolllistctrl.cpp110
-rw-r--r--indra/llui/llscrolllistctrl.h23
-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.cpp501
-rw-r--r--indra/llui/lltextbox.h49
-rw-r--r--indra/llui/lltexteditor.cpp684
-rw-r--r--indra/llui/lltexteditor.h129
-rw-r--r--indra/llui/lluictrl.cpp7
-rw-r--r--indra/llui/lluictrl.h5
-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/llview.cpp14
-rw-r--r--indra/llui/llview.h3
-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
32 files changed, 4056 insertions, 772 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 8b0fcc68c4..95d693cdc4 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -34,6 +34,8 @@ set(llui_SOURCE_FILES
llconsole.cpp
llcontainerview.cpp
llctrlselectioninterface.cpp
+ lldockablefloater.cpp
+ lldockcontrol.cpp
lldraghandle.cpp
lleditmenuhandler.cpp
llf32uictrl.cpp
@@ -47,7 +49,7 @@ set(llui_SOURCE_FILES
llkeywords.cpp
lllayoutstack.cpp
lllineeditor.cpp
- lllink.cpp
+ lllistctrl.cpp
llmenugl.cpp
llmodaldialog.cpp
llmultifloater.cpp
@@ -78,6 +80,7 @@ set(llui_SOURCE_FILES
llstatview.cpp
llstyle.cpp
lltabcontainer.cpp
+ lltextbase.cpp
lltextbox.cpp
lltexteditor.cpp
lltextparser.cpp
@@ -89,6 +92,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
@@ -108,6 +115,8 @@ set(llui_HEADER_FILES
llcontainerview.h
llctrlselectioninterface.h
lldraghandle.h
+ lldockablefloater.h
+ lldockcontrol.h
lleditmenuhandler.h
llf32uictrl.h
llfiltereditor.h
@@ -123,7 +132,7 @@ set(llui_HEADER_FILES
lllayoutstack.h
lllazyvalue.h
lllineeditor.h
- lllink.h
+ lllistctrl.h
llmenugl.h
llmodaldialog.h
llmultifloater.h
@@ -154,6 +163,7 @@ set(llui_HEADER_FILES
llstatview.h
llstyle.h
lltabcontainer.h
+ lltextbase.h
lltextbox.h
lltexteditor.h
lltextparser.h
@@ -167,6 +177,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
@@ -182,12 +196,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/llbutton.cpp b/indra/llui/llbutton.cpp
index 98e8c9a988..bf58e19949 100644
--- a/indra/llui/llbutton.cpp
+++ b/indra/llui/llbutton.cpp
@@ -355,11 +355,19 @@ BOOL LLButton::handleMouseDown(S32 x, S32 y, MASK mask)
setFocus(TRUE);
}
+ /*
+ * ATTENTION! This call fires another mouse down callback.
+ * If you wish to remove this call emit that signal directly
+ * by calling LLUICtrl::mMouseDownSignal(x, y, mask);
+ */
+ LLUICtrl::handleMouseDown(x, y, mask);
+
mMouseDownSignal(this, LLSD());
mMouseDownTimer.start();
mMouseDownFrame = (S32) LLFrameTimer::getFrameCount();
mMouseHeldDownCount = 0;
+
if (getSoundFlags() & MOUSE_DOWN)
{
@@ -378,6 +386,13 @@ BOOL LLButton::handleMouseUp(S32 x, S32 y, MASK mask)
// Always release the mouse
gFocusMgr.setMouseCapture( NULL );
+ /*
+ * ATTENTION! This call fires another mouse up callback.
+ * If you wish to remove this call emit that signal directly
+ * by calling LLUICtrl::mMouseUpSignal(x, y, mask);
+ */
+ LLUICtrl::handleMouseUp(x, y, mask);
+
// Regardless of where mouseup occurs, handle callback
mMouseUpSignal(this, LLSD());
@@ -460,12 +475,16 @@ BOOL LLButton::handleRightMouseUp(S32 x, S32 y, MASK mask)
void LLButton::onMouseEnter(S32 x, S32 y, MASK mask)
{
+ LLUICtrl::onMouseEnter(x, y, mask);
+
if (isInEnabledChain())
mNeedsHighlight = TRUE;
}
void LLButton::onMouseLeave(S32 x, S32 y, MASK mask)
{
+ LLUICtrl::onMouseLeave(x, y, mask);
+
mNeedsHighlight = FALSE;
}
diff --git a/indra/llui/lldockablefloater.cpp b/indra/llui/lldockablefloater.cpp
new file mode 100644
index 0000000000..d0789d6502
--- /dev/null
+++ b/indra/llui/lldockablefloater.cpp
@@ -0,0 +1,86 @@
+/**
+ * @file lldockablefloater.cpp
+ * @brief Creates a panel of a specific kind for a toast
+ *
+ * $LicenseInfo:firstyear=2000&license=viewergpl$
+ *
+ * Copyright (c) 2000-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 "lldockablefloater.h"
+
+LLDockableFloater::LLDockableFloater(LLDockControl* dockControl,
+ const LLSD& key, const Params& params) :
+ LLFloater(key, params), mDockControl(dockControl)
+{
+}
+
+LLDockableFloater::~LLDockableFloater()
+{
+}
+
+BOOL LLDockableFloater::postBuild()
+{
+ mDockTongue = LLUI::getUIImage("windows/Flyout_Pointer.png");
+ LLFloater::setDocked(true);
+ return LLView::postBuild();
+}
+
+void LLDockableFloater::setDocked(bool docked, bool pop_on_undock)
+{
+ if (docked)
+ {
+ mDockControl.get()->on();
+ }
+ else
+ {
+ mDockControl.get()->off();
+ }
+ LLFloater::setDocked(docked, pop_on_undock);
+}
+
+void LLDockableFloater::draw()
+{
+ mDockControl.get()->repositionDockable();
+ mDockControl.get()->drawToungue();
+ LLFloater::draw();
+}
+
+void LLDockableFloater::setDockControl(LLDockControl* dockControl)
+{
+ mDockControl.reset(dockControl);
+}
+const LLUIImagePtr& LLDockableFloater::getDockTongue()
+{
+ return mDockTongue;
+}
+
+LLDockControl* LLDockableFloater::getDockControl()
+{
+ return mDockControl.get();
+}
diff --git a/indra/llui/lldockablefloater.h b/indra/llui/lldockablefloater.h
new file mode 100644
index 0000000000..5ece78a925
--- /dev/null
+++ b/indra/llui/lldockablefloater.h
@@ -0,0 +1,65 @@
+/**
+ * @file lldockablefloater.h
+ * @brief Creates a panel of a specific kind for a toast.
+ *
+ * $LicenseInfo:firstyear=2003&license=viewergpl$
+ *
+ * Copyright (c) 2003-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_DOCKABLEFLOATER_H
+#define LL_DOCKABLEFLOATER_H
+
+#include "llerror.h"
+#include "llfloater.h"
+#include "lldockcontrol.h"
+
+/**
+ * Represents floater that can dock.
+ * In case impossibility deriving from LLDockableFloater use LLDockControl.
+ */
+class LLDockableFloater : public LLFloater
+{
+public:
+ LOG_CLASS(LLDockableFloater);
+ LLDockableFloater(LLDockControl* dockControl, const LLSD& key, const Params& params = getDefaultParams());
+ virtual ~LLDockableFloater();
+
+ /* virtula */BOOL postBuild();
+ /* virtual */void setDocked(bool docked, bool pop_on_undock = true);
+ /* virtual */void draw();
+
+protected:
+ void setDockControl(LLDockControl* dockControl);
+ LLDockControl* getDockControl();
+ const LLUIImagePtr& getDockTongue();
+
+private:
+ std::auto_ptr<LLDockControl> mDockControl;
+ LLUIImagePtr mDockTongue;
+};
+
+#endif /* LL_DOCKABLEFLOATER_H */
diff --git a/indra/llui/lldockcontrol.cpp b/indra/llui/lldockcontrol.cpp
new file mode 100644
index 0000000000..bec7f04cc0
--- /dev/null
+++ b/indra/llui/lldockcontrol.cpp
@@ -0,0 +1,126 @@
+/**
+ * @file lldockcontrol.cpp
+ * @brief Creates a panel of a specific kind for a toast
+ *
+ * $LicenseInfo:firstyear=2000&license=viewergpl$
+ *
+ * Copyright (c) 2000-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 "lldockcontrol.h"
+
+LLDockControl::LLDockControl(LLView* dockWidget, LLFloater* dockableFloater,
+ const LLUIImagePtr& dockTongue, DocAt dockAt, bool enabled) :
+ mDockWidget(dockWidget), mDockableFloater(dockableFloater), mDockTongue(
+ dockTongue)
+{
+ mDockAt = dockAt;
+ if (enabled)
+ {
+ on();
+ }
+ else
+ {
+ off();
+ }
+}
+
+LLDockControl::~LLDockControl()
+{
+}
+
+void LLDockControl::repositionDockable()
+{
+ if (mEnabled)
+ {
+ calculateDockablePosition();
+ }
+}
+
+void LLDockControl::calculateDockablePosition()
+{
+ LLRect dockRect = mDockWidget->calcScreenRect();
+ if (mPrevDockRect != dockRect || mRecalculateDocablePosition)
+ {
+ LLRect dockableRect = mDockableFloater->calcScreenRect();
+ LLRect rootRect = mDockableFloater->getRootView()->getRect();
+
+ S32 x = 0;
+ S32 y = 0;
+ switch (mDockAt)
+ {
+ case TOP:
+ x = dockRect.getCenterX() - dockableRect.getWidth() / 2;
+ y = dockRect.mTop + mDockTongue->getHeight()
+ + dockableRect.getHeight();
+ if (x < rootRect.mLeft)
+ {
+ x = rootRect.mLeft;
+ }
+ if (x + dockableRect.getWidth() > rootRect.mRight)
+ {
+ x = rootRect.mRight - dockableRect.getWidth();
+ }
+ mDockTongueX = dockRect.getCenterX() - mDockTongue->getWidth() / 2;
+ mDockTongueY = dockRect.mTop;
+ break;
+ }
+ dockableRect.setLeftTopAndSize(x, y, dockableRect.getWidth(),
+ dockableRect.getHeight());
+ LLRect localDocableParentRect;
+ mDockableFloater->getParent()->screenRectToLocal(dockableRect,
+ &localDocableParentRect);
+ mDockableFloater->setRect(localDocableParentRect);
+
+ mDockableFloater->screenPointToLocal(mDockTongueX, mDockTongueY,
+ &mDockTongueX, &mDockTongueY);
+ mPrevDockRect = dockRect;
+ mRecalculateDocablePosition = false;
+ }
+}
+
+void LLDockControl::on()
+{
+ mDockableFloater->setCanDrag(false);
+ mEnabled = true;
+ mRecalculateDocablePosition = true;
+}
+
+void LLDockControl::off()
+{
+ mDockableFloater->setCanDrag(true);
+ mEnabled = false;
+}
+
+void LLDockControl::drawToungue()
+{
+ if (mEnabled)
+ {
+ mDockTongue->draw(mDockTongueX, mDockTongueY);
+ }
+}
diff --git a/indra/llui/lldockcontrol.h b/indra/llui/lldockcontrol.h
new file mode 100644
index 0000000000..0e1f4c8e64
--- /dev/null
+++ b/indra/llui/lldockcontrol.h
@@ -0,0 +1,81 @@
+/**
+ * @file lldockcontrol.h
+ * @brief Creates a panel of a specific kind for a toast.
+ *
+ * $LicenseInfo:firstyear=2003&license=viewergpl$
+ *
+ * Copyright (c) 2003-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_DOCKCONTROL_H
+#define LL_DOCKCONTROL_H
+
+#include "llerror.h"
+#include "llview.h"
+#include "llfloater.h"
+#include "lluiimage.h"
+
+/**
+ * Provides services for docking of specified floater.
+ * This class should be used in case impossibility deriving from LLDockableFloater.
+ */
+class LLDockControl
+{
+public:
+ enum DocAt
+ {
+ TOP
+ };
+
+public:
+ LOG_CLASS(LLDockControl);
+ LLDockControl(LLView* dockWidget, LLFloater* dockableFloater,
+ const LLUIImagePtr& dockTongue, DocAt dockAt,
+ bool enabled);
+ virtual ~LLDockControl();
+
+public:
+ void on();
+ void off();
+ void setDock(LLView* dockWidget)
+ { mDockWidget = dockWidget;};
+ void repositionDockable();
+ void drawToungue();
+protected:
+ virtual void calculateDockablePosition();
+private:
+ bool mEnabled;
+ bool mRecalculateDocablePosition;
+ DocAt mDockAt;
+ LLView* mDockWidget;
+ LLRect mPrevDockRect;
+ LLFloater* mDockableFloater;
+ LLUIImagePtr mDockTongue;
+ S32 mDockTongueX;
+ S32 mDockTongueY;
+};
+
+#endif /* LL_DOCKCONTROL_H */
diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp
index 13f862f3af..556ff80991 100644
--- a/indra/llui/llscrollcontainer.cpp
+++ b/indra/llui/llscrollcontainer.cpp
@@ -66,9 +66,12 @@ static LLDefaultChildRegistry::Register<LLScrollContainer> r("scroll_container")
#include "llscrollingpanellist.h"
#include "llcontainerview.h"
#include "llpanel.h"
+#include "lllistctrl.h"
+
static ScrollContainerRegistry::Register<LLScrollingPanelList> r1("scrolling_panel_list");
static ScrollContainerRegistry::Register<LLContainerView> r2("container_view");
static ScrollContainerRegistry::Register<LLPanel> r3("panel", &LLPanel::fromXML);
+static ScrollContainerRegistry::Register<LLListCtrl> r4("list");
LLScrollContainer::Params::Params()
: is_opaque("opaque"),
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp
index 637642cdcd..36a3b007b6 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");
@@ -118,6 +123,7 @@ LLScrollListCtrl::Params::Params()
sort_ascending("sort_ascending", true),
commit_on_keyboard_movement("commit_on_keyboard_movement", true),
heading_height("heading_height"),
+ page_lines("page_lines", 0),
background_visible("background_visible"),
draw_stripes("draw_stripes"),
column_padding("column_padding"),
@@ -140,7 +146,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
: LLUICtrl(p),
mLineHeight(0),
mScrollLines(0),
- mPageLines(0),
+ mPageLines(p.page_lines),
mMaxSelectable(0),
mAllowKeyboardMovement(TRUE),
mCommitOnKeyboardMovement(p.commit_on_keyboard_movement),
@@ -157,6 +163,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
mOnSortChangedCallback( NULL ),
mHighlightedItem(-1),
mBorder(NULL),
+ mPopupMenu(NULL),
mNumDynamicWidthColumns(0),
mTotalStaticColumnWidth(0),
mTotalColumnPadding(0),
@@ -179,7 +186,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,
@@ -189,8 +197,6 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
updateLineHeight();
- mPageLines = mLineHeight? (mItemListRect.getHeight()) / mLineHeight : 0;
-
// Init the scrollbar
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
@@ -207,7 +213,7 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
sbparams.orientation(LLScrollbar::VERTICAL);
sbparams.doc_size(getItemCount());
sbparams.doc_pos(mScrollLines);
- sbparams.page_size(mPageLines);
+ sbparams.page_size( mPageLines ? mPageLines : getItemCount() );
sbparams.change_callback(boost::bind(&LLScrollListCtrl::onScrollChange, this, _1, _2));
sbparams.follows.flags(FOLLOWS_RIGHT | FOLLOWS_TOP | FOLLOWS_BOTTOM);
sbparams.visible(false);
@@ -462,8 +468,12 @@ void LLScrollListCtrl::updateLayout()
getChildView("comment_text")->setShape(mItemListRect);
// how many lines of content in a single "page"
- mPageLines = mLineHeight? mItemListRect.getHeight() / mLineHeight : 0;
- BOOL scrollbar_visible = getItemCount() > mPageLines;
+ S32 page_lines = mLineHeight? mItemListRect.getHeight() / mLineHeight : getItemCount();
+ //if mPageLines is NOT provided display all item
+ if(mPageLines)
+ page_lines = mPageLines;
+
+ BOOL scrollbar_visible = mLineHeight * getItemCount() > mItemListRect.getHeight();
if (scrollbar_visible)
{
// provide space on the right for scrollbar
@@ -472,7 +482,7 @@ void LLScrollListCtrl::updateLayout()
mScrollbar->setOrigin(getRect().getWidth() - mBorderThickness - scrollbar_size, mItemListRect.mBottom);
mScrollbar->reshape(scrollbar_size, mItemListRect.getHeight() + (mDisplayColumnHeaders ? mHeadingHeight : 0));
- mScrollbar->setPageSize( mPageLines );
+ mScrollbar->setPageSize(page_lines);
mScrollbar->setDocSize( getItemCount() );
mScrollbar->setVisible(scrollbar_visible);
@@ -484,6 +494,9 @@ void LLScrollListCtrl::updateLayout()
void LLScrollListCtrl::fitContents(S32 max_width, S32 max_height)
{
S32 height = llmin( getRequiredRect().getHeight(), max_height );
+ if(mPageLines)
+ height = llmin( mPageLines * mLineHeight + (mDisplayColumnHeaders ? mHeadingHeight : 0), height );
+
S32 width = getRect().getWidth();
reshape( width, height );
@@ -714,6 +727,12 @@ void LLScrollListCtrl::setHeadingHeight(S32 heading_height)
updateLayout();
}
+void LLScrollListCtrl::setPageLines(S32 new_page_lines)
+{
+ mPageLines = new_page_lines;
+
+ updateLayout();
+}
BOOL LLScrollListCtrl::selectFirstItem()
{
@@ -1360,7 +1379,7 @@ void LLScrollListCtrl::drawItems()
S32 y = mItemListRect.mTop - mLineHeight;
// allow for partial line at bottom
- S32 num_page_lines = mPageLines + 1;
+ S32 num_page_lines = (mPageLines)? mPageLines : getItemCount() + 1;
LLRect item_rect;
@@ -1692,6 +1711,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;
@@ -1783,7 +1868,7 @@ LLScrollListItem* LLScrollListCtrl::hitItem( S32 x, S32 y )
mLineHeight );
// allow for partial line at bottom
- S32 num_page_lines = mPageLines + 1;
+ S32 num_page_lines = (mPageLines)? mPageLines : getItemCount() + 1;
S32 line = 0;
item_list::iterator iter;
@@ -2348,7 +2433,8 @@ void LLScrollListCtrl::scrollToShowSelected()
}
S32 lowest = mScrollLines;
- S32 highest = mScrollLines + mPageLines;
+ S32 page_lines = (mPageLines)? mPageLines : getItemCount();
+ S32 highest = mScrollLines + page_lines;
if (index < lowest)
{
@@ -2357,7 +2443,7 @@ void LLScrollListCtrl::scrollToShowSelected()
}
else if (highest <= index)
{
- setScrollPos(index - mPageLines + 1);
+ setScrollPos(index - page_lines + 1);
}
}
diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h
index 253a58ab73..5c18f85160 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
@@ -86,6 +87,7 @@ public:
// layout
Optional<S32> column_padding,
+ page_lines,
heading_height;
// sort and search behavior
@@ -270,10 +272,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);
@@ -308,6 +315,11 @@ public:
S32 getMaxContentWidth() { return mMaxContentWidth; }
void setHeadingHeight(S32 heading_height);
+ /**
+ * Sets max visible lines without scroolbar, if this value equals to 0,
+ * then display all items.
+ */
+ void setPageLines(S32 page_lines );
void setCollapseEmptyColumns(BOOL collapse);
LLScrollListItem* hitItem(S32 x,S32 y);
@@ -362,11 +374,13 @@ protected:
typedef std::deque<LLScrollListItem *> item_list;
item_list& getItemList() { return mItemList; }
+ void updateLineHeight();
+
private:
void selectPrevItem(BOOL extend_selection);
void selectNextItem(BOOL extend_selection);
void drawItems();
- void updateLineHeight();
+
void updateLineHeightInsert(LLScrollListItem* item);
void reportInvalidInput();
BOOL isRepeatedChars(const LLWString& string) const;
@@ -375,6 +389,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 +439,7 @@ private:
S32 mHighlightedItem;
class LLViewBorder* mBorder;
+ LLContextMenu *mPopupMenu;
LLWString mSearchString;
LLFrameTimer mSearchTimer;
@@ -438,6 +457,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..30bf182deb 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,49 @@ 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;
+ S32 line_height = llfloor(mDefaultFont->getLineHeight()) + mLineSpacing;
+ y -= line_height;
+ if(y < line_height)
+ break;
}
}
}
@@ -415,3 +495,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..51c259ff53 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
@@ -3477,11 +3412,8 @@ void LLTextEditor::endOfDoc()
// Sets the scrollbar from the cursor position
void LLTextEditor::updateScrollFromCursor()
{
- if (mReadOnly)
- {
- // no cursor in read only mode
- return;
- }
+ // Update scroll position even in read-only mode (when there's no cursor displayed)
+ // because startOfDoc()/endOfDoc() modify cursor position. See EXT-736.
if (!mScrollNeeded)
{
@@ -3596,14 +3528,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 +3555,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 +3678,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 +3762,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 +4116,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 +4125,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 +4211,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 +4320,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 +4581,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 +4619,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 +4640,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/lluictrl.cpp b/indra/llui/lluictrl.cpp
index 7ff942268d..1ab04054ff 100644
--- a/indra/llui/lluictrl.cpp
+++ b/indra/llui/lluictrl.cpp
@@ -257,6 +257,13 @@ BOOL LLUICtrl::handleRightMouseUp(S32 x, S32 y, MASK mask)
return handled;
}
+BOOL LLUICtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
+{
+ BOOL handled = LLView::handleDoubleClick(x, y, mask);
+ mDoubleClickSignal(this, x, y, mask);
+ return handled;
+}
+
// can't tab to children of a non-tab-stop widget
BOOL LLUICtrl::canFocusChildren() const
{
diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h
index 3e2e1f41a1..5011adcfe9 100644
--- a/indra/llui/lluictrl.h
+++ b/indra/llui/lluictrl.h
@@ -166,6 +166,7 @@ public:
/*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask);
/*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask);
/*virtual*/ BOOL handleRightMouseUp(S32 x, S32 y, MASK mask);
+ /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask);
// From LLFocusableElement
/*virtual*/ void setFocus( BOOL b );
@@ -239,6 +240,8 @@ public:
boost::signals2::connection setRightMouseDownCallback( const mouse_signal_t::slot_type& cb ) { return mRightMouseDownSignal.connect(cb); }
boost::signals2::connection setRightMouseUpCallback( const mouse_signal_t::slot_type& cb ) { return mRightMouseUpSignal.connect(cb); }
+ boost::signals2::connection setDoubleClickCallback( const mouse_signal_t::slot_type& cb ) { return mDoubleClickSignal.connect(cb); }
+
// *TODO: Deprecate; for backwards compatability only:
boost::signals2::connection setCommitCallback( boost::function<void (LLUICtrl*,void*)> cb, void* data);
boost::signals2::connection setValidateBeforeCommit( boost::function<bool (const LLSD& data)> cb );
@@ -273,6 +276,8 @@ protected:
mouse_signal_t mMouseUpSignal;
mouse_signal_t mRightMouseDownSignal;
mouse_signal_t mRightMouseUpSignal;
+
+ mouse_signal_t mDoubleClickSignal;
LLViewModelPtr mViewModel;
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/llview.cpp b/indra/llui/llview.cpp
index 4770807ac7..55d94b325f 100644
--- a/indra/llui/llview.cpp
+++ b/indra/llui/llview.cpp
@@ -2735,3 +2735,17 @@ LLView::default_widget_map_t& LLView::getDefaultWidgetMap() const
}
return *mDefaultWidgets;
}
+void LLView::notifyParent(const LLSD& info)
+{
+ LLView* parent = getParent();
+ if(parent)
+ parent->notifyParent(info);
+}
+void LLView::notifyChildren(const LLSD& info)
+{
+ for ( child_list_iter_t child_it = mChildList.begin(); child_it != mChildList.end(); ++child_it)
+ {
+ (*child_it)->notifyChildren(info);
+ }
+}
+
diff --git a/indra/llui/llview.h b/indra/llui/llview.h
index ecc6bf47da..1d48378081 100644
--- a/indra/llui/llview.h
+++ b/indra/llui/llview.h
@@ -549,6 +549,9 @@ public:
virtual void handleReshape(const LLRect& rect, bool by_user);
+ virtual void notifyParent(const LLSD& info);
+ virtual void notifyChildren(const LLSD& info);
+
protected:
void drawDebugRect();
void drawChild(LLView* childp, S32 x_offset = 0, S32 y_offset = 0, BOOL force_draw = FALSE);
diff --git a/indra/llui/tests/llurlentry_stub.cpp b/indra/llui/tests/llurlentry_stub.cpp
new file mode 100644
index 0000000000..26d1f2e067
--- /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=viewergpl$
+ *
+ * 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..610ee3349b
--- /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=viewergpl$
+ *
+ * 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..4dae49db90
--- /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=viewergpl$
+ *
+ * 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());
+ }
+}