1429 lines
40 KiB
C++
1429 lines
40 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")
|
|
{};
|
|
|
|
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;
|
|
|
|
//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::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);
|
|
if (!item_pair) return false;
|
|
|
|
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();
|
|
}
|
|
|
|
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)
|
|
{
|
|
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()
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
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()
|
|
{
|
|
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();
|
|
|
|
if (getParent()) // dummy widgets don't have a parent
|
|
getParent()->notifyParent(params);
|
|
}
|
|
|
|
void LLFlatListView::setNoItemsCommentVisible(bool visible) const
|
|
{
|
|
if (mNoItemsCommentTextbox)
|
|
{
|
|
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)
|
|
{
|
|
mForceShowingUnmatchedItems = show;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void 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)
|
|
{
|
|
notifyParentItemsRectChanged();
|
|
}
|
|
}
|
|
|
|
bool LLFlatListViewEx::hasMatchedItems()
|
|
{
|
|
return mHasMatchedItems;
|
|
}
|
|
|
|
//EOF
|