Files
SingularityViewer/indra/llui/llflatlistview.cpp
Inusaito Sayori 36b75b2398 A Massive Experience Tools (and Unstable Branch) Merge
[XP Tools] Initial merge Cherry Pick

Also modernize llfloaterauction internally, but leave the ui the same for now.
Breaks out script_question_mute() in llviewermessage.cpp to better sync with upstream
Adds support for UnknownScriptQuestion notification (translators need to translate this one~)
RLVa note: Rewrote RLVa permissions handling block just a bit.
Added 13 new capabilities from the XP Tools, I doubt all of them really exist.
Minor update to LLComboBox, decided against implementing LLIconsComboBox for now.
Modified LLExperienceLog::notify to lookup names and display them along with the slurls since our editors don't do that automatically.
Experience tweak: Changed a few notify's to notifytips so that we can click the links to experience profiles from chat instead of via hacked in buttons
Migrated LLFloaterCompileQueue to a proper Instance Tracker so we can call getKey
Modernized LLSD, gives us reverse iterators and the new debugging impl. We needed the reverse iterators.
Experience tweak: Added virtual destructors to responders.
Updated llhandle.h to allow calling getDerivedHandle in public.
Updated LLScrollContainer and LLScrollBar to be more modern.
Added LLFlatListView/flat_list_view from upstream - these don't seem work though?
Added some newer login/logout strings to strings.xml
Thanks for the default timeout policies, Aleric~
To avoid needing to scroll through tabs, about land tabs now are as big as they need to be to display their labels, same on groups
Group Members and Roles has been renamed to just Members because this allows the new Experiences tab enough room to display.
Thanks to Henri Beauchamp (Cool VL Viewer) for the setupList augmentation. (without it, I'd still be stuck)
Thanks to Shyotl for the helpsies~
Added the LSL constants, events, and functions that LL neglected to put in.
Added click callbacks and name lookups for profile linky texts~

Merge is up to 22b4cdc
Old TODO: Get the uis looking nice (profiles? Experiences... floater) - done
Old TODO: Make sure flatlistviews look okay... - Not using
Old TODO: Fix LLFloaterExperiencePicker, right now the panel does not show. - unsure
Old TODO: Remove the llfloaterabout.cpp change. - done

Merges llexperiencecache with upstream and unstable
Introduces LLCoroResponder, TODO: Make everything use this.
Updates Reporter floater to the latest, supports the new cap thingy

Also adds these commits/changes:
[XPTools] Double clicking experiences in namelists should open the profile
Add List.CopyNames support for Experiences
[XP Tools] Some UI work, I'll do more later
[XPTools] More UI Stuff, Later is now!
Allow getSLURL for experiences
WIP Experience list menu
Also make EXPERIENCE > OBJECT, because mainline started OBJECT already
[XPTools] Add Experience support to Name UI
[XPTools] Fix experience profile UI 9c3067e843265587e91c659200a8d783acf2d9b2
[XPTools] Fix experience location showing "last" and getting set to "last"
[XPTools] Move Experiences floater from view menu to world menu
[XPTools] Fix up more UI
[XPTools] Fix experiences panels
[XPTools] Hide pieces of the Experiences menu when they're not usable
[XPTools] More UI work, mostly to get the menus working
[XPTools] The events list is for events, not experiences, remove menu

# Conflicts:
#	indra/llcommon/llsd.cpp - merge with unstable branch
#	indra/llmessage/message_prehash.cpp
#	indra/llmessage/message_prehash.h
#	indra/llui/llscrollbar.cpp
#	indra/llui/llscrollcontainer.cpp
#	indra/llui/llurlentry.cpp
#	indra/llui/llurlregistry.cpp
#	indra/newview/app_settings/keywords.ini
#	indra/newview/app_settings/settings.xml
#	indra/newview/llappviewer.cpp
#	indra/newview/llappviewer.h
#	indra/newview/llassetuploadresponders.cpp
#	indra/newview/llcompilequeue.* - merge stable
#	indra/newview/llfloaterabout.cpp
#	indra/newview/llfloaterland.* - merge unstable
#	indra/newview/llfloaterproperties.cpp
#	indra/newview/llfloaterregioninfo.* - merge unstable
#	indra/newview/llmenucommands.cpp - merge unstable
#	indra/newview/llpreviewscript.cpp - merge unstable
#	indra/newview/llviewermessage.cpp - merge unstable
#	indra/newview/llviewerregion.cpp - merge unstable
#	indra/newview/skins/default/textures/textures.xml - merge unstable
#	indra/newview/skins/default/xui/en-us/strings.xml - merge unstable
2020-02-04 21:18:47 -05:00

1465 lines
37 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 "lluictrlfactory.h"
#include "llflatlistview.h"
static const LLRegisterWidget<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()
: 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),
item_pad("item_pad"),
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 nullptr;
item_pair_t* pair = getItemPair(value);
if (pair) return pair->first;
return nullptr;
}
bool LLFlatListView::valueExists(const LLSD& value) const
{
if (value.isUndefined()) return false;
item_pair_t* pair = getItemPair(value);
return pair != nullptr;
}
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 nullptr;
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 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 std::string& name, const LLRect& rect, bool opaque, const LLColor4& color, const S32& item_pad, bool allow_select, bool multi_select, bool keep_one_selected, bool keep_selection_visible_on_reshape, const std::string& no_items_text)
: LLScrollContainer(name, rect, nullptr, opaque, color)
, mItemComparator(nullptr)
, mItemsPanel(nullptr)
, mItemPad(item_pad)
, mAllowSelection(allow_select)
, mMultipleSelection(multi_select)
, mCommitOnSelectionChange(false)
, mKeepOneItemSelected(keep_one_selected)
, mIsConsecutiveSelection(false)
, mKeepSelectionVisibleOnReshape(keep_selection_visible_on_reshape)
, mPrevNotifyParentRect(LLRect())
, mNoItemsCommentTextbox(nullptr)
{
mBorderThickness = getBorderWidth();
LLRect scroll_rect = getRect();
LLRect items_rect;
setItemsNoScrollWidth(scroll_rect.getWidth());
items_rect.setLeftTopAndSize(mBorderThickness, scroll_rect.getHeight() - mBorderThickness, mItemsNoScrollWidth, 0);
mItemsPanel = new LLPanel("items panel", items_rect);
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);
mSelectedItemsBorder = new LLViewBorder(
"scroll border",
getLastSelectedItemRect(),
LLViewBorder::BEVEL_IN);
mSelectedItemsBorder->setVisible(false);
mItemsPanel->addChild(mSelectedItemsBorder);
{
// create textbox for "No Items" comment text
{
LLRect comment_rect = getRect();
comment_rect.setOriginAndSize(0, 0, comment_rect.getWidth(), comment_rect.getHeight());
comment_rect.stretch(-getBorderWidth());
mNoItemsCommentTextbox = new LLTextBox(no_items_text, comment_rect, no_items_text);
}
mNoItemsCommentTextbox->setBorderVisible(false);
{
mNoItemsCommentTextbox->setFollows(FOLLOWS_ALL);
}
}
};
LLFlatListView::~LLFlatListView()
{
delete_and_clear(mItemPairs);
}
// 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 * (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;
}
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 nullptr;
}
//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 nullptr;
}
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 = nullptr;
item_pair_t* cur_sel_pair = nullptr;
// 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 = nullptr;
}
}
//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 (pairs_iterator_t
iter = mItemPairs.begin(),
iter_end = mItemPairs.end();
iter != iter_end; ++iter)
{
LLPanel* pItem = (*iter)->first;
if (1 == pItem->notify(action))
{
selectItemPair((*iter), false);
mItemsPanel->removeChild(pItem);
detached_items.push_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 (pairs_iterator_t
iter = mItemPairs.begin(),
iter_end = mItemPairs.end();
iter != iter_end; ++iter)
{
(*iter)->first = nullptr;
delete *iter;
}
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 (std::vector<LLPanel*>::const_iterator
detached_iter = detached_items.begin(),
detached_iter_end = detached_items.end();
detached_iter != detached_iter_end; ++detached_iter)
{
LLPanel* pDetachedItem = *detached_iter;
for (pairs_iterator_t
iter = mItemPairs.begin(),
iter_end = mItemPairs.end();
iter != iter_end; ++iter)
{
item_pair_t* item_pair = *iter;
if (item_pair->first == pDetachedItem)
{
mItemPairs.erase(iter);
item_pair->first = nullptr;
delete item_pair;
break;
}
}
}
rearrangeItems();
}
notifyParentItemsRectChanged();
}
}
/************************************************************************/
/* LLFlatListViewEx implementation */
/************************************************************************/
/* Singu Note: Let's not use this for now...
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()
{
return mForceShowingUnmatchedItems;
}
void LLFlatListViewEx::setForceShowingUnmatchedItems(bool show)
{
mForceShowingUnmatchedItems = show;
}
void LLFlatListViewEx::setFilterSubString(const std::string& filter_str)
{
if (0 != LLStringUtil::compareInsensitive(filter_str, mFilterSubString))
{
mFilterSubString = filter_str;
updateNoItemsMessage(mFilterSubString);
filterItems();
}
}
void LLFlatListViewEx::updateItemVisibility(LLPanel* item, const LLSD &action)
{
if (!item) return;
// 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;
item->setVisible(true);
}
else
{
// TODO: implement (re)storing of current selection.
if (!mForceShowingUnmatchedItems)
{
selectItem(item, false);
}
item->setVisible(mForceShowingUnmatchedItems);
}
}
void LLFlatListViewEx::filterItems()
{
typedef std::vector <LLPanel*> item_panel_list_t;
std::string cur_filter = mFilterSubString;
LLStringUtil::toUpper(cur_filter);
LLSD action;
action.with("match_filter", cur_filter);
item_panel_list_t items;
getItems(items);
mHasMatchedItems = false;
for (item_panel_list_t::iterator
iter = items.begin(),
iter_end = items.end();
iter != iter_end; ++iter)
{
LLPanel* pItem = (*iter);
updateItemVisibility(pItem, action);
}
sort();
notifyParentItemsRectChanged();
}
bool LLFlatListViewEx::hasMatchedItems()
{
return mHasMatchedItems;
}
*/
// <singu> Old-style fromXML stuffs
// static
LLView* LLFlatListView::fromXML(LLXMLNodePtr node, LLView *parent, LLUICtrlFactory *factory)
{
// Stuff from scroll container
std::string name("flat_list_view");
node->getAttributeString("name", name);
LLRect rect;
createRect(node, rect, parent, LLRect());
BOOL opaque = FALSE;
node->getAttributeBOOL("opaque", opaque);
LLColor4 color(0,0,0,0);
LLUICtrlFactory::getAttributeColor(node, "color", color);
// Stuff from flat list
bool allow_select = false;
node->getAttribute_bool("allow_select", allow_select);
bool multi_select = false;
node->getAttribute_bool("multi_select", multi_select);
bool keep_one_selected = false;
node->getAttribute_bool("keep_one_selected", keep_one_selected);
bool keep_selection_visible_on_reshape = false;
node->getAttribute_bool("keep_selection_visible_on_reshape", keep_selection_visible_on_reshape);
U32 item_pad;
node->getAttributeU32("item_pad", item_pad);
std::string no_items_text;
node->getAttributeString("no_items_text", no_items_text);
return new LLFlatListView(name, rect, opaque, color, item_pad, allow_select, multi_select, keep_one_selected, keep_selection_visible_on_reshape, no_items_text);
}
// </singu>
//EOF