diff options
Diffstat (limited to 'indra/newview/llpanelpeople.cpp')
-rw-r--r-- | indra/newview/llpanelpeople.cpp | 1371 |
1 files changed, 1075 insertions, 296 deletions
diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 1d7a2748cc..d096b17145 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -2,31 +2,25 @@ * @file llpanelpeople.cpp * @brief Side tray "People" panel * - * $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. * - * 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 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. * - * 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. + * 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. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * 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$ */ @@ -35,54 +29,210 @@ // libs #include "llfloaterreg.h" #include "llmenugl.h" -#include "llsearcheditor.h" +#include "llnotificationsutil.h" +#include "lleventtimer.h" +#include "llfiltereditor.h" #include "lltabcontainer.h" #include "lluictrlfactory.h" #include "llpanelpeople.h" // newview +#include "llaccordionctrl.h" +#include "llaccordionctrltab.h" #include "llagent.h" +#include "llavataractions.h" #include "llavatarlist.h" +#include "llavatarlistitem.h" #include "llcallingcard.h" // for LLAvatarTracker #include "llfloateravatarpicker.h" -#include "llfloaterminiinspector.h" -#include "llfriendactions.h" +//#include "llfloaterminiinspector.h" +#include "llfriendcard.h" #include "llgroupactions.h" #include "llgrouplist.h" +#include "llinventoryobserver.h" +#include "llpanelpeoplemenus.h" +#include "llsidetray.h" +#include "llsidetraypanelcontainer.h" #include "llrecentpeople.h" #include "llviewercontrol.h" // for gSavedSettings #include "llviewermenu.h" // for gMenuHolder #include "llvoiceclient.h" #include "llworld.h" - -using namespace LLOldEvents; +#include "llspeakers.h" #define FRIEND_LIST_UPDATE_TIMEOUT 0.5 #define NEARBY_LIST_UPDATE_INTERVAL 1 -#define RECENT_LIST_UPDATE_DELAY 1 + +static const std::string NEARBY_TAB_NAME = "nearby_panel"; +static const std::string FRIENDS_TAB_NAME = "friends_panel"; +static const std::string GROUP_TAB_NAME = "groups_panel"; +static const std::string RECENT_TAB_NAME = "recent_panel"; + +static const std::string COLLAPSED_BY_USER = "collapsed_by_user"; + +/** Comparator for comparing avatar items by last interaction date */ +class LLAvatarItemRecentComparator : public LLAvatarItemComparator +{ +public: + LLAvatarItemRecentComparator() {}; + virtual ~LLAvatarItemRecentComparator() {}; + +protected: + virtual bool doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const + { + LLRecentPeople& people = LLRecentPeople::instance(); + const LLDate& date1 = people.getDate(avatar_item1->getAvatarId()); + const LLDate& date2 = people.getDate(avatar_item2->getAvatarId()); + + //older comes first + return date1 > date2; + } +}; + +/** Compares avatar items by online status, then by name */ +class LLAvatarItemStatusComparator : public LLAvatarItemComparator +{ +public: + LLAvatarItemStatusComparator() {}; + +protected: + /** + * @return true if item1 < item2, false otherwise + */ + virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const + { + LLAvatarTracker& at = LLAvatarTracker::instance(); + bool online1 = at.isBuddyOnline(item1->getAvatarId()); + bool online2 = at.isBuddyOnline(item2->getAvatarId()); + + if (online1 == online2) + { + std::string name1 = item1->getAvatarName(); + std::string name2 = item2->getAvatarName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; + } + + return online1 > online2; + } +}; + +/** Compares avatar items by distance between you and them */ +class LLAvatarItemDistanceComparator : public LLAvatarItemComparator +{ +public: + typedef std::map < LLUUID, LLVector3d > id_to_pos_map_t; + LLAvatarItemDistanceComparator() {}; + + void updateAvatarsPositions(std::vector<LLVector3d>& positions, uuid_vec_t& uuids) + { + std::vector<LLVector3d>::const_iterator + pos_it = positions.begin(), + pos_end = positions.end(); + + uuid_vec_t::const_iterator + id_it = uuids.begin(), + id_end = uuids.end(); + + LLAvatarItemDistanceComparator::id_to_pos_map_t pos_map; + + mAvatarsPositions.clear(); + + for (;pos_it != pos_end && id_it != id_end; ++pos_it, ++id_it ) + { + mAvatarsPositions[*id_it] = *pos_it; + } + }; + +protected: + virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const + { + const LLVector3d& me_pos = gAgent.getPositionGlobal(); + const LLVector3d& item1_pos = mAvatarsPositions.find(item1->getAvatarId())->second; + const LLVector3d& item2_pos = mAvatarsPositions.find(item2->getAvatarId())->second; + F32 dist1 = dist_vec(item1_pos, me_pos); + F32 dist2 = dist_vec(item2_pos, me_pos); + return dist1 < dist2; + } +private: + id_to_pos_map_t mAvatarsPositions; +}; + +/** Comparator for comparing nearby avatar items by last spoken time */ +class LLAvatarItemRecentSpeakerComparator : public LLAvatarItemNameComparator +{ +public: + LLAvatarItemRecentSpeakerComparator() {}; + virtual ~LLAvatarItemRecentSpeakerComparator() {}; + +protected: + virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const + { + LLPointer<LLSpeaker> lhs = LLActiveSpeakerMgr::instance().findSpeaker(item1->getAvatarId()); + LLPointer<LLSpeaker> rhs = LLActiveSpeakerMgr::instance().findSpeaker(item2->getAvatarId()); + if ( lhs.notNull() && rhs.notNull() ) + { + // Compare by last speaking time + if( lhs->mLastSpokeTime != rhs->mLastSpokeTime ) + return ( lhs->mLastSpokeTime > rhs->mLastSpokeTime ); + } + else if ( lhs.notNull() ) + { + // True if only item1 speaker info available + return true; + } + else if ( rhs.notNull() ) + { + // False if only item2 speaker info available + return false; + } + // By default compare by name. + return LLAvatarItemNameComparator::doCompare(item1, item2); + } +}; + +static const LLAvatarItemRecentComparator RECENT_COMPARATOR; +static const LLAvatarItemStatusComparator STATUS_COMPARATOR; +static LLAvatarItemDistanceComparator DISTANCE_COMPARATOR; +static const LLAvatarItemRecentSpeakerComparator RECENT_SPEAKER_COMPARATOR; static LLRegisterPanelClassWrapper<LLPanelPeople> t_people("panel_people"); //============================================================================= +/** + * Updates given list either on regular basis or on external events (up to implementation). + */ class LLPanelPeople::Updater { public: - typedef boost::function<bool(U32)> callback_t; + typedef boost::function<void()> callback_t; Updater(callback_t cb) : mCallback(cb) { } + virtual ~Updater() { } + + /** + * Activate/deactivate updater. + * + * This may start/stop regular updates. + */ virtual void setActive(bool) {} + protected: - bool updateList(U32 mask = 0) + void updateList() { - return mCallback(mask); + mCallback(); } + callback_t mCallback; }; @@ -95,83 +245,180 @@ public: { mEventTimer.stop(); } + + virtual BOOL tick() // from LLEventTimer + { + return FALSE; + } }; /** * Updates the friends list. + * + * Updates the list on external events which trigger the changed() method. */ class LLFriendListUpdater : public LLAvatarListUpdater, public LLFriendObserver { LOG_CLASS(LLFriendListUpdater); + class LLInventoryFriendCardObserver; + public: + friend class LLInventoryFriendCardObserver; LLFriendListUpdater(callback_t cb) : LLAvatarListUpdater(cb, FRIEND_LIST_UPDATE_TIMEOUT) + , mIsActive(false) { LLAvatarTracker::instance().addObserver(this); + // For notification when SIP online status changes. LLVoiceClient::getInstance()->addObserver(this); + mInvObserver = new LLInventoryFriendCardObserver(this); } + ~LLFriendListUpdater() { + // will be deleted by ~LLInventoryModel + //delete mInvObserver; LLVoiceClient::getInstance()->removeObserver(this); LLAvatarTracker::instance().removeObserver(this); } - /*virtual*/ void setActive(bool val) - { - if (!val) - return; - // Perform updates until all names are loaded. - if (!updateList(LLFriendObserver::ADD)) - changed(LLFriendObserver::ADD); - } /*virtual*/ void changed(U32 mask) { - // events can arrive quickly in bulk - we need not process EVERY one of them - - // so we wait a short while to let others pile-in, and process them in aggregate. - mEventTimer.start(); + if (mIsActive) + { + // events can arrive quickly in bulk - we need not process EVERY one of them - + // so we wait a short while to let others pile-in, and process them in aggregate. + mEventTimer.start(); + } // save-up all the mask-bits which have come-in mMask |= mask; } + + /*virtual*/ BOOL tick() { - if (updateList(mMask)) + if (!mIsActive) return FALSE; + + if (mMask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE)) { - // Got all names, stop updates. - mEventTimer.stop(); - mMask = 0; + updateList(); } + // Stop updates. + mEventTimer.stop(); + mMask = 0; + return FALSE; } + + // virtual + void setActive(bool active) + { + mIsActive = active; + if (active) + { + tick(); + } + } + private: U32 mMask; + LLInventoryFriendCardObserver* mInvObserver; + bool mIsActive; + + /** + * This class is intended for updating Friend List when Inventory Friend Card is added/removed. + * + * The main usage is when Inventory Friends/All content is added while synchronizing with + * friends list on startup is performed. In this case Friend Panel should be updated when + * missing Inventory Friend Card is created. + * *NOTE: updating is fired when Inventory item is added into CallingCards/Friends subfolder. + * Otherwise LLFriendObserver functionality is enough to keep Friends Panel synchronized. + */ + class LLInventoryFriendCardObserver : public LLInventoryObserver + { + LOG_CLASS(LLFriendListUpdater::LLInventoryFriendCardObserver); + + friend class LLFriendListUpdater; + + private: + LLInventoryFriendCardObserver(LLFriendListUpdater* updater) : mUpdater(updater) + { + gInventory.addObserver(this); + } + ~LLInventoryFriendCardObserver() + { + gInventory.removeObserver(this); + } + /*virtual*/ void changed(U32 mask) + { + lldebugs << "Inventory changed: " << mask << llendl; + + // *NOTE: deleting of InventoryItem is performed via moving to Trash. + // That means LLInventoryObserver::STRUCTURE is present in MASK instead of LLInventoryObserver::REMOVE + if ((CALLINGCARD_ADDED & mask) == CALLINGCARD_ADDED) + { + lldebugs << "Calling card added: count: " << gInventory.getChangedIDs().size() + << ", first Inventory ID: "<< (*gInventory.getChangedIDs().begin()) + << llendl; + + bool friendFound = false; + std::set<LLUUID> changedIDs = gInventory.getChangedIDs(); + for (std::set<LLUUID>::const_iterator it = changedIDs.begin(); it != changedIDs.end(); ++it) + { + if (isDescendentOfInventoryFriends(*it)) + { + friendFound = true; + break; + } + } + + if (friendFound) + { + lldebugs << "friend found, panel should be updated" << llendl; + mUpdater->changed(LLFriendObserver::ADD); + } + } + } + + bool isDescendentOfInventoryFriends(const LLUUID& invItemID) + { + LLViewerInventoryItem * item = gInventory.getItem(invItemID); + if (NULL == item) + return false; + + return LLFriendCardsManager::instance().isItemInAnyFriendsList(item); + } + LLFriendListUpdater* mUpdater; + + static const U32 CALLINGCARD_ADDED = LLInventoryObserver::ADD | LLInventoryObserver::CALLING_CARD; + }; }; /** * Periodically updates the nearby people list while the Nearby tab is active. + * + * The period is defined by NEARBY_LIST_UPDATE_INTERVAL constant. */ class LLNearbyListUpdater : public LLAvatarListUpdater { LOG_CLASS(LLNearbyListUpdater); + public: LLNearbyListUpdater(callback_t cb) : LLAvatarListUpdater(cb, NEARBY_LIST_UPDATE_INTERVAL) { setActive(false); } - /*virtual*/ BOOL tick() - { - updateList(); - return FALSE; - } + /*virtual*/ void setActive(bool val) { if (val) { // update immediately and start regular updates - tick(); + updateList(); mEventTimer.start(); } else @@ -180,69 +427,27 @@ public: mEventTimer.stop(); } } -private: -}; -/** - * Updates the recent people list (those the agent has recently interacted with). - */ -class LLRecentListUpdater : public LLAvatarListUpdater -{ - LOG_CLASS(LLRecentListUpdater); -public: - LLRecentListUpdater(callback_t cb) - : LLAvatarListUpdater(cb, RECENT_LIST_UPDATE_DELAY) - { - LLRecentPeople::instance().setChangedCallback(boost::bind(&LLRecentListUpdater::onRecentPeopleChanged, this)); - } -private: /*virtual*/ BOOL tick() { - // Update the list until we get all the names. - if (updateList()) - { - // Got all names, stop updates. - mEventTimer.stop(); - } - + updateList(); return FALSE; } - void onRecentPeopleChanged() - { - if (!updateList()) - { - // Some names are incomplete, schedule another update. - mEventTimer.start(); - } - } +private: }; /** - * Updates the group list on events from LLAgent. + * Updates the recent people list (those the agent has recently interacted with). */ -class LLGroupListUpdater : public LLPanelPeople::Updater, public LLSimpleListener +class LLRecentListUpdater : public LLAvatarListUpdater, public boost::signals2::trackable { - LOG_CLASS(LLGroupListUpdater); + LOG_CLASS(LLRecentListUpdater); + public: - LLGroupListUpdater(callback_t cb) - : LLPanelPeople::Updater(cb) - { - gAgent.addListener(this, "new group"); - } - ~LLGroupListUpdater() - { - gAgent.removeListener(this); - } - /*virtual*/ bool handleEvent(LLPointer<LLEvent> event, const LLSD& userdata) + LLRecentListUpdater(callback_t cb) + : LLAvatarListUpdater(cb, 0) { - // Why is "new group" sufficient? - if (event->desc() == "new group") - { - updateList(); - return true; - } - - return false; + LLRecentPeople::instance().setChangedCallback(boost::bind(&LLRecentListUpdater::updateList, this)); } }; @@ -251,16 +456,19 @@ public: LLPanelPeople::LLPanelPeople() : LLPanel(), mFilterSubString(LLStringUtil::null), - mSearchEditor(NULL), + mFilterSubStringOrig(LLStringUtil::null), + mFilterEditor(NULL), mTabContainer(NULL), - mFriendList(NULL), + mOnlineFriendList(NULL), + mAllFriendList(NULL), mNearbyList(NULL), - mRecentList(NULL) + mRecentList(NULL), + mGroupList(NULL) { - mFriendListUpdater = new LLFriendListUpdater(boost::bind(&LLPanelPeople::onFriendListUpdate,this, _1)); + mFriendListUpdater = new LLFriendListUpdater(boost::bind(&LLPanelPeople::updateFriendList, this)); mNearbyListUpdater = new LLNearbyListUpdater(boost::bind(&LLPanelPeople::updateNearbyList, this)); mRecentListUpdater = new LLRecentListUpdater(boost::bind(&LLPanelPeople::updateRecentList, this)); - mGroupListUpdater = new LLGroupListUpdater (boost::bind(&LLPanelPeople::updateGroupList, this)); + mCommitCallbackRegistrar.add("People.addFriend", boost::bind(&LLPanelPeople::onAddFriendButtonClicked, this)); } LLPanelPeople::~LLPanelPeople() @@ -268,152 +476,292 @@ LLPanelPeople::~LLPanelPeople() delete mNearbyListUpdater; delete mFriendListUpdater; delete mRecentListUpdater; - delete mGroupListUpdater; + + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver(this); + } LLView::deleteViewByHandle(mGroupPlusMenuHandle); - LLView::deleteViewByHandle(mGroupMinusMenuHandle); + LLView::deleteViewByHandle(mNearbyViewSortMenuHandle); + LLView::deleteViewByHandle(mFriendsViewSortMenuHandle); + LLView::deleteViewByHandle(mGroupsViewSortMenuHandle); + LLView::deleteViewByHandle(mRecentViewSortMenuHandle); + +} + +void LLPanelPeople::onFriendsAccordionExpandedCollapsed(LLUICtrl* ctrl, const LLSD& param, LLAvatarList* avatar_list) +{ + if(!avatar_list) + { + llerrs << "Bad parameter" << llendl; + return; + } + + bool expanded = param.asBoolean(); + + setAccordionCollapsedByUser(ctrl, !expanded); + if(!expanded) + { + avatar_list->resetSelection(); + } } BOOL LLPanelPeople::postBuild() { - mSearchEditor = getChild<LLSearchEditor>("filter_input"); + mFilterEditor = getChild<LLFilterEditor>("filter_input"); + mFilterEditor->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); mTabContainer = getChild<LLTabContainer>("tabs"); mTabContainer->setCommitCallback(boost::bind(&LLPanelPeople::onTabSelected, this, _2)); - mTabContainer->selectTabByName("friends_panel"); // must go after setting commit callback - mFriendList = getChild<LLPanel>("friends_panel")->getChild<LLAvatarList>("avatar_list"); - mNearbyList = getChild<LLPanel>("nearby_panel")->getChild<LLAvatarList>("avatar_list"); - mRecentList = getChild<LLPanel>("recent_panel")->getChild<LLAvatarList>("avatar_list"); + LLPanel* friends_tab = getChild<LLPanel>(FRIENDS_TAB_NAME); + // updater is active only if panel is visible to user. + friends_tab->setVisibleCallback(boost::bind(&Updater::setActive, mFriendListUpdater, _2)); + mOnlineFriendList = friends_tab->getChild<LLAvatarList>("avatars_online"); + mAllFriendList = friends_tab->getChild<LLAvatarList>("avatars_all"); + mOnlineFriendList->setNoItemsCommentText(getString("no_friends_online")); + mOnlineFriendList->setShowIcons("FriendsListShowIcons"); + mOnlineFriendList->showPermissions("FriendsListShowPermissions"); + mAllFriendList->setNoItemsCommentText(getString("no_friends")); + mAllFriendList->setShowIcons("FriendsListShowIcons"); + mAllFriendList->showPermissions("FriendsListShowPermissions"); + + LLPanel* nearby_tab = getChild<LLPanel>(NEARBY_TAB_NAME); + nearby_tab->setVisibleCallback(boost::bind(&Updater::setActive, mNearbyListUpdater, _2)); + mNearbyList = nearby_tab->getChild<LLAvatarList>("avatar_list"); + mNearbyList->setNoItemsCommentText(getString("no_one_near")); + mNearbyList->setNoItemsMsg(getString("no_one_near")); + mNearbyList->setNoFilteredItemsMsg(getString("no_one_filtered_near")); + mNearbyList->setShowIcons("NearbyListShowIcons"); + + mRecentList = getChild<LLPanel>(RECENT_TAB_NAME)->getChild<LLAvatarList>("avatar_list"); + mRecentList->setNoItemsCommentText(getString("no_recent_people")); + mRecentList->setNoItemsMsg(getString("no_recent_people")); + mRecentList->setNoFilteredItemsMsg(getString("no_filtered_recent_people")); + mRecentList->setShowIcons("RecentListShowIcons"); + mGroupList = getChild<LLGroupList>("group_list"); + mGroupList->setNoItemsMsg(getString("no_groups_msg")); + mGroupList->setNoFilteredItemsMsg(getString("no_filtered_groups_msg")); + + mNearbyList->setContextMenu(&LLPanelPeopleMenus::gNearbyMenu); + mRecentList->setContextMenu(&LLPanelPeopleMenus::gNearbyMenu); + mAllFriendList->setContextMenu(&LLPanelPeopleMenus::gNearbyMenu); + mOnlineFriendList->setContextMenu(&LLPanelPeopleMenus::gNearbyMenu); - LLPanel* groups_panel = getChild<LLPanel>("groups_panel"); + setSortOrder(mRecentList, (ESortOrder)gSavedSettings.getU32("RecentPeopleSortOrder"), false); + setSortOrder(mAllFriendList, (ESortOrder)gSavedSettings.getU32("FriendsSortOrder"), false); + setSortOrder(mNearbyList, (ESortOrder)gSavedSettings.getU32("NearbyPeopleSortOrder"), false); + + LLPanel* groups_panel = getChild<LLPanel>(GROUP_TAB_NAME); groups_panel->childSetAction("activate_btn", boost::bind(&LLPanelPeople::onActivateButtonClicked, this)); groups_panel->childSetAction("plus_btn", boost::bind(&LLPanelPeople::onGroupPlusButtonClicked, this)); - groups_panel->childSetAction("minus_btn", boost::bind(&LLPanelPeople::onGroupMinusButtonClicked, this)); - LLPanel* friends_panel = getChild<LLPanel>("friends_panel"); + LLPanel* friends_panel = getChild<LLPanel>(FRIENDS_TAB_NAME); friends_panel->childSetAction("add_btn", boost::bind(&LLPanelPeople::onAddFriendWizButtonClicked, this)); friends_panel->childSetAction("del_btn", boost::bind(&LLPanelPeople::onDeleteFriendButtonClicked, this)); - mFriendList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, mFriendList)); - mNearbyList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, mNearbyList)); - mRecentList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, mRecentList)); - mFriendList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mFriendList)); + mOnlineFriendList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + mAllFriendList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + mNearbyList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + mRecentList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + + mOnlineFriendList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mOnlineFriendList)); + mAllFriendList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mAllFriendList)); mNearbyList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mNearbyList)); mRecentList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mRecentList)); - mGroupList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onGroupInfoButtonClicked, this)); + // Set openning IM as default on return action for avatar lists + mOnlineFriendList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); + mAllFriendList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); + mNearbyList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); + mRecentList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); + + mGroupList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onChatButtonClicked, this)); mGroupList->setCommitCallback(boost::bind(&LLPanelPeople::updateButtons, this)); + mGroupList->setReturnCallback(boost::bind(&LLPanelPeople::onChatButtonClicked, this)); + + LLAccordionCtrlTab* accordion_tab = getChild<LLAccordionCtrlTab>("tab_all"); + accordion_tab->setDropDownStateChangedCallback( + boost::bind(&LLPanelPeople::onFriendsAccordionExpandedCollapsed, this, _1, _2, mAllFriendList)); + + accordion_tab = getChild<LLAccordionCtrlTab>("tab_online"); + accordion_tab->setDropDownStateChangedCallback( + boost::bind(&LLPanelPeople::onFriendsAccordionExpandedCollapsed, this, _1, _2, mOnlineFriendList)); buttonSetAction("view_profile_btn", boost::bind(&LLPanelPeople::onViewProfileButtonClicked, this)); - buttonSetAction("add_friend_btn", boost::bind(&LLPanelPeople::onAddFriendButtonClicked, this)); buttonSetAction("group_info_btn", boost::bind(&LLPanelPeople::onGroupInfoButtonClicked, this)); buttonSetAction("chat_btn", boost::bind(&LLPanelPeople::onChatButtonClicked, this)); buttonSetAction("im_btn", boost::bind(&LLPanelPeople::onImButtonClicked, this)); buttonSetAction("call_btn", boost::bind(&LLPanelPeople::onCallButtonClicked, this)); + buttonSetAction("group_call_btn", boost::bind(&LLPanelPeople::onGroupCallButtonClicked, this)); buttonSetAction("teleport_btn", boost::bind(&LLPanelPeople::onTeleportButtonClicked, this)); buttonSetAction("share_btn", boost::bind(&LLPanelPeople::onShareButtonClicked, this)); - buttonSetAction("more_btn", boost::bind(&LLPanelPeople::onMoreButtonClicked, this)); + + getChild<LLPanel>(NEARBY_TAB_NAME)->childSetAction("nearby_view_sort_btn",boost::bind(&LLPanelPeople::onNearbyViewSortButtonClicked, this)); + getChild<LLPanel>(RECENT_TAB_NAME)->childSetAction("recent_viewsort_btn",boost::bind(&LLPanelPeople::onRecentViewSortButtonClicked, this)); + getChild<LLPanel>(FRIENDS_TAB_NAME)->childSetAction("friends_viewsort_btn",boost::bind(&LLPanelPeople::onFriendsViewSortButtonClicked, this)); + getChild<LLPanel>(GROUP_TAB_NAME)->childSetAction("groups_viewsort_btn",boost::bind(&LLPanelPeople::onGroupsViewSortButtonClicked, this)); + + // Must go after setting commit callback and initializing all pointers to children. + mTabContainer->selectTabByName(NEARBY_TAB_NAME); // Create menus. LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + registrar.add("People.Group.Plus.Action", boost::bind(&LLPanelPeople::onGroupPlusMenuItemClicked, this, _2)); - LLMenuGL* plus_menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_group_plus.xml", gMenuHolder); + registrar.add("People.Group.Minus.Action", boost::bind(&LLPanelPeople::onGroupMinusButtonClicked, this)); + registrar.add("People.Friends.ViewSort.Action", boost::bind(&LLPanelPeople::onFriendsViewSortMenuItemClicked, this, _2)); + registrar.add("People.Nearby.ViewSort.Action", boost::bind(&LLPanelPeople::onNearbyViewSortMenuItemClicked, this, _2)); + registrar.add("People.Groups.ViewSort.Action", boost::bind(&LLPanelPeople::onGroupsViewSortMenuItemClicked, this, _2)); + registrar.add("People.Recent.ViewSort.Action", boost::bind(&LLPanelPeople::onRecentViewSortMenuItemClicked, this, _2)); + + enable_registrar.add("People.Group.Minus.Enable", boost::bind(&LLPanelPeople::isRealGroup, this)); + enable_registrar.add("People.Friends.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onFriendsViewSortMenuItemCheck, this, _2)); + enable_registrar.add("People.Recent.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onRecentViewSortMenuItemCheck, this, _2)); + enable_registrar.add("People.Nearby.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onNearbyViewSortMenuItemCheck, this, _2)); + + LLMenuGL* plus_menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_group_plus.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); mGroupPlusMenuHandle = plus_menu->getHandle(); - registrar.add("People.Group.Minus.Action", boost::bind(&LLPanelPeople::onGroupMinusMenuItemClicked, this, _2)); - LLMenuGL* minus_menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_group_minus.xml", gMenuHolder); - mGroupMinusMenuHandle = minus_menu->getHandle(); - // Perform initial update. - mFriendListUpdater->setActive(true); - updateGroupList(); - updateRecentList(); + LLMenuGL* nearby_view_sort = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_people_nearby_view_sort.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if(nearby_view_sort) + mNearbyViewSortMenuHandle = nearby_view_sort->getHandle(); - return TRUE; -} + LLMenuGL* friend_view_sort = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_people_friends_view_sort.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if(friend_view_sort) + mFriendsViewSortMenuHandle = friend_view_sort->getHandle(); -bool LLPanelPeople::refreshFriendNames(U32 changed_mask) -{ - // get all buddies we know about - LLAvatarTracker::buddy_map_t all_buddies; - LLAvatarTracker::instance().copyBuddyList(all_buddies); - - bool have_names = true; + LLMenuGL* group_view_sort = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_people_groups_view_sort.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if(group_view_sort) + mGroupsViewSortMenuHandle = group_view_sort->getHandle(); - if (changed_mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE)) - { - // *TODO: it's suboptimal to rebuild the whole list on online status change. + LLMenuGL* recent_view_sort = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_people_recent_view_sort.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if(recent_view_sort) + mRecentViewSortMenuHandle = recent_view_sort->getHandle(); - // convert the buddy map to vector - std::vector<LLUUID> avatar_ids; - LLAvatarTracker::buddy_map_t::const_iterator buddy_it = all_buddies.begin(); - for (; buddy_it != all_buddies.end(); ++buddy_it) - avatar_ids.push_back(buddy_it->first); + LLVoiceClient::getInstance()->addObserver(this); - // do refresh the friend list - if (avatar_ids.size() > 0) - have_names = mFriendList->updateList(avatar_ids); - else - mFriendList->setCommentText(getString("no_friends")); - } + // call this method in case some list is empty and buttons can be in inconsistent state + updateButtons(); - return have_names; + mOnlineFriendList->setRefreshCompleteCallback(boost::bind(&LLPanelPeople::onFriendListRefreshComplete, this, _1, _2)); + mAllFriendList->setRefreshCompleteCallback(boost::bind(&LLPanelPeople::onFriendListRefreshComplete, this, _1, _2)); + + return TRUE; } -bool LLPanelPeople::updateFriendList(U32 changed_mask) +// virtual +void LLPanelPeople::onChange(EStatusType status, const std::string &channelURI, bool proximal) { - // Refresh names. - if (changed_mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE)) + if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) { - return refreshFriendNames(changed_mask); + return; } - return true; + updateButtons(); } -bool LLPanelPeople::updateNearbyList() +void LLPanelPeople::updateFriendListHelpText() { - std::vector<LLUUID> avatar_ids; + // show special help text for just created account to help finding friends. EXT-4836 + static LLTextBox* no_friends_text = getChild<LLTextBox>("no_friends_help_text"); + + // Seems sometimes all_friends can be empty because of issue with Inventory loading (clear cache, slow connection...) + // So, lets check all lists to avoid overlapping the text with online list. See EXT-6448. + bool any_friend_exists = mAllFriendList->filterHasMatches() || mOnlineFriendList->filterHasMatches(); + no_friends_text->setVisible(!any_friend_exists); + if (no_friends_text->getVisible()) + { + //update help text for empty lists + std::string message_name = mFilterSubString.empty() ? "no_friends_msg" : "no_filtered_friends_msg"; + LLStringUtil::format_map_t args; + args["[SEARCH_TERM]"] = LLURI::escape(mFilterSubStringOrig); + no_friends_text->setText(getString(message_name, args)); + } +} - LLWorld::getInstance()->getAvatars(&avatar_ids, NULL, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); +void LLPanelPeople::updateFriendList() +{ + if (!mOnlineFriendList || !mAllFriendList) + return; - mNearbyList->updateList(avatar_ids); + // get all buddies we know about + const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + LLAvatarTracker::buddy_map_t all_buddies; + av_tracker.copyBuddyList(all_buddies); - if (avatar_ids.size() == 0) - mNearbyList->setCommentText(getString("no_one_near")); + // save them to the online and all friends vectors + uuid_vec_t& online_friendsp = mOnlineFriendList->getIDs(); + uuid_vec_t& all_friendsp = mAllFriendList->getIDs(); - return true; + all_friendsp.clear(); + online_friendsp.clear(); + + LLFriendCardsManager::folderid_buddies_map_t listMap; + + // *NOTE: For now collectFriendsLists returns data only for Friends/All folder. EXT-694. + LLFriendCardsManager::instance().collectFriendsLists(listMap); + if (listMap.size() > 0) + { + lldebugs << "Friends Cards were found, count: " << listMap.begin()->second.size() << llendl; + all_friendsp = listMap.begin()->second; + } + else + { + lldebugs << "Friends Cards were not found" << llendl; + } + + LLAvatarTracker::buddy_map_t::const_iterator buddy_it = all_buddies.begin(); + for (; buddy_it != all_buddies.end(); ++buddy_it) + { + LLUUID buddy_id = buddy_it->first; + if (av_tracker.isBuddyOnline(buddy_id)) + online_friendsp.push_back(buddy_id); + } + + /* + * Avatarlists will be hidden by showFriendsAccordionsIfNeeded(), if they do not have items. + * But avatarlist can be updated only if it is visible @see LLAvatarList::draw(); + * So we need to do force update of lists to avoid inconsistency of data and view of avatarlist. + */ + mOnlineFriendList->setDirty(true, !mOnlineFriendList->filterHasMatches());// do force update if list do NOT have items + mAllFriendList->setDirty(true, !mAllFriendList->filterHasMatches()); + //update trash and other buttons according to a selected item + updateButtons(); + showFriendsAccordionsIfNeeded(); } -bool LLPanelPeople::updateRecentList() +void LLPanelPeople::updateNearbyList() { - std::vector<LLUUID> avatar_ids; + if (!mNearbyList) + return; - LLRecentPeople::instance().get(avatar_ids); - - if (avatar_ids.size() > 0) - return mRecentList->updateList(avatar_ids); + std::vector<LLVector3d> positions; - mRecentList->setCommentText(getString("no_people")); - return true; + LLWorld::getInstance()->getAvatars(&mNearbyList->getIDs(), &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); + mNearbyList->setDirty(); + + DISTANCE_COMPARATOR.updateAvatarsPositions(positions, mNearbyList->getIDs()); + LLActiveSpeakerMgr::instance().update(TRUE); } -bool LLPanelPeople::updateGroupList() +void LLPanelPeople::updateRecentList() { - return mGroupList->updateList(); + if (!mRecentList) + return; + + LLRecentPeople::instance().get(mRecentList->getIDs()); + mRecentList->setDirty(); } void LLPanelPeople::buttonSetVisible(std::string btn_name, BOOL visible) { - // Currently all bottom buttons are wrapped with layout panels. - // Hiding a button has no effect: the panel still occupies its space. - // So we have to hide the whole panel (along with its button) - // to free some space up. - LLButton* btn = getChild<LLView>("button_bar")->getChild<LLButton>(btn_name); - LLPanel* btn_parent = dynamic_cast<LLPanel*>(btn->getParent()); - if (btn_parent) - btn_parent->setVisible(visible); + // To make sure we're referencing the right widget (a child of the button bar). + LLButton* button = getChild<LLView>("button_bar")->getChild<LLButton>(btn_name); + button->setVisible(visible); } void LLPanelPeople::buttonSetEnabled(const std::string& btn_name, bool enabled) @@ -432,79 +780,128 @@ void LLPanelPeople::buttonSetAction(const std::string& btn_name, const commit_si void LLPanelPeople::updateButtons() { - std::string cur_tab = mTabContainer->getCurrentPanel()->getName(); - bool nearby_tab_active = (cur_tab == "nearby_panel"); - bool friends_tab_active = (cur_tab == "friends_panel"); - bool group_tab_active = (cur_tab == "groups_panel"); - bool recent_tab_active = (cur_tab == "recent_panel"); + std::string cur_tab = getActiveTabName(); + bool nearby_tab_active = (cur_tab == NEARBY_TAB_NAME); + bool friends_tab_active = (cur_tab == FRIENDS_TAB_NAME); + bool group_tab_active = (cur_tab == GROUP_TAB_NAME); + //bool recent_tab_active = (cur_tab == RECENT_TAB_NAME); LLUUID selected_id; + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + bool item_selected = (selected_uuids.size() == 1); + bool multiple_selected = (selected_uuids.size() >= 1); + buttonSetVisible("group_info_btn", group_tab_active); buttonSetVisible("chat_btn", group_tab_active); - buttonSetVisible("add_friend_btn", nearby_tab_active || recent_tab_active); buttonSetVisible("view_profile_btn", !group_tab_active); buttonSetVisible("im_btn", !group_tab_active); - buttonSetVisible("teleport_btn", friends_tab_active || group_tab_active); - buttonSetVisible("share_btn", !recent_tab_active && false); // not implemented yet + buttonSetVisible("call_btn", !group_tab_active); + buttonSetVisible("group_call_btn", group_tab_active); + buttonSetVisible("teleport_btn", friends_tab_active); + buttonSetVisible("share_btn", nearby_tab_active || friends_tab_active); if (group_tab_active) { bool cur_group_active = true; - selected_id = mGroupList->getCurrentID(); - if (selected_id.notNull()) + if (item_selected) + { + selected_id = mGroupList->getSelectedUUID(); cur_group_active = (gAgent.getGroupID() == selected_id); + } - bool item_selected = selected_id.notNull(); LLPanel* groups_panel = mTabContainer->getCurrentPanel(); - groups_panel->childSetEnabled("activate_btn", !item_selected || !cur_group_active); // "none" or a non-active group selected - groups_panel->childSetEnabled("plus_btn", item_selected); - groups_panel->childSetEnabled("minus_btn", item_selected); + groups_panel->getChildView("activate_btn")->setEnabled(item_selected && !cur_group_active); // "none" or a non-active group selected + groups_panel->getChildView("minus_btn")->setEnabled(item_selected && selected_id.notNull()); } else { bool is_friend = true; - LLAvatarList* list; // Check whether selected avatar is our friend. - if ((list = getActiveAvatarList()) && (selected_id = list->getCurrentID()).notNull()) + if (item_selected) { + selected_id = selected_uuids.front(); is_friend = LLAvatarTracker::instance().getBuddyInfo(selected_id) != NULL; } - childSetEnabled("add_friend_btn", !is_friend); + LLPanel* cur_panel = mTabContainer->getCurrentPanel(); + if (cur_panel) + { + cur_panel->getChildView("add_friend_btn")->setEnabled(!is_friend); + if (friends_tab_active) + { + cur_panel->getChildView("del_btn")->setEnabled(multiple_selected); + } + } } - bool item_selected = selected_id.notNull(); - buttonSetEnabled("teleport_btn", friends_tab_active && item_selected); - buttonSetEnabled("view_profile_btn", item_selected); - buttonSetEnabled("im_btn", item_selected); - buttonSetEnabled("call_btn", item_selected && false); // not implemented yet - buttonSetEnabled("share_btn", item_selected && false); // not implemented yet - buttonSetEnabled("group_info_btn", item_selected); - buttonSetEnabled("chat_btn", item_selected); + bool enable_calls = LLVoiceClient::getInstance()->isVoiceWorking() && LLVoiceClient::getInstance()->voiceEnabled(); + + buttonSetEnabled("view_profile_btn",item_selected); + buttonSetEnabled("share_btn", item_selected); + buttonSetEnabled("im_btn", multiple_selected); // allow starting the friends conference for multiple selection + buttonSetEnabled("call_btn", multiple_selected && enable_calls); + buttonSetEnabled("teleport_btn", multiple_selected && LLAvatarActions::canOfferTeleport(selected_uuids)); + + bool none_group_selected = item_selected && selected_id.isNull(); + buttonSetEnabled("group_info_btn", !none_group_selected); + buttonSetEnabled("group_call_btn", !none_group_selected && enable_calls); + buttonSetEnabled("chat_btn", !none_group_selected); +} + +std::string LLPanelPeople::getActiveTabName() const +{ + return mTabContainer->getCurrentPanel()->getName(); } -LLAvatarList* LLPanelPeople::getActiveAvatarList() const +LLUUID LLPanelPeople::getCurrentItemID() const { - std::string cur_tab = mTabContainer->getCurrentPanel()->getName(); + std::string cur_tab = getActiveTabName(); - if (cur_tab == "friends_panel") - return mFriendList; - if (cur_tab == "nearby_panel") - return mNearbyList; - if (cur_tab == "recent_panel") - return mRecentList; + if (cur_tab == FRIENDS_TAB_NAME) // this tab has two lists + { + LLUUID cur_online_friend; - return NULL; + if ((cur_online_friend = mOnlineFriendList->getSelectedUUID()).notNull()) + return cur_online_friend; + + return mAllFriendList->getSelectedUUID(); + } + + if (cur_tab == NEARBY_TAB_NAME) + return mNearbyList->getSelectedUUID(); + + if (cur_tab == RECENT_TAB_NAME) + return mRecentList->getSelectedUUID(); + + if (cur_tab == GROUP_TAB_NAME) + return mGroupList->getSelectedUUID(); + + llassert(0 && "unknown tab selected"); + return LLUUID::null; } -LLUUID LLPanelPeople::getCurrentItemID() const +void LLPanelPeople::getCurrentItemIDs(uuid_vec_t& selected_uuids) const { - LLAvatarList* alist = getActiveAvatarList(); - if (alist) - return alist->getCurrentID(); - return mGroupList->getCurrentID(); + std::string cur_tab = getActiveTabName(); + + if (cur_tab == FRIENDS_TAB_NAME) + { + // friends tab has two lists + mOnlineFriendList->getSelectedUUIDs(selected_uuids); + mAllFriendList->getSelectedUUIDs(selected_uuids); + } + else if (cur_tab == NEARBY_TAB_NAME) + mNearbyList->getSelectedUUIDs(selected_uuids); + else if (cur_tab == RECENT_TAB_NAME) + mRecentList->getSelectedUUIDs(selected_uuids); + else if (cur_tab == GROUP_TAB_NAME) + mGroupList->getSelectedUUIDs(selected_uuids); + else + llassert(0 && "unknown tab selected"); + } void LLPanelPeople::showGroupMenu(LLMenuGL* menu) @@ -526,64 +923,144 @@ void LLPanelPeople::showGroupMenu(LLMenuGL* menu) LLMenuGL::showPopup(parent_panel, menu, menu_x, menu_y); } -void LLPanelPeople::onVisibilityChange(BOOL new_visibility) +void LLPanelPeople::setSortOrder(LLAvatarList* list, ESortOrder order, bool save) { - if (new_visibility == FALSE) + switch (order) { - // Don't update anything while we're invisible. - mNearbyListUpdater->setActive(FALSE); + case E_SORT_BY_NAME: + list->sortByName(); + break; + case E_SORT_BY_STATUS: + list->setComparator(&STATUS_COMPARATOR); + list->sort(); + break; + case E_SORT_BY_MOST_RECENT: + list->setComparator(&RECENT_COMPARATOR); + list->sort(); + break; + case E_SORT_BY_RECENT_SPEAKERS: + list->setComparator(&RECENT_SPEAKER_COMPARATOR); + list->sort(); + break; + case E_SORT_BY_DISTANCE: + list->setComparator(&DISTANCE_COMPARATOR); + list->sort(); + break; + default: + llwarns << "Unrecognized people sort order for " << list->getName() << llendl; + return; } - else + + if (save) { - // Make the tab-container re-select current tab - // for onTabSelected() callback to get called. - // (currently this is needed to reactivate nearby list updates - // when we get visible) - mTabContainer->selectTab(mTabContainer->getCurrentPanelIndex()); + std::string setting; + + if (list == mAllFriendList || list == mOnlineFriendList) + setting = "FriendsSortOrder"; + else if (list == mRecentList) + setting = "RecentPeopleSortOrder"; + else if (list == mNearbyList) + setting = "NearbyPeopleSortOrder"; + + if (!setting.empty()) + gSavedSettings.setU32(setting, order); } } -void LLPanelPeople::onSearchEdit(const std::string& search_string) +bool LLPanelPeople::isRealGroup() { - if (mFilterSubString == search_string) + return getCurrentItemID() != LLUUID::null; +} + +void LLPanelPeople::onFilterEdit(const std::string& search_string) +{ + mFilterSubStringOrig = search_string; + LLStringUtil::trimHead(mFilterSubStringOrig); + // Searches are case-insensitive + std::string search_upper = mFilterSubStringOrig; + LLStringUtil::toUpper(search_upper); + + if (mFilterSubString == search_upper) return; - mFilterSubString = search_string; + mFilterSubString = search_upper; - LLStringUtil::toUpper(mFilterSubString); - LLStringUtil::trimHead(mFilterSubString); - mSearchEditor->setText(mFilterSubString); + //store accordion tabs state before any manipulation with accordion tabs + if(!mFilterSubString.empty()) + { + notifyChildren(LLSD().with("action","store_state")); + } + + + // Apply new filter. + mNearbyList->setNameFilter(mFilterSubStringOrig); + mOnlineFriendList->setNameFilter(mFilterSubStringOrig); + mAllFriendList->setNameFilter(mFilterSubStringOrig); + mRecentList->setNameFilter(mFilterSubStringOrig); + mGroupList->setNameFilter(mFilterSubStringOrig); + + setAccordionCollapsedByUser("tab_online", false); + setAccordionCollapsedByUser("tab_all", false); + + showFriendsAccordionsIfNeeded(); + + //restore accordion tabs state _after_ all manipulations... + if(mFilterSubString.empty()) + { + notifyChildren(LLSD().with("action","restore_state")); + } } void LLPanelPeople::onTabSelected(const LLSD& param) { std::string tab_name = getChild<LLPanel>(param.asString())->getName(); - mNearbyListUpdater->setActive(tab_name == "nearby_panel"); updateButtons(); + + showFriendsAccordionsIfNeeded(); + + if (GROUP_TAB_NAME == tab_name) + mFilterEditor->setLabel(getString("groups_filter_label")); + else + mFilterEditor->setLabel(getString("people_filter_label")); } -void LLPanelPeople::onAvatarListDoubleClicked(LLAvatarList* list) +void LLPanelPeople::onAvatarListDoubleClicked(LLUICtrl* ctrl) { - LLUUID clicked_id = list->getCurrentID(); - - if (clicked_id.isNull()) + LLAvatarListItem* item = dynamic_cast<LLAvatarListItem*>(ctrl); + if(!item) + { return; + } - // Open mini-inspector for the avatar being clicked - LLFloaterReg::showInstance("mini_inspector", clicked_id); - // inspector will delete itself on close + LLUUID clicked_id = item->getAvatarId(); + +#if 0 // SJB: Useful for testing, but not currently functional or to spec + LLAvatarActions::showProfile(clicked_id); +#else // spec says open IM window + LLAvatarActions::startIM(clicked_id); +#endif } void LLPanelPeople::onAvatarListCommitted(LLAvatarList* list) { - (void) list; + // Make sure only one of the friends lists (online/all) has selection. + if (getActiveTabName() == FRIENDS_TAB_NAME) + { + if (list == mOnlineFriendList) + mAllFriendList->resetSelection(true); + else if (list == mAllFriendList) + mOnlineFriendList->resetSelection(true); + else + llassert(0 && "commit on unknown friends list"); + } + updateButtons(); } void LLPanelPeople::onViewProfileButtonClicked() { LLUUID id = getCurrentItemID(); - LLFriendActions::showProfile(id); + LLAvatarActions::showProfile(id); } void LLPanelPeople::onAddFriendButtonClicked() @@ -591,16 +1068,32 @@ void LLPanelPeople::onAddFriendButtonClicked() LLUUID id = getCurrentItemID(); if (id.notNull()) { - std::string name; - gCacheName->getFullName(id, name); - LLFriendActions::requestFriendshipDialog(id, name); + LLAvatarActions::requestFriendshipDialog(id); + } +} + +bool LLPanelPeople::isItemsFreeOfFriends(const uuid_vec_t& uuids) +{ + const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + for ( uuid_vec_t::const_iterator + id = uuids.begin(), + id_end = uuids.end(); + id != id_end; ++id ) + { + if (av_tracker.isBuddy (*id)) + { + return false; + } } + return true; } void LLPanelPeople::onAddFriendWizButtonClicked() { // Show add friend wizard. - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(onAvatarPicked, NULL, FALSE, TRUE); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLPanelPeople::onAvatarPicked, _1, _2), FALSE, TRUE); + // Need to disable 'ok' button when friend occurs in selection + if (picker) picker->setOkBtnEnableCb(boost::bind(&LLPanelPeople::isItemsFreeOfFriends, this, _1)); LLFloater* root_floater = gFloaterView->getParentFloater(this); if (root_floater) { @@ -610,60 +1103,69 @@ void LLPanelPeople::onAddFriendWizButtonClicked() void LLPanelPeople::onDeleteFriendButtonClicked() { - LLFriendActions::removeFriendDialog(getCurrentItemID()); + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + + if (selected_uuids.size() == 1) + { + LLAvatarActions::removeFriendDialog( selected_uuids.front() ); + } + else if (selected_uuids.size() > 1) + { + LLAvatarActions::removeFriendsDialog( selected_uuids ); + } } void LLPanelPeople::onGroupInfoButtonClicked() { - LLUUID group_id = getCurrentItemID(); - if (group_id.notNull()) - LLGroupActions::info(group_id); + LLGroupActions::show(getCurrentItemID()); } void LLPanelPeople::onChatButtonClicked() { LLUUID group_id = getCurrentItemID(); if (group_id.notNull()) - LLGroupActions::startChat(group_id); + LLGroupActions::startIM(group_id); } void LLPanelPeople::onImButtonClicked() { - LLUUID id = getCurrentItemID(); - if (id.notNull()) + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + if ( selected_uuids.size() == 1 ) + { + // if selected only one person then start up IM + LLAvatarActions::startIM(selected_uuids.at(0)); + } + else if ( selected_uuids.size() > 1 ) { - LLFriendActions::startIM(id); + // for multiple selection start up friends conference + LLAvatarActions::startConference(selected_uuids); } } void LLPanelPeople::onActivateButtonClicked() { - LLGroupActions::activate(mGroupList->getCurrentID()); + LLGroupActions::activate(mGroupList->getSelectedUUID()); } // static void LLPanelPeople::onAvatarPicked( const std::vector<std::string>& names, - const std::vector<LLUUID>& ids, - void*) + const uuid_vec_t& ids) { if (!names.empty() && !ids.empty()) - LLFriendActions::requestFriendshipDialog(ids[0], names[0]); -} - -bool LLPanelPeople::onFriendListUpdate(U32 changed_mask) -{ - bool have_names = updateFriendList(changed_mask); - - // Update online status in the Recent tab. - // *TODO: isn't it too much to update the whole list? - updateRecentList(); - - return have_names; + LLAvatarActions::requestFriendshipDialog(ids[0], names[0]); } void LLPanelPeople::onGroupPlusButtonClicked() { + if (!gAgent.canJoinGroups()) + { + LLNotificationsUtil::add("JoinedTooManyGroups"); + return; + } + LLMenuGL* plus_menu = (LLMenuGL*)mGroupPlusMenuHandle.get(); if (!plus_menu) return; @@ -673,11 +1175,9 @@ void LLPanelPeople::onGroupPlusButtonClicked() void LLPanelPeople::onGroupMinusButtonClicked() { - LLMenuGL* minus_menu = (LLMenuGL*)mGroupMinusMenuHandle.get(); - if (!minus_menu) - return; - - showGroupMenu(minus_menu); + LLUUID group_id = getCurrentItemID(); + if (group_id.notNull()) + LLGroupActions::leave(group_id); } void LLPanelPeople::onGroupPlusMenuItemClicked(const LLSD& userdata) @@ -687,35 +1187,159 @@ void LLPanelPeople::onGroupPlusMenuItemClicked(const LLSD& userdata) if (chosen_item == "join_group") LLGroupActions::search(); else if (chosen_item == "new_group") - LLGroupActions::create(); + LLGroupActions::createGroup(); } -void LLPanelPeople::onGroupMinusMenuItemClicked(const LLSD& userdata) +void LLPanelPeople::onFriendsViewSortMenuItemClicked(const LLSD& userdata) { std::string chosen_item = userdata.asString(); - LLUUID group_id = getCurrentItemID(); - if (chosen_item == "leave_group") - LLGroupActions::leave(group_id); - /* - else if (chosen_item == "delete_group") - ; // *TODO: how to delete a group? - */ + if (chosen_item == "sort_name") + { + setSortOrder(mAllFriendList, E_SORT_BY_NAME); + } + else if (chosen_item == "sort_status") + { + setSortOrder(mAllFriendList, E_SORT_BY_STATUS); + } + else if (chosen_item == "view_icons") + { + mAllFriendList->toggleIcons(); + mOnlineFriendList->toggleIcons(); + } + else if (chosen_item == "view_permissions") + { + bool show_permissions = !gSavedSettings.getBOOL("FriendsListShowPermissions"); + gSavedSettings.setBOOL("FriendsListShowPermissions", show_permissions); + + mAllFriendList->showPermissions(show_permissions); + mOnlineFriendList->showPermissions(show_permissions); + } +} + +void LLPanelPeople::onGroupsViewSortMenuItemClicked(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + if (chosen_item == "show_icons") + { + mGroupList->toggleIcons(); + } +} + +void LLPanelPeople::onNearbyViewSortMenuItemClicked(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + if (chosen_item == "sort_by_recent_speakers") + { + setSortOrder(mNearbyList, E_SORT_BY_RECENT_SPEAKERS); + } + else if (chosen_item == "sort_name") + { + setSortOrder(mNearbyList, E_SORT_BY_NAME); + } + else if (chosen_item == "view_icons") + { + mNearbyList->toggleIcons(); + } + else if (chosen_item == "sort_distance") + { + setSortOrder(mNearbyList, E_SORT_BY_DISTANCE); + } +} + +bool LLPanelPeople::onNearbyViewSortMenuItemCheck(const LLSD& userdata) +{ + std::string item = userdata.asString(); + U32 sort_order = gSavedSettings.getU32("NearbyPeopleSortOrder"); + + if (item == "sort_by_recent_speakers") + return sort_order == E_SORT_BY_RECENT_SPEAKERS; + if (item == "sort_name") + return sort_order == E_SORT_BY_NAME; + if (item == "sort_distance") + return sort_order == E_SORT_BY_DISTANCE; + + return false; +} + +void LLPanelPeople::onRecentViewSortMenuItemClicked(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + if (chosen_item == "sort_recent") + { + setSortOrder(mRecentList, E_SORT_BY_MOST_RECENT); + } + else if (chosen_item == "sort_name") + { + setSortOrder(mRecentList, E_SORT_BY_NAME); + } + else if (chosen_item == "view_icons") + { + mRecentList->toggleIcons(); + } +} + +bool LLPanelPeople::onFriendsViewSortMenuItemCheck(const LLSD& userdata) +{ + std::string item = userdata.asString(); + U32 sort_order = gSavedSettings.getU32("FriendsSortOrder"); + + if (item == "sort_name") + return sort_order == E_SORT_BY_NAME; + if (item == "sort_status") + return sort_order == E_SORT_BY_STATUS; + + return false; +} + +bool LLPanelPeople::onRecentViewSortMenuItemCheck(const LLSD& userdata) +{ + std::string item = userdata.asString(); + U32 sort_order = gSavedSettings.getU32("RecentPeopleSortOrder"); + + if (item == "sort_recent") + return sort_order == E_SORT_BY_MOST_RECENT; + if (item == "sort_name") + return sort_order == E_SORT_BY_NAME; + + return false; } void LLPanelPeople::onCallButtonClicked() { - // *TODO: not implemented yet + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + + if (selected_uuids.size() == 1) + { + // initiate a P2P voice chat with the selected user + LLAvatarActions::startCall(getCurrentItemID()); + } + else if (selected_uuids.size() > 1) + { + // initiate an ad-hoc voice chat with multiple users + LLAvatarActions::startAdhocCall(selected_uuids); + } +} + +void LLPanelPeople::onGroupCallButtonClicked() +{ + LLGroupActions::startCall(getCurrentItemID()); } void LLPanelPeople::onTeleportButtonClicked() { - LLFriendActions::offerTeleport(getCurrentItemID()); + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + LLAvatarActions::offerTeleport(selected_uuids); } void LLPanelPeople::onShareButtonClicked() { - // *TODO: not implemented yet + LLAvatarActions::share(getCurrentItemID()); } void LLPanelPeople::onMoreButtonClicked() @@ -723,7 +1347,162 @@ void LLPanelPeople::onMoreButtonClicked() // *TODO: not implemented yet } +void LLPanelPeople::onFriendsViewSortButtonClicked() +{ + LLMenuGL* menu = (LLMenuGL*)mFriendsViewSortMenuHandle.get(); + if (!menu) + return; + showGroupMenu(menu); +} + +void LLPanelPeople::onGroupsViewSortButtonClicked() +{ + LLMenuGL* menu = (LLMenuGL*)mGroupsViewSortMenuHandle.get(); + if (!menu) + return; + showGroupMenu(menu); +} + +void LLPanelPeople::onRecentViewSortButtonClicked() +{ + LLMenuGL* menu = (LLMenuGL*)mRecentViewSortMenuHandle.get(); + if (!menu) + return; + showGroupMenu(menu); +} + +void LLPanelPeople::onNearbyViewSortButtonClicked() +{ + LLMenuGL* menu = (LLMenuGL*)mNearbyViewSortMenuHandle.get(); + if (!menu) + return; + showGroupMenu(menu); +} + void LLPanelPeople::onOpen(const LLSD& key) { - mTabContainer->selectTab(key.asInteger()); + std::string tab_name = key["people_panel_tab_name"]; + mFilterEditor -> clear(); + onFilterEdit(""); + + if (!tab_name.empty()) + mTabContainer->selectTabByName(tab_name); } + +bool LLPanelPeople::notifyChildren(const LLSD& info) +{ + if (info.has("task-panel-action") && info["task-panel-action"].asString() == "handle-tri-state") + { + LLSideTrayPanelContainer* container = dynamic_cast<LLSideTrayPanelContainer*>(getParent()); + if (!container) + { + llwarns << "Cannot find People panel container" << llendl; + return true; + } + + if (container->getCurrentPanelIndex() > 0) + { + // if not on the default panel, switch to it + container->onOpen(LLSD().with(LLSideTrayPanelContainer::PARAM_SUB_PANEL_NAME, getName())); + } + else + LLSideTray::getInstance()->collapseSideBar(); + + return true; // this notification is only supposed to be handled by task panels + } + + return LLPanel::notifyChildren(info); +} + +void LLPanelPeople::showAccordion(const std::string name, bool show) +{ + if(name.empty()) + { + llwarns << "No name provided" << llendl; + return; + } + + LLAccordionCtrlTab* tab = getChild<LLAccordionCtrlTab>(name); + tab->setVisible(show); + if(show) + { + // don't expand accordion if it was collapsed by user + if(!isAccordionCollapsedByUser(tab)) + { + // expand accordion + tab->changeOpenClose(false); + } + } +} + +void LLPanelPeople::showFriendsAccordionsIfNeeded() +{ + if(FRIENDS_TAB_NAME == getActiveTabName()) + { + // Expand and show accordions if needed, else - hide them + showAccordion("tab_online", mOnlineFriendList->filterHasMatches()); + showAccordion("tab_all", mAllFriendList->filterHasMatches()); + + // Rearrange accordions + LLAccordionCtrl* accordion = getChild<LLAccordionCtrl>("friends_accordion"); + accordion->arrange(); + + // *TODO: new no_matched_tabs_text attribute was implemented in accordion (EXT-7368). + // this code should be refactored to use it + // keep help text in a synchronization with accordions visibility. + updateFriendListHelpText(); + } +} + +void LLPanelPeople::onFriendListRefreshComplete(LLUICtrl*ctrl, const LLSD& param) +{ + if(ctrl == mOnlineFriendList) + { + showAccordion("tab_online", param.asInteger()); + } + else if(ctrl == mAllFriendList) + { + showAccordion("tab_all", param.asInteger()); + } +} + +void LLPanelPeople::setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed) +{ + if(!acc_tab) + { + llwarns << "Invalid parameter" << llendl; + return; + } + + LLSD param = acc_tab->getValue(); + param[COLLAPSED_BY_USER] = collapsed; + acc_tab->setValue(param); +} + +void LLPanelPeople::setAccordionCollapsedByUser(const std::string& name, bool collapsed) +{ + setAccordionCollapsedByUser(getChild<LLUICtrl>(name), collapsed); +} + +bool LLPanelPeople::isAccordionCollapsedByUser(LLUICtrl* acc_tab) +{ + if(!acc_tab) + { + llwarns << "Invalid parameter" << llendl; + return false; + } + + LLSD param = acc_tab->getValue(); + if(!param.has(COLLAPSED_BY_USER)) + { + return false; + } + return param[COLLAPSED_BY_USER].asBoolean(); +} + +bool LLPanelPeople::isAccordionCollapsedByUser(const std::string& name) +{ + return isAccordionCollapsedByUser(getChild<LLUICtrl>(name)); +} + +// EOF |