/** * @file llfloateravatarpicker.cpp * * $LicenseInfo:firstyear=2003&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * 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 * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llfloateravatarpicker.h" // Viewer includes #include "llagent.h" #include "llcallingcard.h" #include "llfocusmgr.h" #include "llfloaterreg.h" #include "llimview.h" // for gIMMgr #include "lltooldraganddrop.h" // for LLToolDragAndDrop #include "llviewercontrol.h" #include "llviewerregion.h" // getCapability() #include "llworld.h" // Linden libraries #include "llavatarnamecache.h" // IDEVO #include "llbutton.h" #include "llcachename.h" #include "llhttpclient.h" // IDEVO #include "lllineeditor.h" #include "llscrolllistctrl.h" #include "llscrolllistitem.h" #include "llscrolllistcell.h" #include "lltabcontainer.h" #include "lluictrlfactory.h" #include "llfocusmgr.h" #include "lldraghandle.h" #include "message.h" //#include "llsdserialize.h" //put it back as a member once the legacy path is out? static std::map sAvatarNameMap; LLFloaterAvatarPicker* LLFloaterAvatarPicker::show(select_callback_t callback, BOOL allow_multiple, BOOL closeOnSelect, BOOL skip_agent, const std::string& name, LLView * frustumOrigin) { // *TODO: Use a key to allow this not to be an effective singleton LLFloaterAvatarPicker* floater = LLFloaterReg::showTypedInstance("avatar_picker", LLSD(name)); if (!floater) { LL_WARNS() << "Cannot instantiate avatar picker" << LL_ENDL; return NULL; } floater->mSelectionCallback = callback; floater->setAllowMultiple(allow_multiple); floater->mNearMeListComplete = FALSE; floater->mCloseOnSelect = closeOnSelect; floater->mExcludeAgentFromSearchResults = skip_agent; if (!closeOnSelect) { // Use Select/Close std::string select_string = floater->getString("Select"); std::string close_string = floater->getString("Close"); floater->getChild("ok_btn")->setLabel(select_string); floater->getChild("cancel_btn")->setLabel(close_string); } if(frustumOrigin) { floater->mFrustumOrigin = frustumOrigin->getHandle(); } return floater; } // Default constructor LLFloaterAvatarPicker::LLFloaterAvatarPicker(const LLSD& key) : LLFloater(key), mNumResultsReturned(0), mNearMeListComplete(FALSE), mCloseOnSelect(FALSE), mContextConeOpacity (0.f), mContextConeInAlpha(0.f), mContextConeOutAlpha(0.f), mContextConeFadeTime(0.f) { mCommitCallbackRegistrar.add("Refresh.FriendList", boost::bind(&LLFloaterAvatarPicker::populateFriend, this)); mContextConeInAlpha = gSavedSettings.getF32("ContextConeInAlpha"); mContextConeOutAlpha = gSavedSettings.getF32("ContextConeOutAlpha"); mContextConeFadeTime = gSavedSettings.getF32("ContextConeFadeTime"); } BOOL LLFloaterAvatarPicker::postBuild() { getChild("Edit")->setKeystrokeCallback( boost::bind(&LLFloaterAvatarPicker::editKeystroke, this, _1, _2),NULL); childSetAction("Find", boost::bind(&LLFloaterAvatarPicker::onBtnFind, this)); getChildView("Find")->setEnabled(FALSE); childSetAction("Refresh", boost::bind(&LLFloaterAvatarPicker::onBtnRefresh, this)); getChild("near_me_range")->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onRangeAdjust, this)); LLScrollListCtrl* searchresults = getChild("SearchResults"); searchresults->setDoubleClickCallback( boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); searchresults->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onList, this)); getChildView("SearchResults")->setEnabled(FALSE); LLScrollListCtrl* nearme = getChild("NearMe"); nearme->setDoubleClickCallback(boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); nearme->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onList, this)); LLScrollListCtrl* friends = getChild("Friends"); friends->setDoubleClickCallback(boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); getChild("Friends")->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onList, this)); childSetAction("ok_btn", boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); getChildView("ok_btn")->setEnabled(FALSE); childSetAction("cancel_btn", boost::bind(&LLFloaterAvatarPicker::onBtnClose, this)); getChild("Edit")->setFocus(TRUE); LLPanel* search_panel = getChild("SearchPanel"); if (search_panel) { // Start searching when Return is pressed in the line editor. search_panel->setDefaultBtn("Find"); } getChild("SearchResults")->setCommentText(getString("no_results")); getChild("ResidentChooserTabs")->setCommitCallback( boost::bind(&LLFloaterAvatarPicker::onTabChanged, this)); setAllowMultiple(FALSE); center(); populateFriend(); return TRUE; } void LLFloaterAvatarPicker::setOkBtnEnableCb(validate_callback_t cb) { mOkButtonValidateSignal.connect(cb); } void LLFloaterAvatarPicker::onTabChanged() { getChildView("ok_btn")->setEnabled(isSelectBtnEnabled()); } // Destroys the object LLFloaterAvatarPicker::~LLFloaterAvatarPicker() { gFocusMgr.releaseFocusIfNeeded( this ); } void LLFloaterAvatarPicker::onBtnFind() { find(); } static void getSelectedAvatarData(const LLScrollListCtrl* from, uuid_vec_t& avatar_ids, std::vector& avatar_names) { std::vector items = from->getAllSelected(); for (std::vector::iterator iter = items.begin(); iter != items.end(); ++iter) { LLScrollListItem* item = *iter; if (item->getUUID().notNull()) { avatar_ids.push_back(item->getUUID()); std::map::iterator iter = sAvatarNameMap.find(item->getUUID()); if (iter != sAvatarNameMap.end()) { avatar_names.push_back(iter->second); } else { // the only case where it isn't in the name map is friends // but it should be in the name cache LLAvatarName av_name; LLAvatarNameCache::get(item->getUUID(), &av_name); avatar_names.push_back(av_name); } } } } void LLFloaterAvatarPicker::onBtnSelect() { // If select btn not enabled then do not callback if (!isSelectBtnEnabled()) return; if(mSelectionCallback) { std::string acvtive_panel_name; LLScrollListCtrl* list = NULL; LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); if(active_panel) { acvtive_panel_name = active_panel->getName(); } if(acvtive_panel_name == "SearchPanel") { list = getChild("SearchResults"); } else if(acvtive_panel_name == "NearMePanel") { list = getChild("NearMe"); } else if (acvtive_panel_name == "FriendsPanel") { list = getChild("Friends"); } if(list) { uuid_vec_t avatar_ids; std::vector avatar_names; getSelectedAvatarData(list, avatar_ids, avatar_names); mSelectionCallback(avatar_ids, avatar_names); } } getChild("SearchResults")->deselectAllItems(TRUE); getChild("NearMe")->deselectAllItems(TRUE); getChild("Friends")->deselectAllItems(TRUE); if(mCloseOnSelect) { mCloseOnSelect = FALSE; closeFloater(); } } void LLFloaterAvatarPicker::onBtnRefresh() { getChild("NearMe")->deleteAllItems(); getChild("NearMe")->setCommentText(getString("searching")); mNearMeListComplete = FALSE; } void LLFloaterAvatarPicker::onBtnClose() { closeFloater(); } void LLFloaterAvatarPicker::onRangeAdjust() { onBtnRefresh(); } void LLFloaterAvatarPicker::onList() { getChildView("ok_btn")->setEnabled(isSelectBtnEnabled()); } void LLFloaterAvatarPicker::populateNearMe() { BOOL all_loaded = TRUE; BOOL empty = TRUE; LLScrollListCtrl* near_me_scroller = getChild("NearMe"); near_me_scroller->deleteAllItems(); uuid_vec_t avatar_ids; LLWorld::getInstance()->getAvatars(&avatar_ids, NULL, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); for(U32 i=0; iaddElement(element); empty = FALSE; } if (empty) { getChildView("NearMe")->setEnabled(FALSE); getChildView("ok_btn")->setEnabled(FALSE); near_me_scroller->setCommentText(getString("no_one_near")); } else { getChildView("NearMe")->setEnabled(TRUE); getChildView("ok_btn")->setEnabled(TRUE); near_me_scroller->selectFirstItem(); onList(); near_me_scroller->setFocus(TRUE); } if (all_loaded) { mNearMeListComplete = TRUE; } } void LLFloaterAvatarPicker::populateFriend() { LLScrollListCtrl* friends_scroller = getChild("Friends"); friends_scroller->deleteAllItems(); LLCollectAllBuddies collector; LLAvatarTracker::instance().applyFunctor(collector); LLCollectAllBuddies::buddy_map_t::iterator it; for(it = collector.mOnline.begin(); it!=collector.mOnline.end(); it++) { friends_scroller->addStringUUIDItem(it->first, it->second); } for(it = collector.mOffline.begin(); it!=collector.mOffline.end(); it++) { friends_scroller->addStringUUIDItem(it->first, it->second); } friends_scroller->sortByColumnIndex(0, TRUE); } void LLFloaterAvatarPicker::drawFrustum() { if(mFrustumOrigin.get()) { LLView * frustumOrigin = mFrustumOrigin.get(); LLRect origin_rect; frustumOrigin->localRectToOtherView(frustumOrigin->getLocalRect(), &origin_rect, this); // draw context cone connecting color picker with color swatch in parent floater LLRect local_rect = getLocalRect(); if (hasFocus() && frustumOrigin->isInVisibleChain() && mContextConeOpacity > 0.001f) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); LLGLEnable(GL_CULL_FACE); gGL.begin(LLRender::QUADS); { gGL.color4f(0.f, 0.f, 0.f, mContextConeInAlpha * mContextConeOpacity); gGL.vertex2i(origin_rect.mLeft, origin_rect.mTop); gGL.vertex2i(origin_rect.mRight, origin_rect.mTop); gGL.color4f(0.f, 0.f, 0.f, mContextConeOutAlpha * mContextConeOpacity); gGL.vertex2i(local_rect.mRight, local_rect.mTop); gGL.vertex2i(local_rect.mLeft, local_rect.mTop); gGL.color4f(0.f, 0.f, 0.f, mContextConeOutAlpha * mContextConeOpacity); gGL.vertex2i(local_rect.mLeft, local_rect.mTop); gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); gGL.color4f(0.f, 0.f, 0.f, mContextConeInAlpha * mContextConeOpacity); gGL.vertex2i(origin_rect.mLeft, origin_rect.mBottom); gGL.vertex2i(origin_rect.mLeft, origin_rect.mTop); gGL.color4f(0.f, 0.f, 0.f, mContextConeOutAlpha * mContextConeOpacity); gGL.vertex2i(local_rect.mRight, local_rect.mBottom); gGL.vertex2i(local_rect.mRight, local_rect.mTop); gGL.color4f(0.f, 0.f, 0.f, mContextConeInAlpha * mContextConeOpacity); gGL.vertex2i(origin_rect.mRight, origin_rect.mTop); gGL.vertex2i(origin_rect.mRight, origin_rect.mBottom); gGL.color4f(0.f, 0.f, 0.f, mContextConeOutAlpha * mContextConeOpacity); gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); gGL.vertex2i(local_rect.mRight, local_rect.mBottom); gGL.color4f(0.f, 0.f, 0.f, mContextConeInAlpha * mContextConeOpacity); gGL.vertex2i(origin_rect.mRight, origin_rect.mBottom); gGL.vertex2i(origin_rect.mLeft, origin_rect.mBottom); } gGL.end(); } if (gFocusMgr.childHasMouseCapture(getDragHandle())) { mContextConeOpacity = lerp(mContextConeOpacity, gSavedSettings.getF32("PickerContextOpacity"), LLSmoothInterpolation::getInterpolant(mContextConeFadeTime)); } else { mContextConeOpacity = lerp(mContextConeOpacity, 0.f, LLSmoothInterpolation::getInterpolant(mContextConeFadeTime)); } } } void LLFloaterAvatarPicker::draw() { drawFrustum(); // sometimes it is hard to determine when Select/Ok button should be disabled (see LLAvatarActions::shareWithAvatars). // lets check this via mOkButtonValidateSignal callback periodically. static LLFrameTimer timer; if (timer.hasExpired()) { timer.setTimerExpirySec(0.33f); // three times per second should be enough. // simulate list changes. onList(); timer.start(); } LLFloater::draw(); if (!mNearMeListComplete && getChild("ResidentChooserTabs")->getCurrentPanel() == getChild("NearMePanel")) { populateNearMe(); } } BOOL LLFloaterAvatarPicker::visibleItemsSelected() const { LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); if(active_panel == getChild("SearchPanel")) { return getChild("SearchResults")->getFirstSelectedIndex() >= 0; } else if(active_panel == getChild("NearMePanel")) { return getChild("NearMe")->getFirstSelectedIndex() >= 0; } else if(active_panel == getChild("FriendsPanel")) { return getChild("Friends")->getFirstSelectedIndex() >= 0; } return FALSE; } class LLAvatarPickerResponder : public LLHTTPClient::Responder { public: LLUUID mQueryID; std::string mName; LLAvatarPickerResponder(const LLUUID& id, const std::string& name) : mQueryID(id), mName(name) { } /*virtual*/ void completed(U32 status, const std::string& reason, const LLSD& content) { //std::ostringstream ss; //LLSDSerialize::toPrettyXML(content, ss); //LL_INFOS() << ss.str() << LL_ENDL; // in case of invalid characters, the avatar picker returns a 400 // just set it to process so it displays 'not found' if (isGoodStatus(status) || status == 400) { LLFloaterAvatarPicker* floater = LLFloaterReg::findTypedInstance("avatar_picker", mName); if (floater) { floater->processResponse(mQueryID, content); } } else { LL_WARNS() << "avatar picker failed [status:" << status << "]: " << content << LL_ENDL; } } }; void LLFloaterAvatarPicker::find() { //clear our stored LLAvatarNames sAvatarNameMap.clear(); std::string text = getChild("Edit")->getValue().asString(); mQueryID.generate(); std::string url; url.reserve(128); // avoid a memory allocation or two LLViewerRegion* region = gAgent.getRegion(); url = region->getCapability("AvatarPickerSearch"); // Prefer use of capabilities to search on both SLID and display name if (!url.empty()) { // capability urls don't end in '/', but we need one to parse // query parameters correctly if (url.size() > 0 && url[url.size()-1] != '/') { url += "/"; } url += "?page_size=100&names="; std::replace(text.begin(), text.end(), '.', ' '); url += LLURI::escape(text); LL_INFOS() << "avatar picker " << url << LL_ENDL; LLHTTPClient::get(url, new LLAvatarPickerResponder(mQueryID, getKey().asString())); } else { LLMessageSystem* msg = gMessageSystem; msg->newMessage("AvatarPickerRequest"); msg->nextBlock("AgentData"); msg->addUUID("AgentID", gAgent.getID()); msg->addUUID("SessionID", gAgent.getSessionID()); msg->addUUID("QueryID", mQueryID); // not used right now msg->nextBlock("Data"); msg->addString("Name", text); gAgent.sendReliableMessage(); } getChild("SearchResults")->deleteAllItems(); getChild("SearchResults")->setCommentText(getString("searching")); getChildView("ok_btn")->setEnabled(FALSE); mNumResultsReturned = 0; } void LLFloaterAvatarPicker::setAllowMultiple(BOOL allow_multiple) { getChild("SearchResults")->setAllowMultipleSelection(allow_multiple); getChild("NearMe")->setAllowMultipleSelection(allow_multiple); getChild("Friends")->setAllowMultipleSelection(allow_multiple); } LLScrollListCtrl* LLFloaterAvatarPicker::getActiveList() { std::string acvtive_panel_name; LLScrollListCtrl* list = NULL; LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); if(active_panel) { acvtive_panel_name = active_panel->getName(); } if(acvtive_panel_name == "SearchPanel") { list = getChild("SearchResults"); } else if(acvtive_panel_name == "NearMePanel") { list = getChild("NearMe"); } else if (acvtive_panel_name == "FriendsPanel") { list = getChild("Friends"); } return list; } BOOL LLFloaterAvatarPicker::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string& tooltip_msg) { LLScrollListCtrl* list = getActiveList(); if(list) { LLRect rc_list; LLRect rc_point(x,y,x,y); if (localRectToOtherView(rc_point, &rc_list, list)) { // Keep selected only one item list->deselectAllItems(TRUE); list->selectItemAt(rc_list.mLeft, rc_list.mBottom, mask); LLScrollListItem* selection = list->getFirstSelected(); if (selection) { LLUUID session_id = LLUUID::null; LLUUID dest_agent_id = selection->getUUID(); std::string avatar_name = selection->getColumn(0)->getValue().asString(); if (dest_agent_id.notNull() && dest_agent_id != gAgentID) { if (drop) { // Start up IM before give the item session_id = gIMMgr->addSession(avatar_name, IM_NOTHING_SPECIAL, dest_agent_id); } return LLToolDragAndDrop::handleGiveDragAndDrop(dest_agent_id, session_id, drop, cargo_type, cargo_data, accept, getName()); } } } } *accept = ACCEPT_NO; return TRUE; } void LLFloaterAvatarPicker::openFriendsTab() { LLTabContainer* tab_container = getChild("ResidentChooserTabs"); if (tab_container == NULL) { llassert(tab_container != NULL); return; } tab_container->selectTabByName("FriendsPanel"); } // static void LLFloaterAvatarPicker::processAvatarPickerReply(LLMessageSystem* msg, void**) { LLUUID agent_id; LLUUID query_id; LLUUID avatar_id; std::string first_name; std::string last_name; msg->getUUID("AgentData", "AgentID", agent_id); msg->getUUID("AgentData", "QueryID", query_id); // Not for us if (agent_id != gAgent.getID()) return; LLFloaterAvatarPicker* floater = LLFloaterReg::findTypedInstance("avatar_picker"); // floater is closed or these are not results from our last request if (NULL == floater || query_id != floater->mQueryID) { return; } LLScrollListCtrl* search_results = floater->getChild("SearchResults"); // clear "Searching" label on first results if (floater->mNumResultsReturned++ == 0) { search_results->deleteAllItems(); } BOOL found_one = FALSE; S32 num_new_rows = msg->getNumberOfBlocks("Data"); for (S32 i = 0; i < num_new_rows; i++) { msg->getUUIDFast( _PREHASH_Data,_PREHASH_AvatarID, avatar_id, i); msg->getStringFast(_PREHASH_Data,_PREHASH_FirstName, first_name, i); msg->getStringFast(_PREHASH_Data,_PREHASH_LastName, last_name, i); if (avatar_id != agent_id || !floater->isExcludeAgentFromSearchResults()) // exclude agent from search results? { std::string avatar_name; if (avatar_id.isNull()) { LLStringUtil::format_map_t map; map["[TEXT]"] = floater->getChild("Edit")->getValue().asString(); avatar_name = floater->getString("not_found", map); search_results->setEnabled(FALSE); floater->getChildView("ok_btn")->setEnabled(FALSE); } else { avatar_name = LLCacheName::buildFullName(first_name, last_name); search_results->setEnabled(TRUE); found_one = TRUE; LLAvatarName av_name; av_name.fromString(avatar_name); const LLUUID& agent_id = avatar_id; sAvatarNameMap[agent_id] = av_name; } LLSD element; element["id"] = avatar_id; // value element["columns"][0]["column"] = "name"; element["columns"][0]["value"] = avatar_name; search_results->addElement(element); } } if (found_one) { floater->getChildView("ok_btn")->setEnabled(TRUE); search_results->selectFirstItem(); floater->onList(); search_results->setFocus(TRUE); } } void LLFloaterAvatarPicker::processResponse(const LLUUID& query_id, const LLSD& content) { // Check for out-of-date query if (query_id == mQueryID) { LLScrollListCtrl* search_results = getChild("SearchResults"); LLSD agents = content["agents"]; // clear "Searching" label on first results search_results->deleteAllItems(); LLSD item; LLSD::array_const_iterator it = agents.beginArray(); for ( ; it != agents.endArray(); ++it) { const LLSD& row = *it; if (row["id"].asUUID() != gAgent.getID() || !mExcludeAgentFromSearchResults) { item["id"] = row["id"]; LLSD& columns = item["columns"]; columns[0]["column"] = "name"; columns[0]["value"] = row["display_name"]; columns[1]["column"] = "username"; columns[1]["value"] = row["username"]; search_results->addElement(item); // add the avatar name to our list LLAvatarName avatar_name; avatar_name.fromLLSD(row); sAvatarNameMap[row["id"].asUUID()] = avatar_name; } } if (search_results->isEmpty()) { LLStringUtil::format_map_t map; map["[TEXT]"] = getChild("Edit")->getValue().asString(); LLSD item; item["id"] = LLUUID::null; item["columns"][0]["column"] = "name"; item["columns"][0]["value"] = getString("not_found", map); search_results->addElement(item); search_results->setEnabled(false); getChildView("ok_btn")->setEnabled(false); } else { getChildView("ok_btn")->setEnabled(true); search_results->setEnabled(true); search_results->sortByColumnIndex(1, TRUE); std::string text = getChild("Edit")->getValue().asString(); if (!search_results->selectItemByLabel(text, TRUE, 1)) { search_results->selectFirstItem(); } onList(); search_results->setFocus(TRUE); } } } //static void LLFloaterAvatarPicker::editKeystroke(LLLineEditor* caller, void* user_data) { getChildView("Find")->setEnabled(caller->getText().size() > 0); } // virtual BOOL LLFloaterAvatarPicker::handleKeyHere(KEY key, MASK mask) { if (key == KEY_RETURN && mask == MASK_NONE) { if (getChild("Edit")->hasFocus()) { onBtnFind(); } else { onBtnSelect(); } return TRUE; } else if (key == KEY_ESCAPE && mask == MASK_NONE) { closeFloater(); return TRUE; } return LLFloater::handleKeyHere(key, mask); } bool LLFloaterAvatarPicker::isSelectBtnEnabled() { bool ret_val = visibleItemsSelected(); if ( ret_val ) { std::string acvtive_panel_name; LLScrollListCtrl* list = NULL; LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); if(active_panel) { acvtive_panel_name = active_panel->getName(); } if(acvtive_panel_name == "SearchPanel") { list = getChild("SearchResults"); } else if(acvtive_panel_name == "NearMePanel") { list = getChild("NearMe"); } else if (acvtive_panel_name == "FriendsPanel") { list = getChild("Friends"); } if(list) { uuid_vec_t avatar_ids; std::vector avatar_names; getSelectedAvatarData(list, avatar_ids, avatar_names); if (avatar_ids.size() >= 1) { ret_val = mOkButtonValidateSignal.num_slots()?mOkButtonValidateSignal(avatar_ids):true; } else { ret_val = false; } } } return ret_val; }