diff options
Diffstat (limited to 'indra/newview/llavatarlist.cpp')
-rw-r--r-- | indra/newview/llavatarlist.cpp | 604 |
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); + } } |