diff options
Diffstat (limited to 'indra/llui')
-rw-r--r-- | indra/llui/CMakeLists.txt | 2 | ||||
-rw-r--r-- | indra/llui/lldockablefloater.cpp | 14 | ||||
-rw-r--r-- | indra/llui/lldockablefloater.h | 2 | ||||
-rw-r--r-- | indra/llui/lldockcontrol.cpp | 23 | ||||
-rw-r--r-- | indra/llui/lldockcontrol.h | 4 | ||||
-rw-r--r-- | indra/llui/llfiltereditor.cpp | 74 | ||||
-rw-r--r-- | indra/llui/llfiltereditor.h | 30 | ||||
-rw-r--r-- | indra/llui/llflatlistview.cpp | 494 | ||||
-rw-r--r-- | indra/llui/llflatlistview.h | 262 | ||||
-rw-r--r-- | indra/llui/llfloater.cpp | 11 | ||||
-rw-r--r-- | indra/llui/lllineeditor.h | 2 | ||||
-rw-r--r-- | indra/llui/llsearcheditor.cpp | 82 | ||||
-rw-r--r-- | indra/llui/llsearcheditor.h | 24 | ||||
-rw-r--r-- | indra/llui/lltabcontainer.cpp | 11 | ||||
-rw-r--r-- | indra/llui/llurlentry.cpp | 81 | ||||
-rw-r--r-- | indra/llui/llurlentry.h | 28 | ||||
-rw-r--r-- | indra/llui/llurlregistry.cpp | 7 | ||||
-rw-r--r-- | indra/llui/llview.h | 2 | ||||
-rw-r--r-- | indra/llui/tests/llurlentry_test.cpp | 72 |
19 files changed, 996 insertions, 229 deletions
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index cc9362a252..fce6b759a4 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -40,6 +40,7 @@ set(llui_SOURCE_FILES lleditmenuhandler.cpp llf32uictrl.cpp llfiltereditor.cpp + llflatlistview.cpp llfloater.cpp llfloaterreg.cpp llflyoutbutton.cpp @@ -122,6 +123,7 @@ set(llui_HEADER_FILES lleditmenuhandler.h llf32uictrl.h llfiltereditor.h + llflatlistview.h llfloater.h llfloaterreg.h llflyoutbutton.h diff --git a/indra/llui/lldockablefloater.cpp b/indra/llui/lldockablefloater.cpp index 29f78f6290..ed15d9d922 100644 --- a/indra/llui/lldockablefloater.cpp +++ b/indra/llui/lldockablefloater.cpp @@ -35,7 +35,7 @@ #include "lldockablefloater.h" //static -LLDockableFloater* LLDockableFloater::instance = NULL; +LLHandle<LLFloater> LLDockableFloater::instanceHandle; LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, const LLSD& key, const Params& params) : @@ -57,21 +57,21 @@ BOOL LLDockableFloater::postBuild() void LLDockableFloater::resetInstance() { - if (instance != this) + if (instanceHandle.get() != this) { - if (instance != NULL && instance->isDocked()) + if (instanceHandle.get() != NULL && instanceHandle.get()->isDocked()) { //closeFloater() is not virtual - if (instance->canClose()) + if (instanceHandle.get()->canClose()) { - instance->closeFloater(); + instanceHandle.get()->closeFloater(); } else { - instance->setVisible(FALSE); + instanceHandle.get()->setVisible(FALSE); } } - instance = this; + instanceHandle = getHandle(); } } diff --git a/indra/llui/lldockablefloater.h b/indra/llui/lldockablefloater.h index b977888803..1d0e89cef5 100644 --- a/indra/llui/lldockablefloater.h +++ b/indra/llui/lldockablefloater.h @@ -69,7 +69,7 @@ protected: private: std::auto_ptr<LLDockControl> mDockControl; LLUIImagePtr mDockTongue; - static LLDockableFloater* instance; + static LLHandle<LLFloater> instanceHandle; }; #endif /* LL_DOCKABLEFLOATER_H */ diff --git a/indra/llui/lldockcontrol.cpp b/indra/llui/lldockcontrol.cpp index bec7f04cc0..d666f2be56 100644 --- a/indra/llui/lldockcontrol.cpp +++ b/indra/llui/lldockcontrol.cpp @@ -48,12 +48,25 @@ LLDockControl::LLDockControl(LLView* dockWidget, LLFloater* dockableFloater, { off(); } + + if (dockWidget != NULL) { + repositionDockable(); + } } LLDockControl::~LLDockControl() { } +void LLDockControl::setDock(LLView* dockWidget) +{ + mDockWidget = dockWidget; + if (mDockWidget != NULL) + { + repositionDockable(); + } +} + void LLDockControl::repositionDockable() { if (mEnabled) @@ -65,11 +78,14 @@ void LLDockControl::repositionDockable() void LLDockControl::calculateDockablePosition() { LLRect dockRect = mDockWidget->calcScreenRect(); - if (mPrevDockRect != dockRect || mRecalculateDocablePosition) + LLRect rootRect = mDockableFloater->getRootView()->getRect(); + + // recalculate dockable position if dock position changed + // or root view rect changed or recalculation is forced + if (mPrevDockRect != dockRect || mRootRect != rootRect + || mRecalculateDocablePosition) { LLRect dockableRect = mDockableFloater->calcScreenRect(); - LLRect rootRect = mDockableFloater->getRootView()->getRect(); - S32 x = 0; S32 y = 0; switch (mDockAt) @@ -100,6 +116,7 @@ void LLDockControl::calculateDockablePosition() mDockableFloater->screenPointToLocal(mDockTongueX, mDockTongueY, &mDockTongueX, &mDockTongueY); mPrevDockRect = dockRect; + mRootRect = rootRect; mRecalculateDocablePosition = false; } } diff --git a/indra/llui/lldockcontrol.h b/indra/llui/lldockcontrol.h index 0e1f4c8e64..7d8d5c7653 100644 --- a/indra/llui/lldockcontrol.h +++ b/indra/llui/lldockcontrol.h @@ -60,8 +60,7 @@ public: public: void on(); void off(); - void setDock(LLView* dockWidget) - { mDockWidget = dockWidget;}; + void setDock(LLView* dockWidget); void repositionDockable(); void drawToungue(); protected: @@ -72,6 +71,7 @@ private: DocAt mDockAt; LLView* mDockWidget; LLRect mPrevDockRect; + LLRect mRootRect; LLFloater* mDockableFloater; LLUIImagePtr mDockTongue; S32 mDockTongueX; diff --git a/indra/llui/llfiltereditor.cpp b/indra/llui/llfiltereditor.cpp index 26b5f2e182..390504234d 100644 --- a/indra/llui/llfiltereditor.cpp +++ b/indra/llui/llfiltereditor.cpp @@ -37,81 +37,15 @@ #include "llfiltereditor.h" LLFilterEditor::LLFilterEditor(const LLFilterEditor::Params& p) -: LLUICtrl(p) +: LLSearchEditor(p) { - LLLineEditor::Params line_editor_p(p); - line_editor_p.name("filter edit box"); - line_editor_p.rect(getLocalRect()); - line_editor_p.follows.flags(FOLLOWS_ALL); - line_editor_p.text_pad_right(getRect().getHeight()); - line_editor_p.revert_on_esc(false); - line_editor_p.keystroke_callback(boost::bind(&LLUICtrl::onCommit, this)); - - mFilterEditor = LLUICtrlFactory::create<LLLineEditor>(line_editor_p); - addChild(mFilterEditor); - - S32 btn_width = getRect().getHeight(); // button is square, and as tall as search editor - LLRect clear_btn_rect(getRect().getWidth() - btn_width, getRect().getHeight(), getRect().getWidth(), 0); - LLButton::Params button_params(p.clear_filter_button); - button_params.name(std::string("clear filter")); - button_params.rect(clear_btn_rect) ; - button_params.follows.flags(FOLLOWS_RIGHT|FOLLOWS_TOP); - button_params.tab_stop(false); - button_params.click_callback.function(boost::bind(&LLFilterEditor::onClearFilter, this, _2)); - - mClearFilterButton = LLUICtrlFactory::create<LLButton>(button_params); - mFilterEditor->addChild(mClearFilterButton); -} - -//virtual -void LLFilterEditor::setValue(const LLSD& value ) -{ - mFilterEditor->setValue(value); -} - -//virtual -LLSD LLFilterEditor::getValue() const -{ - return mFilterEditor->getValue(); -} - -//virtual -BOOL LLFilterEditor::setTextArg( const std::string& key, const LLStringExplicit& text ) -{ - return mFilterEditor->setTextArg(key, text); } -//virtual -BOOL LLFilterEditor::setLabelArg( const std::string& key, const LLStringExplicit& text ) -{ - return mFilterEditor->setLabelArg(key, text); -} - -//virtual -void LLFilterEditor::setLabel( const LLStringExplicit &new_label ) -{ - mFilterEditor->setLabel(new_label); -} - -//virtual -void LLFilterEditor::clear() -{ - if (mFilterEditor) - { - mFilterEditor->clear(); - } -} -void LLFilterEditor::draw() +void LLFilterEditor::handleKeystroke() { - mClearFilterButton->setVisible(!mFilterEditor->getWText().empty()); - - LLUICtrl::draw(); -} + this->LLSearchEditor::handleKeystroke(); -void LLFilterEditor::onClearFilter(const LLSD& data) -{ - setText(LLStringUtil::null); + // Commit on every keystroke. onCommit(); } - diff --git a/indra/llui/llfiltereditor.h b/indra/llui/llfiltereditor.h index fceb82af8d..c43a76b130 100644 --- a/indra/llui/llfiltereditor.h +++ b/indra/llui/llfiltereditor.h @@ -42,18 +42,14 @@ #ifndef LL_FILTEREDITOR_H #define LL_FILTEREDITOR_H -#include "lllineeditor.h" -#include "llbutton.h" +#include "llsearcheditor.h" -class LLFilterEditor : public LLUICtrl +class LLFilterEditor : public LLSearchEditor { public: - struct Params : public LLInitParam::Block<Params, LLLineEditor::Params> + struct Params : public LLInitParam::Block<Params, LLSearchEditor::Params> { - Optional<LLButton::Params> clear_filter_button; - Params() - : clear_filter_button("clear_filter_button") { name = "filter_editor"; } @@ -62,26 +58,8 @@ public: protected: LLFilterEditor(const Params&); friend class LLUICtrlFactory; -public: - virtual ~LLFilterEditor() {} - - /*virtual*/ void draw(); - - void setText(const LLStringExplicit &new_text) { mFilterEditor->setText(new_text); } - - // LLUICtrl interface - virtual void setValue(const LLSD& value ); - virtual LLSD getValue() const; - virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text ); - virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); - virtual void setLabel( const LLStringExplicit &new_label ); - virtual void clear(); - -private: - void onClearFilter(const LLSD& data); - LLLineEditor* mFilterEditor; - LLButton* mClearFilterButton; + /*virtual*/ void handleKeystroke(); }; #endif // LL_FILTEREDITOR_H diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp new file mode 100644 index 0000000000..75334acb39 --- /dev/null +++ b/indra/llui/llflatlistview.cpp @@ -0,0 +1,494 @@ +/** + * @file llflatlistview.cpp + * @brief LLFlatListView base class + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpanel.h" + +#include "llflatlistview.h" + +static const LLDefaultChildRegistry::Register<LLFlatListView> flat_list_view("flat_list_view"); + +const LLSD SELECTED_EVENT = LLSD().insert("selected", true); +const LLSD UNSELECTED_EVENT = LLSD().insert("selected", false); + +LLFlatListView::Params::Params() +: item_pad("item_pad"), + allow_select("allow_select"), + multi_select("multi_select"), + keep_one_selected("keep_one_selected") +{}; + +void LLFlatListView::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */) +{ + LLScrollContainer::reshape(width, height, called_from_parent); + setItemsNoScrollWidth(width); + rearrangeItems(); +} + +bool LLFlatListView::addItem(LLPanel* item, LLSD value /* = LLUUID::null*/, EAddPosition pos /*= ADD_BOTTOM*/) +{ + if (!item) return false; + if (value.isUndefined()) return false; + + //force uniqueness of items, easiest check but unreliable + if (item->getParent() == mItemsPanel) return false; + + item_pair_t* new_pair = new item_pair_t(item, value); + switch (pos) + { + case ADD_TOP: + mItemPairs.push_front(new_pair); + //in LLView::draw() children are iterated in backorder + mItemsPanel->addChildInBack(item); + break; + case ADD_BOTTOM: + mItemPairs.push_back(new_pair); + mItemsPanel->addChild(item); + break; + default: + break; + } + + //_4 is for MASK + item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + item->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + + rearrangeItems(); + return true; +} + + +bool LLFlatListView::insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, LLSD value /*= LLUUID::null*/) +{ + if (!after_item) return false; + if (!item_to_add) return false; + if (value.isUndefined()) return false; + + if (mItemPairs.empty()) return false; + + //force uniqueness of items, easiest check but unreliable + if (item_to_add->getParent() == mItemsPanel) return false; + + item_pair_t* after_pair = getItemPair(after_item); + if (!after_pair) return false; + + item_pair_t* new_pair = new item_pair_t(item_to_add, value); + if (after_pair == mItemPairs.back()) + { + mItemPairs.push_back(new_pair); + mItemsPanel->addChild(item_to_add); + } + else + { + pairs_iterator_t it = mItemPairs.begin(); + ++it; + while (it != mItemPairs.end()) + { + if (*it == after_pair) + { + mItemPairs.insert(++it, new_pair); + mItemsPanel->addChild(item_to_add); + break; + } + } + } + + //_4 is for MASK + item_to_add->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + item_to_add->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4)); + + rearrangeItems(); + return true; +} + + +bool LLFlatListView::removeItem(LLPanel* item) +{ + if (!item) return false; + if (item->getParent() != mItemsPanel) return false; + + item_pair_t* item_pair = getItemPair(item); + if (!item_pair) return false; + + return removeItemPair(item_pair); +} + +bool LLFlatListView::removeItemByValue(const LLSD& value) +{ + if (value.isUndefined()) return false; + + item_pair_t* item_pair = getItemPair(value); + if (!item_pair) return false; + + return removeItemPair(item_pair); +} + +bool LLFlatListView::removeItemByUUID(LLUUID& uuid) +{ + return removeItemByValue(LLSD(uuid)); +} + +LLPanel* LLFlatListView::getItemByValue(LLSD& value) const +{ + if (value.isDefined()) return NULL; + + item_pair_t* pair = getItemPair(value); + if (pair) return pair->first; + return NULL; +} + +bool LLFlatListView::selectItem(LLPanel* item, bool select /*= true*/) +{ + if (!item) return false; + if (item->getParent() != mItemsPanel) return false; + + item_pair_t* item_pair = getItemPair(item); + if (!item_pair) return false; + + return selectItemPair(item_pair, select); +} + +bool LLFlatListView::selectItemByValue(const LLSD& value, bool select /*= true*/) +{ + if (value.isUndefined()) return false; + + item_pair_t* item_pair = getItemPair(value); + if (!item_pair) return false; + + return selectItemPair(item_pair, select); +} + +bool LLFlatListView::selectItemByUUID(LLUUID& uuid, bool select /* = true*/) +{ + return selectItemByValue(LLSD(uuid), select); +} + + +LLSD LLFlatListView::getSelectedValue() const +{ + if (mSelectedItemPairs.empty()) return LLSD(); + + item_pair_t* first_selected_pair = mSelectedItemPairs.front(); + return first_selected_pair->second; +} + +void LLFlatListView::getSelectedValues(std::vector<LLSD>& selected_values) const +{ + if (mSelectedItemPairs.empty()) return; + + for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) + { + selected_values.push_back((*it)->second); + } +} + +LLUUID LLFlatListView::getSelectedUUID() const +{ + const LLSD& value = getSelectedValue(); + if (value.isDefined() && value.isUUID()) + { + return value.asUUID(); + } + else + { + return LLUUID::null; + } +} + +void LLFlatListView::getSelectedUUIDs(std::vector<LLUUID>& selected_uuids) const +{ + if (mSelectedItemPairs.empty()) return; + + for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) + { + selected_uuids.push_back((*it)->second.asUUID()); + } +} + +LLPanel* LLFlatListView::getSelectedItem() const +{ + if (mSelectedItemPairs.empty()) return NULL; + + return mSelectedItemPairs.front()->first; +} + +void LLFlatListView::getSelectedItems(std::vector<LLPanel*>& selected_items) const +{ + if (mSelectedItemPairs.empty()) return; + + for (pairs_const_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) + { + selected_items.push_back((*it)->first); + } +} + +void LLFlatListView::resetSelection() +{ + if (mSelectedItemPairs.empty()) return; + + for (pairs_iterator_t it= mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) + { + item_pair_t* pair_to_deselect = *it; + LLPanel* item = pair_to_deselect->first; + item->setValue(UNSELECTED_EVENT); + } + + mSelectedItemPairs.clear(); +} + +void LLFlatListView::clear() +{ + // do not use LLView::deleteAllChildren to avoid removing nonvisible items. drag-n-drop for ex. + for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + mItemsPanel->removeChild((*it)->first); + delete (*it)->first; + delete *it; + } + mItemPairs.clear(); + mSelectedItemPairs.clear(); +} + + +////////////////////////////////////////////////////////////////////////// +// PROTECTED STUFF +////////////////////////////////////////////////////////////////////////// + + +LLFlatListView::LLFlatListView(const LLFlatListView::Params& p) +: LLScrollContainer(p), + mItemsPanel(NULL), + mItemPad(p.item_pad), + mAllowSelection(p.allow_select), + mMultipleSelection(p.multi_select), + mKeepOneItemSelected(p.keep_one_selected) +{ + mBorderThickness = getBorderWidth(); + + LLRect scroll_rect = getRect(); + LLRect items_rect; + + setItemsNoScrollWidth(scroll_rect.getWidth()); + items_rect.setLeftTopAndSize(mBorderThickness, scroll_rect.getHeight() - mBorderThickness, mItemsNoScrollWidth, 0); + + LLPanel::Params pp; + pp.rect(items_rect); + mItemsPanel = LLUICtrlFactory::create<LLPanel> (pp); + addChild(mItemsPanel); + + //we don't need to stretch in vertical direction on reshaping by a parent + //no bottom following! + mItemsPanel->setFollows(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_TOP); +}; + +void LLFlatListView::rearrangeItems() +{ + static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0); + + if (mItemPairs.empty()) return; + + //calculating required height - assuming items can be of different height + //list should accommodate all its items + S32 height = 0; + + pairs_iterator_t it = mItemPairs.begin(); + for (; it != mItemPairs.end(); ++it) + { + LLPanel* item = (*it)->first; + height += item->getRect().getHeight(); + } + height += mItemPad * (mItemPairs.size() - 1); + + LLRect rc = mItemsPanel->getRect(); + S32 width = mItemsNoScrollWidth; + + // update width to avoid horizontal scrollbar + if (height > getRect().getHeight() - 2 * mBorderThickness) + width -= scrollbar_size; + + //changes the bottom, end of the list goes down in the scroll container + rc.setLeftTopAndSize(rc.mLeft, rc.mTop, width, height); + mItemsPanel->setRect(rc); + + //reshaping items + S32 item_new_top = height; + pairs_iterator_t it2, first_it = mItemPairs.begin(); + for (it2 = first_it; it2 != mItemPairs.end(); ++it2) + { + LLPanel* item = (*it2)->first; + LLRect rc = item->getRect(); + if(it2 != first_it) + { + item_new_top -= (rc.getHeight() + mItemPad); + } + rc.setLeftTopAndSize(rc.mLeft, item_new_top, width, rc.getHeight()); + item->reshape(rc.getWidth(), rc.getHeight()); + item->setRect(rc); + } +} + +void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask) +{ + if (!item_pair) return; + + bool select_item = !isSelected(item_pair); + + //*TODO find a better place for that enforcing stuff + if (mKeepOneItemSelected && numSelected() == 1 && !select_item) return; + + if (!(mask & MASK_CONTROL) || !mMultipleSelection) resetSelection(); + selectItemPair(item_pair, select_item); +} + +LLFlatListView::item_pair_t* LLFlatListView::getItemPair(LLPanel* item) const +{ + llassert(item); + + for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + item_pair_t* item_pair = *it; + if (item_pair->first == item) return item_pair; + } + return NULL; +} + +//compares two LLSD's +bool llsds_are_equal(const LLSD& llsd_1, const LLSD& llsd_2) +{ + llassert(llsd_1.isDefined()); + llassert(llsd_2.isDefined()); + + if (llsd_1.type() != llsd_2.type()) return false; + + if (!llsd_1.isMap()) + { + if (llsd_1.isUUID()) return llsd_1.asUUID() == llsd_2.asUUID(); + + //assumptions that string representaion is enough for other types + return llsd_1.asString() == llsd_2.asString(); + } + + if (llsd_1.size() != llsd_2.size()) return false; + + LLSD::map_const_iterator llsd_1_it = llsd_1.beginMap(); + LLSD::map_const_iterator llsd_2_it = llsd_2.beginMap(); + for (S32 i = 0; i < llsd_1.size(); ++i) + { + if ((*llsd_1_it).first != (*llsd_2_it).first) return false; + if (!llsds_are_equal((*llsd_1_it).second, (*llsd_2_it).second)) return false; + ++llsd_1_it; + ++llsd_2_it; + } + return true; +} + +LLFlatListView::item_pair_t* LLFlatListView::getItemPair(const LLSD& value) const +{ + llassert(value.isDefined()); + + for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + item_pair_t* item_pair = *it; + if (llsds_are_equal(item_pair->second, value)) return item_pair; + } + return NULL; +} + +bool LLFlatListView::selectItemPair(item_pair_t* item_pair, bool select) +{ + llassert(item_pair); + + if (!mAllowSelection && select) return false; + + if (isSelected(item_pair) == select) return true; //already in specified selection state + if (select) + { + mSelectedItemPairs.push_back(item_pair); + } + else + { + mSelectedItemPairs.remove(item_pair); + } + + //a way of notifying panel of selection state changes + LLPanel* item = item_pair->first; + item->setValue(select ? SELECTED_EVENT : UNSELECTED_EVENT); + return true; +} + +bool LLFlatListView::isSelected(item_pair_t* item_pair) const +{ + llassert(item_pair); + + pairs_const_iterator_t it_end = mSelectedItemPairs.end(); + return std::find(mSelectedItemPairs.begin(), it_end, item_pair) != it_end; +} + +bool LLFlatListView::removeItemPair(item_pair_t* item_pair) +{ + llassert(item_pair); + + bool deleted = false; + for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it) + { + item_pair_t* _item_pair = *it; + if (_item_pair == item_pair) + { + mItemPairs.erase(it); + deleted = true; + break; + } + } + + if (!deleted) return false; + + for (pairs_iterator_t it = mSelectedItemPairs.begin(); it != mSelectedItemPairs.end(); ++it) + { + item_pair_t* selected_item_pair = *it; + if (selected_item_pair == item_pair) + { + it = mSelectedItemPairs.erase(it); + break; + } + } + + mItemsPanel->removeChild(item_pair->first); + delete item_pair->first; + delete item_pair; + + rearrangeItems(); + + return true; +} + + diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h new file mode 100644 index 0000000000..bd0b419f4f --- /dev/null +++ b/indra/llui/llflatlistview.h @@ -0,0 +1,262 @@ +/** + * @file llflatlistview.h + * @brief LLFlatListView base class + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLATLISTVIEW_H +#define LL_LLFLATLISTVIEW_H + +#include "llscrollcontainer.h" + + +class LLPanel; + +/** + * LLFlatListView represents a flat list ui control that operates on items in a form of LLPanel's. + * LLSD can be associated with each added item, it can keep data from an item in digested form. + * Associated LLSD's can be of any type (singular, a map etc.). + * Items (LLPanel's subclasses) can be of different height. + * The list is LLPanel created in itself and grows in height while new items are added. + * + * The control can manage selection of its items when the flag "allow_select" is set. Also ability to select + * multiple items (by using CTRL) is enabled through setting the flag "multi_select" - if selection is not allowed that flag + * is ignored. The option "keep_one_selected" forces at least one item to be selected at any time (only for mouse events on items) + * since any item of the list was selected. + * + * Examples of using this control are presented in Picks panel (Me Profile and Profile View), where this control is used to + * manage the list of pick items. + * + * ASSUMPTIONS AND STUFF + * - NULL pointers and undefined LLSD's are not accepted by any method of this class unless specified otherwise + * - Order of returned selected items are not guaranteed + * - The control assumes that all items being added are unique. + */ +class LLFlatListView : public LLScrollContainer +{ +public: + + struct Params : public LLInitParam::Block<Params, LLScrollContainer::Params> + { + /** turning on/off selection support */ + Optional<bool> allow_select; + + /** turning on/off multiple selection (works while clicking and holding CTRL)*/ + Optional<bool> multi_select; + + /** don't allow to deselect all selected items (for mouse events on items only) */ + Optional<bool> keep_one_selected; + + /** padding between items */ + Optional<U32> item_pad; + + Params(); + }; + + virtual ~LLFlatListView() { clear(); }; + + + /** Overridden LLPanel's reshape, height is ignored, the list sets its height to accommodate all items */ + virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + + + /** + * Adds and item and LLSD value associated with it to the list at specified position + * @return true if the item was added, false otherwise + */ + virtual bool addItem(LLPanel* item, LLSD value = LLUUID::null, EAddPosition pos = ADD_BOTTOM); + + /** + * Insert item_to_add along with associated value to the list right after the after_item. + * @return true if the item was successfully added, false otherwise + */ + virtual bool insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, LLSD value = LLUUID::null); + + /** + * Remove specified item + * @return true if the item was removed, false otherwise + */ + virtual bool removeItem(LLPanel* item); + + /** + * Remove an item specified by value + * @return true if the item was removed, false otherwise + */ + virtual bool removeItemByValue(const LLSD& value); + + /** + * Remove an item specified by uuid + * @return true if the item was removed, false otherwise + */ + virtual bool removeItemByUUID(LLUUID& uuid); + + /** + * Get an item by value + * @return the item as LLPanel if associated with value, NULL otherwise + */ + virtual LLPanel* getItemByValue(LLSD& value) const; + + /** + * Select or deselect specified item based on select + * @return true if succeed, false otherwise + */ + virtual bool selectItem(LLPanel* item, bool select = true); + + /** + * Select or deselect an item by associated value based on select + * @return true if succeed, false otherwise + */ + virtual bool selectItemByValue(const LLSD& value, bool select = true); + + /** + * Select or deselect an item by associated uuid based on select + * @return true if succeed, false otherwise + */ + virtual bool selectItemByUUID(LLUUID& uuid, bool select = true); + + + + /** + * Get LLSD associated with the first selected item + */ + virtual LLSD getSelectedValue() const; + + /** + * Get LLSD's associated with selected items. + * @param selected_values std::vector being populated with LLSD associated with selected items + */ + virtual void getSelectedValues(std::vector<LLSD>& selected_values) const; + + + /** + * Get LLUUID associated with selected item + * @return LLUUID if such was associated with selected item + */ + virtual LLUUID getSelectedUUID() const; + + /** + * Get LLUUIDs associated with selected items + * @param selected_uuids An std::vector being populated with LLUUIDs associated with selected items + */ + virtual void getSelectedUUIDs(std::vector<LLUUID>& selected_uuids) const; + + /** Get the top selected item */ + virtual LLPanel* getSelectedItem() const; + + /** + * Get selected items + * @param selected_items An std::vector being populated with pointers to selected items + */ + virtual void getSelectedItems(std::vector<LLPanel*>& selected_items) const; + + + /** Resets selection of items */ + virtual void resetSelection(); + + + /** Turn on/off multiple selection support */ + void setAllowMultipleSelection(bool allow) { mMultipleSelection = allow; } + + /** Turn on/off selection support */ + void setAllowSelection(bool can_select) { mAllowSelection = can_select; } + + + /** 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(); } + + + /** Removes all items from the list */ + virtual void clear(); + + +protected: + + /** Pairs LLpanel representing a single item LLPanel and LLSD associated with it */ + typedef std::pair<LLPanel*, LLSD> item_pair_t; + + typedef std::list<item_pair_t*> pairs_list_t; + typedef pairs_list_t::iterator pairs_iterator_t; + typedef pairs_list_t::const_iterator pairs_const_iterator_t; + + + friend class LLUICtrlFactory; + LLFlatListView(const LLFlatListView::Params& p); + + /** Manage selection on mouse events */ + void onItemMouseClick(item_pair_t* item_pair, MASK mask); + + /** Updates position of items */ + virtual void rearrangeItems(); + + virtual item_pair_t* getItemPair(LLPanel* item) const; + + virtual item_pair_t* getItemPair(const LLSD& value) const; + + virtual bool selectItemPair(item_pair_t* item_pair, bool select); + + virtual bool isSelected(item_pair_t* item_pair) const; + + virtual bool removeItemPair(item_pair_t* item_pair); + + +private: + + void setItemsNoScrollWidth(S32 new_width) {mItemsNoScrollWidth = new_width - 2 * mBorderThickness;} + + +private: + + LLPanel* mItemsPanel; + + S32 mItemsNoScrollWidth; + + S32 mBorderThickness; + + /** Items padding */ + U32 mItemPad; + + /** Selection support flag */ + bool mAllowSelection; + + /** Multiselection support flag, ignored if selection is not supported */ + bool mMultipleSelection; + + bool mKeepOneItemSelected; + + /** All pairs of the list */ + pairs_list_t mItemPairs; + + /** Selected pairs for faster access */ + pairs_list_t mSelectedItemPairs; +}; + +#endif diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index c027b59c71..228e23b67e 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -1491,7 +1491,8 @@ LLFloater* LLFloater::getClosableFloaterFromFocus() // The focused floater may not be closable, // Find and close a parental floater that is closeable, if any. - for(LLFloater* floater_to_close = focused_floater; + LLFloater* prev_floater = NULL; + for(LLFloater* floater_to_close = focused_floater; NULL != floater_to_close; floater_to_close = gFloaterView->getParentFloater(floater_to_close)) { @@ -1499,6 +1500,14 @@ LLFloater* LLFloater::getClosableFloaterFromFocus() { return floater_to_close; } + + // If floater has as parent root view + // gFloaterView->getParentFloater(floater_to_close) returns + // the same floater_to_close, so we need to check this. + if (prev_floater == floater_to_close) { + break; + } + prev_floater = floater_to_close; } return NULL; diff --git a/indra/llui/lllineeditor.h b/indra/llui/lllineeditor.h index a024f48cc6..339aad30fb 100644 --- a/indra/llui/lllineeditor.h +++ b/indra/llui/lllineeditor.h @@ -189,6 +189,7 @@ public: // Selects characters 'start' to 'end'. void setSelection(S32 start, S32 end); + virtual void getSelectionRange(S32 *position, S32 *length) const; void setCommitOnFocusLost( BOOL b ) { mCommitOnFocusLost = b; } void setRevertOnEsc( BOOL b ) { mRevertOnEsc = b; } @@ -277,7 +278,6 @@ private: const segment_lengths_t &preedit_segment_lengths, const standouts_t &preedit_standouts, S32 caret_position); virtual void markAsPreedit(S32 position, S32 length); virtual void getPreeditRange(S32 *position, S32 *length) const; - virtual void getSelectionRange(S32 *position, S32 *length) const; virtual BOOL getPreeditLocation(S32 query_position, LLCoordGL *coord, LLRect *bounds, LLRect *control) const; virtual S32 getPreeditFontSize() const; diff --git a/indra/llui/llsearcheditor.cpp b/indra/llui/llsearcheditor.cpp index fbcbb55b85..b87f645f3f 100644 --- a/indra/llui/llsearcheditor.cpp +++ b/indra/llui/llsearcheditor.cpp @@ -38,30 +38,68 @@ LLSearchEditor::LLSearchEditor(const LLSearchEditor::Params& p) : LLUICtrl(p) + , mSearchButton(NULL) + , mClearButton(NULL) { - S32 btn_top = p.search_button.top_pad + p.search_button.rect.height; - S32 btn_right = p.search_button.rect.width + p.search_button.left_pad; - LLRect search_btn_rect(p.search_button.left_pad, btn_top, btn_right, p.search_button.top_pad); + S32 srch_btn_top = p.search_button.top_pad + p.search_button.rect.height; + S32 srch_btn_right = p.search_button.rect.width + p.search_button.left_pad; + LLRect srch_btn_rect(p.search_button.left_pad, srch_btn_top, srch_btn_right, p.search_button.top_pad); + S32 text_pad_left = p.text_pad_left; + if (p.search_button_visible) + text_pad_left += srch_btn_rect.getWidth(); + + // Set up line editor. LLLineEditor::Params line_editor_params(p); line_editor_params.name("filter edit box"); line_editor_params.rect(getLocalRect()); line_editor_params.follows.flags(FOLLOWS_ALL); - line_editor_params.text_pad_left(p.text_pad_left + search_btn_rect.getWidth()); + line_editor_params.text_pad_left(text_pad_left); + line_editor_params.revert_on_esc(false); line_editor_params.commit_callback.function(boost::bind(&LLUICtrl::onCommit, this)); + line_editor_params.keystroke_callback(boost::bind(&LLSearchEditor::handleKeystroke, this)); mSearchEditor = LLUICtrlFactory::create<LLLineEditor>(line_editor_params); addChild(mSearchEditor); - LLButton::Params button_params(p.search_button); - button_params.name(std::string("clear filter")); - button_params.rect(search_btn_rect) ; - button_params.follows.flags(FOLLOWS_RIGHT|FOLLOWS_TOP); - button_params.tab_stop(false); - button_params.click_callback.function(boost::bind(&LLUICtrl::onCommit, this)); + if (p.search_button_visible) + { + // Set up search button. + LLButton::Params srch_btn_params(p.search_button); + srch_btn_params.name(std::string("search button")); + srch_btn_params.rect(srch_btn_rect) ; + srch_btn_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_TOP); + srch_btn_params.tab_stop(false); + srch_btn_params.click_callback.function(boost::bind(&LLUICtrl::onCommit, this)); + + mSearchButton = LLUICtrlFactory::create<LLButton>(srch_btn_params); + mSearchEditor->addChild(mSearchButton); + } + + if (p.clear_button_visible) + { + // Set up clear button. + S32 clr_btn_width = getRect().getHeight(); // button is square, and as tall as search editor + LLRect clear_btn_rect(getRect().getWidth() - clr_btn_width, getRect().getHeight(), getRect().getWidth(), 0); + LLButton::Params clr_btn_params(p.clear_button); + clr_btn_params.name(std::string("clear button")); + clr_btn_params.rect(clear_btn_rect) ; + clr_btn_params.follows.flags(FOLLOWS_RIGHT|FOLLOWS_TOP); + clr_btn_params.tab_stop(false); + clr_btn_params.click_callback.function(boost::bind(&LLSearchEditor::onClearButtonClick, this, _2)); + + mClearButton = LLUICtrlFactory::create<LLButton>(clr_btn_params); + mSearchEditor->addChild(mClearButton); + } +} + +//virtual +void LLSearchEditor::draw() +{ + if (mClearButton) + mClearButton->setVisible(!mSearchEditor->getWText().empty()); - mSearchButton = LLUICtrlFactory::create<LLButton>(button_params); - mSearchEditor->addChild(mSearchButton); + LLUICtrl::draw(); } //virtual @@ -89,6 +127,12 @@ BOOL LLSearchEditor::setLabelArg( const std::string& key, const LLStringExplicit } //virtual +void LLSearchEditor::setLabel( const LLStringExplicit &new_label ) +{ + mSearchEditor->setLabel(new_label); +} + +//virtual void LLSearchEditor::clear() { if (mSearchEditor) @@ -96,3 +140,17 @@ void LLSearchEditor::clear() mSearchEditor->clear(); } } + +void LLSearchEditor::onClearButtonClick(const LLSD& data) +{ + setText(LLStringUtil::null); + mSearchEditor->doDelete(); // force keystroke callback +} + +void LLSearchEditor::handleKeystroke() +{ + if (mKeystrokeCallback) + { + mKeystrokeCallback(this, getValue()); + } +} diff --git a/indra/llui/llsearcheditor.h b/indra/llui/llsearcheditor.h index cd2867b493..f395e7e816 100644 --- a/indra/llui/llsearcheditor.h +++ b/indra/llui/llsearcheditor.h @@ -50,10 +50,15 @@ class LLSearchEditor : public LLUICtrl public: struct Params : public LLInitParam::Block<Params, LLLineEditor::Params> { - Optional<LLButton::Params> search_button; + Optional<LLButton::Params> search_button, clear_button; + Optional<bool> search_button_visible, clear_button_visible; + Optional<commit_callback_t> keystroke_callback; Params() : search_button("search_button") + , search_button_visible("search_button_visible") + , clear_button("clear_button") + , clear_button_visible("clear_button_visible") { name = "search_editor"; } @@ -66,26 +71,29 @@ protected: public: virtual ~LLSearchEditor() {} + /*virtual*/ void draw(); + void setText(const LLStringExplicit &new_text) { mSearchEditor->setText(new_text); } const std::string& getText() const { return mSearchEditor->getText(); } - // LLUICtrl interface virtual void setValue(const LLSD& value ); virtual LLSD getValue() const; virtual BOOL setTextArg( const std::string& key, const LLStringExplicit& text ); virtual BOOL setLabelArg( const std::string& key, const LLStringExplicit& text ); + virtual void setLabel( const LLStringExplicit &new_label ); virtual void clear(); - void setKeystrokeCallback(LLLineEditor::callback_t callback, void* user_data) - { - if(mSearchEditor) - mSearchEditor->setKeystrokeCallback(callback,user_data); - } + void setKeystrokeCallback( commit_callback_t cb ) { mKeystrokeCallback = cb; } + +protected: + void onClearButtonClick(const LLSD& data); + virtual void handleKeystroke(); -private: + commit_callback_t mKeystrokeCallback; LLLineEditor* mSearchEditor; LLButton* mSearchButton; + LLButton* mClearButton; }; #endif // LL_SEARCHEDITOR_H diff --git a/indra/llui/lltabcontainer.cpp b/indra/llui/lltabcontainer.cpp index cabd0be522..720ca692f7 100644 --- a/indra/llui/lltabcontainer.cpp +++ b/indra/llui/lltabcontainer.cpp @@ -306,7 +306,7 @@ void LLTabContainer::draw() setScrollPosPixels((S32)lerp((F32)getScrollPosPixels(), (F32)target_pixel_scroll, LLCriticalDamp::getInterpolant(0.08f))); - BOOL has_scroll_arrows = (mMaxScrollPos > 0) || (mScrollPosPixels > 0); + BOOL has_scroll_arrows = !getTabsHidden() && ((mMaxScrollPos > 0) || (mScrollPosPixels > 0)); if (!mIsVertical) { mJumpPrevArrowBtn->setVisible( has_scroll_arrows ); @@ -429,7 +429,7 @@ BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) { static LLUICachedControl<S32> tabcntrv_pad ("UITabCntrvPad", 0); BOOL handled = FALSE; - BOOL has_scroll_arrows = (getMaxScrollPos() > 0); + BOOL has_scroll_arrows = (getMaxScrollPos() > 0) && !getTabsHidden(); if (has_scroll_arrows) { @@ -498,7 +498,7 @@ BOOL LLTabContainer::handleMouseDown( S32 x, S32 y, MASK mask ) BOOL LLTabContainer::handleHover( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; - BOOL has_scroll_arrows = (getMaxScrollPos() > 0); + BOOL has_scroll_arrows = (getMaxScrollPos() > 0) && !getTabsHidden(); if (has_scroll_arrows) { @@ -540,7 +540,7 @@ BOOL LLTabContainer::handleHover( S32 x, S32 y, MASK mask ) BOOL LLTabContainer::handleMouseUp( S32 x, S32 y, MASK mask ) { BOOL handled = FALSE; - BOOL has_scroll_arrows = (getMaxScrollPos() > 0); + BOOL has_scroll_arrows = (getMaxScrollPos() > 0) && !getTabsHidden(); if (has_scroll_arrows) { @@ -1840,12 +1840,11 @@ void LLTabContainer::updateMaxScrollPos() void LLTabContainer::commitHoveredButton(S32 x, S32 y) { - if (hasMouseCapture()) + if (!getTabsHidden() && hasMouseCapture()) { for(tuple_list_t::iterator iter = mTabList.begin(); iter != mTabList.end(); ++iter) { LLTabTuple* tuple = *iter; - tuple->mButton->setVisible( TRUE ); S32 local_x = x - tuple->mButton->getRect().mLeft; S32 local_y = y - tuple->mButton->getRect().mBottom; if (tuple->mButton->pointInView(local_x, local_y) && tuple->mButton->getEnabled() && !tuple->mTabPanel->getVisible()) diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 85f9064115..c20212c375 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -339,54 +339,75 @@ std::string LLUrlEntryGroup::getLabel(const std::string &url, const LLUrlLabelCa } /// -/// LLUrlEntryEvent Describes a Second Life event Url, e.g., -/// secondlife:///app/event/700727/about +/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., +/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about /// -LLUrlEntryEvent::LLUrlEntryEvent() +LLUrlEntryParcel::LLUrlEntryParcel() { - mPattern = boost::regex("secondlife:///app/event/[\\da-f-]+/about", + mPattern = boost::regex("secondlife:///app/parcel/[\\da-f-]+/about", boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_event.xml"; - mTooltip = LLTrans::getString("TooltipEventUrl"); + mMenuName = "menu_url_parcel.xml"; + mTooltip = LLTrans::getString("TooltipParcelUrl"); } -std::string LLUrlEntryEvent::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +std::string LLUrlEntryParcel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) { return unescapeUrl(url); } -/// -/// LLUrlEntryClassified Describes a Second Life classified Url, e.g., -/// secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about -/// -LLUrlEntryClassified::LLUrlEntryClassified() +// +// LLUrlEntryPlace Describes secondlife:///<location> URLs +// +LLUrlEntryPlace::LLUrlEntryPlace() { - mPattern = boost::regex("secondlife:///app/classified/[\\da-f-]+/about", + mPattern = boost::regex("secondlife://\\S+/?(\\d+/\\d+/\\d+|\\d+/\\d+)/?", boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_classified.xml"; - mTooltip = LLTrans::getString("TooltipClassifiedUrl"); + mMenuName = "menu_url_slurl.xml"; + mTooltip = LLTrans::getString("TooltipSLURL"); } -std::string LLUrlEntryClassified::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +std::string LLUrlEntryPlace::getLabel(const std::string &url, const LLUrlLabelCallback &cb) { - return unescapeUrl(url); -} + // + // we handle SLURLs in the following formats: + // - secondlife://Place/X/Y/Z + // - secondlife://Place/X/Y + // + LLURI uri(url); + std::string location = unescapeUrl(uri.hostName()); + LLSD path_array = uri.pathArray(); + S32 path_parts = path_array.size(); + if (path_parts == 3) + { + // handle slurl with (X,Y,Z) coordinates + std::string x = path_array[0]; + std::string y = path_array[1]; + std::string z = path_array[2]; + return location + " (" + x + "," + y + "," + z + ")"; + } + else if (path_parts == 2) + { + // handle slurl with (X,Y) coordinates + std::string x = path_array[0]; + std::string y = path_array[1]; + return location + " (" + x + "," + y + ")"; + } -/// -/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., -/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about -/// -LLUrlEntryParcel::LLUrlEntryParcel() -{ - mPattern = boost::regex("secondlife:///app/parcel/[\\da-f-]+/about", - boost::regex::perl|boost::regex::icase); - mMenuName = "menu_url_parcel.xml"; - mTooltip = LLTrans::getString("TooltipParcelUrl"); + return url; } -std::string LLUrlEntryParcel::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +std::string LLUrlEntryPlace::getLocation(const std::string &url) const { - return unescapeUrl(url); + // return the part of the Url after secondlife:// part + const std::string search_string = "://"; + size_t pos = url.find(search_string); + if (pos == std::string::npos) + { + return ""; + } + + pos += search_string.size(); + return url.substr(pos, url.size() - pos); } // diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index f3e76dbec0..54053872df 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -170,36 +170,26 @@ private: }; /// -/// LLUrlEntryEvent Describes a Second Life event Url, e.g., -/// secondlife:///app/event/700727/about -/// -class LLUrlEntryEvent : public LLUrlEntryBase -{ -public: - LLUrlEntryEvent(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); -}; - -/// -/// LLUrlEntryClassified Describes a Second Life classified Url, e.g., -/// secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about +/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., +/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about /// -class LLUrlEntryClassified : public LLUrlEntryBase +class LLUrlEntryParcel : public LLUrlEntryBase { public: - LLUrlEntryClassified(); + LLUrlEntryParcel(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); }; /// -/// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., -/// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about +/// LLUrlEntryPlace Describes a Second Life location Url, e.g., +/// secondlife:///Ahern/50/50/50 /// -class LLUrlEntryParcel : public LLUrlEntryBase +class LLUrlEntryPlace : public LLUrlEntryBase { public: - LLUrlEntryParcel(); + LLUrlEntryPlace(); /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLocation(const std::string &url) const; }; /// diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 938375ad13..f2d340deb7 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -49,11 +49,10 @@ LLUrlRegistry::LLUrlRegistry() registerUrl(new LLUrlEntryHTTPLabel()); registerUrl(new LLUrlEntryAgent()); registerUrl(new LLUrlEntryGroup()); - registerUrl(new LLUrlEntryEvent()); - registerUrl(new LLUrlEntryClassified()); registerUrl(new LLUrlEntryParcel()); registerUrl(new LLUrlEntryTeleport()); registerUrl(new LLUrlEntryObjectIM()); + registerUrl(new LLUrlEntryPlace()); registerUrl(new LLUrlEntrySL()); registerUrl(new LLUrlEntrySLLabel()); } @@ -118,8 +117,8 @@ static bool matchRegex(const char *text, boost::regex regex, U32 &start, U32 &en bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb) { - // test for the trivial case of no text and get out fast - if (text.empty()) + // avoid costly regexes if there is clearly no URL in the text + if (text.find("://") == std::string::npos) { return false; } diff --git a/indra/llui/llview.h b/indra/llui/llview.h index d80c2af568..1f7e5afaae 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -633,7 +633,7 @@ template <class T> T* LLView::getChild(const std::string& name, BOOL recurse) co // did we find *something* with that name? if (child) { - llwarns << "Found child named " << name << " but of wrong type " << typeid(child).name() << ", expecting " << typeid(T*).name() << llendl; + llwarns << "Found child named " << name << " but of wrong type " << typeid(*child).name() << ", expecting " << typeid(T*).name() << llendl; } result = getDefaultWidget<T>(name); if (!result) diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index 610ee3349b..1e7a0f7f2c 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -308,56 +308,52 @@ namespace tut void object::test<6>() { // - // test LLUrlEntryEvent - secondlife://app/event Urls + // test LLUrlEntryPlace - secondlife://<location> URLs // - LLUrlEntryEvent url; + LLUrlEntryPlace url; boost::regex r = url.getPattern(); - testRegex("Invalid Event Url", r, - "secondlife:///app/event/FOO/about", + testRegex("no valid slurl [1]", r, + "secondlife://Ahern/FOO/50/", ""); - testRegex("Event Url ", r, - "secondlife:///app/event/700727/about", - "secondlife:///app/event/700727/about"); + testRegex("Ahern (50,50,50) [1]", r, + "secondlife://Ahern/50/50/50/", + "secondlife://Ahern/50/50/50/"); - testRegex("Event Url in text", r, - "XXX secondlife:///app/event/700727/about XXX", - "secondlife:///app/event/700727/about"); + testRegex("Ahern (50,50,50) [2]", r, + "XXX secondlife://Ahern/50/50/50/ XXX", + "secondlife://Ahern/50/50/50/"); - testRegex("Event Url multicase", r, - "XXX secondlife:///APP/Event/700727/about XXX", - "secondlife:///APP/Event/700727/about"); - } + testRegex("Ahern (50,50,50) [3]", r, + "XXX secondlife://Ahern/50/50/50 XXX", + "secondlife://Ahern/50/50/50"); - template<> template<> - void object::test<7>() - { - // - // test LLUrlEntryClassified - secondlife://app/classified Urls - // - LLUrlEntryClassified url; - boost::regex r = url.getPattern(); + testRegex("Ahern (50,50,50) multicase", r, + "XXX SecondLife://Ahern/50/50/50/ XXX", + "SecondLife://Ahern/50/50/50/"); - testRegex("Invalid Classified Url", r, - "secondlife:///app/classified/00128854-XXXX-5649-7ca6-5dfaa7514ab2/about", - ""); + testRegex("Ahern (50,50) [1]", r, + "XXX secondlife://Ahern/50/50/ XXX", + "secondlife://Ahern/50/50/"); - testRegex("Classified Url ", r, - "secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about", - "secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about"); + testRegex("Ahern (50,50) [2]", r, + "XXX secondlife://Ahern/50/50 XXX", + "secondlife://Ahern/50/50"); - testRegex("Classified Url in text", r, - "XXX secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about XXX", - "secondlife:///app/classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/about"); + // DEV-21577: In-world SLURLs containing "(" or ")" are not treated as a hyperlink in chat + testRegex("SLURL with brackets", r, + "XXX secondlife://Burning%20Life%20(Hyper)/27/210/30 XXX", + "secondlife://Burning%20Life%20(Hyper)/27/210/30"); - testRegex("Classified Url multicase", r, - "XXX secondlife:///APP/Classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/About XXX", - "secondlife:///APP/Classified/00128854-c36a-5649-7ca6-5dfaa7514ab2/About"); + // DEV-35459: SLURLs and teleport Links not parsed properly + testRegex("SLURL with quote", r, + "XXX secondlife://A'ksha%20Oasis/41/166/701 XXX", + "secondlife://A'ksha%20Oasis/41/166/701"); } template<> template<> - void object::test<8>() + void object::test<7>() { // // test LLUrlEntryParcel - secondlife://app/parcel Urls @@ -382,7 +378,7 @@ namespace tut "secondlife:///APP/Parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/About"); } template<> template<> - void object::test<9>() + void object::test<8>() { // // test LLUrlEntryTeleport - secondlife://app/teleport URLs @@ -458,7 +454,7 @@ namespace tut } template<> template<> - void object::test<10>() + void object::test<9>() { // // test LLUrlEntrySL - general secondlife:// URLs @@ -496,7 +492,7 @@ namespace tut } template<> template<> - void object::test<11>() + void object::test<10>() { // // test LLUrlEntrySLLabel - general secondlife:// URLs with labels |