mirror of
https://github.com/FirestormViewer/phoenix-firestorm.git
synced 2026-08-14 08:53:53 +00:00
LLFlatListView::sort() rearranged items via rearrangeItems(), but never notified the parent accordion tab about the updated content height. When outfit items were loaded without an active filter this notification was skipped entirely, so that left the flat list stuck with an outdated size. Because LLFlatListView inherits from LLScrollContainer, it ended up showing its own scrollbar inside the accordion's scrollbar - hopefully fix it by calling notifyParentItemsRectChanged() after rearrangeItems() in sort(). The call is guarded by mPrevNotifyParentRect, so redundant calls should be safe. Now... would it scale with thousands of outfits that some residents have? Let's see in the nightlies... Signed-off-by: PanteraPolnocy <panterapolnocy@gmail.com>
1467 lines
42 KiB
C++
1467 lines
42 KiB
C++
/**
|
|
* @file llflatlistview.cpp
|
|
* @brief LLFlatListView base class and extension to support messages for several cases of an empty list.
|
|
*
|
|
* $LicenseInfo:firstyear=2009&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include "llpanel.h"
|
|
#include "lltextbox.h"
|
|
|
|
#include "llflatlistview.h"
|
|
|
|
static const LLDefaultChildRegistry::Register<LLFlatListView> flat_list_view("flat_list_view");
|
|
|
|
const LLSD SELECTED_EVENT = LLSD().with("selected", true);
|
|
const LLSD UNSELECTED_EVENT = LLSD().with("selected", false);
|
|
|
|
//forward declaration
|
|
bool llsds_are_equal(const LLSD& llsd_1, const LLSD& llsd_2);
|
|
|
|
LLFlatListView::Params::Params()
|
|
: item_pad("item_pad"),
|
|
allow_select("allow_select"),
|
|
multi_select("multi_select"),
|
|
keep_one_selected("keep_one_selected"),
|
|
keep_selection_visible_on_reshape("keep_selection_visible_on_reshape",false),
|
|
no_items_text("no_items_text"),
|
|
magical_hacky_height_padding("magical_hacky_height_padding")
|
|
{};
|
|
|
|
void LLFlatListView::reshape(S32 width, S32 height, bool called_from_parent /* = true */)
|
|
{
|
|
S32 delta = height - getRect().getHeight();
|
|
LLScrollContainer::reshape(width, height, called_from_parent);
|
|
setItemsNoScrollWidth(width);
|
|
rearrangeItems();
|
|
|
|
if(delta!= 0 && mKeepSelectionVisibleOnReshape)
|
|
{
|
|
ensureSelectedVisible();
|
|
}
|
|
}
|
|
|
|
const LLRect& LLFlatListView::getItemsRect() const
|
|
{
|
|
return mItemsPanel->getRect();
|
|
}
|
|
|
|
bool LLFlatListView::addItem(LLPanel * item, const LLSD& value /*= LLUUID::null*/, EAddPosition pos /*= ADD_BOTTOM*/,bool rearrange /*= true*/)
|
|
{
|
|
if (!item) return false;
|
|
if (value.isUndefined()) return false; // item stays an orphan?!!!
|
|
|
|
//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:
|
|
// <FS:ND> Reaching here means leaking new_pair and item. It also means item is never added.
|
|
// pos can be ADD_TOP, ADD_BOTTOM and ADD_DEFAULT. So what's default?
|
|
// I rolled a dice and then put it into the bottom bucket, so at least something better than leaking
|
|
// and throwing items away does happen.
|
|
mItemPairs.push_back(new_pair);
|
|
mItemsPanel->addChild(item);
|
|
// </FS:ND>
|
|
|
|
break;
|
|
}
|
|
|
|
//_4 is for MASK
|
|
item->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4));
|
|
item->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4));
|
|
|
|
// Children don't accept the focus
|
|
item->setTabStop(false);
|
|
|
|
if (rearrange)
|
|
{
|
|
rearrangeItems();
|
|
notifyParentItemsRectChanged();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool LLFlatListView::addItemPairs(pairs_list_t panel_list, bool rearrange /*= true*/)
|
|
{
|
|
if (!mItemComparator)
|
|
{
|
|
LL_WARNS_ONCE() << "No comparator specified for inserting FlatListView items." << LL_ENDL;
|
|
return false;
|
|
}
|
|
if (panel_list.size() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// presort list so that it will be easier to sort elements into mItemPairs
|
|
panel_list.sort(ComparatorAdaptor(*mItemComparator));
|
|
|
|
pairs_const_iterator_t new_pair_it = panel_list.begin();
|
|
item_pair_t* new_pair = *new_pair_it;
|
|
pairs_iterator_t pair_it = mItemPairs.begin();
|
|
item_pair_t* item_pair = *pair_it;
|
|
|
|
// sort panel_list into mItemPars
|
|
while (new_pair_it != panel_list.end() && pair_it != mItemPairs.end())
|
|
{
|
|
if (!new_pair->first || new_pair->first->getParent() == mItemsPanel)
|
|
{
|
|
// iterator already used or we are reusing existing panel
|
|
new_pair_it++;
|
|
new_pair = *new_pair_it;
|
|
}
|
|
else if (mItemComparator->compare(new_pair->first, item_pair->first))
|
|
{
|
|
LLPanel* panel = new_pair->first;
|
|
|
|
mItemPairs.insert(pair_it, new_pair);
|
|
mItemsPanel->addChild(panel);
|
|
|
|
//_4 is for MASK
|
|
panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, new_pair, _4));
|
|
panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, new_pair, _4));
|
|
// Children don't accept the focus
|
|
panel->setTabStop(false);
|
|
}
|
|
else
|
|
{
|
|
pair_it++;
|
|
item_pair = *pair_it;
|
|
}
|
|
}
|
|
|
|
// Add what is left of panel_list into the end of mItemPairs.
|
|
for (; new_pair_it != panel_list.end(); ++new_pair_it)
|
|
{
|
|
item_pair_t* item_pair = *new_pair_it;
|
|
LLPanel *panel = item_pair->first;
|
|
if (panel && panel->getParent() != mItemsPanel)
|
|
{
|
|
mItemPairs.push_back(item_pair);
|
|
mItemsPanel->addChild(panel);
|
|
|
|
//_4 is for MASK
|
|
panel->setMouseDownCallback(boost::bind(&LLFlatListView::onItemMouseClick, this, item_pair, _4));
|
|
panel->setRightMouseDownCallback(boost::bind(&LLFlatListView::onItemRightMouseClick, this, item_pair, _4));
|
|
// Children don't accept the focus
|
|
panel->setTabStop(false);
|
|
}
|
|
}
|
|
|
|
if (rearrange)
|
|
{
|
|
rearrangeItems();
|
|
notifyParentItemsRectChanged();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LLFlatListView::insertItemAfter(LLPanel* after_item, LLPanel* item_to_add, const 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();
|
|
for (; it != mItemPairs.end(); ++it)
|
|
{
|
|
if (*it == after_pair)
|
|
{
|
|
// insert new elements before the element at position of passed iterator.
|
|
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::onItemRightMouseClick, this, new_pair, _4));
|
|
|
|
rearrangeItems();
|
|
notifyParentItemsRectChanged();
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LLFlatListView::removeItem(LLPanel* item, bool rearrange)
|
|
{
|
|
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, rearrange);
|
|
}
|
|
|
|
bool LLFlatListView::removeItemByValue(const LLSD& value, bool rearrange)
|
|
{
|
|
if (value.isUndefined()) return false;
|
|
|
|
item_pair_t* item_pair = getItemPair(value);
|
|
// <FS:Beq> try to find where the notifications get stuck
|
|
// if (!item_pair) return false;
|
|
if (!item_pair)
|
|
{
|
|
LL_WARNS() << "LLFlatListView::removeItemByValue: item_pair not found" << LL_ENDL;
|
|
return false;
|
|
}
|
|
// </FS:Beq>
|
|
return removeItemPair(item_pair, rearrange);
|
|
}
|
|
|
|
bool LLFlatListView::removeItemByUUID(const LLUUID& uuid, bool rearrange)
|
|
{
|
|
return removeItemByValue(LLSD(uuid), rearrange);
|
|
}
|
|
|
|
LLPanel* LLFlatListView::getItemByValue(const LLSD& value) const
|
|
{
|
|
if (value.isUndefined()) 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(const 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(uuid_vec_t& 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(bool no_commit_on_deselection /*= false*/)
|
|
{
|
|
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();
|
|
|
|
if (mCommitOnSelectionChange && !no_commit_on_deselection)
|
|
{
|
|
onCommit();
|
|
}
|
|
|
|
// Stretch selected item rect to ensure it won't be clipped
|
|
mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
|
|
}
|
|
|
|
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 static_cast<U32>(mItemPairs.size());
|
|
}
|
|
}
|
|
|
|
void LLFlatListView::clear()
|
|
{
|
|
// This will clear mSelectedItemPairs, calling all appropriate callbacks.
|
|
resetSelection();
|
|
|
|
// 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);
|
|
(*it)->first->die();
|
|
delete *it;
|
|
}
|
|
mItemPairs.clear();
|
|
|
|
// also set items panel height to zero. Reshape it to allow reshaping of non-item children
|
|
LLRect rc = mItemsPanel->getRect();
|
|
rc.mBottom = rc.mTop;
|
|
mItemsPanel->reshape(rc.getWidth(), rc.getHeight());
|
|
mItemsPanel->setRect(rc);
|
|
|
|
setNoItemsCommentVisible(true);
|
|
notifyParentItemsRectChanged();
|
|
}
|
|
|
|
void LLFlatListView::sort()
|
|
{
|
|
if (!mItemComparator)
|
|
{
|
|
LL_WARNS() << "No comparator specified for sorting FlatListView items." << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
mItemPairs.sort(ComparatorAdaptor(*mItemComparator));
|
|
rearrangeItems();
|
|
notifyParentItemsRectChanged(); // <FS:PP> FIRE-36479 Multiple nested scrollbars in Appearance window
|
|
}
|
|
|
|
bool LLFlatListView::updateValue(const LLSD& old_value, const LLSD& new_value)
|
|
{
|
|
if (old_value.isUndefined() || new_value.isUndefined()) return false;
|
|
if (llsds_are_equal(old_value, new_value)) return false;
|
|
|
|
item_pair_t* item_pair = getItemPair(old_value);
|
|
if (!item_pair) return false;
|
|
|
|
item_pair->second = new_value;
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// PROTECTED STUFF
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
LLFlatListView::LLFlatListView(const LLFlatListView::Params& p)
|
|
: LLScrollContainer(p)
|
|
, mItemComparator(NULL)
|
|
, mItemsPanel(NULL)
|
|
, mItemPad(p.item_pad)
|
|
, mAllowSelection(p.allow_select)
|
|
, mMultipleSelection(p.multi_select)
|
|
, mKeepOneItemSelected(p.keep_one_selected)
|
|
, mCommitOnSelectionChange(false)
|
|
, mPrevNotifyParentRect(LLRect())
|
|
, mNoItemsCommentTextbox(NULL)
|
|
, mIsConsecutiveSelection(false)
|
|
, mKeepSelectionVisibleOnReshape(p.keep_selection_visible_on_reshape)
|
|
, mFocusOnItemClicked(true)
|
|
, mMagicalHackyHeightPadding(p.magical_hacky_height_padding)
|
|
{
|
|
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);
|
|
|
|
LLViewBorder::Params params;
|
|
params.name("scroll border");
|
|
params.rect(getLastSelectedItemRect());
|
|
params.visible(false);
|
|
params.bevel_style(LLViewBorder::BEVEL_IN);
|
|
mSelectedItemsBorder = LLUICtrlFactory::create<LLViewBorder> (params);
|
|
mItemsPanel->addChild( mSelectedItemsBorder );
|
|
|
|
{
|
|
// create textbox for "No Items" comment text
|
|
LLTextBox::Params text_p = p.no_items_text;
|
|
if (!text_p.rect.isProvided())
|
|
{
|
|
LLRect comment_rect = getRect();
|
|
comment_rect.setOriginAndSize(0, 0, comment_rect.getWidth(), comment_rect.getHeight());
|
|
comment_rect.stretch(-getBorderWidth());
|
|
text_p.rect(comment_rect);
|
|
}
|
|
text_p.border_visible(false);
|
|
|
|
if (!text_p.follows.isProvided())
|
|
{
|
|
text_p.follows.flags(FOLLOWS_ALL);
|
|
}
|
|
mNoItemsCommentTextbox = LLUICtrlFactory::create<LLTextBox>(text_p, this);
|
|
}
|
|
};
|
|
|
|
LLFlatListView::~LLFlatListView()
|
|
{
|
|
for (pairs_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it)
|
|
{
|
|
mItemsPanel->removeChild((*it)->first);
|
|
(*it)->first->die();
|
|
delete *it;
|
|
}
|
|
mItemPairs.clear();
|
|
}
|
|
|
|
// virtual
|
|
void LLFlatListView::draw()
|
|
{
|
|
// Highlight border if a child of this container has keyboard focus
|
|
if( mSelectedItemsBorder->getVisible() )
|
|
{
|
|
mSelectedItemsBorder->setKeyboardFocusHighlight( hasFocus() );
|
|
}
|
|
LLScrollContainer::draw();
|
|
}
|
|
|
|
// virtual
|
|
bool LLFlatListView::postBuild()
|
|
{
|
|
setTabStop(true);
|
|
return LLScrollContainer::postBuild();
|
|
}
|
|
|
|
void LLFlatListView::rearrangeItems()
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED;
|
|
static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
|
|
|
|
setNoItemsCommentVisible(0==size());
|
|
|
|
if (mItemPairs.empty()) return;
|
|
|
|
//calculating required height - assuming items can be of different height
|
|
//list should accommodate all its items
|
|
S32 height = 0;
|
|
|
|
S32 invisible_children_count = 0;
|
|
pairs_iterator_t it = mItemPairs.begin();
|
|
for (; it != mItemPairs.end(); ++it)
|
|
{
|
|
LLPanel* item = (*it)->first;
|
|
|
|
// skip invisible child
|
|
if (!item->getVisible())
|
|
{
|
|
++invisible_children_count;
|
|
continue;
|
|
}
|
|
|
|
height += item->getRect().getHeight();
|
|
}
|
|
|
|
// add paddings between items, excluding invisible ones
|
|
height += mItemPad * (static_cast<S32>(mItemPairs.size()) - invisible_children_count - 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;
|
|
|
|
// skip invisible child
|
|
if (!item->getVisible())
|
|
continue;
|
|
|
|
LLRect rc = item->getRect();
|
|
rc.setLeftTopAndSize(rc.mLeft, item_new_top, width, rc.getHeight());
|
|
item->reshape(rc.getWidth(), rc.getHeight());
|
|
item->setRect(rc);
|
|
|
|
// move top for next item in list
|
|
item_new_top -= (rc.getHeight() + mItemPad);
|
|
}
|
|
|
|
// Stretch selected item rect to ensure it won't be clipped
|
|
mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
|
|
}
|
|
|
|
void LLFlatListView::onItemMouseClick(item_pair_t* item_pair, MASK mask)
|
|
{
|
|
if (!item_pair) return;
|
|
|
|
if (!item_pair->first)
|
|
{
|
|
LL_WARNS() << "Attempt to selet an item pair containing null panel item" << LL_ENDL;
|
|
return;
|
|
}
|
|
|
|
if (mFocusOnItemClicked)
|
|
{
|
|
setFocus(true);
|
|
}
|
|
|
|
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_SHIFT) && !(mask & MASK_CONTROL)
|
|
&& mMultipleSelection && !mSelectedItemPairs.empty() )
|
|
{
|
|
item_pair_t* last_selected_pair = mSelectedItemPairs.back();
|
|
|
|
// If item_pair is already selected - do nothing
|
|
if (last_selected_pair == item_pair)
|
|
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.
|
|
for (pairs_iterator_t
|
|
iter = mItemPairs.begin(),
|
|
iter_end = mItemPairs.end();
|
|
iter != iter_end; ++iter)
|
|
{
|
|
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;
|
|
}
|
|
if (!cur->first->getVisible())
|
|
{
|
|
// Skip invisible item pairs.
|
|
continue;
|
|
}
|
|
if (grab_items)
|
|
{
|
|
pairs_to_select.push_back(cur);
|
|
}
|
|
}
|
|
|
|
if (reverse)
|
|
{
|
|
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;
|
|
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)
|
|
{
|
|
// Update last selected item border.
|
|
mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
|
|
}
|
|
return;
|
|
}
|
|
|
|
//no need to do additional commit on selection reset
|
|
if (!(mask & MASK_CONTROL) || !mMultipleSelection) resetSelection(true);
|
|
|
|
//only CTRL usage allows to deselect an item, usual clicking on an item cannot deselect it
|
|
if (mask & MASK_CONTROL)
|
|
selectItemPair(item_pair, select_item);
|
|
else
|
|
selectItemPair(item_pair, true);
|
|
}
|
|
|
|
void LLFlatListView::onItemRightMouseClick(item_pair_t* item_pair, MASK mask)
|
|
{
|
|
if (!item_pair)
|
|
return;
|
|
|
|
// Forbid deselecting of items on right mouse button click if mMultipleSelection flag is set on,
|
|
// because some of derived classes may have context menu and selected items must be kept.
|
|
if ( !(mask & MASK_CONTROL) && mMultipleSelection && isSelected(item_pair) )
|
|
return;
|
|
|
|
// else got same behavior as at onItemMouseClick
|
|
onItemMouseClick(item_pair, mask);
|
|
}
|
|
|
|
bool LLFlatListView::handleKeyHere(KEY key, MASK mask)
|
|
{
|
|
bool reset_selection = (mask != MASK_SHIFT);
|
|
bool handled = false;
|
|
switch (key)
|
|
{
|
|
case KEY_RETURN:
|
|
{
|
|
if (mSelectedItemPairs.size() && mask == MASK_NONE)
|
|
{
|
|
mOnReturnSignal(this, getValue());
|
|
handled = true;
|
|
}
|
|
break;
|
|
}
|
|
case KEY_UP:
|
|
{
|
|
if ( !selectNextItemPair(true, reset_selection) && reset_selection)
|
|
{
|
|
// If case we are in accordion tab notify parent to go to the previous accordion
|
|
if(notifyParent(LLSD().with("action","select_prev")) > 0 )//message was processed
|
|
resetSelection();
|
|
}
|
|
break;
|
|
}
|
|
case KEY_DOWN:
|
|
{
|
|
if ( !selectNextItemPair(false, reset_selection) && reset_selection)
|
|
{
|
|
// If case we are in accordion tab notify parent to go to the next accordion
|
|
if( notifyParent(LLSD().with("action","select_next")) > 0 ) //message was processed
|
|
resetSelection();
|
|
}
|
|
break;
|
|
}
|
|
case KEY_ESCAPE:
|
|
{
|
|
if (mask == MASK_NONE)
|
|
{
|
|
setFocus(false); // pass focus to the game area (EXT-8357)
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ( ( key == KEY_UP || key == KEY_DOWN ) && mSelectedItemPairs.size() )
|
|
{
|
|
ensureSelectedVisible();
|
|
/*
|
|
LLRect visible_rc = getVisibleContentRect();
|
|
LLRect selected_rc = getLastSelectedItemRect();
|
|
|
|
if ( !visible_rc.contains (selected_rc) )
|
|
{
|
|
// But scroll in Items panel coordinates
|
|
scrollToShowRect(selected_rc);
|
|
}
|
|
|
|
// In case we are in accordion tab notify parent to show selected rectangle
|
|
LLRect screen_rc;
|
|
localRectToScreen(selected_rc, &screen_rc);
|
|
notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue()));*/
|
|
|
|
handled = true;
|
|
}
|
|
|
|
return handled ? handled : LLScrollContainer::handleKeyHere(key, mask);
|
|
}
|
|
|
|
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);
|
|
|
|
if (mCommitOnSelectionChange)
|
|
{
|
|
onCommit();
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
void LLFlatListView::scrollToShowFirstSelectedItem()
|
|
{
|
|
if (!mSelectedItemPairs.size()) return;
|
|
|
|
LLRect selected_rc = mSelectedItemPairs.front()->first->getRect();
|
|
|
|
if (selected_rc.isValid())
|
|
{
|
|
scrollToShowRect(selected_rc);
|
|
}
|
|
}
|
|
|
|
LLRect LLFlatListView::getLastSelectedItemRect()
|
|
{
|
|
if (!mSelectedItemPairs.size())
|
|
{
|
|
return LLRect::null;
|
|
}
|
|
|
|
return mSelectedItemPairs.back()->first->getRect();
|
|
}
|
|
|
|
void LLFlatListView::selectFirstItem ()
|
|
{
|
|
// 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 ()
|
|
{
|
|
// 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()
|
|
{
|
|
LLRect selected_rc = getLastSelectedItemRect();
|
|
|
|
if ( selected_rc.isValid() )
|
|
{
|
|
scrollToShowRect(selected_rc);
|
|
}
|
|
}
|
|
|
|
|
|
// virtual
|
|
bool LLFlatListView::selectNextItemPair(bool is_up_direction, bool reset_selection)
|
|
{
|
|
// No items - no actions!
|
|
if ( 0 == size() )
|
|
return false;
|
|
|
|
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
|
|
if ( is_up_direction )
|
|
{
|
|
// Find current selected item position in mItemPairs list
|
|
pairs_list_t::reverse_iterator sel_it = std::find(mItemPairs.rbegin(), mItemPairs.rend(), cur_sel_pair);
|
|
|
|
for (;++sel_it != mItemPairs.rend();)
|
|
{
|
|
// skip invisible items
|
|
if ( (*sel_it)->first->getVisible() )
|
|
{
|
|
to_sel_pair = *sel_it;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Find current selected item position in mItemPairs list
|
|
pairs_list_t::iterator sel_it = std::find(mItemPairs.begin(), mItemPairs.end(), cur_sel_pair);
|
|
|
|
for (;++sel_it != mItemPairs.end();)
|
|
{
|
|
// skip invisible items
|
|
if ( (*sel_it)->first->getVisible() )
|
|
{
|
|
to_sel_pair = *sel_it;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
// Force selection to first item
|
|
if (is_up_direction)
|
|
selectLastItem();
|
|
else
|
|
selectFirstItem();
|
|
// Mark it as consecutive selection
|
|
mIsConsecutiveSelection = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LLFlatListView::canSelectAll() const
|
|
{
|
|
return 0 != size() && mAllowSelection && mMultipleSelection;
|
|
}
|
|
|
|
void LLFlatListView::selectAll()
|
|
{
|
|
if (!mAllowSelection || !mMultipleSelection)
|
|
return;
|
|
|
|
mSelectedItemPairs.clear();
|
|
|
|
for (pairs_const_iterator_t it= mItemPairs.begin(); it != mItemPairs.end(); ++it)
|
|
{
|
|
item_pair_t* item_pair = *it;
|
|
mSelectedItemPairs.push_back(item_pair);
|
|
//a way of notifying panel of selection state changes
|
|
LLPanel* item = item_pair->first;
|
|
item->setValue(SELECTED_EVENT);
|
|
}
|
|
|
|
if (mCommitOnSelectionChange)
|
|
{
|
|
onCommit();
|
|
}
|
|
|
|
// Stretch selected item rect to ensure it won't be clipped
|
|
mSelectedItemsBorder->setRect(getLastSelectedItemRect().stretch(-1));
|
|
}
|
|
|
|
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, bool rearrange)
|
|
{
|
|
llassert(item_pair);
|
|
|
|
bool deleted = false;
|
|
bool selection_changed = 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;
|
|
}
|
|
}
|
|
|
|
// <FS:Beq> try to find where the notifications get stuck
|
|
// if (!deleted) return false;
|
|
if (!deleted)
|
|
{
|
|
LL_WARNS() << "LLFlatListView::removeItemPair: item not found" << LL_ENDL;
|
|
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);
|
|
selection_changed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mItemsPanel->removeChild(item_pair->first);
|
|
item_pair->first->die();
|
|
delete item_pair;
|
|
|
|
if (rearrange)
|
|
{
|
|
rearrangeItems();
|
|
notifyParentItemsRectChanged();
|
|
}
|
|
|
|
if (selection_changed && mCommitOnSelectionChange)
|
|
{
|
|
onCommit();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void LLFlatListView::notifyParentItemsRectChanged()
|
|
{
|
|
LL_PROFILE_ZONE_SCOPED;
|
|
S32 comment_height = 0;
|
|
|
|
// take into account comment text height if exists
|
|
if (mNoItemsCommentTextbox && mNoItemsCommentTextbox->getVisible())
|
|
{
|
|
// top text padding inside the textbox is included into the height
|
|
comment_height = mNoItemsCommentTextbox->getTextPixelHeight();
|
|
|
|
// take into account a distance from parent's top border to textbox's top
|
|
comment_height += getRect().getHeight() - mNoItemsCommentTextbox->getRect().mTop;
|
|
}
|
|
|
|
LLRect req_rect = getItemsRect();
|
|
|
|
// get maximum of items total height and comment text height
|
|
req_rect.setOriginAndSize(req_rect.mLeft, req_rect.mBottom, req_rect.getWidth(), llmax(req_rect.getHeight(), comment_height));
|
|
|
|
// take into account border size.
|
|
req_rect.stretch(getBorderWidth());
|
|
|
|
if (req_rect == mPrevNotifyParentRect)
|
|
return;
|
|
|
|
mPrevNotifyParentRect = req_rect;
|
|
|
|
LLSD params;
|
|
params["action"] = "size_changes";
|
|
params["width"] = req_rect.getWidth();
|
|
params["height"] = req_rect.getHeight() + mMagicalHackyHeightPadding;
|
|
|
|
if (getParent()) // dummy widgets don't have a parent
|
|
getParent()->notifyParent(params);
|
|
}
|
|
|
|
void LLFlatListView::setNoItemsCommentVisible(bool visible) const
|
|
{
|
|
if (mNoItemsCommentTextbox)
|
|
{
|
|
// AO: mSelectedItemsBorder doesn't behave correctly when item spacing changes dynamically.
|
|
// disable it for now.
|
|
// mSelectedItemsBorder->setVisible(!visible);
|
|
mNoItemsCommentTextbox->setVisible(visible);
|
|
}
|
|
}
|
|
|
|
void LLFlatListView::getItems(std::vector<LLPanel*>& items) const
|
|
{
|
|
if (mItemPairs.empty()) return;
|
|
|
|
items.clear();
|
|
for (pairs_const_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it)
|
|
{
|
|
items.push_back((*it)->first);
|
|
}
|
|
}
|
|
|
|
void LLFlatListView::getValues(std::vector<LLSD>& values) const
|
|
{
|
|
if (mItemPairs.empty()) return;
|
|
|
|
values.clear();
|
|
for (pairs_const_iterator_t it = mItemPairs.begin(); it != mItemPairs.end(); ++it)
|
|
{
|
|
values.push_back((*it)->second);
|
|
}
|
|
}
|
|
|
|
// virtual
|
|
void LLFlatListView::onFocusReceived()
|
|
{
|
|
if (size())
|
|
{
|
|
mSelectedItemsBorder->setVisible(true);
|
|
}
|
|
gEditMenuHandler = this;
|
|
}
|
|
// virtual
|
|
void LLFlatListView::onFocusLost()
|
|
{
|
|
mSelectedItemsBorder->setVisible(false);
|
|
// Route menu back to the default
|
|
if (gEditMenuHandler == this)
|
|
{
|
|
gEditMenuHandler = NULL;
|
|
}
|
|
}
|
|
|
|
//virtual
|
|
S32 LLFlatListView::notify(const LLSD& info)
|
|
{
|
|
if (info.has("action"))
|
|
{
|
|
std::string str_action = info["action"];
|
|
if (str_action == "select_first")
|
|
{
|
|
setFocus(true);
|
|
selectFirstItem();
|
|
return 1;
|
|
}
|
|
else if (str_action == "select_last")
|
|
{
|
|
setFocus(true);
|
|
selectLastItem();
|
|
return 1;
|
|
}
|
|
}
|
|
else if (info.has("rearrange"))
|
|
{
|
|
rearrangeItems();
|
|
notifyParentItemsRectChanged();
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void LLFlatListView::detachItems(std::vector<LLPanel*>& detached_items)
|
|
{
|
|
LLSD action;
|
|
action.with("detach", LLSD());
|
|
// Clear detached_items list
|
|
detached_items.clear();
|
|
// Go through items and detach valid items, remove them from items panel
|
|
// and add to detached_items.
|
|
for (auto item_pair : mItemPairs)
|
|
{
|
|
LLPanel* pItem = item_pair->first;
|
|
if (1 == pItem->notify(action))
|
|
{
|
|
selectItemPair(item_pair, false);
|
|
mItemsPanel->removeChild(pItem);
|
|
detached_items.emplace_back(pItem);
|
|
}
|
|
}
|
|
if (!detached_items.empty())
|
|
{
|
|
// Some items were detached, clean ourself from unusable memory
|
|
if (detached_items.size() == mItemPairs.size())
|
|
{
|
|
// This way will be faster if all items were disconnected
|
|
for (auto item_pair : mItemPairs)
|
|
{
|
|
item_pair->first = nullptr;
|
|
delete item_pair;
|
|
}
|
|
mItemPairs.clear();
|
|
// Also set items panel height to zero.
|
|
// Reshape it to allow reshaping of non-item children.
|
|
LLRect rc = mItemsPanel->getRect();
|
|
rc.mBottom = rc.mTop;
|
|
mItemsPanel->reshape(rc.getWidth(), rc.getHeight());
|
|
mItemsPanel->setRect(rc);
|
|
setNoItemsCommentVisible(true);
|
|
}
|
|
else
|
|
{
|
|
for (auto detached_item : detached_items)
|
|
{
|
|
auto found_pos = std::find_if(mItemPairs.begin(), mItemPairs.end(), [detached_item](auto item_pair) { return item_pair->first == detached_item; });
|
|
if (found_pos != mItemPairs.end())
|
|
{
|
|
mItemPairs.erase(found_pos);
|
|
auto item_pair = *found_pos;
|
|
item_pair->first = nullptr;
|
|
delete item_pair;
|
|
}
|
|
}
|
|
rearrangeItems();
|
|
}
|
|
notifyParentItemsRectChanged();
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************************/
|
|
/* 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)
|
|
, mForceShowingUnmatchedItems(false)
|
|
, mHasMatchedItems(false)
|
|
{
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
bool LLFlatListViewEx::getForceShowingUnmatchedItems() const
|
|
{
|
|
return mForceShowingUnmatchedItems;
|
|
}
|
|
|
|
void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show, bool notify_parent)
|
|
{
|
|
if (mForceShowingUnmatchedItems != show)
|
|
{
|
|
mForceShowingUnmatchedItems = show;
|
|
if (!mFilterSubString.empty())
|
|
{
|
|
updateNoItemsMessage(mFilterSubString);
|
|
filterItems(false, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLFlatListViewEx::setFilterSubString(const std::string& filter_str, bool notify_parent)
|
|
{
|
|
if (0 != LLStringUtil::compareInsensitive(filter_str, mFilterSubString))
|
|
{
|
|
mFilterSubString = filter_str;
|
|
updateNoItemsMessage(mFilterSubString);
|
|
filterItems(false, notify_parent);
|
|
}
|
|
}
|
|
|
|
bool LLFlatListViewEx::updateItemVisibility(LLPanel* item, const LLSD &action)
|
|
{
|
|
if (!item)
|
|
return false;
|
|
|
|
bool visible = true;
|
|
|
|
// 0 signifies that filter is matched,
|
|
// i.e. we don't hide items that don't support 'match_filter' action, separators etc.
|
|
if (0 == item->notify(action))
|
|
{
|
|
mHasMatchedItems = true;
|
|
}
|
|
else
|
|
{
|
|
// TODO: implement (re)storing of current selection.
|
|
if (!mForceShowingUnmatchedItems)
|
|
{
|
|
selectItem(item, false);
|
|
visible = false;
|
|
}
|
|
}
|
|
|
|
if (item->getVisible() != visible)
|
|
{
|
|
item->setVisible(visible);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool LLFlatListViewEx::filterItems(bool re_sort, bool notify_parent)
|
|
{
|
|
std::string cur_filter = mFilterSubString;
|
|
LLStringUtil::toUpper(cur_filter);
|
|
|
|
LLSD action;
|
|
action.with("match_filter", cur_filter);
|
|
|
|
mHasMatchedItems = false;
|
|
bool visibility_changed{ false };
|
|
for (auto item_pair : getItemPairs())
|
|
{
|
|
LLPanel* pItem = item_pair->first;
|
|
visibility_changed |= updateItemVisibility(pItem, action);
|
|
}
|
|
|
|
if (re_sort)
|
|
{
|
|
sort();
|
|
}
|
|
|
|
if (visibility_changed && notify_parent)
|
|
{
|
|
rearrangeItems();
|
|
notifyParentItemsRectChanged();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LLFlatListViewEx::hasMatchedItems()
|
|
{
|
|
return mHasMatchedItems;
|
|
}
|
|
|
|
//EOF
|