/** * @file llfloaterexperiencepicker.cpp * @brief Implementation of llfloaterexperiencepicker * @author dolphin@lindenlab.com * * $LicenseInfo:firstyear=2014&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2014, 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 "llfloaterexperiencepicker.h" #include "lllineeditor.h" #include "llfloaterreg.h" #include "llscrolllistctrl.h" #include "llviewerregion.h" #include "llagent.h" #include "llexperiencecache.h" #include "llslurl.h" #include "llavatarnamecache.h" #include "llfloaterexperienceprofile.h" #include "llcombobox.h" #include "llviewercontrol.h" #include "lldraghandle.h" #define BTN_FIND "find" #define BTN_OK "ok_btn" #define BTN_CANCEL "cancel_btn" #define BTN_PROFILE "profile_btn" #define TEXT_EDIT "edit" #define TEXT_MATURITY "maturity" #define LIST_RESULTS "search_results" #define PANEL_SEARCH "search_panel" const static std::string columnSpace = " "; class LLExperiencePickerResponder : public LLHTTPClient::Responder { public: LLUUID mQueryID; LLHandle mParent; LLExperiencePickerResponder(const LLUUID& id, const LLHandle& parent) : mQueryID(id), mParent(parent) { } void completed(U32 status, const std::string& reason, const LLSD& content) { if (isGoodStatus(status)) { if(mParent.isDead()) return; LLFloaterExperiencePicker* floater =mParent.get(); if (floater) { floater->processResponse(mQueryID, content); } } else { llwarns << "avatar picker failed [status:" << status << "]: " << content << llendl; } } }; LLFloaterExperiencePicker* LLFloaterExperiencePicker::show( select_callback_t callback, const LLUUID& key, BOOL allow_multiple, BOOL closeOnSelect, LLView * frustumOrigin ) { LLFloaterExperiencePicker* floater = LLFloaterReg::showTypedInstance("experience_search", key); if (!floater) { llwarns << "Cannot instantiate experience picker" << llendl; return NULL; } floater->mSelectionCallback = callback; floater->mCloseOnSelect = closeOnSelect; floater->setAllowMultiple(allow_multiple); if(frustumOrigin) { floater->mFrustumOrigin = frustumOrigin->getHandle(); } return floater; } void LLFloaterExperiencePicker::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"), LLCriticalDamp::getInterpolant(mContextConeFadeTime)); } else { mContextConeOpacity = lerp(mContextConeOpacity, 0.f, LLCriticalDamp::getInterpolant(mContextConeFadeTime)); } } } void LLFloaterExperiencePicker::draw() { drawFrustum(); LLFloater::draw(); } LLFloaterExperiencePicker::LLFloaterExperiencePicker( const LLSD& key ) :LLFloater(key) ,mContextConeOpacity (0.f) ,mContextConeInAlpha(0.f) ,mContextConeOutAlpha(0.f) ,mContextConeFadeTime(0.f) { setDefaultFilters(); mContextConeInAlpha = gSavedSettings.getF32("ContextConeInAlpha"); mContextConeOutAlpha = gSavedSettings.getF32("ContextConeOutAlpha"); mContextConeFadeTime = gSavedSettings.getF32("ContextConeFadeTime"); } LLFloaterExperiencePicker::~LLFloaterExperiencePicker() { gFocusMgr.releaseFocusIfNeeded( this ); } BOOL LLFloaterExperiencePicker::postBuild() { getChild(TEXT_EDIT)->setKeystrokeCallback( boost::bind(&LLFloaterExperiencePicker::editKeystroke, this, _1, _2),NULL); childSetAction(BTN_FIND, boost::bind(&LLFloaterExperiencePicker::onBtnFind, this)); getChildView(BTN_FIND)->setEnabled(FALSE); LLScrollListCtrl* searchresults = getChild(LIST_RESULTS); searchresults->setDoubleClickCallback( boost::bind(&LLFloaterExperiencePicker::onBtnSelect, this)); searchresults->setCommitCallback(boost::bind(&LLFloaterExperiencePicker::onList, this)); getChildView(LIST_RESULTS)->setEnabled(FALSE); getChild(LIST_RESULTS)->setCommentText(getString("no_results")); childSetAction(BTN_OK, boost::bind(&LLFloaterExperiencePicker::onBtnSelect, this)); getChildView(BTN_OK)->setEnabled(FALSE); childSetAction(BTN_CANCEL, boost::bind(&LLFloaterExperiencePicker::onBtnClose, this)); childSetAction(BTN_PROFILE, boost::bind(&LLFloaterExperiencePicker::onBtnProfile, this)); getChildView(BTN_PROFILE)->setEnabled(FALSE); getChild(TEXT_MATURITY)->setCurrentByIndex(2); getChild(TEXT_MATURITY)->setCommitCallback(boost::bind(&LLFloaterExperiencePicker::onMaturity, this)); getChild(TEXT_EDIT)->setFocus(TRUE); LLPanel* search_panel = getChild(PANEL_SEARCH); if (search_panel) { // Start searching when Return is pressed in the line editor. search_panel->setDefaultBtn(BTN_FIND); } return TRUE; } void LLFloaterExperiencePicker::editKeystroke( class LLLineEditor* caller, void* user_data ) { getChildView(BTN_FIND)->setEnabled(caller->getText().size() > 0); } void LLFloaterExperiencePicker::onBtnFind() { find(); } void LLFloaterExperiencePicker::onBtnSelect() { if(!isSelectButtonEnabled()) { return; } if(mSelectionCallback) { const LLScrollListCtrl* results = getChild(LIST_RESULTS); uuid_vec_t experience_ids; getSelectedExperienceIds(results, experience_ids); mSelectionCallback(experience_ids); getChild(LIST_RESULTS)->deselectAllItems(TRUE); if(mCloseOnSelect) { mCloseOnSelect = FALSE; closeFloater(); } } else { onBtnProfile(); } } void LLFloaterExperiencePicker::onList() { bool enabled = isSelectButtonEnabled(); getChildView(BTN_OK)->setEnabled(enabled); enabled = enabled && getChild(LIST_RESULTS)->getNumSelected() == 1; getChildView(BTN_PROFILE)->setEnabled(enabled); } void LLFloaterExperiencePicker::onBtnClose() { closeFloater(); } void LLFloaterExperiencePicker::find() { std::string text = getChild(TEXT_EDIT)->getValue().asString(); mQueryID.generate(); std::string url; url.reserve(128+text.size()); LLViewerRegion* region = gAgent.getRegion(); url = region->getCapability("FindExperienceByName"); if (!url.empty()) { url+="?query="; url+=LLURI::escape(text); LLHTTPClient::get(url, new LLExperiencePickerResponder(mQueryID, getDerivedHandle())); } getChild(LIST_RESULTS)->deleteAllItems(); getChild(LIST_RESULTS)->setCommentText(getString("searching")); getChildView(BTN_OK)->setEnabled(FALSE); getChildView(BTN_PROFILE)->setEnabled(FALSE); } bool LLFloaterExperiencePicker::isSelectButtonEnabled() { LLScrollListCtrl* list=getChild(LIST_RESULTS); return list->getFirstSelectedIndex() >=0; } void LLFloaterExperiencePicker::getSelectedExperienceIds( const LLScrollListCtrl* results, uuid_vec_t &experience_ids ) { std::vector items = results->getAllSelected(); for(std::vector::iterator it = items.begin(); it != items.end(); ++it) { LLScrollListItem* item = *it; if (item->getUUID().notNull()) { experience_ids.push_back(item->getUUID()); } } } void LLFloaterExperiencePicker::setAllowMultiple( bool allow_multiple ) { getChild(LIST_RESULTS)->setAllowMultipleSelection(allow_multiple); } void name_callback(const LLHandle& floater, const LLUUID& experience_id, const LLUUID& agent_id, const LLAvatarName& av_name) { if(floater.isDead()) return; LLFloaterExperiencePicker* picker = floater.get(); LLScrollListCtrl* search_results = picker->getChild(LIST_RESULTS); LLScrollListItem* item = search_results->getItem(experience_id); if(!item) return; item->getColumn(2)->setValue(columnSpace+av_name.getDisplayName()); } void LLFloaterExperiencePicker::processResponse( const LLUUID& query_id, const LLSD& content ) { if(query_id != mQueryID) { return; } mResponse = content; const LLSD& experiences=mResponse["experience_keys"]; LLSD::array_const_iterator it = experiences.beginArray(); for ( ; it != experiences.endArray(); ++it) { LLExperienceCache::insert(*it); } filterContent(); } void LLFloaterExperiencePicker::onBtnProfile() { LLScrollListItem* item = getChild(LIST_RESULTS)->getFirstSelected(); if(item) { LLFloaterReg::showInstance("experience_profile", item->getUUID(), true); } } std::string LLFloaterExperiencePicker::getMaturityString(int maturity) { if(maturity <= SIM_ACCESS_PG) { return getString("maturity_icon_general"); } else if(maturity <= SIM_ACCESS_MATURE) { return getString("maturity_icon_moderate"); } return getString("maturity_icon_adult"); } void LLFloaterExperiencePicker::filterContent() { LLScrollListCtrl* search_results = getChild(LIST_RESULTS); const LLSD& experiences=mResponse["experience_keys"]; search_results->deleteAllItems(); LLSD item; LLSD::array_const_iterator it = experiences.beginArray(); for ( ; it != experiences.endArray(); ++it) { const LLSD& experience = *it; if(isExperienceHidden(experience)) continue; item["id"]=experience[LLExperienceCache::EXPERIENCE_ID]; LLSD& columns = item["columns"]; columns[0]["column"] = "maturity"; columns[0]["value"] = getMaturityString(experience[LLExperienceCache::MATURITY].asInteger()); columns[0]["type"]="icon"; columns[0]["halign"]="right"; columns[1]["column"] = "experience_name"; columns[1]["value"] = columnSpace+experience[LLExperienceCache::NAME].asString(); columns[2]["column"] = "owner"; columns[2]["value"] = columnSpace+getString("loading"); search_results->addElement(item); LLAvatarNameCache::get(experience[LLExperienceCache::AGENT_ID], boost::bind(name_callback, getDerivedHandle(), experience[LLExperienceCache::EXPERIENCE_ID], _1, _2)); } if (search_results->isEmpty()) { LLStringUtil::format_map_t map; std::string search_text = childGetText(TEXT_EDIT); map["[TEXT]"] = search_text; if (search_text.empty()) { getChild(LIST_RESULTS)->setCommentText(getString("no_results")); } else { getChild(LIST_RESULTS)->setCommentText(getString("not_found", map)); } search_results->setEnabled(false); getChildView(BTN_OK)->setEnabled(false); getChildView(BTN_PROFILE)->setEnabled(false); } else { getChildView(BTN_OK)->setEnabled(true); search_results->setEnabled(true); search_results->sortByColumnIndex(1, TRUE); std::string text = getChild(TEXT_EDIT)->getValue().asString(); if (!search_results->selectItemByLabel(text, TRUE, 1)) { search_results->selectFirstItem(); } onList(); search_results->setFocus(TRUE); } } void LLFloaterExperiencePicker::onMaturity() { if(mResponse.has("experience_keys") && mResponse["experience_keys"].beginArray() != mResponse["experience_keys"].endArray()) { filterContent(); } } bool LLFloaterExperiencePicker::isExperienceHidden( const LLSD& experience) const { bool hide=false; filter_list::const_iterator it = mFilters.begin(); for(/**/;it != mFilters.end(); ++it) { if((*it)(experience)){ return true; } } return hide; } bool LLFloaterExperiencePicker::FilterOverRating( const LLSD& experience ) { int maturity = getChild(TEXT_MATURITY)->getSelectedValue().asInteger(); return experience[LLExperienceCache::MATURITY].asInteger() > maturity; } bool LLFloaterExperiencePicker::FilterWithProperty( const LLSD& experience, S32 prop) { return (experience[LLExperienceCache::PROPERTIES].asInteger() & prop) != 0; } bool LLFloaterExperiencePicker::FilterWithoutProperty( const LLSD& experience, S32 prop ) { return (experience[LLExperienceCache::PROPERTIES].asInteger() & prop) == 0; } void LLFloaterExperiencePicker::setDefaultFilters() { mFilters.clear(); addFilter(boost::bind(&LLFloaterExperiencePicker::FilterOverRating, this, _1)); }