summaryrefslogtreecommitdiff
path: root/indra/llui
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llui')
-rw-r--r--indra/llui/CMakeLists.txt4
-rw-r--r--indra/llui/llaccordionctrl.cpp40
-rw-r--r--indra/llui/llaccordionctrl.h1
-rw-r--r--indra/llui/llaccordionctrltab.cpp81
-rw-r--r--indra/llui/llaccordionctrltab.h8
-rw-r--r--indra/llui/llbutton.cpp5
-rw-r--r--indra/llui/llbutton.h1
-rw-r--r--indra/llui/llfiltereditor.cpp1
-rw-r--r--indra/llui/llflatlistview.cpp249
-rw-r--r--indra/llui/llflatlistview.h73
-rw-r--r--indra/llui/llfloater.h4
-rw-r--r--indra/llui/lliconctrl.h1
-rw-r--r--indra/llui/lllineeditor.cpp2
-rw-r--r--indra/llui/llloadingindicator.cpp133
-rw-r--r--indra/llui/llloadingindicator.h93
-rw-r--r--indra/llui/llnotifications.cpp158
-rw-r--r--indra/llui/llnotifications.h35
-rw-r--r--indra/llui/llscrolllistctrl.cpp2
-rw-r--r--indra/llui/llspinctrl.cpp5
-rw-r--r--indra/llui/llspinctrl.h1
-rw-r--r--indra/llui/lltextbase.cpp1
-rw-r--r--indra/llui/llui.cpp4
-rw-r--r--indra/llui/lluictrlfactory.cpp5
-rw-r--r--indra/llui/lluifwd.h1
-rw-r--r--indra/llui/llurlaction.cpp17
-rw-r--r--indra/llui/llurlaction.h3
-rw-r--r--indra/llui/llurlentry.cpp61
-rw-r--r--indra/llui/llurlentry.h15
-rw-r--r--indra/llui/llurlregistry.cpp6
-rw-r--r--indra/llui/tests/llurlentry_test.cpp26
30 files changed, 845 insertions, 191 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 532b6b6524..88a1652671 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -52,6 +52,7 @@ set(llui_SOURCE_FILES
llkeywords.cpp
lllayoutstack.cpp
lllineeditor.cpp
+ llloadingindicator.cpp
lllocalcliprect.cpp
llmenubutton.cpp
llmenugl.cpp
@@ -100,6 +101,7 @@ set(llui_SOURCE_FILES
lluictrlfactory.cpp
lluiimage.cpp
lluistring.cpp
+ lluitextutil.cpp
llundo.cpp
llurlaction.cpp
llurlentry.cpp
@@ -144,6 +146,7 @@ set(llui_HEADER_FILES
lllayoutstack.h
lllazyvalue.h
lllineeditor.h
+ llloadingindicator.h
lllocalcliprect.h
llmenubutton.h
llmenugl.h
@@ -195,6 +198,7 @@ set(llui_HEADER_FILES
llui.h
lluiimage.h
lluistring.h
+ lluitextutil.h
llundo.h
llurlaction.h
llurlentry.h
diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp
index cdcf780d2e..5d1d57cbb2 100644
--- a/indra/llui/llaccordionctrl.cpp
+++ b/indra/llui/llaccordionctrl.cpp
@@ -329,14 +329,34 @@ void LLAccordionCtrl::addCollapsibleCtrl(LLView* view)
LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(view);
if(!accordion_tab)
return;
- if(std::find(getChildList()->begin(),getChildList()->end(),accordion_tab) == getChildList()->end())
+ if(std::find(beginChild(), endChild(), accordion_tab) == endChild())
addChild(accordion_tab);
mAccordionTabs.push_back(accordion_tab);
-
+
accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLAccordionCtrl::onCollapseCtrlCloseOpen, this, mAccordionTabs.size() - 1) );
}
+void LLAccordionCtrl::removeCollapsibleCtrl(LLView* view)
+{
+ LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(view);
+ if(!accordion_tab)
+ return;
+
+ if(std::find(beginChild(), endChild(), accordion_tab) != endChild())
+ removeChild(accordion_tab);
+
+ for (std::vector<LLAccordionCtrlTab*>::iterator iter = mAccordionTabs.begin();
+ iter != mAccordionTabs.end(); ++iter)
+ {
+ if (accordion_tab == (*iter))
+ {
+ mAccordionTabs.erase(iter);
+ break;
+ }
+ }
+}
+
void LLAccordionCtrl::arrangeSinge()
{
S32 panel_left = BORDER_MARGIN; // Margin from left side of Splitter
@@ -648,15 +668,23 @@ S32 LLAccordionCtrl::notifyParent(const LLSD& info)
LLAccordionCtrlTab* accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]);
if(accordion_tab->hasFocus() && i>0)
{
+ bool prev_visible_tab_found = false;
while(i>0)
{
if(mAccordionTabs[--i]->getVisible())
+ {
+ prev_visible_tab_found = true;
break;
+ }
+ }
+
+ if (prev_visible_tab_found)
+ {
+ accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]);
+ accordion_tab->notify(LLSD().with("action","select_last"));
+ return 1;
}
-
- accordion_tab = dynamic_cast<LLAccordionCtrlTab*>(mAccordionTabs[i]);
- accordion_tab->notify(LLSD().with("action","select_last"));
- return 1;
+ break;
}
}
return 0;
diff --git a/indra/llui/llaccordionctrl.h b/indra/llui/llaccordionctrl.h
index 7c29e545b7..ab7d6548ca 100644
--- a/indra/llui/llaccordionctrl.h
+++ b/indra/llui/llaccordionctrl.h
@@ -92,6 +92,7 @@ public:
virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
void addCollapsibleCtrl(LLView* view);
+ void removeCollapsibleCtrl(LLView* view);
void arrange();
diff --git a/indra/llui/llaccordionctrltab.cpp b/indra/llui/llaccordionctrltab.cpp
index dfb427f293..0db2dca615 100644
--- a/indra/llui/llaccordionctrltab.cpp
+++ b/indra/llui/llaccordionctrltab.cpp
@@ -32,12 +32,13 @@
#include "linden_common.h"
-#include "lluictrl.h"
-#include "llscrollbar.h"
#include "llaccordionctrltab.h"
-#include "lllocalcliprect.h"
+#include "lllocalcliprect.h"
+#include "llscrollbar.h"
#include "lltextbox.h"
+#include "lluictrl.h"
+#include "lluitextutil.h"
static const std::string DD_BUTTON_NAME = "dd_button";
static const std::string DD_TEXTBOX_NAME = "dd_textbox";
@@ -72,7 +73,8 @@ public:
virtual BOOL postBuild();
- void setTitle(const std::string& title);
+ std::string getTitle();
+ void setTitle(const std::string& title, const std::string& hl);
virtual void onMouseEnter(S32 x, S32 y, MASK mask);
virtual void onMouseLeave(S32 x, S32 y, MASK mask);
@@ -146,10 +148,28 @@ BOOL LLAccordionCtrlTab::LLAccordionCtrlTabHeader::postBuild()
return TRUE;
}
-void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::setTitle(const std::string& title)
+std::string LLAccordionCtrlTab::LLAccordionCtrlTabHeader::getTitle()
{
if(mHeaderTextbox)
- mHeaderTextbox->setText(title);
+ {
+ return mHeaderTextbox->getText();
+ }
+ else
+ {
+ return LLStringUtil::null;
+ }
+}
+
+void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::setTitle(const std::string& title, const std::string& hl)
+{
+ if(mHeaderTextbox)
+ {
+ LLTextUtil::textboxSetHighlightedVal(
+ mHeaderTextbox,
+ LLStyle::Params(),
+ title,
+ hl);
+ }
}
void LLAccordionCtrlTab::LLAccordionCtrlTabHeader::draw()
@@ -425,6 +445,9 @@ bool LLAccordionCtrlTab::addChild(LLView* child, S32 tab_group)
setDisplayChildren(getDisplayChildren());
}
+ if (!mContainerPanel)
+ mContainerPanel = findContainerView();
+
return res;
}
@@ -433,6 +456,47 @@ void LLAccordionCtrlTab::setAccordionView(LLView* panel)
addChild(panel,0);
}
+std::string LLAccordionCtrlTab::getTitle()
+{
+ LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME);
+ if (header)
+ {
+ return header->getTitle();
+ }
+ else
+ {
+ return LLStringUtil::null;
+ }
+}
+
+void LLAccordionCtrlTab::setTitle(const std::string& title, const std::string& hl)
+{
+ LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME);
+ if (header)
+ {
+ header->setTitle(title, hl);
+ }
+}
+
+boost::signals2::connection LLAccordionCtrlTab::setFocusReceivedCallback(const focus_signal_t::slot_type& cb)
+{
+ LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME);
+ if (header)
+ {
+ return header->setFocusReceivedCallback(cb);
+ }
+ return boost::signals2::connection();
+}
+
+boost::signals2::connection LLAccordionCtrlTab::setFocusLostCallback(const focus_signal_t::slot_type& cb)
+{
+ LLAccordionCtrlTabHeader* header = findChild<LLAccordionCtrlTabHeader>(DD_HEADER_NAME);
+ if (header)
+ {
+ return header->setFocusLostCallback(cb);
+ }
+ return boost::signals2::connection();
+}
LLView* LLAccordionCtrlTab::findContainerView()
{
@@ -551,7 +615,8 @@ S32 LLAccordionCtrlTab::notifyParent(const LLSD& info)
}
//LLAccordionCtrl should rearrange accodion tab if one of accordion change its size
- getParent()->notifyParent(info);
+ if (getParent()) // A parent may not be set if tabs are added dynamically.
+ getParent()->notifyParent(info);
return 1;
}
else if(str_action == "select_prev")
@@ -871,5 +936,3 @@ BOOL LLAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask)
}
return LLUICtrl::handleToolTip(x, y, mask);
}
-
-
diff --git a/indra/llui/llaccordionctrltab.h b/indra/llui/llaccordionctrltab.h
index fb19d17e99..f5b7fd0af6 100644
--- a/indra/llui/llaccordionctrltab.h
+++ b/indra/llui/llaccordionctrltab.h
@@ -113,6 +113,14 @@ public:
void setAccordionView(LLView* panel);
LLView* getAccordionView() { return mContainerPanel; };
+ std::string getTitle();
+
+ // Set text and highlight substring in LLAccordionCtrlTabHeader
+ void setTitle(const std::string& title, const std::string& hl = LLStringUtil::null);
+
+ boost::signals2::connection setFocusReceivedCallback(const focus_signal_t::slot_type& cb);
+ boost::signals2::connection setFocusLostCallback(const focus_signal_t::slot_type& cb);
+
bool getCollapsible() {return mCollapsible;};
void setCollapsible(bool collapsible) {mCollapsible = collapsible;};
diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp
index 33c6a8b6ac..0255061b12 100644
--- a/indra/llui/llbutton.cpp
+++ b/indra/llui/llbutton.cpp
@@ -1003,6 +1003,11 @@ void LLButton::setImageDisabledSelected(LLPointer<LLUIImage> image)
mFadeWhenDisabled = TRUE;
}
+void LLButton::setImagePressed(LLPointer<LLUIImage> image)
+{
+ mImagePressed = image;
+}
+
void LLButton::setImageHoverSelected(LLPointer<LLUIImage> image)
{
mImageHoverSelected = image;
diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h
index 6a1e3a9425..a4d81ed6c3 100644
--- a/indra/llui/llbutton.h
+++ b/indra/llui/llbutton.h
@@ -246,6 +246,7 @@ public:
void setImageHoverUnselected(LLPointer<LLUIImage> image);
void setImageDisabled(LLPointer<LLUIImage> image);
void setImageDisabledSelected(LLPointer<LLUIImage> image);
+ void setImagePressed(LLPointer<LLUIImage> image);
void setCommitOnReturn(BOOL commit) { mCommitOnReturn = commit; }
BOOL getCommitOnReturn() const { return mCommitOnReturn; }
diff --git a/indra/llui/llfiltereditor.cpp b/indra/llui/llfiltereditor.cpp
index 390504234d..6c80275713 100644
--- a/indra/llui/llfiltereditor.cpp
+++ b/indra/llui/llfiltereditor.cpp
@@ -39,6 +39,7 @@
LLFilterEditor::LLFilterEditor(const LLFilterEditor::Params& p)
: LLSearchEditor(p)
{
+ setCommitOnFocusLost(FALSE); // we'll commit on every keystroke, don't re-commit when we take focus away (i.e. we go to interact with the actual results!)
}
diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp
index 82f054c4b7..6bd16c9ee6 100644
--- a/indra/llui/llflatlistview.cpp
+++ b/indra/llui/llflatlistview.cpp
@@ -1,10 +1,10 @@
/**
* @file llflatlistview.cpp
- * @brief LLFlatListView base class
+ * @brief LLFlatListView base class and extension to support messages for several cases of an empty list.
*
* $LicenseInfo:firstyear=2009&license=viewergpl$
*
- * Copyright (c) 2009, Linden Research, Inc.
+ * Copyright (c) 2009-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -297,6 +297,27 @@ void LLFlatListView::setNoItemsCommentText(const std::string& comment_text)
mNoItemsCommentTextbox->setValue(comment_text);
}
+U32 LLFlatListView::size(const bool only_visible_items) const
+{
+ if (only_visible_items)
+ {
+ U32 size = 0;
+ for (pairs_const_iterator_t
+ iter = mItemPairs.begin(),
+ iter_end = mItemPairs.end();
+ iter != iter_end; ++iter)
+ {
+ if ((*iter)->first->getVisible())
+ ++size;
+ }
+ return size;
+ }
+ else
+ {
+ return mItemPairs.size();
+ }
+}
+
void LLFlatListView::clear()
{
// do not use LLView::deleteAllChildren to avoid removing nonvisible items. drag-n-drop for ex.
@@ -358,6 +379,7 @@ LLFlatListView::LLFlatListView(const LLFlatListView::Params& p)
, mCommitOnSelectionChange(false)
, mPrevNotifyParentRect(LLRect())
, mNoItemsCommentTextbox(NULL)
+ , mIsConsecutiveSelection(false)
{
mBorderThickness = getBorderWidth();
@@ -426,7 +448,7 @@ void LLFlatListView::rearrangeItems()
{
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
- setNoItemsCommentVisible(mItemPairs.empty());
+ setNoItemsCommentVisible(0==size());
if (mItemPairs.empty()) return;
@@ -515,6 +537,7 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
return;
bool grab_items = false;
+ bool reverse = false;
pairs_list_t pairs_to_select;
// Pick out items from list between last selected and current clicked item_pair.
@@ -526,6 +549,8 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
item_pair_t* cur = *iter;
if (cur == last_selected_pair || cur == item_pair)
{
+ // We've got reverse selection if last grabed item isn't a new selection.
+ reverse = grab_items && (cur != item_pair);
grab_items = !grab_items;
// Skip last selected and current clicked item pairs.
continue;
@@ -541,26 +566,35 @@ void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
}
}
- if (select_item)
+ if (reverse)
{
- pairs_to_select.push_back(item_pair);
+ pairs_to_select.reverse();
}
+ pairs_to_select.push_back(item_pair);
+
for (pairs_iterator_t
iter = pairs_to_select.begin(),
iter_end = pairs_to_select.end();
iter != iter_end; ++iter)
{
item_pair_t* pair_to_select = *iter;
- selectItemPair(pair_to_select, true);
+ if (isSelected(pair_to_select))
+ {
+ // Item was already selected but there is a need to keep order from last selected pair to new selection.
+ // Do it here to prevent extra mCommitOnSelectionChange in selectItemPair().
+ mSelectedItemPairs.remove(pair_to_select);
+ mSelectedItemPairs.push_back(pair_to_select);
+ }
+ else
+ {
+ selectItemPair(pair_to_select, true);
+ }
}
if (!select_item)
{
- // Item was already selected but there is a need to update last selected item and its border.
- // Do it here to prevent extra mCommitOnSelectionChange in selectItemPair().
- mSelectedItemPairs.remove(item_pair);
- mSelectedItemPairs.push_back(item_pair);
+ // Update last selected item border.
mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
}
return;
@@ -728,6 +762,8 @@ bool LLFlatListView::selectItemPair(item_pair_t* item_pair, bool select)
// Stretch selected item rect to ensure it won't be clipped
mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
+ // By default mark it as not consecutive selection
+ mIsConsecutiveSelection = false;
return true;
}
@@ -744,14 +780,44 @@ LLRect LLFlatListView::getLastSelectedItemRect()
void LLFlatListView::selectFirstItem ()
{
- selectItemPair(mItemPairs.front(), true);
- ensureSelectedVisible();
+ // No items - no actions!
+ if (0 == size()) return;
+
+ // Select first visible item
+ for (pairs_iterator_t
+ iter = mItemPairs.begin(),
+ iter_end = mItemPairs.end();
+ iter != iter_end; ++iter)
+ {
+ // skip invisible items
+ if ( (*iter)->first->getVisible() )
+ {
+ selectItemPair(*iter, true);
+ ensureSelectedVisible();
+ break;
+ }
+ }
}
void LLFlatListView::selectLastItem ()
{
- selectItemPair(mItemPairs.back(), true);
- ensureSelectedVisible();
+ // No items - no actions!
+ if (0 == size()) return;
+
+ // Select last visible item
+ for (pairs_list_t::reverse_iterator
+ r_iter = mItemPairs.rbegin(),
+ r_iter_end = mItemPairs.rend();
+ r_iter != r_iter_end; ++r_iter)
+ {
+ // skip invisible items
+ if ( (*r_iter)->first->getVisible() )
+ {
+ selectItemPair(*r_iter, true);
+ ensureSelectedVisible();
+ break;
+ }
+ }
}
void LLFlatListView::ensureSelectedVisible()
@@ -769,14 +835,25 @@ void LLFlatListView::ensureSelectedVisible()
bool LLFlatListView::selectNextItemPair(bool is_up_direction, bool reset_selection)
{
// No items - no actions!
- if ( !mItemPairs.size() )
+ if ( 0 == size() )
return false;
-
- item_pair_t* to_sel_pair = NULL;
- item_pair_t* cur_sel_pair = NULL;
+ if (!mIsConsecutiveSelection)
+ {
+ // Leave only one item selected if list has not consecutive selection
+ if (mSelectedItemPairs.size() && !reset_selection)
+ {
+ item_pair_t* cur_sel_pair = mSelectedItemPairs.back();
+ resetSelection();
+ selectItemPair (cur_sel_pair, true);
+ }
+ }
+
if ( mSelectedItemPairs.size() )
{
+ item_pair_t* to_sel_pair = NULL;
+ item_pair_t* cur_sel_pair = NULL;
+
// Take the last selected pair
cur_sel_pair = mSelectedItemPairs.back();
// Bases on given direction choose next item to select
@@ -810,42 +887,46 @@ bool LLFlatListView::selectNextItemPair(bool is_up_direction, bool reset_selecti
}
}
}
+
+ if ( to_sel_pair )
+ {
+ bool select = true;
+ if ( reset_selection )
+ {
+ // Reset current selection if we were asked about it
+ resetSelection();
+ }
+ else
+ {
+ // If item already selected and no reset request than we should deselect last selected item.
+ select = (mSelectedItemPairs.end() == std::find(mSelectedItemPairs.begin(), mSelectedItemPairs.end(), to_sel_pair));
+ }
+ // Select/Deselect next item
+ selectItemPair(select ? to_sel_pair : cur_sel_pair, select);
+ // Mark it as consecutive selection
+ mIsConsecutiveSelection = true;
+ return true;
+ }
}
else
{
// If there weren't selected items then choose the first one bases on given direction
- cur_sel_pair = (is_up_direction) ? mItemPairs.back() : mItemPairs.front();
// Force selection to first item
- to_sel_pair = cur_sel_pair;
- }
-
-
- if ( to_sel_pair )
- {
- bool select = true;
-
- if ( reset_selection )
- {
- // Reset current selection if we were asked about it
- resetSelection();
- }
+ if (is_up_direction)
+ selectLastItem();
else
- {
- // If item already selected and no reset request than we should deselect last selected item.
- select = (mSelectedItemPairs.end() == std::find(mSelectedItemPairs.begin(), mSelectedItemPairs.end(), to_sel_pair));
- }
-
- // Select/Deselect next item
- selectItemPair(select ? to_sel_pair : cur_sel_pair, select);
-
+ selectFirstItem();
+ // Mark it as consecutive selection
+ mIsConsecutiveSelection = true;
return true;
}
+
return false;
}
BOOL LLFlatListView::canSelectAll() const
{
- return !mItemPairs.empty() && mAllowSelection && mMultipleSelection;
+ return 0 != size() && mAllowSelection && mMultipleSelection;
}
void LLFlatListView::selectAll()
@@ -1122,4 +1203,90 @@ void LLFlatListView::detachItems(std::vector<LLPanel*>& detached_items)
}
}
+
+/************************************************************************/
+/* LLFlatListViewEx implementation */
+/************************************************************************/
+LLFlatListViewEx::Params::Params()
+: no_items_msg("no_items_msg")
+, no_filtered_items_msg("no_filtered_items_msg")
+{
+
+}
+
+LLFlatListViewEx::LLFlatListViewEx(const Params& p)
+: LLFlatListView(p)
+, mNoFilteredItemsMsg(p.no_filtered_items_msg)
+, mNoItemsMsg(p.no_items_msg)
+{
+
+}
+
+void LLFlatListViewEx::updateNoItemsMessage(const std::string& filter_string)
+{
+ bool items_filtered = !filter_string.empty();
+ if (items_filtered)
+ {
+ // items were filtered
+ LLStringUtil::format_map_t args;
+ args["[SEARCH_TERM]"] = LLURI::escape(filter_string);
+ std::string text = mNoFilteredItemsMsg;
+ LLStringUtil::format(text, args);
+ setNoItemsCommentText(text);
+ }
+ else
+ {
+ // list does not contain any items at all
+ setNoItemsCommentText(mNoItemsMsg);
+ }
+
+}
+
+void LLFlatListViewEx::setFilterSubString(const std::string& filter_str)
+{
+ if (0 != LLStringUtil::compareInsensitive(filter_str, mFilterSubString))
+ {
+ mFilterSubString = filter_str;
+ updateNoItemsMessage(mFilterSubString);
+ filterItems();
+ }
+}
+
+void LLFlatListViewEx::filterItems()
+{
+ typedef std::vector <LLPanel*> item_panel_list_t;
+
+ std::string cur_filter = mFilterSubString;
+ LLStringUtil::toUpper(cur_filter);
+
+ LLSD action;
+ action.with("match_filter", cur_filter);
+
+ item_panel_list_t items;
+ getItems(items);
+
+ for (item_panel_list_t::iterator
+ iter = items.begin(),
+ iter_end = items.end();
+ iter != iter_end; ++iter)
+ {
+ LLPanel* pItem = (*iter);
+ // 0 signifies that filter is matched,
+ // i.e. we don't hide items that don't support 'match_filter' action, separators etc.
+ if (0 == pItem->notify(action))
+ {
+ pItem->setVisible(true);
+ }
+ else
+ {
+ // TODO: implement (re)storing of current selection.
+ selectItem(pItem, false);
+ pItem->setVisible(false);
+ }
+ }
+
+ rearrangeItems();
+ notifyParentItemsRectChanged();
+}
+
//EOF
diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h
index e3c07e811f..f4e0426f15 100644
--- a/indra/llui/llflatlistview.h
+++ b/indra/llui/llflatlistview.h
@@ -1,10 +1,10 @@
/**
* @file llflatlistview.h
- * @brief LLFlatListView base class
+ * @brief LLFlatListView base class and extension to support messages for several cases of an empty list.
*
* $LicenseInfo:firstyear=2009&license=viewergpl$
*
- * Copyright (c) 2009, Linden Research, Inc.
+ * Copyright (c) 2009-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -264,9 +264,8 @@ public:
/** Get number of selected items in the list */
U32 numSelected() const {return mSelectedItemPairs.size(); }
- /** Get number of items in the list */
- U32 size() const { return mItemPairs.size(); }
-
+ /** Get number of (visible) items in the list */
+ U32 size(const bool only_visible_items = true) const;
/** Removes all items from the list */
virtual void clear();
@@ -411,6 +410,8 @@ private:
bool mKeepOneItemSelected;
+ bool mIsConsecutiveSelection;
+
/** All pairs of the list */
pairs_list_t mItemPairs;
@@ -430,4 +431,66 @@ private:
commit_signal_t mOnReturnSignal;
};
+/**
+ * Extends LLFlatListView functionality to show different messages when there are no items in the
+ * list depend on whether they are filtered or not.
+ *
+ * Class provides one message per case of empty list.
+ * It also provides protected updateNoItemsMessage() method to be called each time when derived list
+ * is changed to update base mNoItemsCommentTextbox value.
+ *
+ * It is implemented to avoid duplication of this functionality in concrete implementations of the
+ * lists. It is intended to be used as a base class for lists which should support two different
+ * messages for empty state. Can be improved to support more than two messages via state-to-message map.
+ */
+class LLFlatListViewEx : public LLFlatListView
+{
+public:
+ struct Params : public LLInitParam::Block<Params, LLFlatListView::Params>
+ {
+ /**
+ * Contains a message for empty list when it does not contain any items at all.
+ */
+ Optional<std::string> no_items_msg;
+
+ /**
+ * Contains a message for empty list when its items are removed by filtering.
+ */
+ Optional<std::string> no_filtered_items_msg;
+ Params();
+ };
+
+ // *WORKAROUND: two methods to overload appropriate Params due to localization issue:
+ // no_items_msg & no_filtered_items_msg attributes are not defined as translatable in VLT. See EXT-5931
+ void setNoItemsMsg(const std::string& msg) { mNoItemsMsg = msg; }
+ void setNoFilteredItemsMsg(const std::string& msg) { mNoFilteredItemsMsg = msg; }
+
+ /**
+ * Sets up new filter string and filters the list.
+ */
+ void setFilterSubString(const std::string& filter_str);
+
+ /**
+ * Filters the list, rearranges and notifies parent about shape changes.
+ * Derived classes may want to overload rearrangeItems() to exclude repeated separators after filtration.
+ */
+ void filterItems();
+
+protected:
+ LLFlatListViewEx(const Params& p);
+
+ /**
+ * Applies a message for empty list depend on passed argument.
+ *
+ * @param filter_string - if is not empty, message for filtered items will be set, otherwise for
+ * completely empty list. Value of filter string will be passed as search_term in SLURL.
+ */
+ void updateNoItemsMessage(const std::string& filter_string);
+
+private:
+ std::string mNoFilteredItemsMsg;
+ std::string mNoItemsMsg;
+ std::string mFilterSubString;
+};
+
#endif
diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h
index 403723d9d8..444711de81 100644
--- a/indra/llui/llfloater.h
+++ b/indra/llui/llfloater.h
@@ -432,11 +432,15 @@ private:
class LLFloaterView : public LLUICtrl
{
+public:
+ struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>{};
+
protected:
LLFloaterView (const Params& p);
friend class LLUICtrlFactory;
public:
+
/*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE);
void reshapeFloater(S32 width, S32 height, BOOL called_from_parent, BOOL adjust_vertical);
diff --git a/indra/llui/lliconctrl.h b/indra/llui/lliconctrl.h
index 66368f979b..7e37600409 100644
--- a/indra/llui/lliconctrl.h
+++ b/indra/llui/lliconctrl.h
@@ -73,6 +73,7 @@ public:
std::string getImageName() const;
void setColor(const LLColor4& color) { mColor = color; }
+ void setImage(LLPointer<LLUIImage> image) { mImagep = image; }
private:
void setIconImageDrawSize() ;
diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp
index 843f72d8e4..45f9de8e8d 100644
--- a/indra/llui/lllineeditor.cpp
+++ b/indra/llui/lllineeditor.cpp
@@ -1440,7 +1440,7 @@ BOOL LLLineEditor::handleUnicodeCharHere(llwchar uni_char)
BOOL LLLineEditor::canDoDelete() const
{
- return ( !mReadOnly && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) );
+ return ( !mReadOnly && mText.length() > 0 && (!mPassDelete || (hasSelection() || (getCursor() < mText.length()))) );
}
void LLLineEditor::doDelete()
diff --git a/indra/llui/llloadingindicator.cpp b/indra/llui/llloadingindicator.cpp
new file mode 100644
index 0000000000..f8b029e19c
--- /dev/null
+++ b/indra/llui/llloadingindicator.cpp
@@ -0,0 +1,133 @@
+/**
+ * @file llloadingindicator.cpp
+ * @brief Perpetual loading indicator
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ *
+ * Copyright (c) 2010, 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 "llloadingindicator.h"
+
+// Linden library includes
+#include "llsingleton.h"
+
+// Project includes
+#include "lluictrlfactory.h"
+#include "lluiimage.h"
+
+static LLDefaultChildRegistry::Register<LLLoadingIndicator> r("loading_indicator");
+
+///////////////////////////////////////////////////////////////////////////////
+// LLLoadingIndicator::Data class
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Pre-loaded images shared by all instances of the widget
+ */
+class LLLoadingIndicator::Data: public LLSingleton<LLLoadingIndicator::Data>
+{
+public:
+ /*virtual*/ void initSingleton(); // from LLSingleton
+
+ LLPointer<LLUIImage> getNextImage(S8& idx) const;
+ U8 getImagesCount() const { return NIMAGES; }
+private:
+
+ static const U8 NIMAGES = 12;
+ LLPointer<LLUIImage> mImages[NIMAGES];
+};
+
+// virtual
+// Called right after the instance gets constructed.
+void LLLoadingIndicator::Data::initSingleton()
+{
+ // Load images.
+ for (U8 i = 0; i < NIMAGES; ++i)
+ {
+ std::string img_name = llformat("Progress_%d", i+1);
+ mImages[i] = LLUI::getUIImage(img_name, 0);
+ llassert(mImages[i]);
+ }
+}
+
+LLPointer<LLUIImage> LLLoadingIndicator::Data::getNextImage(S8& idx) const
+{
+ // Calculate next index, performing array bounds checking.
+ idx = (idx >= NIMAGES || idx < 0) ? 0 : (idx + 1) % NIMAGES;
+ return mImages[idx];
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// LLLoadingIndicator class
+///////////////////////////////////////////////////////////////////////////////
+
+LLLoadingIndicator::LLLoadingIndicator(const Params& p)
+: LLUICtrl(p)
+ , mRotationsPerSec(p.rotations_per_sec > 0 ? p.rotations_per_sec : 1.0f)
+ , mCurImageIdx(-1)
+{
+ // Select initial image.
+ mCurImagep = Data::instance().getNextImage(mCurImageIdx);
+
+ // Start timer for switching images.
+ start();
+}
+
+void LLLoadingIndicator::draw()
+{
+ // Time to switch to the next image?
+ if (mImageSwitchTimer.getStarted() && mImageSwitchTimer.hasExpired())
+ {
+ // Switch to the next image.
+ mCurImagep = Data::instance().getNextImage(mCurImageIdx);
+
+ // Restart timer.
+ start();
+ }
+
+ // Draw current image.
+ if( mCurImagep.notNull() )
+ {
+ mCurImagep->draw(getLocalRect(), LLColor4::white % getDrawContext().mAlpha);
+ }
+
+ LLUICtrl::draw();
+}
+
+void LLLoadingIndicator::stop()
+{
+ mImageSwitchTimer.stop();
+}
+
+void LLLoadingIndicator::start()
+{
+ mImageSwitchTimer.start();
+ F32 period = 1.0f / (Data::instance().getImagesCount() * mRotationsPerSec);
+ mImageSwitchTimer.setTimerExpirySec(period);
+}
diff --git a/indra/llui/llloadingindicator.h b/indra/llui/llloadingindicator.h
new file mode 100644
index 0000000000..32dd1fead8
--- /dev/null
+++ b/indra/llui/llloadingindicator.h
@@ -0,0 +1,93 @@
+/**
+ * @file llloadingindicator.h
+ * @brief Perpetual loading indicator
+ *
+ * $LicenseInfo:firstyear=2010&license=viewergpl$
+ *
+ * Copyright (c) 2010, 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_LLLOADINGINDICATOR_H
+#define LL_LLLOADINGINDICATOR_H
+
+#include "lluictrl.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// class LLLoadingIndicator
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Perpetual loading indicator (a la MacOSX or YouTube)
+ *
+ * Number of rotations per second can be overriden
+ * with the "roations_per_sec" parameter.
+ *
+ * Can start/stop spinning.
+ *
+ * @see start()
+ * @see stop()
+ */
+class LLLoadingIndicator
+: public LLUICtrl
+{
+ LOG_CLASS(LLLoadingIndicator);
+public:
+ struct Params : public LLInitParam::Block<Params, LLUICtrl::Params>
+ {
+ Optional<F32> rotations_per_sec;
+ Params()
+ : rotations_per_sec("rotations_per_sec", 1.0f)
+ {}
+ };
+
+ virtual ~LLLoadingIndicator() {}
+
+ // llview overrides
+ virtual void draw();
+
+ /**
+ * Stop spinning.
+ */
+ void stop();
+
+ /**
+ * Start spinning.
+ */
+ void start();
+
+private:
+ LLLoadingIndicator(const Params&);
+ friend class LLUICtrlFactory;
+
+ class Data;
+
+ F32 mRotationsPerSec;
+ S8 mCurImageIdx;
+ LLPointer<LLUIImage> mCurImagep;
+ LLFrameTimer mImageSwitchTimer;
+};
+
+#endif // LL_LLLOADINGINDICATOR_H
diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp
index 65ef53443b..7b8f51ae3c 100644
--- a/indra/llui/llnotifications.cpp
+++ b/indra/llui/llnotifications.cpp
@@ -48,122 +48,38 @@
const std::string NOTIFICATION_PERSIST_VERSION = "0.93";
-// local channel for notification history
-class LLNotificationHistoryChannel : public LLNotificationChannel
+// Local channel for persistent notifications
+// Stores only persistent notifications.
+// Class users can use connectChanged() to process persistent notifications
+// (see LLNotificationStorage for example).
+class LLPersistentNotificationChannel : public LLNotificationChannel
{
- LOG_CLASS(LLNotificationHistoryChannel);
+ LOG_CLASS(LLPersistentNotificationChannel);
public:
- LLNotificationHistoryChannel(const std::string& filename) :
- LLNotificationChannel("History", "Visible", &historyFilter, LLNotificationComparators::orderByUUID()),
- mFileName(filename)
+ LLPersistentNotificationChannel() :
+ LLNotificationChannel("Persistent", "Visible", &notificationFilter, LLNotificationComparators::orderByUUID())
{
- connectChanged(boost::bind(&LLNotificationHistoryChannel::historyHandler, this, _1));
- loadPersistentNotifications();
}
private:
- bool historyHandler(const LLSD& payload)
- {
- // we ignore "load" messages, but rewrite the persistence file on any other
- std::string sigtype = payload["sigtype"];
- if (sigtype != "load")
- {
- savePersistentNotifications();
- }
- return false;
- }
-
- // The history channel gets all notifications except those that have been cancelled
- static bool historyFilter(LLNotificationPtr pNotification)
- {
- return !pNotification->isCancelled();
- }
- void savePersistentNotifications()
+ // The channel gets all persistent notifications except those that have been canceled
+ static bool notificationFilter(LLNotificationPtr pNotification)
{
- /* NOTE: As of 2009-11-09 the reload of notifications on startup does not
- work, and has not worked for months. Skip saving notifications until the
- read can be fixed, because this hits the disk once per notification and
- causes log spam. James
-
- llinfos << "Saving open notifications to " << mFileName << llendl;
+ bool handle_notification = false;
- llofstream notify_file(mFileName.c_str());
- if (!notify_file.is_open())
- {
- llwarns << "Failed to open " << mFileName << llendl;
- return;
- }
-
- LLSD output;
- output["version"] = NOTIFICATION_PERSIST_VERSION;
- LLSD& data = output["data"];
-
- for (LLNotificationSet::iterator it = mItems.begin(); it != mItems.end(); ++it)
- {
- if (!LLNotifications::instance().templateExists((*it)->getName())) continue;
+ handle_notification = pNotification->isPersistent()
+ && !pNotification->isCancelled();
- // only store notifications flagged as persisting
- LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate((*it)->getName());
- if (!templatep->mPersist) continue;
-
- data.append((*it)->asLLSD());
- }
-
- LLPointer<LLSDFormatter> formatter = new LLSDXMLFormatter();
- formatter->format(output, notify_file, LLSDFormatter::OPTIONS_PRETTY);
- */
+ return handle_notification;
}
- void loadPersistentNotifications()
- {
- llinfos << "Loading open notifications from " << mFileName << llendl;
-
- llifstream notify_file(mFileName.c_str());
- if (!notify_file.is_open())
- {
- llwarns << "Failed to open " << mFileName << llendl;
- return;
- }
-
- LLSD input;
- LLPointer<LLSDParser> parser = new LLSDXMLParser();
- if (parser->parse(notify_file, input, LLSDSerialize::SIZE_UNLIMITED) < 0)
- {
- llwarns << "Failed to parse open notifications" << llendl;
- return;
- }
-
- if (input.isUndefined()) return;
- std::string version = input["version"];
- if (version != NOTIFICATION_PERSIST_VERSION)
- {
- llwarns << "Bad open notifications version: " << version << llendl;
- return;
- }
- LLSD& data = input["data"];
- if (data.isUndefined()) return;
-
- LLNotifications& instance = LLNotifications::instance();
- for (LLSD::array_const_iterator notification_it = data.beginArray();
- notification_it != data.endArray();
- ++notification_it)
- {
- instance.add(LLNotificationPtr(new LLNotification(*notification_it)));
- }
- }
-
- //virtual
void onDelete(LLNotificationPtr pNotification)
{
- // we want to keep deleted notifications in our log
+ // we want to keep deleted notifications in our log, otherwise some
+ // notifications will be lost on exit.
mItems.insert(pNotification);
-
- return;
}
-
-private:
- std::string mFileName;
};
bool filterIgnoredNotifications(LLNotificationPtr notification)
@@ -417,6 +333,10 @@ LLNotification::LLNotification(const LLNotification::Params& p) :
mTemporaryResponder = true;
}
+ else if(p.functor.responder.isChosen())
+ {
+ mResponder = p.functor.responder;
+ }
if(p.responder.isProvided())
{
@@ -462,6 +382,12 @@ LLSD LLNotification::asLLSD()
output["priority"] = (S32)mPriority;
output["responseFunctor"] = mResponseFunctorName;
output["reusable"] = mIsReusable;
+
+ if(mResponder)
+ {
+ output["responder"] = mResponder->asLLSD();
+ }
+
return output;
}
@@ -571,12 +497,20 @@ void LLNotification::respond(const LLSD& response)
// *TODO may remove mRespondedTo and use mResponce.isDefined() in isRespondedTo()
mRespondedTo = true;
mResponse = response;
- // look up the functor
- LLNotificationFunctorRegistry::ResponseFunctor functor =
- LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName);
- // and then call it
- functor(asLLSD(), response);
-
+
+ if(mResponder)
+ {
+ mResponder->handleRespond(asLLSD(), response);
+ }
+ else
+ {
+ // look up the functor
+ LLNotificationFunctorRegistry::ResponseFunctor functor =
+ LLNotificationFunctorRegistry::instance().getFunctor(mResponseFunctorName);
+ // and then call it
+ functor(asLLSD(), response);
+ }
+
if (mTemporaryResponder && !isReusable())
{
LLNotificationFunctorRegistry::instance().unregisterFunctor(mResponseFunctorName);
@@ -621,6 +555,11 @@ void LLNotification::setResponseFunctor(const LLNotificationFunctorRegistry::Res
LLNotificationFunctorRegistry::instance().registerFunctor(mResponseFunctorName, cb);
}
+void LLNotification::setResponseFunctor(const LLNotificationResponderPtr& responder)
+{
+ mResponder = responder;
+}
+
bool LLNotification::payloadContainsAll(const std::vector<std::string>& required_fields) const
{
for(std::vector<std::string>::const_iterator required_fields_it = required_fields.begin();
@@ -1116,12 +1055,9 @@ void LLNotifications::createDefaultChannels()
LLNotificationChannel::buildChannel("Visible", "Ignore",
&LLNotificationFilters::includeEverything);
- // create special history channel
- //std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_PER_SL_ACCOUNT, "open_notifications.xml" );
- // use ^^^ when done debugging notifications serialization
- std::string notifications_log_file = gDirUtilp->getExpandedFilename ( LL_PATH_USER_SETTINGS, "open_notifications.xml" );
+ // create special persistent notification channel
// this isn't a leak, don't worry about the empty "new"
- new LLNotificationHistoryChannel(notifications_log_file);
+ new LLPersistentNotificationChannel();
// connect action methods to these channels
LLNotifications::instance().getChannel("Expiration")->
diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h
index 1799ca65b7..c942a32512 100644
--- a/indra/llui/llnotifications.h
+++ b/indra/llui/llnotifications.h
@@ -116,8 +116,23 @@ typedef enum e_notification_priority
NOTIFICATION_PRIORITY_CRITICAL
} ENotificationPriority;
+class LLNotificationResponderInterface
+{
+public:
+ LLNotificationResponderInterface(){};
+ virtual ~LLNotificationResponderInterface(){};
+
+ virtual void handleRespond(const LLSD& notification, const LLSD& response) = 0;
+
+ virtual LLSD asLLSD() = 0;
+
+ virtual void fromLLSD(const LLSD& params) = 0;
+};
+
typedef boost::function<void (const LLSD&, const LLSD&)> LLNotificationResponder;
+typedef boost::shared_ptr<LLNotificationResponderInterface> LLNotificationResponderPtr;
+
typedef LLFunctorRegistry<LLNotificationResponder> LLNotificationFunctorRegistry;
typedef LLFunctorRegistration<LLNotificationResponder> LLNotificationFunctorRegistration;
@@ -303,10 +318,12 @@ public:
{
Alternative<std::string> name;
Alternative<LLNotificationFunctorRegistry::ResponseFunctor> function;
+ Alternative<LLNotificationResponderPtr> responder;
Functor()
: name("functor_name"),
- function("functor")
+ function("functor"),
+ responder("responder")
{}
};
Optional<Functor> functor;
@@ -349,12 +366,13 @@ private:
bool mIgnored;
ENotificationPriority mPriority;
LLNotificationFormPtr mForm;
- void* mResponderObj;
+ void* mResponderObj; // TODO - refactor/remove this field
bool mIsReusable;
-
+ LLNotificationResponderPtr mResponder;
+
// a reference to the template
LLNotificationTemplatePtr mTemplatep;
-
+
/*
We want to be able to store and reload notifications so that they can survive
a shutdown/restart of the client. So we can't simply pass in callbacks;
@@ -393,6 +411,8 @@ public:
void setResponseFunctor(const LLNotificationFunctorRegistry::ResponseFunctor& cb);
+ void setResponseFunctor(const LLNotificationResponderPtr& responder);
+
typedef enum e_response_template_type
{
WITHOUT_DEFAULT_BUTTON,
@@ -459,7 +479,12 @@ public:
{
return mTemplatep->mName;
}
-
+
+ bool isPersistent() const
+ {
+ return mTemplatep->mPersist;
+ }
+
const LLUUID& id() const
{
return mId;
diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp
index db0f2bd6e2..94eade06ad 100644
--- a/indra/llui/llscrolllistctrl.cpp
+++ b/indra/llui/llscrolllistctrl.cpp
@@ -282,6 +282,8 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p)
text_p.border_visible(false);
text_p.rect(mItemListRect);
text_p.follows.flags(FOLLOWS_ALL);
+ // word wrap was added accroding to the EXT-6841
+ text_p.wrap(true);
addChild(LLUICtrlFactory::create<LLTextBox>(text_p));
}
diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp
index 491cd7b6f3..b47f21ed8a 100644
--- a/indra/llui/llspinctrl.cpp
+++ b/indra/llui/llspinctrl.cpp
@@ -457,3 +457,8 @@ BOOL LLSpinCtrl::handleKeyHere(KEY key, MASK mask)
return FALSE;
}
+BOOL LLSpinCtrl::handleDoubleClick(S32 x, S32 y, MASK mask)
+{
+ // just treat a double click as a second click
+ return handleMouseDown(x, y, mask);
+}
diff --git a/indra/llui/llspinctrl.h b/indra/llui/llspinctrl.h
index 00d6f86f83..06201255d2 100644
--- a/indra/llui/llspinctrl.h
+++ b/indra/llui/llspinctrl.h
@@ -94,6 +94,7 @@ public:
virtual BOOL handleScrollWheel(S32 x,S32 y,S32 clicks);
virtual BOOL handleKeyHere(KEY key, MASK mask);
+ virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask);
void onEditorCommit(const LLSD& data);
static void onEditorGainFocus(LLFocusableElement* caller, void *userdata);
diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp
index e08026eaf4..390ec234d3 100644
--- a/indra/llui/lltextbase.cpp
+++ b/indra/llui/lltextbase.cpp
@@ -1507,6 +1507,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_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.ShowProfile", boost::bind(&LLUrlAction::showProfile, url));
registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url));
registrar.add("Url.CopyLabel", boost::bind(&LLUrlAction::copyLabelToClipboard, url));
registrar.add("Url.CopyUrl", boost::bind(&LLUrlAction::copyURLToClipboard, url));
diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp
index f9a4ed7285..bf12384a28 100644
--- a/indra/llui/llui.cpp
+++ b/indra/llui/llui.cpp
@@ -56,6 +56,7 @@
#include "llfloaterreg.h"
#include "llmenugl.h"
#include "llmenubutton.h"
+#include "llloadingindicator.h"
#include "llwindow.h"
// for registration
@@ -94,7 +95,10 @@ std::list<std::string> gUntranslated;
static LLDefaultChildRegistry::Register<LLFilterEditor> register_filter_editor("filter_editor");
static LLDefaultChildRegistry::Register<LLFlyoutButton> register_flyout_button("flyout_button");
static LLDefaultChildRegistry::Register<LLSearchEditor> register_search_editor("search_editor");
+
+// register other widgets which otherwise may not be linked in
static LLDefaultChildRegistry::Register<LLMenuButton> register_menu_button("menu_button");
+static LLDefaultChildRegistry::Register<LLLoadingIndicator> register_loading_indicator("loading_indicator");
//
diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp
index 27237800d4..930dadc377 100644
--- a/indra/llui/lluictrlfactory.cpp
+++ b/indra/llui/lluictrlfactory.cpp
@@ -435,7 +435,10 @@ void LLUICtrlFactory::registerWidget(const std::type_info* widget_type, const st
std::string* existing_tag = LLWidgetNameRegistry::instance().getValue(param_block_type);
if (existing_tag != NULL && *existing_tag != tag)
{
- llerrs << "Duplicate entry for T::Params, try creating empty param block in derived classes that inherit T::Params" << llendl;
+ std::cerr << "Duplicate entry for T::Params, try creating empty param block in derived classes that inherit T::Params" << std::endl;
+ // forcing crash here
+ char* foo = 0;
+ *foo = 1;
}
LLWidgetNameRegistry ::instance().defaultRegistrar().add(param_block_type, tag);
// associate widget type with factory function
diff --git a/indra/llui/lluifwd.h b/indra/llui/lluifwd.h
index f99bb39fdd..d6047b943c 100644
--- a/indra/llui/lluifwd.h
+++ b/indra/llui/lluifwd.h
@@ -39,6 +39,7 @@ class LLComboBox;
class LLDragHandle;
class LLFloater;
class LLIconCtrl;
+class LLLoadingIndicator;
class LLLineEditor;
class LLMenuGL;
class LLPanel;
diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp
index 679db5e39b..2f13a56b42 100644
--- a/indra/llui/llurlaction.cpp
+++ b/indra/llui/llurlaction.cpp
@@ -146,3 +146,20 @@ void LLUrlAction::copyLabelToClipboard(std::string url)
LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(match.getLabel()));
}
}
+
+void LLUrlAction::showProfile(std::string url)
+{
+ // Get id from 'secondlife:///app/{cmd}/{id}/{action}'
+ // and show its profile
+ LLURI uri(url);
+ LLSD path_array = uri.pathArray();
+ if (path_array.size() == 4)
+ {
+ std::string id_str = path_array.get(2).asString();
+ if (LLUUID::validate(id_str))
+ {
+ std::string cmd_str = path_array.get(1).asString();
+ executeSLURL("secondlife:///app/" + cmd_str + "/" + id_str + "/about");
+ }
+ }
+}
diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h
index 4830cf27ef..b96faf1b3f 100644
--- a/indra/llui/llurlaction.h
+++ b/indra/llui/llurlaction.h
@@ -79,6 +79,9 @@ public:
/// copy a Url to the clipboard
static void copyURLToClipboard(std::string url);
+ /// if the Url specifies an SL command in the form like 'app/{cmd}/{id}/*', show its profile
+ static void showProfile(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));
diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp
index 736de651da..fd56a87345 100644
--- a/indra/llui/llurlentry.cpp
+++ b/indra/llui/llurlentry.cpp
@@ -41,6 +41,9 @@
#include "lltrans.h"
#include "lluicolortable.h"
+#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))"
+
+
LLUrlEntryBase::LLUrlEntryBase() :
mColor(LLUIColorTable::instance().getColor("HTMLLinkColor")),
mDisabledLink(false)
@@ -303,10 +306,11 @@ std::string LLUrlEntrySLURL::getLocation(const std::string &url) const
//
// LLUrlEntryAgent Describes a Second Life agent Url, e.g.,
// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about
+// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about
//
LLUrlEntryAgent::LLUrlEntryAgent()
{
- mPattern = boost::regex("secondlife:///app/agent/[\\da-f-]+/\\w+",
+ mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/\\w+",
boost::regex::perl|boost::regex::icase);
mMenuName = "menu_url_agent.xml";
mIcon = "Generic_Person";
@@ -418,10 +422,11 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa
// LLUrlEntryGroup Describes a Second Life group Url, e.g.,
// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about
// secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect
+// x-grid-location-info://lincoln.lindenlab.com/app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect
//
LLUrlEntryGroup::LLUrlEntryGroup()
{
- mPattern = boost::regex("secondlife:///app/group/[\\da-f-]+/\\w+",
+ mPattern = boost::regex(APP_HEADER_REGEX "/group/[\\da-f-]+/\\w+",
boost::regex::perl|boost::regex::icase);
mMenuName = "menu_url_group.xml";
mIcon = "Generic_Group";
@@ -482,7 +487,8 @@ LLUrlEntryInventory::LLUrlEntryInventory()
//*TODO: add supporting of inventory item names with whitespaces
//this pattern cann't parse for example
//secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces&param2=value
- mPattern = boost::regex("secondlife:///app/inventory/[\\da-f-]+/\\w+\\S*",
+ //x-grid-location-info://lincoln.lindenlab.com/app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces&param2=value
+ mPattern = boost::regex(APP_HEADER_REGEX "/inventory/[\\da-f-]+/\\w+\\S*",
boost::regex::perl|boost::regex::icase);
mMenuName = "menu_url_inventory.xml";
}
@@ -525,10 +531,11 @@ std::string LLUrlEntryObjectIM::getLocation(const std::string &url) const
///
/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g.,
/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about
+/// x-grid-location-info://lincoln.lindenlab.com/app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about
///
LLUrlEntryParcel::LLUrlEntryParcel()
{
- mPattern = boost::regex("secondlife:///app/parcel/[\\da-f-]+/about",
+ mPattern = boost::regex(APP_HEADER_REGEX "/parcel/[\\da-f-]+/about",
boost::regex::perl|boost::regex::icase);
mMenuName = "menu_url_parcel.xml";
mTooltip = LLTrans::getString("TooltipParcelUrl");
@@ -544,7 +551,7 @@ std::string LLUrlEntryParcel::getLabel(const std::string &url, const LLUrlLabelC
//
LLUrlEntryPlace::LLUrlEntryPlace()
{
- mPattern = boost::regex("secondlife://\\S+/?(\\d+/\\d+/\\d+|\\d+/\\d+)/?",
+ mPattern = boost::regex("((x-grid-location-info://[-\\w\\.]+/region/)|(secondlife://))\\S+/?(\\d+/\\d+/\\d+|\\d+/\\d+)/?",
boost::regex::perl|boost::regex::icase);
mMenuName = "menu_url_slurl.xml";
mTooltip = LLTrans::getString("TooltipSLURL");
@@ -589,10 +596,11 @@ std::string LLUrlEntryPlace::getLocation(const std::string &url) const
//
// 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/
//
LLUrlEntryTeleport::LLUrlEntryTeleport()
{
- mPattern = boost::regex("secondlife:///app/teleport/\\S+(/\\d+)?(/\\d+)?(/\\d+)?/?\\S*",
+ mPattern = boost::regex(APP_HEADER_REGEX "/teleport/\\S+(/\\d+)?(/\\d+)?(/\\d+)?/?\\S*",
boost::regex::perl|boost::regex::icase);
mMenuName = "menu_url_teleport.xml";
mTooltip = LLTrans::getString("TooltipTeleportUrl");
@@ -610,7 +618,12 @@ std::string LLUrlEntryTeleport::getLabel(const std::string &url, const LLUrlLabe
LLURI uri(url);
LLSD path_array = uri.pathArray();
S32 path_parts = path_array.size();
- const std::string label = LLTrans::getString("SLurlLabelTeleport");
+ std::string host = uri.hostName();
+ std::string label = LLTrans::getString("SLurlLabelTeleport");
+ if (!host.empty())
+ {
+ label += " " + host;
+ }
if (path_parts == 6)
{
// handle teleport url with (X,Y,Z) coordinates
@@ -709,7 +722,7 @@ std::string LLUrlEntrySLLabel::getTooltip(const std::string &string) const
//
LLUrlEntryWorldMap::LLUrlEntryWorldMap()
{
- mPattern = boost::regex("secondlife:///app/worldmap/\\S+/?(\\d+)?/?(\\d+)?/?(\\d+)?/?\\S*",
+ mPattern = boost::regex(APP_HEADER_REGEX "/worldmap/\\S+/?(\\d+)?/?(\\d+)?/?(\\d+)?/?\\S*",
boost::regex::perl|boost::regex::icase);
mMenuName = "menu_url_map.xml";
mTooltip = LLTrans::getString("TooltipMapUrl");
@@ -765,3 +778,35 @@ std::string LLUrlEntryNoLink::getLabel(const std::string &url, const LLUrlLabelC
{
return getUrl(url);
}
+
+//
+// LLUrlEntryIcon describes an icon with <icon>...</icon> tags
+//
+LLUrlEntryIcon::LLUrlEntryIcon()
+{
+ mPattern = boost::regex("<icon\\s*>\\s*([^<]*)?\\s*</icon\\s*>",
+ boost::regex::perl|boost::regex::icase);
+ mDisabledLink = true;
+}
+
+std::string LLUrlEntryIcon::getUrl(const std::string &url) const
+{
+ return LLStringUtil::null;
+}
+
+std::string LLUrlEntryIcon::getLabel(const std::string &url, const LLUrlLabelCallback &cb)
+{
+ return LLStringUtil::null;
+}
+
+std::string LLUrlEntryIcon::getIcon(const std::string &url)
+{
+ // Grep icon info between <icon>...</icon> tags
+ // matches[1] contains the icon name/path
+ boost::match_results<std::string::const_iterator> matches;
+ mIcon = (boost::regex_match(url, matches, mPattern) && matches[1].matched)
+ ? matches[1]
+ : LLStringUtil::null;
+ LLStringUtil::trim(mIcon);
+ return mIcon;
+}
diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h
index 29575d752c..71f030677a 100644
--- a/indra/llui/llurlentry.h
+++ b/indra/llui/llurlentry.h
@@ -77,7 +77,7 @@ public:
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
- std::string getIcon() const { return mIcon; }
+ virtual std::string getIcon(const std::string &url) { return mIcon; }
/// Return the color to render the displayed text
LLUIColor getColor() const { return mColor; }
@@ -296,4 +296,17 @@ public:
/*virtual*/ std::string getUrl(const std::string &string) const;
};
+///
+/// LLUrlEntryIcon describes an icon with <icon>...</icon> tags
+///
+class LLUrlEntryIcon : public LLUrlEntryBase
+{
+public:
+ LLUrlEntryIcon();
+ /*virtual*/ std::string getUrl(const std::string &string) const;
+ /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb);
+ /*virtual*/ std::string getIcon(const std::string &url);
+};
+
+
#endif
diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp
index 0a70aa586a..4341286eb4 100644
--- a/indra/llui/llurlregistry.cpp
+++ b/indra/llui/llurlregistry.cpp
@@ -45,6 +45,7 @@ LLUrlRegistry::LLUrlRegistry()
{
// Urls are matched in the order that they were registered
registerUrl(new LLUrlEntryNoLink());
+ registerUrl(new LLUrlEntryIcon());
registerUrl(new LLUrlEntrySLURL());
registerUrl(new LLUrlEntryHTTP());
registerUrl(new LLUrlEntryHTTPLabel());
@@ -135,7 +136,8 @@ static bool stringHasUrl(const std::string &text)
text.find(".net") != std::string::npos ||
text.find(".edu") != std::string::npos ||
text.find(".org") != std::string::npos ||
- text.find("<nolink>") != std::string::npos);
+ text.find("<nolink>") != std::string::npos ||
+ text.find("<icon") != std::string::npos);
}
bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb)
@@ -177,7 +179,7 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL
match_entry->getUrl(url),
match_entry->getLabel(url, cb),
match_entry->getTooltip(url),
- match_entry->getIcon(),
+ match_entry->getIcon(url),
match_entry->getColor(),
match_entry->getMenuName(),
match_entry->getLocation(url),
diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp
index cbb303a059..4463b6cc6f 100644
--- a/indra/llui/tests/llurlentry_test.cpp
+++ b/indra/llui/tests/llurlentry_test.cpp
@@ -286,6 +286,13 @@ namespace tut
"XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar",
"secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar");
+ testRegex("Standalone Agent Url ", url,
+ "x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about",
+ "x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about");
+
+ testRegex("Standalone Agent Url Multicase with Text", url,
+ "M x-grid-location-info://lincoln.lindenlab.com/app/AGENT/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about M",
+ "x-grid-location-info://lincoln.lindenlab.com/app/AGENT/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about");
}
template<> template<>
@@ -315,6 +322,15 @@ namespace tut
testRegex("Group Url multicase", url,
"XXX secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About XXX",
"secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About");
+
+ testRegex("Standalone Group Url ", url,
+ "x-grid-location-info://lincoln.lindenlab.com/app/group/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about",
+ "x-grid-location-info://lincoln.lindenlab.com/app/group/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about");
+
+ testRegex("Standalone Group Url Multicase ith Text", url,
+ "M x-grid-location-info://lincoln.lindenlab.com/app/GROUP/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about M",
+ "x-grid-location-info://lincoln.lindenlab.com/app/GROUP/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about");
+
}
template<> template<>
@@ -361,7 +377,11 @@ namespace tut
// DEV-35459: SLURLs and teleport Links not parsed properly
testRegex("SLURL with quote", url,
"XXX secondlife://A'ksha%20Oasis/41/166/701 XXX",
- "secondlife://A%27ksha%20Oasis/41/166/701");
+ "secondlife://A%27ksha%20Oasis/41/166/701");
+
+ testRegex("Standalone All Hands (50,50) [2] with text", url,
+ "XXX x-grid-location-info://lincoln.lindenlab.com/region/All%20Hands/50/50/50 XXX",
+ "x-grid-location-info://lincoln.lindenlab.com/region/All%20Hands/50/50/50");
}
template<> template<>
@@ -461,6 +481,10 @@ namespace tut
testRegex("Teleport url with quote", url,
"XXX secondlife:///app/teleport/A'ksha%20Oasis/41/166/701 XXX",
"secondlife:///app/teleport/A%27ksha%20Oasis/41/166/701");
+
+ testRegex("Standalone All Hands", url,
+ "XXX x-grid-location-info://lincoln.lindenlab.com/app/teleport/All%20Hands/50/50/50 XXX",
+ "x-grid-location-info://lincoln.lindenlab.com/app/teleport/All%20Hands/50/50/50");
}
template<> template<>