phoenix-firestorm/indra/llui/llflatlistview.cpp

660 lines
17 KiB
C++

/**
* @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 "lltextbox.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);
static const std::string COMMENT_TEXTBOX = "comment_text";
//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")
{};
void LLFlatListView::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */)
{
LLScrollContainer::reshape(width, height, called_from_parent);
setItemsNoScrollWidth(width);
rearrangeItems();
}
const LLRect& LLFlatListView::getItemsRect() const
{
return mItemsPanel->getRect();
}
bool LLFlatListView::addItem(LLPanel * item, const 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();
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::onItemMouseClick, this, new_pair, _4));
rearrangeItems();
notifyParentItemsRectChanged();
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(const LLUUID& uuid)
{
return removeItemByValue(LLSD(uuid));
}
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(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(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();
}
}
void LLFlatListView::setNoItemsCommentText(const std::string& comment_text)
{
if (NULL == mNoItemsCommentTextbox)
{
LLRect comment_rect = getRect();
comment_rect.setOriginAndSize(0, 0, comment_rect.getWidth(), comment_rect.getHeight());
comment_rect.stretch(-getBorderWidth());
LLTextBox::Params text_p;
text_p.name(COMMENT_TEXTBOX);
text_p.border_visible(false);
text_p.rect(comment_rect);
text_p.follows.flags(FOLLOWS_ALL);
mNoItemsCommentTextbox = LLUICtrlFactory::create<LLTextBox>(text_p, this);
}
mNoItemsCommentTextbox->setValue(comment_text);
}
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);
(*it)->first->die();
delete *it;
}
mItemPairs.clear();
mSelectedItemPairs.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)
{
llwarns << "No comparator specified for sorting FlatListView items." << llendl;
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)
{
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);
setNoItemsCommentVisible(mItemPairs.empty());
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 * (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);
}
}
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);
if (mCommitOnSelectionChange)
{
onCommit();
}
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);
item_pair->first->die();
delete item_pair;
rearrangeItems();
notifyParentItemsRectChanged();
return true;
}
void LLFlatListView::notifyParentItemsRectChanged()
{
S32 comment_height = 0;
// take into account comment text height if exists
if (mNoItemsCommentTextbox && mNoItemsCommentTextbox->getVisible())
{
comment_height = mNoItemsCommentTextbox->getTextPixelHeight();
}
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();
getParent()->notifyParent(params);
}
void LLFlatListView::setNoItemsCommentVisible(bool visible) const
{
if (mNoItemsCommentTextbox)
{
if (visible)
{
// We have to update child rect here because of issues with rect after reshaping while creating LLTextbox
// It is possible to have invalid LLRect if Flat List is in LLAccordionTab
LLRect comment_rect = getLocalRect();
comment_rect.stretch(-getBorderWidth());
mNoItemsCommentTextbox->setRect(comment_rect);
}
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);
}
}
//EOF