/** * @file llavatarlist.h * @brief Generic avatar list * * $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 "llviewerprecompiledheaders.h" #include "llavatarlist.h" // newview #include "llcallingcard.h" // for LLAvatarTracker #include "llcachename.h" #include "llrecentpeople.h" #include "llvoiceclient.h" #include "llviewercontrol.h" // for gSavedSettings static LLDefaultChildRegistry::Register 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; 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 items; getItems(items); for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) { static_cast(*it)->setAvatarIconVisible(mShowIcons); } } static bool findInsensitive(std::string haystack, const std::string& needle_upper) { LLStringUtil::toUpper(haystack); return haystack.find(needle_upper) != std::string::npos; } //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) { } LLAvatarList::LLAvatarList(const Params& p) : LLFlatListView(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) { setCommitOnSelectionChange(true); // Set default sort order. setComparator(&NAME_COMPARATOR); if (mShowLastInteractionTime) { mLITUpdateTimer = new LLTimer(); mLITUpdateTimer->setTimerExpirySec(0); // zero to force initial update mLITUpdateTimer->start(); } } LLAvatarList::~LLAvatarList() { delete mLITUpdateTimer; } void LLAvatarList::setShowIcons(std::string param_name) { mIconParamName= param_name; mShowIcons = gSavedSettings.getBOOL(mIconParamName); } // virtual void LLAvatarList::draw() { // *NOTE dzaporozhan // Call refresh() after draw() to avoid flickering of avatar list items. LLFlatListView::draw(); if (mDirty) refresh(); if (mShowLastInteractionTime && mLITUpdateTimer->hasExpired()) { updateLastInteractionTimes(); mLITUpdateTimer->setTimerExpirySec(LIT_UPDATE_PERIOD); // restart the timer } } void LLAvatarList::setNameFilter(const std::string& filter) { if (mNameFilter != filter) { mNameFilter = filter; setDirty(); } } void LLAvatarList::sortByName() { setComparator(&NAME_COMPARATOR); 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 selected_ids; getSelectedUUIDs(selected_ids); LLUUID current_id = getSelectedUUID(); // Determine what to add and what to remove. std::vector added, removed; LLAvatarList::computeDifference(getIDs(), added, removed); // Handle added items. unsigned nadded = 0; for (std::vector::const_iterator it=added.begin(); it != added.end(); it++) { std::string name; 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 (std::vector::const_iterator it=removed.begin(); it != removed.end(); it++) { removeItemByUUID(*it); modified = true; } // Handle filter. if (have_filter) { std::vector cur_values; getValues(cur_values); for (std::vector::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 sort(); // re-select items // 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, lets send refresh_complete signal. if(!dirty) { std::vector cur_values; getValues(cur_values); mRefreshCompleteSignal(this, LLSD((S32)cur_values.size())); } // Commit if we've added/removed items. if (modified) onCommit(); } bool LLAvatarList::filterHasMatches() { uuid_vector_t values = getIDs(); for (uuid_vector_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); } void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, BOOL is_online, EAddPosition pos) { LLAvatarListItem* item = new LLAvatarListItem(); item->showInfoBtn(true); item->showSpeakingIndicator(true); item->setName(name); item->setAvatarId(id, mIgnoreOnlineStatus); item->setOnline(mIgnoreOnlineStatus ? true : is_online); item->showLastInteractionTime(mShowLastInteractionTime); item->setContextMenu(mContextMenu); item->childSetVisible("info_btn", false); item->setAvatarIconVisible(mShowIcons); item->setShowInfoBtn(mShowInfoBtn); item->setShowProfileBtn(mShowProfileBtn); addItem(item, id, pos); } // virtual BOOL LLAvatarList::handleRightMouseDown(S32 x, S32 y, MASK mask) { BOOL handled = LLUICtrl::handleRightMouseDown(x, y, mask); if ( mContextMenu ) { std::vector selected_uuids; getSelectedUUIDs(selected_uuids); mContextMenu->show(this, selected_uuids, x, y); } return handled; } void LLAvatarList::computeDifference( const std::vector& vnew_unsorted, std::vector& vadded, std::vector& vremoved) { std::vector vcur; std::vector vnew = vnew_unsorted; // Convert LLSDs to LLUUIDs. { std::vector vcur_values; getValues(vcur_values); for (size_t i=0; i::iterator it; size_t maxsize = llmax(vcur.size(), vnew.size()); vadded.resize(maxsize); vremoved.resize(maxsize); // what to remove it = set_difference(vcur.begin(), vcur.end(), vnew.begin(), vnew.end(), vremoved.begin()); vremoved.erase(it, vremoved.end()); // what to add it = set_difference(vnew.begin(), vnew.end(), vcur.begin(), vcur.end(), vadded.begin()); vadded.erase(it, vadded.end()); } static std::string format_secs(S32 secs) { // *TODO: reinventing the wheel? // *TODO: i18n static const int LL_AL_MIN = 60; static const int LL_AL_HOUR = LL_AL_MIN * 60; static const int LL_AL_DAY = LL_AL_HOUR * 24; static const int LL_AL_WEEK = LL_AL_DAY * 7; static const int LL_AL_MONTH = LL_AL_DAY * 31; static const int LL_AL_YEAR = LL_AL_DAY * 365; std::string s; if (secs >= LL_AL_YEAR) s = llformat("%dy", secs / LL_AL_YEAR); else if (secs >= LL_AL_MONTH) s = llformat("%dmon", secs / LL_AL_MONTH); else if (secs >= LL_AL_WEEK) s = llformat("%dw", secs / LL_AL_WEEK); else if (secs >= LL_AL_DAY) s = llformat("%dd", secs / LL_AL_DAY); else if (secs >= LL_AL_HOUR) s = llformat("%dh", secs / LL_AL_HOUR); else if (secs >= LL_AL_MIN) s = llformat("%dm", secs / LL_AL_MIN); else s = llformat("%ds", secs); return s; } // Refresh shown time of our last interaction with all listed avatars. void LLAvatarList::updateLastInteractionTimes() { S32 now = (S32) LLDate::now().secondsSinceEpoch(); std::vector items; getItems(items); for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) { // *TODO: error handling LLAvatarListItem* item = static_cast(*it); S32 secs_since = now - (S32) LLRecentPeople::instance().getDate(item->getAvatarId()).secondsSinceEpoch(); if (secs_since >= 0) item->setLastInteractionTime(format_secs(secs_since)); } } bool LLAvatarItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const { const LLAvatarListItem* avatar_item1 = dynamic_cast(item1); const LLAvatarListItem* avatar_item2 = dynamic_cast(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; }