summaryrefslogtreecommitdiff
path: root/indra/newview/llavatarlist.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llavatarlist.cpp')
-rw-r--r--indra/newview/llavatarlist.cpp604
1 files changed, 501 insertions, 103 deletions
diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp
index 08f0cf8842..09083dcb98 100644
--- a/indra/newview/llavatarlist.cpp
+++ b/indra/newview/llavatarlist.cpp
@@ -2,31 +2,25 @@
* @file llavatarlist.h
* @brief Generic avatar list
*
- * $LicenseInfo:firstyear=2009&license=viewergpl$
- *
- * Copyright (c) 2009, Linden Research, Inc.
- *
+ * $LicenseInfo:firstyear=2009&license=viewerlgpl$
* 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
+ * 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.
*
- * 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
+ * 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.
*
- * 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.
+ * 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
*
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
@@ -34,130 +28,534 @@
#include "llavatarlist.h"
+// common
+#include "lltrans.h"
+#include "llcommonutils.h"
+
+// llui
+#include "lltextutil.h"
+
// newview
+#include "llagentdata.h" // for comparator
+#include "llavatariconctrl.h"
#include "llcallingcard.h" // for LLAvatarTracker
#include "llcachename.h"
+#include "lllistcontextmenu.h"
+#include "llrecentpeople.h"
+#include "lluuid.h"
+#include "llvoiceclient.h"
+#include "llviewercontrol.h" // for gSavedSettings
+
+static LLDefaultChildRegistry::Register<LLAvatarList> r("avatar_list");
+
+// Last interaction time update period.
+static const F32 LIT_UPDATE_PERIOD = 5;
+
+// Maximum number of avatars that can be added to a list in one pass.
+// Used to limit time spent for avatar list update per frame.
+static const unsigned ADD_LIMIT = 50;
+
+bool LLAvatarList::contains(const LLUUID& id)
+{
+ const uuid_vec_t& ids = getIDs();
+ return std::find(ids.begin(), ids.end(), id) != ids.end();
+}
+
+void LLAvatarList::toggleIcons()
+{
+ // Save the new value for new items to use.
+ mShowIcons = !mShowIcons;
+ gSavedSettings.setBOOL(mIconParamName, mShowIcons);
+
+ // Show/hide icons for all existing items.
+ std::vector<LLPanel*> items;
+ getItems(items);
+ for( std::vector<LLPanel*>::const_iterator it = items.begin(); it != items.end(); it++)
+ {
+ static_cast<LLAvatarListItem*>(*it)->setAvatarIconVisible(mShowIcons);
+ }
+}
+
+void LLAvatarList::setSpeakingIndicatorsVisible(bool visible)
+{
+ // Save the new value for new items to use.
+ mShowSpeakingIndicator = visible;
+
+ // Show/hide icons for all existing items.
+ std::vector<LLPanel*> items;
+ getItems(items);
+ for( std::vector<LLPanel*>::const_iterator it = items.begin(); it != items.end(); it++)
+ {
+ static_cast<LLAvatarListItem*>(*it)->showSpeakingIndicator(mShowSpeakingIndicator);
+ }
+}
+
+void LLAvatarList::showPermissions(bool visible)
+{
+ // Save the value for new items to use.
+ mShowPermissions = visible;
+
+ // Enable or disable showing permissions icons for all existing items.
+ std::vector<LLPanel*> items;
+ getItems(items);
+ for(std::vector<LLPanel*>::const_iterator it = items.begin(), end_it = items.end(); it != end_it; ++it)
+ {
+ static_cast<LLAvatarListItem*>(*it)->setShowPermissions(mShowPermissions);
+ }
+}
+
+static bool findInsensitive(std::string haystack, const std::string& needle_upper)
+{
+ LLStringUtil::toUpper(haystack);
+ return haystack.find(needle_upper) != std::string::npos;
+}
+
-static LLDefaultWidgetRegistry::Register<LLAvatarList> r("avatar_list");
+//comparators
+static const LLAvatarItemNameComparator NAME_COMPARATOR;
+static const LLFlatListView::ItemReverseComparator REVERSE_NAME_COMPARATOR(NAME_COMPARATOR);
LLAvatarList::Params::Params()
+: ignore_online_status("ignore_online_status", false)
+, show_last_interaction_time("show_last_interaction_time", false)
+, show_info_btn("show_info_btn", true)
+, show_profile_btn("show_profile_btn", true)
+, show_speaking_indicator("show_speaking_indicator", true)
+, show_permissions_granted("show_permissions_granted", false)
{
- draw_heading = true;
- draw_stripes = false;
- multi_select = false;
- column_padding = 0;
- search_column = LIST_NAME;
- sort_column = LIST_NAME;
}
LLAvatarList::LLAvatarList(const Params& p)
-: LLScrollListCtrl(p)
+: LLFlatListViewEx(p)
+, mIgnoreOnlineStatus(p.ignore_online_status)
+, mShowLastInteractionTime(p.show_last_interaction_time)
+, mContextMenu(NULL)
+, mDirty(true) // to force initial update
+, mLITUpdateTimer(NULL)
+, mShowIcons(true)
+, mShowInfoBtn(p.show_info_btn)
+, mShowProfileBtn(p.show_profile_btn)
+, mShowSpeakingIndicator(p.show_speaking_indicator)
+, mShowPermissions(p.show_permissions_granted)
{
- setCommitOnSelectionChange(TRUE); // there's no such param in LLScrollListCtrl::Params
+ setCommitOnSelectionChange(true);
- // "name" column
- {
- LLScrollListColumn::Params col_params;
- col_params.name = "name";
- col_params.header.label = "Name"; // *TODO: localize or remove the header
- col_params.width.dynamic_width = true;
- addColumn(col_params);
- }
-
- // invisible "id" column
- {
- LLScrollListColumn::Params col_params;
- col_params.name = "id";
- col_params.width.pixel_width = 0;
- addColumn(col_params);
- }
+ // Set default sort order.
+ setComparator(&NAME_COMPARATOR);
- // The corresponding parameters don't work because we create columns dynamically.
- sortByColumnIndex(LIST_NAME, TRUE);
- setSearchColumn(LIST_NAME);
+ if (mShowLastInteractionTime)
+ {
+ mLITUpdateTimer = new LLTimer();
+ mLITUpdateTimer->setTimerExpirySec(0); // zero to force initial update
+ mLITUpdateTimer->start();
+ }
+}
+
+LLAvatarList::~LLAvatarList()
+{
+ delete mLITUpdateTimer;
}
-std::vector<LLUUID> LLAvatarList::getSelectedIDs()
+void LLAvatarList::setShowIcons(std::string param_name)
{
- LLUUID selected_id;
- std::vector<LLUUID> avatar_ids;
- std::vector<LLScrollListItem*> selected = getAllSelected();
- for(std::vector<LLScrollListItem*>::iterator itr = selected.begin(); itr != selected.end(); ++itr)
+ mIconParamName= param_name;
+ mShowIcons = gSavedSettings.getBOOL(mIconParamName);
+}
+
+// virtual
+void LLAvatarList::draw()
+{
+ // *NOTE dzaporozhan
+ // Call refresh() after draw() to avoid flickering of avatar list items.
+
+ LLFlatListViewEx::draw();
+
+ if (mDirty)
+ refresh();
+
+ if (mShowLastInteractionTime && mLITUpdateTimer->hasExpired())
{
- avatar_ids.push_back((*itr)->getUUID());
+ updateLastInteractionTimes();
+ mLITUpdateTimer->setTimerExpirySec(LIT_UPDATE_PERIOD); // restart the timer
}
- return avatar_ids;
}
-void LLAvatarList::addItem(const LLUUID& id, const std::string& name, BOOL is_bold, EAddPosition pos)
+//virtual
+void LLAvatarList::clear()
{
- std::string fullname;
+ getIDs().clear();
+ setDirty(true);
+ LLFlatListViewEx::clear();
+}
- // Populate list item.
- LLSD element;
- element["id"] = id;
+void LLAvatarList::setNameFilter(const std::string& filter)
+{
+ std::string filter_upper = filter;
+ LLStringUtil::toUpper(filter_upper);
+ if (mNameFilter != filter_upper)
+ {
+ mNameFilter = filter_upper;
- LLSD& friend_column = element["columns"][LIST_NAME];
- friend_column["column"] = "name";
- friend_column["value"] = name;
+ // update message for empty state here instead of refresh() to avoid blinking when switch
+ // between tabs.
+ updateNoItemsMessage(filter);
+ setDirty();
+ }
+}
- LLScrollListItem* new_itemp = addElement(element, pos);
+void LLAvatarList::sortByName()
+{
+ setComparator(&NAME_COMPARATOR);
+ sort();
+}
- // Indicate buddy online status.
- // (looks like parsing font parameters from LLSD is broken)
- if (is_bold)
+void LLAvatarList::setDirty(bool val /*= true*/, bool force_refresh /*= false*/)
+{
+ mDirty = val;
+ if(mDirty && force_refresh)
{
- LLScrollListText* name_textp = dynamic_cast<LLScrollListText*>(new_itemp->getColumn(LIST_NAME));
- if (name_textp)
- name_textp->setFontStyle(LLFontGL::BOLD);
- else
- {
- llwarns << "Name column not found" << llendl;
- }
+ refresh();
}
}
-BOOL LLAvatarList::updateList(const std::vector<LLUUID>& all_buddies)
+void LLAvatarList::addAvalineItem(const LLUUID& item_id, const LLUUID& session_id, const std::string& item_name)
{
- BOOL have_names = TRUE;
-
+ LL_DEBUGS("Avaline") << "Adding avaline item into the list: " << item_name << "|" << item_id << ", session: " << session_id << LL_ENDL;
+ LLAvalineListItem* item = new LLAvalineListItem(/*hide_number=*/false);
+ item->setAvatarId(item_id, session_id, true, false);
+ item->setName(item_name);
+
+ addItem(item, item_id);
+ mIDs.push_back(item_id);
+ sort();
+}
+
+//////////////////////////////////////////////////////////////////////////
+// PROTECTED SECTION
+//////////////////////////////////////////////////////////////////////////
+
+void LLAvatarList::refresh()
+{
+ bool have_names = TRUE;
+ bool add_limit_exceeded = false;
+ bool modified = false;
+ bool have_filter = !mNameFilter.empty();
+
// Save selection.
- std::vector<LLUUID> selected_ids = getSelectedIDs();
- LLUUID current_id = getCurrentID();
- S32 pos = getScrollPos();
-
- std::vector<LLUUID>::const_iterator buddy_it = all_buddies.begin();
- deleteAllItems();
- for(; buddy_it != all_buddies.end(); ++buddy_it)
+ uuid_vec_t selected_ids;
+ getSelectedUUIDs(selected_ids);
+ LLUUID current_id = getSelectedUUID();
+
+ // Determine what to add and what to remove.
+ uuid_vec_t added, removed;
+ LLAvatarList::computeDifference(getIDs(), added, removed);
+
+ // Handle added items.
+ unsigned nadded = 0;
+ for (uuid_vec_t::const_iterator it=added.begin(); it != added.end(); it++)
{
std::string name;
- const LLUUID& buddy_id = *buddy_it;
- have_names &= gCacheName->getFullName(buddy_id, name);
- addItem(buddy_id, name, LLAvatarTracker::instance().isBuddyOnline(buddy_id));
+ const LLUUID& buddy_id = *it;
+ have_names &= (bool)gCacheName->getFullName(buddy_id, name);
+ if (!have_filter || findInsensitive(name, mNameFilter))
+ {
+ if (nadded >= ADD_LIMIT)
+ {
+ add_limit_exceeded = true;
+ break;
+ }
+ else
+ {
+ addNewItem(buddy_id, name, LLAvatarTracker::instance().isBuddyOnline(buddy_id));
+ modified = true;
+ nadded++;
+ }
+ }
+ }
+
+ // Handle removed items.
+ for (uuid_vec_t::const_iterator it=removed.begin(); it != removed.end(); it++)
+ {
+ removeItemByUUID(*it);
+ modified = true;
+ }
+
+ // Handle filter.
+ if (have_filter)
+ {
+ std::vector<LLSD> cur_values;
+ getValues(cur_values);
+
+ for (std::vector<LLSD>::const_iterator it=cur_values.begin(); it != cur_values.end(); it++)
+ {
+ std::string name;
+ const LLUUID& buddy_id = it->asUUID();
+ have_names &= (bool)gCacheName->getFullName(buddy_id, name);
+ if (!findInsensitive(name, mNameFilter))
+ {
+ removeItemByUUID(buddy_id);
+ modified = true;
+ }
+ }
}
// Changed item in place, need to request sort and update columns
// because we might have changed data in a column on which the user
// has already sorted. JC
- sortItems();
+ sort();
// re-select items
- selectMultiple(selected_ids);
- setCurrentByID(current_id);
-#if 0
- // Restore selection.
- if(selected_ids.size() > 0)
- {
- // only non-null if friends was already found. This may fail,
- // but we don't really care here, because refreshUI() will
- // clean up the interface.
- for(std::vector<LLUUID>::iterator itr = selected_ids.begin(); itr != selected_ids.end(); ++itr)
+ // selectMultiple(selected_ids); // TODO: implement in LLFlatListView if need
+ selectItemByUUID(current_id);
+
+ // If the name filter is specified and the names are incomplete,
+ // we need to re-update when the names are complete so that
+ // the filter can be applied correctly.
+ //
+ // Otherwise, if we have no filter then no need to update again
+ // because the items will update their names.
+ bool dirty = add_limit_exceeded || (have_filter && !have_names);
+ setDirty(dirty);
+
+ // Refreshed all items.
+ if(!dirty)
+ {
+ // Highlight items matching the filter.
+ std::vector<LLPanel*> items;
+ getItems(items);
+ for( std::vector<LLPanel*>::const_iterator it = items.begin(); it != items.end(); it++)
{
- setSelectedByValue(*itr, true);
+ static_cast<LLAvatarListItem*>(*it)->setHighlight(mNameFilter);
}
+
+ // Send refresh_complete signal.
+ mRefreshCompleteSignal(this, LLSD((S32)size(false)));
}
-#endif
- setScrollPos(pos);
- return have_names;
+ // Commit if we've added/removed items.
+ if (modified)
+ onCommit();
+}
+
+bool LLAvatarList::filterHasMatches()
+{
+ uuid_vec_t values = getIDs();
+
+ for (uuid_vec_t::const_iterator it=values.begin(); it != values.end(); it++)
+ {
+ std::string name;
+ const LLUUID& buddy_id = *it;
+ BOOL have_name = gCacheName->getFullName(buddy_id, name);
+
+ // If name has not been loaded yet we consider it as a match.
+ // When the name will be loaded the filter will be applied again(in refresh()).
+
+ if (have_name && !findInsensitive(name, mNameFilter))
+ {
+ continue;
+ }
+
+ return true;
+ }
+ return false;
+}
+
+boost::signals2::connection LLAvatarList::setRefreshCompleteCallback(const commit_signal_t::slot_type& cb)
+{
+ return mRefreshCompleteSignal.connect(cb);
+}
+
+boost::signals2::connection LLAvatarList::setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb)
+{
+ return mItemDoubleClickSignal.connect(cb);
+}
+
+//virtual
+S32 LLAvatarList::notifyParent(const LLSD& info)
+{
+ if (info.has("sort") && &NAME_COMPARATOR == mItemComparator)
+ {
+ sort();
+ return 1;
+ }
+ return LLFlatListViewEx::notifyParent(info);
+}
+
+void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, BOOL is_online, EAddPosition pos)
+{
+ LLAvatarListItem* item = new LLAvatarListItem();
+ item->setName(name);
+ item->setAvatarId(id, mSessionID, mIgnoreOnlineStatus);
+ item->setOnline(mIgnoreOnlineStatus ? true : is_online);
+ item->showLastInteractionTime(mShowLastInteractionTime);
+
+ item->setAvatarIconVisible(mShowIcons);
+ item->setShowInfoBtn(mShowInfoBtn);
+ item->setShowProfileBtn(mShowProfileBtn);
+ item->showSpeakingIndicator(mShowSpeakingIndicator);
+ item->setShowPermissions(mShowPermissions);
+
+ item->setDoubleClickCallback(boost::bind(&LLAvatarList::onItemDoubleClicked, this, _1, _2, _3, _4));
+
+ addItem(item, id, pos);
+}
+
+// virtual
+BOOL LLAvatarList::handleRightMouseDown(S32 x, S32 y, MASK mask)
+{
+ BOOL handled = LLUICtrl::handleRightMouseDown(x, y, mask);
+ if ( mContextMenu )
+ {
+ uuid_vec_t selected_uuids;
+ getSelectedUUIDs(selected_uuids);
+ mContextMenu->show(this, selected_uuids, x, y);
+ }
+ return handled;
+}
+
+void LLAvatarList::setVisible(BOOL visible)
+{
+ if ( visible == FALSE && mContextMenu )
+ {
+ mContextMenu->hide();
+ }
+ LLFlatListViewEx::setVisible(visible);
+}
+
+void LLAvatarList::computeDifference(
+ const uuid_vec_t& vnew_unsorted,
+ uuid_vec_t& vadded,
+ uuid_vec_t& vremoved)
+{
+ uuid_vec_t vcur;
+
+ // Convert LLSDs to LLUUIDs.
+ {
+ std::vector<LLSD> vcur_values;
+ getValues(vcur_values);
+
+ for (size_t i=0; i<vcur_values.size(); i++)
+ vcur.push_back(vcur_values[i].asUUID());
+ }
+
+ LLCommonUtils::computeDifference(vnew_unsorted, vcur, vadded, vremoved);
+}
+
+// Refresh shown time of our last interaction with all listed avatars.
+void LLAvatarList::updateLastInteractionTimes()
+{
+ S32 now = (S32) LLDate::now().secondsSinceEpoch();
+ std::vector<LLPanel*> items;
+ getItems(items);
+
+ for( std::vector<LLPanel*>::const_iterator it = items.begin(); it != items.end(); it++)
+ {
+ // *TODO: error handling
+ LLAvatarListItem* item = static_cast<LLAvatarListItem*>(*it);
+ S32 secs_since = now - (S32) LLRecentPeople::instance().getDate(item->getAvatarId()).secondsSinceEpoch();
+ if (secs_since >= 0)
+ item->setLastInteractionTime(secs_since);
+ }
+}
+
+void LLAvatarList::onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask)
+{
+ mItemDoubleClickSignal(ctrl, x, y, mask);
+}
+
+bool LLAvatarItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const
+{
+ const LLAvatarListItem* avatar_item1 = dynamic_cast<const LLAvatarListItem*>(item1);
+ const LLAvatarListItem* avatar_item2 = dynamic_cast<const LLAvatarListItem*>(item2);
+
+ if (!avatar_item1 || !avatar_item2)
+ {
+ llerror("item1 and item2 cannot be null", 0);
+ return true;
+ }
+
+ return doCompare(avatar_item1, avatar_item2);
+}
+
+bool LLAvatarItemNameComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const
+{
+ std::string name1 = avatar_item1->getAvatarName();
+ std::string name2 = avatar_item2->getAvatarName();
+
+ LLStringUtil::toUpper(name1);
+ LLStringUtil::toUpper(name2);
+
+ return name1 < name2;
+}
+bool LLAvatarItemAgentOnTopComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const
+{
+ //keep agent on top, if first is agent,
+ //then we need to return true to elevate this id, otherwise false.
+ if(avatar_item1->getAvatarId() == gAgentID)
+ {
+ return true;
+ }
+ else if (avatar_item2->getAvatarId() == gAgentID)
+ {
+ return false;
+ }
+ return LLAvatarItemNameComparator::doCompare(avatar_item1,avatar_item2);
+}
+
+/************************************************************************/
+/* class LLAvalineListItem */
+/************************************************************************/
+LLAvalineListItem::LLAvalineListItem(bool hide_number/* = true*/) : LLAvatarListItem(false)
+, mIsHideNumber(hide_number)
+{
+ // should not use buildPanel from the base class to ensure LLAvalineListItem::postBuild is called.
+ buildFromFile( "panel_avatar_list_item.xml");
+}
+
+BOOL LLAvalineListItem::postBuild()
+{
+ BOOL rv = LLAvatarListItem::postBuild();
+
+ if (rv)
+ {
+ setOnline(true);
+ showLastInteractionTime(false);
+ setShowProfileBtn(false);
+ setShowInfoBtn(false);
+ mAvatarIcon->setValue("Avaline_Icon");
+ mAvatarIcon->setToolTip(std::string(""));
+ }
+ return rv;
+}
+
+// to work correctly this method should be called AFTER setAvatarId for avaline callers with hidden phone number
+void LLAvalineListItem::setName(const std::string& name)
+{
+ if (mIsHideNumber)
+ {
+ static U32 order = 0;
+ typedef std::map<LLUUID, U32> avaline_callers_nums_t;
+ static avaline_callers_nums_t mAvalineCallersNums;
+
+ llassert(getAvatarId() != LLUUID::null);
+
+ const LLUUID &uuid = getAvatarId();
+
+ if (mAvalineCallersNums.find(uuid) == mAvalineCallersNums.end())
+ {
+ mAvalineCallersNums[uuid] = ++order;
+ LL_DEBUGS("Avaline") << "Set name for new avaline caller: " << uuid << ", order: " << order << LL_ENDL;
+ }
+ LLStringUtil::format_map_t args;
+ args["[ORDER]"] = llformat("%u", mAvalineCallersNums[uuid]);
+ std::string hidden_name = LLTrans::getString("AvalineCaller", args);
+
+ LL_DEBUGS("Avaline") << "Avaline caller: " << uuid << ", name: " << hidden_name << LL_ENDL;
+ LLAvatarListItem::setName(hidden_name);
+ }
+ else
+ {
+ const std::string& formatted_phone = LLTextUtil::formatPhoneNumber(name);
+ LLAvatarListItem::setName(formatted_phone);
+ }
}