summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/CMakeLists.txt2
-rw-r--r--indra/llui/lllayoutstack.cpp54
-rw-r--r--indra/llui/lllayoutstack.h23
-rw-r--r--indra/llui/lllineeditor.cpp3
-rw-r--r--indra/llui/lllineeditor.h3
-rw-r--r--indra/llui/llnotifications.cpp11
-rw-r--r--indra/llui/llnotifications.h1
-rw-r--r--indra/llui/llnotificationtemplate.h14
-rw-r--r--indra/llui/lltexteditor.cpp10
-rw-r--r--indra/llui/lltexteditor.h1
-rw-r--r--indra/llui/lluictrl.cpp2
-rw-r--r--indra/llui/llurlentry.cpp63
-rw-r--r--indra/llui/llurlentry.h12
-rw-r--r--indra/llui/llurlregistry.cpp1
-rw-r--r--indra/llui/llview.h9
-rw-r--r--indra/llui/llwindowshade.cpp328
-rw-r--r--indra/llui/llwindowshade.h69
-rw-r--r--indra/llui/tests/llurlentry_test.cpp149
18 files changed, 704 insertions, 51 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 864f3f699e..33ab2e93b5 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -111,6 +111,7 @@ set(llui_SOURCE_FILES
llviewmodel.cpp
llview.cpp
llviewquery.cpp
+ llwindowshade.cpp
)
set(llui_HEADER_FILES
@@ -210,6 +211,7 @@ set(llui_HEADER_FILES
llviewmodel.h
llview.h
llviewquery.h
+ llwindowshade.h
)
set_source_files_properties(${llui_HEADER_FILES}
diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp
index ac30fce392..19ac4c58a8 100644
--- a/indra/llui/lllayoutstack.cpp
+++ b/indra/llui/lllayoutstack.cpp
@@ -38,6 +38,12 @@
static LLDefaultChildRegistry::Register<LLLayoutStack> register_layout_stack("layout_stack");
static LLLayoutStack::LayoutStackRegistry::Register<LLLayoutPanel> register_layout_panel("layout_panel");
+void LLLayoutStack::OrientationNames::declareValues()
+{
+ declare("horizontal", HORIZONTAL);
+ declare("vertical", VERTICAL);
+}
+
//
// LLLayoutPanel
//
@@ -47,47 +53,47 @@ LLLayoutPanel::LLLayoutPanel(const Params& p)
mMaxDim(p.max_dim),
mAutoResize(p.auto_resize),
mUserResize(p.user_resize),
- mCollapsed(FALSE),
- mCollapseAmt(0.f),
- mVisibleAmt(1.f), // default to fully visible
- mResizeBar(NULL)
- {
+ mCollapsed(FALSE),
+ mCollapseAmt(0.f),
+ mVisibleAmt(1.f), // default to fully visible
+ mResizeBar(NULL)
+{
// panels initialized as hidden should not start out partially visible
if (!getVisible())
- {
+ {
mVisibleAmt = 0.f;
- }
- }
+ }
+}
void LLLayoutPanel::initFromParams(const Params& p)
- {
+{
LLPanel::initFromParams(p);
setFollowsNone();
- }
+}
LLLayoutPanel::~LLLayoutPanel()
- {
- // probably not necessary, but...
- delete mResizeBar;
- mResizeBar = NULL;
- }
+{
+ // probably not necessary, but...
+ delete mResizeBar;
+ mResizeBar = NULL;
+}
F32 LLLayoutPanel::getCollapseFactor(LLLayoutStack::ELayoutOrientation orientation)
- {
+{
if (orientation == LLLayoutStack::HORIZONTAL)
- {
- F32 collapse_amt =
- clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinDim / (F32)llmax(1, getRect().getWidth()));
- return mVisibleAmt * collapse_amt;
- }
- else
+ {
+ F32 collapse_amt =
+ clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, (F32)mMinDim / (F32)llmax(1, getRect().getWidth()));
+ return mVisibleAmt * collapse_amt;
+ }
+ else
{
F32 collapse_amt =
clamp_rescale(mCollapseAmt, 0.f, 1.f, 1.f, llmin(1.f, (F32)mMinDim / (F32)llmax(1, getRect().getHeight())));
return mVisibleAmt * collapse_amt;
- }
}
+}
//
// LLLayoutStack
@@ -109,7 +115,7 @@ LLLayoutStack::LLLayoutStack(const LLLayoutStack::Params& p)
mMinWidth(0),
mMinHeight(0),
mPanelSpacing(p.border_size),
- mOrientation((p.orientation() == "vertical") ? VERTICAL : HORIZONTAL),
+ mOrientation(p.orientation),
mAnimate(p.animate),
mAnimatedThisFrame(false),
mClip(p.clip),
diff --git a/indra/llui/lllayoutstack.h b/indra/llui/lllayoutstack.h
index 2fc6164d7a..4ac8ef0ee9 100644
--- a/indra/llui/lllayoutstack.h
+++ b/indra/llui/lllayoutstack.h
@@ -37,12 +37,24 @@ class LLLayoutPanel;
class LLLayoutStack : public LLView, public LLInstanceTracker<LLLayoutStack>
{
public:
+ typedef enum e_layout_orientation
+ {
+ HORIZONTAL,
+ VERTICAL
+ } ELayoutOrientation;
+
+ struct OrientationNames
+ : public LLInitParam::TypeValuesHelper<ELayoutOrientation, OrientationNames>
+ {
+ static void declareValues();
+ };
+
struct LayoutStackRegistry : public LLChildRegistry<LayoutStackRegistry>
{};
struct Params : public LLInitParam::Block<Params, LLView::Params>
{
- Mandatory<std::string> orientation;
+ Mandatory<ELayoutOrientation, OrientationNames> orientation;
Optional<S32> border_size;
Optional<bool> animate,
clip;
@@ -54,12 +66,6 @@ public:
typedef LayoutStackRegistry child_registry_t;
- typedef enum e_layout_orientation
- {
- HORIZONTAL,
- VERTICAL
- } ELayoutOrientation;
-
virtual ~LLLayoutStack();
/*virtual*/ void draw();
@@ -171,6 +177,9 @@ public:
~LLLayoutPanel();
void initFromParams(const Params& p);
+ void setMinDim(S32 value) { mMinDim = value; }
+ void setMaxDim(S32 value) { mMaxDim = value; }
+
protected:
LLLayoutPanel(const Params& p) ;
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index aed391c780..7e348656a9 100644
--- a/indra/llui/lllineeditor.cpp
+++ b/indra/llui/lllineeditor.cpp
@@ -88,6 +88,7 @@ LLLineEditor::Params::Params()
revert_on_esc("revert_on_esc", true),
commit_on_focus_lost("commit_on_focus_lost", true),
ignore_tab("ignore_tab", true),
+ is_password("is_password", false),
cursor_color("cursor_color"),
text_color("text_color"),
text_readonly_color("text_readonly_color"),
@@ -129,7 +130,7 @@ LLLineEditor::LLLineEditor(const LLLineEditor::Params& p)
mBorderThickness( 0 ),
mIgnoreArrowKeys( FALSE ),
mIgnoreTab( p.ignore_tab ),
- mDrawAsterixes( FALSE ),
+ mDrawAsterixes( p.is_password ),
mSelectAllonFocusReceived( p.select_on_focus ),
mPassDelete(FALSE),
mReadOnly(FALSE),
diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h
index ce2dfdeeb8..723423a5b9 100644
--- a/indra/llui/lllineeditor.h
+++ b/indra/llui/lllineeditor.h
@@ -85,7 +85,8 @@ public:
Optional<bool> select_on_focus,
revert_on_esc,
commit_on_focus_lost,
- ignore_tab;
+ ignore_tab,
+ is_password;
// colors
Optional<LLUIColor> cursor_color,
diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp
index c347e15792..cd0f0e36b0 100644
--- a/indra/llui/llnotifications.cpp
+++ b/indra/llui/llnotifications.cpp
@@ -82,6 +82,7 @@ LLNotificationForm::FormButton::FormButton()
LLNotificationForm::FormInput::FormInput()
: type("type"),
+ text("text"),
max_length_chars("max_length_chars"),
width("width", 0),
value("value")
@@ -421,7 +422,7 @@ LLNotificationTemplate::LLNotificationTemplate(const LLNotificationTemplate::Par
it != end_it;
++it)
{
- mUniqueContext.push_back(it->key);
+ mUniqueContext.push_back(it->value);
}
lldebugs << "notification \"" << mName << "\": tag count is " << p.tags.size() << llendl;
@@ -792,13 +793,19 @@ bool LLNotification::isEquivalentTo(LLNotificationPtr that) const
{
const LLSD& these_substitutions = this->getSubstitutions();
const LLSD& those_substitutions = that->getSubstitutions();
+ const LLSD& this_payload = this->getPayload();
+ const LLSD& that_payload = that->getPayload();
// highlander bit sez there can only be one of these
for (std::vector<std::string>::const_iterator it = mTemplatep->mUniqueContext.begin(), end_it = mTemplatep->mUniqueContext.end();
it != end_it;
++it)
{
- if (these_substitutions.get(*it).asString() != those_substitutions.get(*it).asString())
+ // if templates differ in either substitution strings or payload with the given field name
+ // then they are considered inequivalent
+ // use of get() avoids converting the LLSD value to a map as the [] operator would
+ if (these_substitutions.get(*it).asString() != those_substitutions.get(*it).asString()
+ || this_payload.get(*it).asString() != that_payload.get(*it).asString())
{
return false;
}
diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h
index f8f4469958..34d3537781 100644
--- a/indra/llui/llnotifications.h
+++ b/indra/llui/llnotifications.h
@@ -195,6 +195,7 @@ public:
Mandatory<std::string> type;
Optional<S32> width;
Optional<S32> max_length_chars;
+ Optional<std::string> text;
Optional<std::string> value;
FormInput();
diff --git a/indra/llui/llnotificationtemplate.h b/indra/llui/llnotificationtemplate.h
index 5a6ab40a2e..eff572b553 100644
--- a/indra/llui/llnotificationtemplate.h
+++ b/indra/llui/llnotificationtemplate.h
@@ -74,11 +74,13 @@ struct LLNotificationTemplate
struct UniquenessContext : public LLInitParam::Block<UniquenessContext>
{
- Mandatory<std::string> key;
+ Mandatory<std::string> value;
UniquenessContext()
- : key("key")
- {}
+ : value("value")
+ {
+ addSynonym(value, "key");
+ }
};
@@ -88,7 +90,7 @@ struct LLNotificationTemplate
// this idiom allows
// <notification unique="true">
// as well as
- // <notification> <unique> <context key=""/> </unique>...
+ // <notification> <unique> <context></context> </unique>...
Optional<bool> dummy_val;
public:
Multiple<UniquenessContext> contexts;
@@ -243,8 +245,8 @@ struct LLNotificationTemplate
// (used for things like progress indications, or repeating warnings
// like "the grid is going down in N minutes")
bool mUnique;
- // if we want to be unique only if a certain part of the payload is constant
- // specify the field names for the payload. The notification will only be
+ // if we want to be unique only if a certain part of the payload or substitutions args
+ // are constant specify the field names for the payload. The notification will only be
// combined if all of the fields named in the context are identical in the
// new and the old notification; otherwise, the notification will be
// duplicated. This is to support suppressing duplicate offers from the same
diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp
index 94bf716e7d..5a46c7c98e 100644
--- a/indra/llui/lltexteditor.cpp
+++ b/indra/llui/lltexteditor.cpp
@@ -277,6 +277,8 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) :
mHPad += UI_TEXTEDITOR_LINE_NUMBER_MARGIN;
updateRects();
}
+
+ mParseOnTheFly = TRUE;
}
void LLTextEditor::initFromParams( const LLTextEditor::Params& p)
@@ -324,8 +326,10 @@ void LLTextEditor::setText(const LLStringExplicit &utf8str, const LLStyle::Param
blockUndo();
deselect();
-
+
+ mParseOnTheFly = FALSE;
LLTextBase::setText(utf8str, input_params);
+ mParseOnTheFly = TRUE;
resetDirty();
}
@@ -1367,6 +1371,7 @@ void LLTextEditor::pastePrimary()
// paste from primary (itsprimary==true) or clipboard (itsprimary==false)
void LLTextEditor::pasteHelper(bool is_primary)
{
+ mParseOnTheFly = FALSE;
bool can_paste_it;
if (is_primary)
{
@@ -1450,6 +1455,7 @@ void LLTextEditor::pasteHelper(bool is_primary)
deselect();
onKeyStroke();
+ mParseOnTheFly = TRUE;
}
@@ -2385,7 +2391,7 @@ void LLTextEditor::loadKeywords(const std::string& filename,
void LLTextEditor::updateSegments()
{
- if (mReflowIndex < S32_MAX && mKeywords.isLoaded())
+ if (mReflowIndex < S32_MAX && mKeywords.isLoaded() && mParseOnTheFly)
{
LLFastTimer ft(FTM_SYNTAX_HIGHLIGHTING);
// HACK: No non-ascii keywords for now
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index 58ecefdccb..9e4b95003b 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -315,6 +315,7 @@ private:
BOOL mAllowEmbeddedItems;
bool mShowContextMenu;
+ bool mParseOnTheFly;
LLUUID mSourceID;
diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp
index afd60cbb3e..0a06b5e74f 100644
--- a/indra/llui/lluictrl.cpp
+++ b/indra/llui/lluictrl.cpp
@@ -827,7 +827,7 @@ LLUICtrl* LLUICtrl::findRootMostFocusRoot()
{
LLUICtrl* focus_root = NULL;
LLUICtrl* next_view = this;
- while(next_view)
+ while(next_view && next_view->hasTabStop())
{
if (next_view->isFocusRoot())
{
diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp
index e51f28e2e9..4f7b4be526 100644
--- a/indra/llui/llurlentry.cpp
+++ b/indra/llui/llurlentry.cpp
@@ -806,6 +806,69 @@ std::string LLUrlEntryPlace::getLocation(const std::string &url) const
}
//
+// LLUrlEntryRegion Describes secondlife:///app/region/REGION_NAME/X/Y/Z URLs, e.g.
+// secondlife:///app/region/Ahern/128/128/0
+//
+LLUrlEntryRegion::LLUrlEntryRegion()
+{
+ mPattern = boost::regex("secondlife:///app/region/[^/\\s]+(/\\d+)?(/\\d+)?(/\\d+)?/?",
+ boost::regex::perl|boost::regex::icase);
+ mMenuName = "menu_url_slurl.xml";
+ mTooltip = LLTrans::getString("TooltipSLURL");
+}
+
+std::string LLUrlEntryRegion::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ //
+ // we handle SLURLs in the following formats:
+ // - secondlife:///app/region/Place/X/Y/Z
+ // - secondlife:///app/region/Place/X/Y
+ // - secondlife:///app/region/Place/X
+ // - secondlife:///app/region/Place
+ //
+
+ LLSD path_array = LLURI(url).pathArray();
+ S32 path_parts = path_array.size();
+
+ if (path_parts < 3) // no region name
+ {
+ llwarns << "Failed to parse url [" << url << "]" << llendl;
+ return url;
+ }
+
+ std::string label = unescapeUrl(path_array[2]); // region name
+
+ if (path_parts > 3) // secondlife:///app/region/Place/X
+ {
+ std::string x = path_array[3];
+ label += " (" + x;
+
+ if (path_parts > 4) // secondlife:///app/region/Place/X/Y
+ {
+ std::string y = path_array[4];
+ label += "," + y;
+
+ if (path_parts > 5) // secondlife:///app/region/Place/X/Y/Z
+ {
+ std::string z = path_array[5];
+ label = label + "," + z;
+ }
+ }
+
+ label += ")";
+ }
+
+ return label;
+}
+
+std::string LLUrlEntryRegion::getLocation(const std::string &url) const
+{
+ LLSD path_array = LLURI(url).pathArray();
+ std::string region_name = unescapeUrl(path_array[2]);
+ return region_name;
+}
+
+//
// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g.,
// secondlife:///app/teleport/Ahern/50/50/50/
// x-grid-location-info://lincoln.lindenlab.com/app/teleport/Ahern/50/50/50/
diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h
index 43a667c390..1791739061 100644
--- a/indra/llui/llurlentry.h
+++ b/indra/llui/llurlentry.h
@@ -302,6 +302,18 @@ public:
};
///
+/// LLUrlEntryRegion Describes a Second Life location Url, e.g.,
+/// secondlife:///app/region/Ahern/128/128/0
+///
+class LLUrlEntryRegion : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryRegion();
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+ /*virtual*/ std::string getLocation(const std::string &url) const;
+};
+
+///
/// LLUrlEntryTeleport Describes a Second Life teleport Url, e.g.,
/// secondlife:///app/teleport/Ahern/50/50/50/
///
diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp
index 478b412d5e..523ee5d78c 100644
--- a/indra/llui/llurlregistry.cpp
+++ b/indra/llui/llurlregistry.cpp
@@ -54,6 +54,7 @@ LLUrlRegistry::LLUrlRegistry()
registerUrl(new LLUrlEntryGroup());
registerUrl(new LLUrlEntryParcel());
registerUrl(new LLUrlEntryTeleport());
+ registerUrl(new LLUrlEntryRegion());
registerUrl(new LLUrlEntryWorldMap());
registerUrl(new LLUrlEntryObjectIM());
registerUrl(new LLUrlEntryPlace());
diff --git a/indra/llui/llview.h b/indra/llui/llview.h
index a5d8e31640..d2bbd663b8 100644
--- a/indra/llui/llview.h
+++ b/indra/llui/llview.h
@@ -413,14 +413,9 @@ public:
LLControlVariable *findControl(const std::string& name);
- // Moved setValue(), getValue(), setControlValue(), setControlName(),
- // controlListener() to LLUICtrl because an LLView is NOT assumed to
- // contain a value. If that's what you want, use LLUICtrl instead.
-// virtual bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata);
-
const child_list_t* getChildList() const { return &mChildList; }
- const child_list_const_iter_t beginChild() { return mChildList.begin(); }
- const child_list_const_iter_t endChild() { return mChildList.end(); }
+ child_list_const_iter_t beginChild() const { return mChildList.begin(); }
+ child_list_const_iter_t endChild() const { return mChildList.end(); }
// LLMouseHandler functions
// Default behavior is to pass events to children
diff --git a/indra/llui/llwindowshade.cpp b/indra/llui/llwindowshade.cpp
new file mode 100644
index 0000000000..77e94385d4
--- /dev/null
+++ b/indra/llui/llwindowshade.cpp
@@ -0,0 +1,328 @@
+/**
+ * @file LLWindowShade.cpp
+ * @brief Notification dialog that slides down and optionally disabled a piece of UI
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+#include "llwindowshade.h"
+
+#include "lllayoutstack.h"
+#include "lltextbox.h"
+#include "lliconctrl.h"
+#include "llbutton.h"
+#include "llcheckboxctrl.h"
+#include "lllineeditor.h"
+
+const S32 MIN_NOTIFICATION_AREA_HEIGHT = 30;
+const S32 MAX_NOTIFICATION_AREA_HEIGHT = 100;
+
+LLWindowShade::Params::Params()
+: bg_image("bg_image"),
+ modal("modal", false),
+ text_color("text_color"),
+ can_close("can_close", true)
+{
+ mouse_opaque = false;
+}
+
+LLWindowShade::LLWindowShade(const LLWindowShade::Params& params)
+: LLUICtrl(params),
+ mNotification(params.notification),
+ mModal(params.modal),
+ mFormHeight(0),
+ mTextColor(params.text_color)
+{
+ setFocusRoot(true);
+}
+
+void LLWindowShade::initFromParams(const LLWindowShade::Params& params)
+{
+ LLUICtrl::initFromParams(params);
+
+ LLLayoutStack::Params layout_p;
+ layout_p.name = "notification_stack";
+ layout_p.rect = params.rect;
+ layout_p.follows.flags = FOLLOWS_ALL;
+ layout_p.mouse_opaque = false;
+ layout_p.orientation = LLLayoutStack::VERTICAL;
+ layout_p.border_size = 0;
+
+ LLLayoutStack* stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p);
+ addChild(stackp);
+
+ LLLayoutPanel::Params panel_p;
+ panel_p.rect = LLRect(0, 30, 800, 0);
+ panel_p.name = "notification_area";
+ panel_p.visible = false;
+ panel_p.user_resize = false;
+ panel_p.background_visible = true;
+ panel_p.bg_alpha_image = params.bg_image;
+ panel_p.auto_resize = false;
+ LLLayoutPanel* notification_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
+ stackp->addChild(notification_panel);
+
+ panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>();
+ panel_p.auto_resize = true;
+ panel_p.user_resize = false;
+ panel_p.rect = params.rect;
+ panel_p.name = "background_area";
+ panel_p.mouse_opaque = false;
+ panel_p.background_visible = false;
+ panel_p.bg_alpha_color = LLColor4(0.f, 0.f, 0.f, 0.2f);
+ LLLayoutPanel* dummy_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
+ stackp->addChild(dummy_panel);
+
+ layout_p = LLUICtrlFactory::getDefaultParams<LLLayoutStack>();
+ layout_p.rect = LLRect(0, 30, 800, 0);
+ layout_p.follows.flags = FOLLOWS_ALL;
+ layout_p.orientation = LLLayoutStack::HORIZONTAL;
+ stackp = LLUICtrlFactory::create<LLLayoutStack>(layout_p);
+ notification_panel->addChild(stackp);
+
+ panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>();
+ panel_p.rect.height = 30;
+ LLLayoutPanel* panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
+ stackp->addChild(panel);
+
+ LLIconCtrl::Params icon_p;
+ icon_p.name = "notification_icon";
+ icon_p.rect = LLRect(5, 23, 21, 8);
+ panel->addChild(LLUICtrlFactory::create<LLIconCtrl>(icon_p));
+
+ LLTextBox::Params text_p;
+ text_p.rect = LLRect(31, 20, panel->getRect().getWidth() - 5, 0);
+ text_p.follows.flags = FOLLOWS_ALL;
+ text_p.text_color = mTextColor;
+ text_p.font = LLFontGL::getFontSansSerifSmall();
+ text_p.font.style = "BOLD";
+ text_p.name = "notification_text";
+ text_p.use_ellipses = true;
+ text_p.wrap = true;
+ panel->addChild(LLUICtrlFactory::create<LLTextBox>(text_p));
+
+ panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>();
+ panel_p.auto_resize = false;
+ panel_p.user_resize = false;
+ panel_p.name="form_elements";
+ panel_p.rect = LLRect(0, 30, 130, 0);
+ LLLayoutPanel* form_elements_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
+ stackp->addChild(form_elements_panel);
+
+ if (params.can_close)
+ {
+ panel_p = LLUICtrlFactory::getDefaultParams<LLLayoutPanel>();
+ panel_p.auto_resize = false;
+ panel_p.user_resize = false;
+ panel_p.rect = LLRect(0, 30, 25, 0);
+ LLLayoutPanel* close_panel = LLUICtrlFactory::create<LLLayoutPanel>(panel_p);
+ stackp->addChild(close_panel);
+
+ LLButton::Params button_p;
+ button_p.name = "close_notification";
+ button_p.rect = LLRect(5, 23, 21, 7);
+ button_p.image_color.control="DkGray_66";
+ button_p.image_unselected.name="Icon_Close_Foreground";
+ button_p.image_selected.name="Icon_Close_Press";
+ button_p.click_callback.function = boost::bind(&LLWindowShade::onCloseNotification, this);
+
+ close_panel->addChild(LLUICtrlFactory::create<LLButton>(button_p));
+ }
+
+ LLSD payload = mNotification->getPayload();
+
+ LLNotificationFormPtr formp = mNotification->getForm();
+ LLLayoutPanel& notification_area = getChildRef<LLLayoutPanel>("notification_area");
+ notification_area.getChild<LLUICtrl>("notification_icon")->setValue(mNotification->getIcon());
+ notification_area.getChild<LLUICtrl>("notification_text")->setValue(mNotification->getMessage());
+ notification_area.getChild<LLUICtrl>("notification_text")->setToolTip(mNotification->getMessage());
+
+ LLNotificationForm::EIgnoreType ignore_type = formp->getIgnoreType();
+ LLLayoutPanel& form_elements = notification_area.getChildRef<LLLayoutPanel>("form_elements");
+ form_elements.deleteAllChildren();
+
+ const S32 FORM_PADDING_HORIZONTAL = 10;
+ const S32 FORM_PADDING_VERTICAL = 3;
+ const S32 WIDGET_HEIGHT = 24;
+ const S32 LINE_EDITOR_WIDTH = 120;
+ S32 cur_x = FORM_PADDING_HORIZONTAL;
+ S32 cur_y = FORM_PADDING_VERTICAL + WIDGET_HEIGHT;
+ S32 form_width = cur_x;
+
+ if (ignore_type != LLNotificationForm::IGNORE_NO)
+ {
+ LLCheckBoxCtrl::Params checkbox_p;
+ checkbox_p.name = "ignore_check";
+ checkbox_p.rect = LLRect(cur_x, cur_y, cur_x, cur_y - WIDGET_HEIGHT);
+ checkbox_p.label = formp->getIgnoreMessage();
+ checkbox_p.label_text.text_color = LLColor4::black;
+ checkbox_p.commit_callback.function = boost::bind(&LLWindowShade::onClickIgnore, this, _1);
+ checkbox_p.initial_value = formp->getIgnored();
+
+ LLCheckBoxCtrl* check = LLUICtrlFactory::create<LLCheckBoxCtrl>(checkbox_p);
+ check->setRect(check->getBoundingRect());
+ form_elements.addChild(check);
+ cur_x = check->getRect().mRight + FORM_PADDING_HORIZONTAL;
+ form_width = llmax(form_width, cur_x);
+ }
+
+ for (S32 i = 0; i < formp->getNumElements(); i++)
+ {
+ LLSD form_element = formp->getElement(i);
+ std::string type = form_element["type"].asString();
+ if (type == "button")
+ {
+ LLButton::Params button_p;
+ button_p.name = form_element["name"];
+ button_p.label = form_element["text"];
+ button_p.rect = LLRect(cur_x, cur_y, cur_x, cur_y - WIDGET_HEIGHT);
+ button_p.click_callback.function = boost::bind(&LLWindowShade::onClickNotificationButton, this, form_element["name"].asString());
+ button_p.auto_resize = true;
+
+ LLButton* button = LLUICtrlFactory::create<LLButton>(button_p);
+ button->autoResize();
+ form_elements.addChild(button);
+
+ if (form_element["default"].asBoolean())
+ {
+ form_elements.setDefaultBtn(button);
+ }
+
+ cur_x = button->getRect().mRight + FORM_PADDING_HORIZONTAL;
+ form_width = llmax(form_width, cur_x);
+ }
+ else if (type == "text" || type == "password")
+ {
+ // if not at beginning of line...
+ if (cur_x != FORM_PADDING_HORIZONTAL)
+ {
+ // start new line
+ cur_x = FORM_PADDING_HORIZONTAL;
+ cur_y -= WIDGET_HEIGHT + FORM_PADDING_VERTICAL;
+ }
+ LLTextBox::Params label_p;
+ label_p.name = form_element["name"].asString() + "_label";
+ label_p.rect = LLRect(cur_x, cur_y, cur_x + LINE_EDITOR_WIDTH, cur_y - WIDGET_HEIGHT);
+ label_p.initial_value = form_element["text"];
+ label_p.text_color = mTextColor;
+ label_p.font_valign = LLFontGL::VCENTER;
+ label_p.v_pad = 5;
+ LLTextBox* textbox = LLUICtrlFactory::create<LLTextBox>(label_p);
+ textbox->reshapeToFitText();
+ textbox->reshape(textbox->getRect().getWidth(), form_elements.getRect().getHeight() - 2 * FORM_PADDING_VERTICAL);
+ form_elements.addChild(textbox);
+ cur_x = textbox->getRect().mRight + FORM_PADDING_HORIZONTAL;
+
+ LLLineEditor::Params line_p;
+ line_p.name = form_element["name"];
+ line_p.keystroke_callback = boost::bind(&LLWindowShade::onEnterNotificationText, this, _1, form_element["name"].asString());
+ line_p.is_password = type == "password";
+ line_p.rect = LLRect(cur_x, cur_y, cur_x + LINE_EDITOR_WIDTH, cur_y - WIDGET_HEIGHT);
+
+ LLLineEditor* line_editor = LLUICtrlFactory::create<LLLineEditor>(line_p);
+ form_elements.addChild(line_editor);
+ form_width = llmax(form_width, cur_x + LINE_EDITOR_WIDTH + FORM_PADDING_HORIZONTAL);
+
+ // reset to start of next line
+ cur_x = FORM_PADDING_HORIZONTAL;
+ cur_y -= WIDGET_HEIGHT + FORM_PADDING_VERTICAL;
+ }
+ }
+
+ mFormHeight = form_elements.getRect().getHeight() - (cur_y - FORM_PADDING_VERTICAL) + WIDGET_HEIGHT;
+ form_elements.reshape(form_width, mFormHeight);
+ form_elements.setMinDim(form_width);
+
+ // move all form elements back onto form surface
+ S32 delta_y = WIDGET_HEIGHT + FORM_PADDING_VERTICAL - cur_y;
+ for (child_list_const_iter_t it = form_elements.getChildList()->begin(), end_it = form_elements.getChildList()->end();
+ it != end_it;
+ ++it)
+ {
+ (*it)->translate(0, delta_y);
+ }
+}
+
+void LLWindowShade::show()
+{
+ getChildRef<LLLayoutPanel>("notification_area").setVisible(true);
+ getChildRef<LLLayoutPanel>("background_area").setBackgroundVisible(mModal);
+
+ setMouseOpaque(mModal);
+}
+
+void LLWindowShade::draw()
+{
+ LLRect message_rect = getChild<LLTextBox>("notification_text")->getTextBoundingRect();
+
+ LLLayoutPanel* notification_area = getChild<LLLayoutPanel>("notification_area");
+
+ notification_area->reshape(notification_area->getRect().getWidth(),
+ llclamp(message_rect.getHeight() + 10,
+ llmin(mFormHeight, MAX_NOTIFICATION_AREA_HEIGHT),
+ MAX_NOTIFICATION_AREA_HEIGHT));
+
+ LLUICtrl::draw();
+ if (mNotification && !mNotification->isActive())
+ {
+ hide();
+ }
+}
+
+void LLWindowShade::hide()
+{
+ getChildRef<LLLayoutPanel>("notification_area").setVisible(false);
+ getChildRef<LLLayoutPanel>("background_area").setBackgroundVisible(false);
+
+ setMouseOpaque(false);
+}
+
+void LLWindowShade::onCloseNotification()
+{
+ LLNotifications::instance().cancel(mNotification);
+}
+
+void LLWindowShade::onClickIgnore(LLUICtrl* ctrl)
+{
+ bool check = ctrl->getValue().asBoolean();
+ if (mNotification && mNotification->getForm()->getIgnoreType() == LLNotificationForm::IGNORE_SHOW_AGAIN)
+ {
+ // question was "show again" so invert value to get "ignore"
+ check = !check;
+ }
+ mNotification->setIgnored(check);
+}
+
+void LLWindowShade::onClickNotificationButton(const std::string& name)
+{
+ if (!mNotification) return;
+
+ mNotificationResponse[name] = true;
+
+ mNotification->respond(mNotificationResponse);
+}
+
+void LLWindowShade::onEnterNotificationText(LLUICtrl* ctrl, const std::string& name)
+{
+ mNotificationResponse[name] = ctrl->getValue().asString();
+}
diff --git a/indra/llui/llwindowshade.h b/indra/llui/llwindowshade.h
new file mode 100644
index 0000000000..0047195929
--- /dev/null
+++ b/indra/llui/llwindowshade.h
@@ -0,0 +1,69 @@
+/**
+ * @file llwindowshade.h
+ * @brief Notification dialog that slides down and optionally disabled a piece of UI
+ *
+ * $LicenseInfo:firstyear=2006&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLWINDOWSHADE_H
+#define LL_LLWINDOWSHADE_H
+
+#include "lluictrl.h"
+#include "llnotifications.h"
+
+class LLWindowShade : public LLUICtrl
+{
+public:
+ struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>
+ {
+ Mandatory<LLNotificationPtr> notification;
+ Optional<LLUIImage*> bg_image;
+ Optional<LLUIColor> text_color;
+ Optional<bool> modal,
+ can_close;
+
+ Params();
+ };
+
+ void show();
+ /*virtual*/ void draw();
+ void hide();
+
+private:
+ friend class LLUICtrlFactory;
+
+ LLWindowShade(const Params& p);
+ void initFromParams(const Params& params);
+
+ void onCloseNotification();
+ void onClickNotificationButton(const std::string& name);
+ void onEnterNotificationText(LLUICtrl* ctrl, const std::string& name);
+ void onClickIgnore(LLUICtrl* ctrl);
+
+ LLNotificationPtr mNotification;
+ LLSD mNotificationResponse;
+ bool mModal;
+ S32 mFormHeight;
+ LLUIColor mTextColor;
+};
+
+#endif // LL_LLWINDOWSHADE_H
diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp
index 59c0826ad7..8f0a48018f 100644
--- a/indra/llui/tests/llurlentry_test.cpp
+++ b/indra/llui/tests/llurlentry_test.cpp
@@ -103,6 +103,45 @@ namespace tut
ensure_equals(testname, url, expected);
}
+ void dummyCallback(const std::string &url, const std::string &label, const std::string& icon)
+ {
+ }
+
+ void testLabel(const std::string &testname, LLUrlEntryBase &entry,
+ const char *text, const std::string &expected)
+ {
+ boost::regex regex = entry.getPattern();
+ std::string label = "";
+ 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);
+ std::string url = std::string(text+start, end-start);
+ label = entry.getLabel(url, boost::bind(dummyCallback, _1, _2, _3));
+ }
+ ensure_equals(testname, label, expected);
+ }
+
+ void testLocation(const std::string &testname, LLUrlEntryBase &entry,
+ const char *text, const std::string &expected)
+ {
+ boost::regex regex = entry.getPattern();
+ std::string location = "";
+ 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);
+ std::string url = std::string(text+start, end-start);
+ location = entry.getLocation(url);
+ }
+ ensure_equals(testname, location, expected);
+ }
+
+
template<> template<>
void object::test<1>()
{
@@ -697,4 +736,114 @@ namespace tut
"<nolink>My Object</nolink>",
"My Object");
}
+
+ template<> template<>
+ void object::test<13>()
+ {
+ //
+ // test LLUrlEntryRegion - secondlife:///app/region/<location> URLs
+ //
+ LLUrlEntryRegion url;
+
+ // Regex tests.
+ testRegex("no valid region", url,
+ "secondlife:///app/region/",
+ "");
+
+ testRegex("invalid coords", url,
+ "secondlife:///app/region/Korea2/a/b/c",
+ "secondlife:///app/region/Korea2/"); // don't count invalid coords
+
+ testRegex("Ahern (50,50,50) [1]", url,
+ "secondlife:///app/region/Ahern/50/50/50/",
+ "secondlife:///app/region/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50,50) [2]", url,
+ "XXX secondlife:///app/region/Ahern/50/50/50/ XXX",
+ "secondlife:///app/region/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50,50) [3]", url,
+ "XXX secondlife:///app/region/Ahern/50/50/50 XXX",
+ "secondlife:///app/region/Ahern/50/50/50");
+
+ testRegex("Ahern (50,50,50) multicase", url,
+ "XXX secondlife:///app/region/Ahern/50/50/50/ XXX",
+ "secondlife:///app/region/Ahern/50/50/50/");
+
+ testRegex("Ahern (50,50) [1]", url,
+ "XXX secondlife:///app/region/Ahern/50/50/ XXX",
+ "secondlife:///app/region/Ahern/50/50/");
+
+ testRegex("Ahern (50,50) [2]", url,
+ "XXX secondlife:///app/region/Ahern/50/50 XXX",
+ "secondlife:///app/region/Ahern/50/50");
+
+ // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat
+ testRegex("Region with brackets", url,
+ "XXX secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30 XXX",
+ "secondlife:///app/region/Burning%20Life%20(Hyper)/27/210/30");
+
+ // DEV-35459: SLURLs and teleport Links not parsed properly
+ testRegex("Region with quote", url,
+ "XXX secondlife:///app/region/A'ksha%20Oasis/41/166/701 XXX",
+ "secondlife:///app/region/A%27ksha%20Oasis/41/166/701");
+
+ // Rendering tests.
+ testLabel("Render /app/region/Ahern/50/50/50/", url,
+ "secondlife:///app/region/Ahern/50/50/50/",
+ "Ahern (50,50,50)");
+
+ testLabel("Render /app/region/Ahern/50/50/50", url,
+ "secondlife:///app/region/Ahern/50/50/50",
+ "Ahern (50,50,50)");
+
+ testLabel("Render /app/region/Ahern/50/50/", url,
+ "secondlife:///app/region/Ahern/50/50/",
+ "Ahern (50,50)");
+
+ testLabel("Render /app/region/Ahern/50/50", url,
+ "secondlife:///app/region/Ahern/50/50",
+ "Ahern (50,50)");
+
+ testLabel("Render /app/region/Ahern/50/", url,
+ "secondlife:///app/region/Ahern/50/",
+ "Ahern (50)");
+
+ testLabel("Render /app/region/Ahern/50", url,
+ "secondlife:///app/region/Ahern/50",
+ "Ahern (50)");
+
+ testLabel("Render /app/region/Ahern/", url,
+ "secondlife:///app/region/Ahern/",
+ "Ahern");
+
+ testLabel("Render /app/region/Ahern/ within context", url,
+ "XXX secondlife:///app/region/Ahern/ XXX",
+ "Ahern");
+
+ testLabel("Render /app/region/Ahern", url,
+ "secondlife:///app/region/Ahern",
+ "Ahern");
+
+ testLabel("Render /app/region/Ahern within context", url,
+ "XXX secondlife:///app/region/Ahern XXX",
+ "Ahern");
+
+ testLabel("Render /app/region/Product%20Engine/", url,
+ "secondlife:///app/region/Product%20Engine/",
+ "Product Engine");
+
+ testLabel("Render /app/region/Product%20Engine", url,
+ "secondlife:///app/region/Product%20Engine",
+ "Product Engine");
+
+ // Location parsing texts.
+ testLocation("Location /app/region/Ahern/50/50/50/", url,
+ "secondlife:///app/region/Ahern/50/50/50/",
+ "Ahern");
+
+ testLocation("Location /app/region/Product%20Engine", url,
+ "secondlife:///app/region/Product%20Engine",
+ "Product Engine");
+ }
}