diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 27e71c52da..6ef4c663d0 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -85,6 +85,7 @@ set(viewer_SOURCE_FILES chatbar_as_cmdline.cpp kcwlinterface.cpp floatermedialists.cpp + fscontactlist.cpp fscontactsfloater.cpp fsareasearch.cpp fsdata.cpp @@ -686,6 +687,7 @@ set(viewer_HEADER_FILES chatbar_as_cmdline.h kcwlinterface.h floatermedialists.h + fscontactlist.h fscontactsfloater.h fsradarlistctrl.h fsareasearch.h diff --git a/indra/newview/fscontactlist.cpp b/indra/newview/fscontactlist.cpp new file mode 100644 index 0000000000..1104315e78 --- /dev/null +++ b/indra/newview/fscontactlist.cpp @@ -0,0 +1,1316 @@ +/** +* @file fscontactlist.cpp +* @brief Firestorm contacts list control +* +* $LicenseInfo:firstyear=2011&license=fsviewerlgpl$ +* Phoenix Firestorm Viewer Source Code +* Copyright (C) 2011, The Phoenix Viewer Project, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* The Phoenix Viewer Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "fscontactlist.h" + +// libs +#include "llavatarname.h" +#include "llnotificationsutil.h" +#include "lleventtimer.h" + +// llui +#include "llaccordionctrl.h" +#include "llaccordionctrltab.h" +// #include "llfiltereditor.h" +// #include "llfloaterreg.h" +#include "llmenubutton.h" +// #include "llmenugl.h" +//#include "lltextutil.h" +#include "lltoggleablemenu.h" +#include "lluictrlfactory.h" + +// newview +// #include "llavatarpropertiesprocessor.h" +#include "llagent.h" +#include "llavataractions.h" +#include "llavatarlist.h" +#include "llavatarlistitem.h" +#include "llcallingcard.h" // for LLAvatarTracker +#include "llfriendcard.h" +#include "llinventoryobserver.h" +#include "llfloateravatarpicker.h" +#include "llfriendcard.h" +#include "llinventoryobserver.h" +#include "llpanelpeoplemenus.h" +#include "llstartup.h" +#include "llviewercontrol.h" // for gSavedSettings +#include "llviewermenu.h" // for gMenuHolder +#include "llvoiceclient.h" +// #include "llworld.h" +// [RLVa:KB] - Checked: 2010-06-04 (RLVa-1.2.2a) +#include "rlvhandler.h" +// [/RLVa:KB] +// #include +// #include +// using namespace std; +// using namespace boost; + +// common +//#include "lltrans.h" +//#include "llcommonutils.h" + + +#define FRIEND_LIST_UPDATE_TIMEOUT 0.5 + + +static LLRegisterPanelClassWrapper t_contact_list("contact_list"); +static LLRegisterPanelClassWrapper t_contact_set("contact_set"); + +//============================================================================= + +/** Compares avatar items by online status, then by name */ +class LLAvatarItemStatusComparator : public LLAvatarItemNameComparator +{ +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) + { + return LLAvatarItemNameComparator::doCompare(item1, item2); + } + + return online1 > online2; + } +}; + +/** Compares avatar items by online status, then by name */ +class LLAvatarItemUserNameComparator : public LLAvatarItemComparator +{ +public: + LLAvatarItemUserNameComparator() {}; + +protected: + /** + * @return true if item1 < item2, false otherwise + */ + virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const + { + std::string name1 = item1->getUserName(); + std::string name2 = item2->getUserName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; + } +}; + +/** Compares avatar items by online status, then by name */ +class LLAvatarItemStatusUserNameComparator : public LLAvatarItemUserNameComparator +{ +public: + LLAvatarItemStatusUserNameComparator() {}; + +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) + { + return LLAvatarItemUserNameComparator::doCompare(item1, item2); + } + + return online1 > online2; + } +}; + +static const LLAvatarItemStatusComparator STATUS_COMPARATOR; +static const LLAvatarItemUserNameComparator USERNAME_COMPARATOR; +static const LLAvatarItemStatusUserNameComparator STATUSUSERNAME_COMPARATOR; + +//============================================================================= + +/** + * Updates given list either on regular basis or on external events (up to implementation). + */ +class FSContactList::Updater +{ +public: + typedef boost::function callback_t; + Updater(callback_t cb) + : mCallback(cb) + { + } + + virtual ~Updater() + { + } + + /** + * Activate/deactivate updater. + * + * This may start/stop regular updates. + */ + virtual void setActive(bool) {} + +protected: + void update() + { + mCallback(); + } + + callback_t mCallback; +}; + +/** + * Update buttons on changes in our friend relations (STORM-557). + */ +class LLButtonsUpdater : public FSContactList::Updater, public LLFriendObserver +{ +public: + LLButtonsUpdater(callback_t cb) + : FSContactList::Updater(cb) + { + LLAvatarTracker::instance().addObserver(this); + } + + ~LLButtonsUpdater() + { + LLAvatarTracker::instance().removeObserver(this); + } + + /*virtual*/ void changed(U32 mask) + { + (void) mask; + update(); + } +}; + +//============================================================================= + +class LLAvatarListUpdater : public FSContactList::Updater, public LLEventTimer +{ +public: + LLAvatarListUpdater(callback_t cb, F32 period) + : LLEventTimer(period), + FSContactList::Updater(cb) + { + 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 changed(U32 mask) + { + 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 (!mIsActive) return FALSE; + + if (mMask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE)) + { + update(); + } + + // 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; + + static bool synchronize_friends_folders = true; + if (synchronize_friends_folders) + { + // Checks whether "Friends" and "Friends/All" folders exist in "Calling Cards" folder, + // fetches their contents if needed and synchronizes it with buddies list. + // If the folders are not found they are created. + LLFriendCardsManager::instance().syncFriendCardsFolders(); + synchronize_friends_folders = false; + } + + // *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) + if ((mask & LLInventoryObserver::CALLING_CARD || + mask & LLFolderType::FT_CALLINGCARD) + && (mask & LLInventoryObserver::ADD || + mask & LLInventoryObserver::REMOVE || + mask & LLInventoryObserver::STRUCTURE)) + { + lldebugs << "Calling card added: count: " << gInventory.getChangedIDs().size() + << ", first Inventory ID: "<< (*gInventory.getChangedIDs().begin()) + << llendl; + + bool friendFound = false; + std::set changedIDs = gInventory.getChangedIDs(); + for (std::set::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; + }; +}; + + +//============================================================================= + +const LLAccordionCtrlTab::Params& get_accordion_tab_params() +{ + static LLAccordionCtrlTab::Params tab_params; + static bool initialized = false; + if (!initialized) + { + initialized = true; + + LLXMLNodePtr xmlNode; + if (LLUICtrlFactory::getLayeredXMLNode("contact_set_accordion_tab.xml", xmlNode)) + { + LLXUIParser parser; + parser.readXUI(xmlNode, tab_params, "contact_set_accordion_tab.xml"); + } + else + { + llwarns << "Failed to read xml of Outfit's Accordion Tab from contact_set_accordion_tab.xml" << llendl; + } + } + + return tab_params; +} + +//============================================================================= + +FSContactList::FSContactList(const FSContactList::Params& p /*= LLPanel::Params()*/) +: LLPanel(p) +{ + + if (LLStartUp::getStartupState() > STATE_LOGIN_WAIT) + { + mFriendListUpdater = new LLFriendListUpdater(boost::bind(&FSContactList::updateFriendList, this)); + } + mButtonsUpdater = new LLButtonsUpdater(boost::bind(&FSContactList::updateButtons, this)); +} + +FSContactList::~FSContactList() +{ + delete mButtonsUpdater; + if (LLStartUp::getStartupState() > STATE_LOGIN_WAIT) + { + delete mFriendListUpdater; + } + mContactSets.clear(); + + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver(this); + } +} + +BOOL FSContactList::postBuild() +{ + mAccordion = getChild("accordion_list"); + + if (LLStartUp::getStartupState() > STATE_LOGIN_WAIT) + { + setVisibleCallback(boost::bind(&Updater::setActive, mFriendListUpdater, _2)); + } + + //HACK* this should be checked against the value in llfriendcard + mAllFriendList = addContactSet(LLUUID::null, "All"); + + setSortOrder((ESortOrder)gSavedSettings.getU32("FSContactsSortOrder"), false); + + // addContactSet("Test"); + LLFriendCardsManager::instance().setFriendsDirLoadedCallback(boost::bind(&FSContactList::onFriendsLoaded, this)); + + // Create menus. + mViewOptionMenuButton = getChild("sort_options_gear_btn"); + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add("ContactList.ViewOption.Action", boost::bind(&FSContactList::onViewOptionChecked, this, _2)); + enable_registrar.add("ContactList.ViewOption.Check", boost::bind(&FSContactList::isViewOptionChecked, this, _2)); + + registrar.add("ContactList.CreateSet.Action", boost::bind(&FSContactList::onCreateContactSet, this)); + registrar.add("ContactList.CreateSet.Enabled", boost::bind(&FSContactList::isFriendsLoaded, this)); + + + mViewOptionMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_contact_list_sort_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mViewOptionMenuButton->setMenu(mViewOptionMenu, LLMenuButton::MP_BOTTOM_LEFT); + + + buttonSetAction("add_btn", boost::bind(&FSContactList::onAddFriendWizButtonClicked, this)); + buttonSetAction("del_btn", boost::bind(&FSContactList::onDeleteFriendButtonClicked, this)); + buttonSetAction("GlobalOnlineStatusToggle", boost::bind(&FSContactList::onGlobalVisToggleButtonClicked, this)); + buttonSetAction("view_profile_btn", boost::bind(&FSContactList::onViewProfileButtonClicked, this)); + buttonSetAction("im_btn", boost::bind(&FSContactList::onImButtonClicked, this)); + buttonSetAction("call_btn", boost::bind(&FSContactList::onCallButtonClicked, this)); + buttonSetAction("teleport_btn", boost::bind(&FSContactList::onTeleportButtonClicked, this)); + buttonSetAction("share_btn", boost::bind(&FSContactList::onShareButtonClicked, this)); + + // call this method in case some list is empty and buttons can be in inconsistent state + updateButtons(); + + return TRUE; +} + +void FSContactList::onFriendsLoaded() +{ + mIsFriendsLoaded = true; +} + +void FSContactList::onCreateContactSet() +{ + LLNotificationsUtil::add("CreateContactSet", LLSD(), LLSD(), boost::bind(onContactSetCreate, _1, _2)); +} + +// static +void FSContactList::onContactSetCreate(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; // canceled + + std::string set_name = response["new_name"].asString(); + LLStringUtil::trim(set_name); + if (!set_name.empty()) + { + LLFriendCardsManager::instance().createFriendSubfolder(set_name); + } +} + +FSContactSet* FSContactList::addContactSet(const LLUUID& sub_folder_ID, const std::string& set_name) +{ + LLAccordionCtrlTab::Params tab_params(get_accordion_tab_params()); + LLAccordionCtrlTab* tab = LLUICtrlFactory::create(tab_params); + if (!tab) return NULL; + + + // FSContactSet* contact_list_set = LLUICtrlFactory::create(tab_params.contact_list_set); + FSContactSet* contact_list_set = new FSContactSet(); + + //HACK* this should be checked against the value in llfriendcard + contact_list_set->setHandleFriendsAll(set_name == "All"); + + contact_list_set->setTab(tab); + contact_list_set->setName(set_name); + + tab->addChild(contact_list_set); + + mContactSets[sub_folder_ID] = contact_list_set; + + LLAvatarList* avatar_list = contact_list_set->getList(); + + + avatar_list->setItemDoubleClickCallback(boost::bind(&FSContactList::onAvatarListDoubleClicked, this, _1)); + avatar_list->setCommitCallback(boost::bind(&FSContactList::onAvatarListCommitted, this, avatar_list)); + avatar_list->setReturnCallback(boost::bind(&FSContactList::onImButtonClicked, this)); + // avatar_list->setRefreshCompleteCallback(boost::bind(&FSContactList::onFriendListRefreshComplete, this, _1, _2)); + + + // *TODO: LLUICtrlFactory::defaultBuilder does not use "display_children" from xml. Should be investigated. + tab->setDisplayChildren(true); + + mAccordion->addCollapsibleCtrl(tab); + + // tab->setDropDownStateChangedCallback( + // boost::bind(&FSContactList::onFriendsAccordionExpandedCollapsed, this, _1, _2, avatar_list)); + + return contact_list_set; +} + +// Unused for now +#if 0 +void FSContactList::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(); + } +} +#endif + +FSContactSet* FSContactList::getConstactSet(const LLUUID& sub_folder_ID) +{ + return mContactSets[sub_folder_ID]; +} + +FSContactSet* FSContactList::getOrCreateConstactSet(const LLUUID& sub_folder_ID) +{ + LLViewerInventoryCategory* cat = gInventory.getCategory(sub_folder_ID); + std::string cat_name = cat ? cat->getName() : "unknown"; + + contact_set_map_t::iterator it = mContactSets.find(sub_folder_ID); + if(it != mContactSets.end()) + { + FSContactSet* contact_list_set = it->second; + contact_list_set->setName(cat_name); + + return contact_list_set; + } + + //is new set, make list control for it + llinfos << "Making new contact set control for: " << cat_name << llendl; + return addContactSet(sub_folder_ID, cat_name); +} + +void FSContactList::updateFriendList() +{ + + LLFriendCardsManager::folderid_buddies_map_t listMap; + LLFriendCardsManager::instance().collectFriendsLists(listMap); + + if (listMap.size() > 0) + { + lldebugs << "Friends Cards were found, count: " << listMap.begin()->second.size() << llendl; + + LLFriendCardsManager::folderid_buddies_map_t::iterator dir_it; + for (dir_it = listMap.begin(); dir_it != listMap.end(); ++dir_it) + { + FSContactSet* contact_list_set = getOrCreateConstactSet(dir_it->first); + contact_list_set->update(dir_it->second); + } + } + else + { + llinfos << "Friends Cards were not found" << llendl; + } + + // Friends/All is processed from LLAvatarTracker instead for reliablity and speed reasons + // Calling Cards takes time to fetch and it could take several minutes from login for to load + mAllFriendList->update(); + + updateButtons(); +} + +void FSContactList::buttonSetEnabled(const std::string& btn_name, bool enabled) +{ + // To make sure we're referencing the right widget (a child of the button bar). + LLButton* button = getChild("button_bar")->getChild(btn_name); + button->setEnabled(enabled); +} + +void FSContactList::buttonSetAction(const std::string& btn_name, const commit_signal_t::slot_type& cb) +{ + // To make sure we're referencing the right widget (a child of the button bar). + LLButton* button = getChild("button_bar")->getChild(btn_name); + button->setClickedCallback(cb); +} + +void FSContactList::updateButtons() +{ + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + + bool item_selected = (selected_uuids.size() == 1); + bool multiple_selected = (selected_uuids.size() >= 1); + + 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)); +} + +LLUUID FSContactList::getCurrentItemID() const +{ + LLUUID cur_online_friend; + + //for(av_list_t::const_iterator it = mContactSets.begin(), end_it = mContactSets.end(); it != end_it; ++it) + for (contact_set_map_t::const_iterator it = mContactSets.begin(); it != mContactSets.end(); ++it) + { + LLAvatarList* avatar_list = it->second->getList(); + if ((cur_online_friend = avatar_list->getSelectedUUID()).notNull()) + break; + } + + return cur_online_friend; +} + +void FSContactList::getCurrentItemIDs(uuid_vec_t& selected_uuids) const +{ + //for(av_list_t::const_iterator it = mContactSets.begin(), end_it = mContactSets.end(); it != end_it; ++it) + for (contact_set_map_t::const_iterator it = mContactSets.begin(); it != mContactSets.end(); ++it) + { + LLAvatarList* avatar_list = it->second->getList(); + avatar_list->getSelectedUUIDs(selected_uuids); + } +} + +void FSContactList::onViewOptionChecked(LLSD::String param) +{ + if ("sortby_name" == param) + { + setSortOrder(E_SORT_BY_NAME); + } + else if ("sortby_username" == param) + { + setSortOrder(E_SORT_BY_USERNAME); + } + else if ("sortby_statusname" == param) + { + setSortOrder(E_SORT_BY_STATUS); + } + else if ("sortby_statususername" == param) + { + setSortOrder(E_SORT_BY_STATUSUSERNAME); + } + else if ("view_icons" == param) + { + //for(av_list_t::iterator it = mContactSets.begin(), end_it = mContactSets.end(); it != end_it; ++it) + for (contact_set_map_t::iterator it = mContactSets.begin(); it != mContactSets.end(); ++it) + { + LLAvatarList* avatar_list = it->second->getList(); + avatar_list->toggleIcons(); + } + } + else if ("view_permissions" == param) + { + bool show_permissions = !gSavedSettings.getBOOL("FriendsListShowPermissions"); + gSavedSettings.setBOOL("FriendsListShowPermissions", show_permissions); + + //for(av_list_t::iterator it = mContactSets.begin(), end_it = mContactSets.end(); it != end_it; ++it) + for (contact_set_map_t::iterator it = mContactSets.begin(); it != mContactSets.end(); ++it) + { + LLAvatarList* avatar_list = it->second->getList(); + avatar_list->showPermissions(show_permissions); + } + } + +} + +bool FSContactList::isViewOptionChecked(LLSD::String param) +{ + if ("sortby_name" == param) + { + return gSavedSettings.getU32("FSContactsSortOrder") == E_SORT_BY_NAME; + } + else if ("sortby_username" == param) + { + return gSavedSettings.getU32("FSContactsSortOrder") == E_SORT_BY_USERNAME; + } + else if ("sortby_statusname" == param) + { + return gSavedSettings.getU32("FSContactsSortOrder") == E_SORT_BY_STATUS; + } + else if ("sortby_statususername" == param) + { + return gSavedSettings.getU32("FSContactsSortOrder") == E_SORT_BY_STATUSUSERNAME; + } + + return false; +} + +void FSContactList::setSortOrder(ESortOrder order, bool save) +{ + //for(av_list_t::iterator it = mContactSets.begin(), end_it = mContactSets.end(); it != end_it; ++it) + for (contact_set_map_t::iterator it = mContactSets.begin(); it != mContactSets.end(); ++it) + { + FSContactSet* contact_set = it->second; + contact_set->setSortOrder(order); + } + + if (save) + { + gSavedSettings.setU32("FSContactsSortOrder", order); + } +} + +void FSContactList::showPermissions(bool visible) +{ + //for(av_list_t::iterator it = mContactSets.begin(), end_it = mContactSets.end(); it != end_it; ++it) + for (contact_set_map_t::iterator it = mContactSets.begin(); it != mContactSets.end(); ++it) + { + LLAvatarList* avatar_list = it->second->getList(); + avatar_list->showPermissions(visible); + } +} + +void FSContactList::forceUpdate() +{ + updateFriendList(); +} + +// Unused for now +#if 0 +void FSContactList::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_upper; + + //store accordion tabs state before any manipulation with accordion tabs + if(!mFilterSubString.empty()) + { + notifyChildren(LLSD().with("action","store_state")); + } + + + // Apply new filter. + // AO: currently broken + // 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")); + } +} +#endif + +void FSContactList::onAvatarListDoubleClicked(LLUICtrl* ctrl) +{ + LLAvatarListItem* item = dynamic_cast(ctrl); + if(!item) + { + return; + } + + LLUUID clicked_id = item->getAvatarId(); + LLAvatarActions::startIM(clicked_id); +} + +void FSContactList::onAvatarListCommitted(LLAvatarList* list) +{ + //for(av_list_t::iterator it = mContactSets.begin(), end_it = mContactSets.end(); it != end_it; ++it) + for (contact_set_map_t::iterator it = mContactSets.begin(); it != mContactSets.end(); ++it) + { + LLAvatarList* avatar_list = it->second->getList(); + if (avatar_list != list) + avatar_list->resetSelection(true); + } + + updateButtons(); +} + +void FSContactList::onViewProfileButtonClicked() +{ + LLUUID id = getCurrentItemID(); + LLAvatarActions::showProfile(id); +} + +bool FSContactList::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 FSContactList::onAddFriendWizButtonClicked() +{ + // Show add friend wizard. + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&FSContactList::onAvatarPicked, _1, _2), FALSE, TRUE); + // Need to disable 'ok' button when friend occurs in selection + if (picker) picker->setOkBtnEnableCb(boost::bind(&FSContactList::isItemsFreeOfFriends, this, _1)); + LLFloater* root_floater = gFloaterView->getParentFloater(this); + if (root_floater) + { + root_floater->addDependentFloater(picker); + } +} + +// static +void FSContactList::onAvatarPicked(const uuid_vec_t& ids, const std::vector names) +{ + if (!names.empty() && !ids.empty()) + LLAvatarActions::requestFriendshipDialog(ids[0], names[0].getCompleteName()); +} + +void FSContactList::onGlobalVisToggleButtonClicked() +// Iterate through friends lists, toggling status permission on or off +{ + bool vis = getChild("GlobalOnlineStatusToggle")->getValue().asBoolean(); + gSavedSettings.setBOOL("GlobalOnlineStatusToggle", vis); + + const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + LLAvatarTracker::buddy_map_t all_buddies; + av_tracker.copyBuddyList(all_buddies); + 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; + const LLRelationship* relation = LLAvatarTracker::instance().getBuddyInfo(buddy_id); + if (relation == NULL) + { + // Lets have a warning log message instead of having a crash. EXT-4947. + llwarns << "Trying to modify rights for non-friend avatar. Skipped." << llendl; + return; + } + + S32 cur_rights = relation->getRightsGrantedTo(); + S32 new_rights = 0; + if (vis) + new_rights = LLRelationship::GRANT_ONLINE_STATUS + (cur_rights & LLRelationship::GRANT_MAP_LOCATION) + (cur_rights & LLRelationship::GRANT_MODIFY_OBJECTS); + else + new_rights = (cur_rights & LLRelationship::GRANT_MAP_LOCATION) + (cur_rights & LLRelationship::GRANT_MODIFY_OBJECTS); + + LLAvatarPropertiesProcessor::getInstance()->sendFriendRights(buddy_id,new_rights); + } + + showPermissions(true); + + LLSD args; + args["MESSAGE"] = + llformat("Due to server load, mass toggling visibility can take a while to become effective. Please be patient." ); + LLNotificationsUtil::add("GenericAlert", args); +} + +void FSContactList::onDeleteFriendButtonClicked() +{ + 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 FSContactList::onImButtonClicked() +{ + 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 ) + { + // for multiple selection start up friends conference + LLAvatarActions::startConference(selected_uuids); + } +} + +void FSContactList::onCallButtonClicked() +{ + 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 FSContactList::onTeleportButtonClicked() +{ + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + LLAvatarActions::offerTeleport(selected_uuids); +} + +void FSContactList::onShareButtonClicked() +{ + LLAvatarActions::share(getCurrentItemID()); +} + +// Unused for now +#if 0 +void FSContactList::showAccordion(const std::string name, bool show) +{ + if(name.empty()) + { + llwarns << "No name provided" << llendl; + return; + } + + LLAccordionCtrlTab* tab = getChild(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 FSContactList::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("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 FSContactList::onFriendListRefreshComplete(LLUICtrl*ctrl, const LLSD& param) +{ + if(ctrl == mOnlineFriendList) + { + showAccordion("tab_online", param.asInteger()); + } + else if(ctrl == mAllFriendList) + { + showAccordion("tab_all", param.asInteger()); + } +} + +void FSContactList::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 FSContactList::setAccordionCollapsedByUser(const std::string& name, bool collapsed) +{ + setAccordionCollapsedByUser(getChild(name), collapsed); +} + +bool FSContactList::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 FSContactList::isAccordionCollapsedByUser(const std::string& name) +{ + return isAccordionCollapsedByUser(getChild(name)); +} +#endif + +// virtual +void FSContactList::onChange(EStatusType status, const std::string &channelURI, bool proximal) +{ + if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) + { + return; + } + + updateButtons(); +} + + +FSContactSet::FSContactSet(/*const Params& p /*= LLPanel::Params()*/) +: LLPanel() +, mAvatarList(NULL) +, mViewOptionMenuButton(NULL) +, mViewOptionMenu(NULL) +, mInfoTextLabel(NULL) +, mShowOffline(true) +, mHandleFriendsAll(true) +{ + buildFromFile("panel_contact_list_set.xml"); +} + +BOOL FSContactSet::postBuild() +{ + mAvatarList = getChild("avatar_list"); + mAvatarList->setNoItemsCommentText(getString("no_friends")); + mAvatarList->setShowIcons("FriendsListShowIcons"); + mAvatarList->showPermissions(gSavedSettings.getBOOL("FriendsListShowPermissions")); + mAvatarList->setContextMenu(&LLPanelPeopleMenus::gNearbyMenu); + + setSortOrder((ESortOrder)gSavedSettings.getU32("FSContactsSortOrder")); + + // initilize info text, for now show offline is default on start + mInfoTextLabel = getChild("list_info_text"); + mInfoTextLabel->setValue(getString("showing_offline")); + + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + registrar.add("ContactSet.ViewOption.Action", boost::bind(&FSContactSet::onViewOptionChecked, this, _2)); + + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + enable_registrar.add("ContactSet.ViewOption.Check", boost::bind(&FSContactSet::isViewOptionChecked, this, _2)); + enable_registrar.add("ContactSet.ViewOption.Enabled", boost::bind(&FSContactSet::isViewOptionEnabled, this, _2)); + + mViewOptionMenuButton = getChild("options_gear_btn"); + mViewOptionMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_contact_set_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mViewOptionMenuButton->setMenu(mViewOptionMenu, LLMenuButton::MP_BOTTOM_LEFT); + + + + return TRUE; +} + +FSContactSet::~FSContactSet() +{ +} + +void FSContactSet::setTab(LLAccordionCtrlTab* tab) +{ + mTab = tab; + setShape(tab->getLocalRect()); +} + +void FSContactSet::setName(std::string name) +{ + mName = name; + mTab->setName(name); + mTab->setTitle(name); +} + +void FSContactSet::update( const uuid_vec_t& buddies_uuids ) +{ + if (mHandleFriendsAll) + return; //shouldn't happen, but just incase + + + lldebugs << "Friends added to the list: " << buddies_uuids.size() << llendl; + + uuid_vec_t& all_friendsp = mAvatarList->getIDs(); + all_friendsp.clear(); + + mFriendsIDs = all_friendsp; + + if (mShowOffline) + { + all_friendsp = buddies_uuids; + } + else + { + const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + for ( uuid_vec_t::const_iterator + id = buddies_uuids.begin(), + id_end = buddies_uuids.end(); + id != id_end; ++id ) + { + if (av_tracker.isBuddyOnline(*id)) + all_friendsp.push_back(*id); + } + } + + mAvatarList->setDirty(true, !mAvatarList->filterHasMatches());// do force update if list do NOT have items + mAvatarList->sort(); + +} + +void FSContactSet::update() +{ + if (!mHandleFriendsAll) + return; //shouldn't happen, but just incase + + // Friends/All is processed from LLAvatarTracker instead for reliablity and speed reasons + // Calling Cards takes time to fetch and it could take several minutes from login for to load + + // get all buddies we know about + const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + LLAvatarTracker::buddy_map_t all_buddies; + av_tracker.copyBuddyList(all_buddies); + // save them to the avatar_list vector + uuid_vec_t& all_friendsp = mAvatarList->getIDs(); + all_friendsp.clear(); + + if (mShowOffline) + { + uuid_vec_t buddies_uuids; + LLAvatarTracker::buddy_map_t::const_iterator buddies_iter; + + // Fill the avatar list with friends UUIDs + for (buddies_iter = all_buddies.begin(); buddies_iter != all_buddies.end(); ++buddies_iter) + { + buddies_uuids.push_back(buddies_iter->first); + } + + if (buddies_uuids.size() > 0) + { + lldebugs << "Friends added to the list: " << buddies_uuids.size() << llendl; + all_friendsp = buddies_uuids; + } + else + { + llwarns << "Friends Cards failed to load" << llendl; + lldebugs << "No friends found" << llendl; + } + } + else + { + 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)) + all_friendsp.push_back(buddy_id); + } + } + + mAvatarList->setDirty(true, !mAvatarList->filterHasMatches());// do force update if list do NOT have items + mAvatarList->sort(); +} + +void FSContactSet::forceUpdate() +{ + if (mHandleFriendsAll) + { + update(); + } + else + { + update(mFriendsIDs); + } +} + + +void FSContactSet::onViewOptionChecked(LLSD::String param) +{ + if ("show_offline" == param) + { + mShowOffline = !mShowOffline; + if (mShowOffline) + mInfoTextLabel->setValue(getString("showing_offline")); + else + mInfoTextLabel->setValue(getString("hiding_offline")); + + forceUpdate(); + } + +} + +bool FSContactSet::isViewOptionChecked(LLSD::String param) +{ + if ("show_offline" == param) + { + return mShowOffline; + } + + return false; +} + +bool FSContactSet::isViewOptionEnabled(LLSD::String param) +{ + //does nothing for now + return true; +} + +void FSContactSet::setSortOrder(ESortOrder order) +{ + switch (order) + { + case E_SORT_BY_NAME: + mAvatarList->sortByName(); + break; + case E_SORT_BY_USERNAME: + mAvatarList->setComparator(&USERNAME_COMPARATOR); + mAvatarList->sort(); + break; + case E_SORT_BY_STATUS: + mAvatarList->setComparator(&STATUS_COMPARATOR); + mAvatarList->sort(); + break; + case E_SORT_BY_STATUSUSERNAME: + mAvatarList->setComparator(&STATUSUSERNAME_COMPARATOR); + mAvatarList->sort(); + break; + default: + llwarns << "Unrecognized people sort order for " << mAvatarList->getName() << llendl; + return; + } +} diff --git a/indra/newview/fscontactlist.h b/indra/newview/fscontactlist.h new file mode 100644 index 0000000000..c6b7eb15d6 --- /dev/null +++ b/indra/newview/fscontactlist.h @@ -0,0 +1,222 @@ +/** +* @file fscontactlist.h +* @brief Firestorm contacts list control +* +* $LicenseInfo:firstyear=2011&license=fsviewerlgpl$ +* Phoenix Firestorm Viewer Source Code +* Copyright (C) 2011, The Phoenix Viewer Project, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* The Phoenix Viewer Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA +* $/LicenseInfo$ +*/ + +#ifndef FS_FSCONTACTLIST_H +#define FS_FSCONTACTLIST_H + +#include "llpanel.h" + +#include "llcallingcard.h" // for avatar tracker +#include "llvoiceclient.h" +#include "llavatarnamecache.h" + +// class FSContactListSet; +class LLAvatarList; +class LLAvatarName; +class LLAccordionCtrl; +class LLAccordionCtrlTab; +// class LLFilterEditor; +class LLMenuButton; +class LLToggleableMenu; +class LLMenuGL; +class LLTextBox; + + +typedef enum e_sort_oder +{ + E_SORT_BY_NAME = 0, + E_SORT_BY_USERNAME = 1, + E_SORT_BY_STATUS = 2, + E_SORT_BY_STATUSUSERNAME = 3 +} ESortOrder; + + + +/// convenience class for holding keyframes mapped to sliders +class FSContactSet : public LLPanel +{ + LOG_CLASS(FSContactSet); +public: + // FSContactSet(const Params& p = LLPanel::Params()); + FSContactSet(); + virtual ~FSContactSet(); + + /*virtual*/ BOOL postBuild(); + + void update( const uuid_vec_t& buddies_uuids ); + void update(); + + LLAvatarList* getList() { return mAvatarList; } + + void setTab(LLAccordionCtrlTab* tab); + LLAccordionCtrlTab* getTab() { return mTab; } + + void setName(std::string name); + + void setHandleFriendsAll( bool handel_all ) { mHandleFriendsAll = handel_all; } + + void setSortOrder( ESortOrder order ); + +protected: + friend class LLUICtrlFactory; + + + void onViewOptionChecked(LLSD::String param); + bool isViewOptionChecked(LLSD::String param); + bool isViewOptionEnabled(LLSD::String param); + void forceUpdate(); + + LLAccordionCtrlTab* mTab; + LLAvatarList* mAvatarList; + LLMenuButton* mViewOptionMenuButton; + LLToggleableMenu* mViewOptionMenu; + LLTextBox* mInfoTextLabel; + bool mShowOffline; + bool mHandleFriendsAll; + LLUUID mFriendSubFolderID; + std::string mName; + + // this contains all friends for this set + uuid_vec_t mFriendsIDs; +}; + + +class FSContactList : public LLPanel, public LLVoiceClientStatusObserver +{ + LOG_CLASS(FSContactList); +public: + + FSContactList(const Params& p = LLPanel::Params()); + virtual ~FSContactList(); + + /*virtual*/ BOOL postBuild(); + + bool isFriendsLoaded() { return mIsFriendsLoaded; } + + +private: + + + + typedef std::map contact_set_map_t; + contact_set_map_t mContactSets; + + void onCreateContactSet(); + static void onContactSetCreate(const LLSD& notification, const LLSD& response); + + FSContactSet* addContactSet(const LLUUID& sub_folder_ID, const std::string& set_name); + FSContactSet* getConstactSet(const LLUUID& sub_folder_ID); + FSContactSet* getOrCreateConstactSet(const LLUUID& sub_folder_ID); + + FSContactSet* mAllFriendList; + +public: + + // Implements LLVoiceClientStatusObserver::onChange() to enable call buttons + // when voice is available + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + + // internals + class Updater; + +private: + + + + // methods indirectly called by the updaters + // void updateFriendListHelpText(); + void updateFriendList(); + void forceUpdate(); + + + + bool isItemsFreeOfFriends(const uuid_vec_t& uuids); + + void updateButtons(); + LLUUID getCurrentItemID() const; + void getCurrentItemIDs(uuid_vec_t& selected_uuids) const; + void buttonSetEnabled(const std::string& btn_name, bool enabled); + void buttonSetAction(const std::string& btn_name, const commit_signal_t::slot_type& cb); + + + void onViewOptionChecked(LLSD::String param); + bool isViewOptionChecked(LLSD::String param); + // bool isViewOptionEnabled(LLSD::String param); + void setSortOrder(ESortOrder order, bool save = true); + +private: + // UI callbacks + // void onFilterEdit(const std::string& search_string); + void onViewProfileButtonClicked(); + void onAddFriendWizButtonClicked(); + void onGlobalVisToggleButtonClicked(); + void onDeleteFriendButtonClicked(); + void onImButtonClicked(); + void onCallButtonClicked(); + void onTeleportButtonClicked(); + void onShareButtonClicked(); + void onAvatarListDoubleClicked(LLUICtrl* ctrl); + void onAvatarListCommitted(LLAvatarList* list); + + void showPermissions(bool visible); + + // misc callbacks + static void onAvatarPicked(const uuid_vec_t& ids, const std::vector names); + + // void onFriendsAccordionExpandedCollapsed(LLUICtrl* ctrl, const LLSD& param, LLAvatarList* avatar_list); + + // void showAccordion(const std::string name, bool show); + + // void showFriendsAccordionsIfNeeded(); + + // void onFriendListRefreshComplete(LLUICtrl*ctrl, const LLSD& param); + + // void setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed); + // void setAccordionCollapsedByUser(const std::string& name, bool collapsed); + // bool isAccordionCollapsedByUser(LLUICtrl* acc_tab); + // bool isAccordionCollapsedByUser(const std::string& name); + + void onFriendsLoaded(); + + LLHandle mFriendsViewSortMenuHandle; + + Updater* mFriendListUpdater; + Updater* mButtonsUpdater; + + LLMenuButton* mFriendsGearButton; + + std::string mFilterSubString; + std::string mFilterSubStringOrig; + + LLMenuButton* mViewOptionMenuButton; + LLToggleableMenu* mViewOptionMenu; + LLAccordionCtrl* mAccordion; + + bool mIsFriendsLoaded; +}; + + +#endif // FS_FSCONTACTLIST_H diff --git a/indra/newview/fscontactsfloater.cpp b/indra/newview/fscontactsfloater.cpp index 2753d92b4b..8deff1c8ec 100644 --- a/indra/newview/fscontactsfloater.cpp +++ b/indra/newview/fscontactsfloater.cpp @@ -1,6 +1,6 @@ /** - * @file - * @brief + * @file fscontactsfloater.cpp + * @brief Firestorm contacts floater * * $LicenseInfo:firstyear=2011&license=fsviewerlgpl$ * Phoenix Firestorm Viewer Source Code @@ -36,6 +36,11 @@ #include "llfloater.h" #include "lltabcontainer.h" +// #include "llaccordionctrl.h" +// #include "llaccordionctrltab.h" +// #include "llflatlistview.h" +#include "fscontactlist.h" + #include "llavataractions.h" #include "llavatarlist.h" #include "llavatarlistitem.h" @@ -81,6 +86,7 @@ protected: FSFloaterContacts* mFloater; }; + // // FSFloaterContacts // @@ -90,6 +96,8 @@ FSFloaterContacts::FSFloaterContacts(const LLSD& seed) mTabContainer(NULL), mObserver(NULL), mFriendsList(NULL), + mContactsList(NULL), + // mFriendsListAccordion(NULL), mGroupList(NULL), mAllowRightsChange(TRUE), mNumRightsChanged(0), @@ -113,6 +121,9 @@ BOOL FSFloaterContacts::postBuild() { mTabContainer = getChild("friends_and_groups"); mFriendsTab = getChild(FRIENDS_TAB_NAME); + + //KC - incomplete + mContactsList = mFriendsTab->findChild("contacts_lists_panel"); mFriendsList = mFriendsTab->getChild("friend_list"); mFriendsList->setMaxSelectable(MAX_FRIEND_SELECT); diff --git a/indra/newview/fscontactsfloater.h b/indra/newview/fscontactsfloater.h index 126640791c..9f780dcfd7 100644 --- a/indra/newview/fscontactsfloater.h +++ b/indra/newview/fscontactsfloater.h @@ -1,6 +1,6 @@ /** * @file fscontactsfloater.h - * @brief + * @brief Firestorm contacts floater * * $LicenseInfo:firstyear=2011&license=fsviewerlgpl$ * Phoenix Firestorm Viewer Source Code @@ -44,6 +44,7 @@ class LLGroupList; class LLRelationship; class LLPanel; class LLTabContainer; +class FSContactList; class FSFloaterContacts : public LLFloater { @@ -131,6 +132,9 @@ private: void updateButtons(); LLTabContainer* mTabContainer; + + FSContactList* mContactsList; + // FSContactList* mFriendsListAccordion; LLFriendObserver* mObserver; BOOL mAllowRightsChange; diff --git a/indra/newview/llfriendcard.cpp b/indra/newview/llfriendcard.cpp index 11401d6c68..aa61ba33c6 100644 --- a/indra/newview/llfriendcard.cpp +++ b/indra/newview/llfriendcard.cpp @@ -125,6 +125,29 @@ bool LLFindAgentCallingCard::operator()(LLInventoryCategory* cat, LLInventoryIte return mIsAgentCallingCardFound; } +/** + * Class LLFindAgentCallingCard + * + * An inventory collector functor for checking that agent's own calling card + * exists within the Calling Cards category and its sub-folders. + */ +class LLFindFriendCallingCard : public LLInventoryCollectFunctor +{ +public: + LLFindFriendCallingCard() {} + virtual ~LLFindFriendCallingCard() {} + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); +}; + +bool LLFindFriendCallingCard::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + if (item && item->getType() == LLAssetType::AT_CALLINGCARD && item->getCreatorUUID() != gAgentID) + { + return true; + } + return false; +} + /** * Class for fetching initial friend cards data * @@ -322,6 +345,125 @@ void LLFriendCardsManager::syncFriendCardsFolders() } +const LLUUID& LLFriendCardsManager::findFriendSubfolderUUID(const std::string& nonLocalizedName) const +{ + LLUUID friendFolderUUID = findFriendFolderUUIDImpl(); + + return findChildFolderUUID(friendFolderUUID, nonLocalizedName); +} + +//Unused ast this time +#if 0 +void LLFriendCardsManager::getOrCreateFriendSubfolder(LLUUID& sub_folder_ID, const std::string& nonLocalizedName, callback_t cb) +{ + sub_folder_ID = findFriendSubfolderUUID(nonLocalizedName); + + if (!sub_folder_ID.notNull()) + { + LLUUID friends_folder_ID = findFriendFolderUUIDImpl(); + + if (!gInventory.isCategoryComplete(friends_folder_ID)) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(friends_folder_ID); + std::string cat_name = cat ? cat->getName() : "unknown"; + llwarns << "Failed to find \"" << cat_name << "\" category descendents in Category Tree." << llendl; + } + + sub_folder_ID = gInventory.createNewCategory(friends_folder_ID, + LLFolderType::FT_CALLINGCARD, nonLocalizedName); + } + + if (sub_folder_ID.notNull()) + { + fetchAndCheckFolderDescendents(sub_folder_ID, cb); + } +} +#endif + +void LLFriendCardsManager::createFriendSubfolder(const std::string& nonLocalizedName) +{ + LLUUID sub_folder_ID = findFriendSubfolderUUID(nonLocalizedName); + + if (!sub_folder_ID.notNull()) + { + LLUUID friends_folder_ID = findFriendFolderUUIDImpl(); + + if (!gInventory.isCategoryComplete(friends_folder_ID)) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(friends_folder_ID); + std::string cat_name = cat ? cat->getName() : "unknown"; + llwarns << "Failed to find \"" << cat_name << "\" category descendents in Category Tree." << llendl; + } + + sub_folder_ID = gInventory.createNewCategory(friends_folder_ID, + LLFolderType::FT_CALLINGCARD, nonLocalizedName); + } +} + +boost::signals2::connection LLFriendCardsManager::setFriendsDirLoadedCallback(const friends_loaded_signal_t::slot_type& cb) +{ + return mFriendsLoadedSignal.connect(cb); +} + +//Unused at this time +#if 0 +void LLFriendCardsManager::collectFriendSet(const LLUUID& sub_folder_ID, uuid_vec_t buddyUUIDs) const +{ + LLInventoryModel::item_array_t* items; + LLInventoryModel::cat_array_t* fakeCatsArg; + LLInventoryModel::item_array_t::const_iterator itBuddy; // to iterate Buddies in each List + + gInventory.getDirectDescendentsOf(sub_folder_ID, fakeCatsArg, items); + + for (itBuddy = items->begin(); itBuddy != items->end(); ++itBuddy) + { + buddyUUIDs.push_back((*itBuddy)->getCreatorUUID()); + } +} +#endif + +void LLFriendCardsManager::collectFriendsLists(folderid_buddies_map_t& folderBuddiesMap) const +{ + folderBuddiesMap.clear(); + + LLInventoryModel::cat_array_t* listFolders; + LLInventoryModel::item_array_t* fakeItems; + + // get folders in the Friend folder. Items should be NULL due to Cards should be in lists. + gInventory.getDirectDescendentsOf(findFriendFolderUUIDImpl(), listFolders, fakeItems); + + if (NULL == listFolders) + return; + + LLInventoryModel::cat_array_t::const_iterator itCats; // to iterate Friend Lists (categories) + LLInventoryModel::item_array_t::const_iterator itBuddy; // to iterate Buddies in each List + + for (itCats = listFolders->begin(); itCats != listFolders->end(); ++itCats) + { + + // Everything but Friends/All content will collected here + // Friends/All will be handled by LLAvatarTracker + if ((*itCats)->getUUID() == findFriendAllSubfolderUUIDImpl()) + continue; + + //NOTE* getDirectDescendentsOf is not used here as it was not returning all items most of the time. + LLInventoryModel::cat_array_t fakeCatsArg; + LLInventoryModel::item_array_t items; + LLFindFriendCallingCard collector; + gInventory.collectDescendentsIf((*itCats)->getUUID(), fakeCatsArg, items, LLInventoryModel::EXCLUDE_TRASH, collector); + + uuid_vec_t buddyUUIDs; + for (itBuddy = items.begin(); itBuddy != items.end(); ++itBuddy) + { + buddyUUIDs.push_back((*itBuddy)->getCreatorUUID()); + } + + folderBuddiesMap.insert(make_pair((*itCats)->getUUID(), buddyUUIDs)); + } +} + + + /************************************************************************/ /* Private Methods */ /************************************************************************/ @@ -513,6 +655,10 @@ void LLFriendCardsManager::syncFriendsFolder() const LLUUID& buddy_id = (*buddy_it).first; addFriendCardToInventory(buddy_id); } + + mFriendsLoadedSignal(); + // This is a single-shot signal. Forget callbacks to save resources. + mFriendsLoadedSignal.disconnect_all_slots(); } class CreateFriendCardCallback : public LLInventoryCallback diff --git a/indra/newview/llfriendcard.h b/indra/newview/llfriendcard.h index 48a9f70079..8347ea40ee 100644 --- a/indra/newview/llfriendcard.h +++ b/indra/newview/llfriendcard.h @@ -27,6 +27,7 @@ #ifndef LL_LLFRIENDCARD_H #define LL_LLFRIENDCARD_H +#include #include "llcallingcard.h" #include "llinventorymodel.h" // for LLInventoryModel::item_array_t @@ -44,6 +45,8 @@ class LLFriendCardsManager public: typedef std::map folderid_buddies_map_t; + typedef boost::function callback_t; + typedef boost::signals2::signal friends_loaded_signal_t; // LLFriendObserver implementation void changed(U32 mask) @@ -78,9 +81,29 @@ public: * (creates them otherwise) and fetches their contents to synchronize with Agent's Friends List. */ void syncFriendCardsFolders(); + + + const LLUUID& findFriendSubfolderUUID(const std::string& nonLocalizedName) const; + // void getOrCreateFriendSubfolder(LLUUID& sub_folder_ID, const std::string& nonLocalizedName, callback_t cb); + void createFriendSubfolder(const std::string& nonLocalizedName); + boost::signals2::connection setFriendsDirLoadedCallback(const friends_loaded_signal_t::slot_type& cb); + //void collectFriendSet(const LLUUID& sub_folder_ID, uuid_vec_t buddyUUIDs) const; + + /*! + * \brief + * Collects folders' IDs with the buddies' IDs in the Inventory Calling Card/Friends folder. + * + * \param folderBuddiesMap + * map into collected data will be put. It will be cleared before adding new data. + * + * Each item in the out map is a pair where first is an LLViewerInventoryCategory UUID, + * second is a vector with UUID of Avatars from this folder. + * + * NOTE* Friends/All is excluded in folderBuddiesMap, LLAvatarTracker should be used instead + */ + void collectFriendsLists(folderid_buddies_map_t& folderBuddiesMap) const; private: - typedef boost::function callback_t; LLFriendCardsManager(); ~LLFriendCardsManager(); @@ -144,6 +167,8 @@ private: typedef std::set avatar_uuid_set_t; avatar_uuid_set_t mBuddyIDSet; + + friends_loaded_signal_t mFriendsLoadedSignal; }; #endif // LL_LLFRIENDCARD_H diff --git a/indra/newview/skins/default/xui/en/contact_set_accordion_tab.xml b/indra/newview/skins/default/xui/en/contact_set_accordion_tab.xml new file mode 100644 index 0000000000..d30af76647 --- /dev/null +++ b/indra/newview/skins/default/xui/en/contact_set_accordion_tab.xml @@ -0,0 +1,37 @@ + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/floater_fs_contacts.xml b/indra/newview/skins/default/xui/en/floater_fs_contacts.xml index 2c22813474..2ff04eb8d5 100644 --- a/indra/newview/skins/default/xui/en/floater_fs_contacts.xml +++ b/indra/newview/skins/default/xui/en/floater_fs_contacts.xml @@ -25,21 +25,21 @@ name="no_groups_msg" value="Looking for Groups to join? Try [secondlife:///app/search/groups Search]." /> + use_ellipses="true"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/menu_contact_set_gear.xml b/indra/newview/skins/default/xui/en/menu_contact_set_gear.xml new file mode 100644 index 0000000000..85e041330f --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_contact_set_gear.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 156c81bd4b..e4b1fdecf2 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -8339,6 +8339,25 @@ Could not add [GRID] to the grid list. You may remove this name by right clicking the name and choosing Remove Alias. - + + New contact set name: + confirm +
+ +