/** * @file fsfloatersearch.cpp * @brief Firestorm Search Floater * * $LicenseInfo:firstyear=2012&license=fsviewerlgpl$ * Phoenix Firestorm Viewer Source Code * Copyright (C) 2012, Cinder Roxley * * 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 Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA * http://www.firestormviewer.org * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "fsfloatersearch.h" #include "lldispatcher.h" #include "llagent.h" #include "llavataractions.h" #include "llavatarname.h" #include "llavatarnamecache.h" #include "llavatarpropertiesprocessor.h" #include "llclassifiedflags.h" #include "llclassifiedinfo.h" #include "llcombobox.h" #include "lldateutil.h" #include "lleventflags.h" #include "lleventnotifier.h" #include "llfloaterreg.h" #include "llfloaterworldmap.h" #include "llgroupactions.h" #include "llgroupmgr.h" #include "llloadingindicator.h" #include "lllogininstance.h" #include "llnotificationsutil.h" #include "llpanelprofile.h" #include "llpanelprofileclassifieds.h" #include "llparcel.h" #include "llproductinforequest.h" #include "llqueryflags.h" #include "llregionhandle.h" #include "llremoteparcelrequest.h" #include "lltimer.h" #include "lltrans.h" #include "llviewercontrol.h" #include "llviewergenericmessage.h" #include "llviewernetwork.h" #include "llviewerregion.h" #include "llworldmapmessage.h" #include "message.h" #include #include #include #include static const S32 MIN_SEARCH_STRING_SIZE = 2; static const S32 RESULT_PAGE_SIZE = 100; // (observeur) Hack to avoid Find to be called several times (due to a bug in llsearchcombobox) static std::chrono::time_point lastRequestTime; static const S32 REQUEST_MIN_ELAPSED_TIME = 500; std::string filterShortWords(std::string query_string); void fillSearchComboBox(LLSearchComboBox* search_combo); //////////////////////////////////////// // Observer Classes // //////////////////////////////////////// class FSSearchRemoteParcelInfoObserver : public LLRemoteParcelInfoObserver { public: FSSearchRemoteParcelInfoObserver(FSFloaterSearch* floater, bool for_events) : LLRemoteParcelInfoObserver(), mParent(floater), mForEvents(for_events) {} ~FSSearchRemoteParcelInfoObserver() { // remove any in-flight observers std::set::iterator it; for (it = mParcelIDs.begin(); it != mParcelIDs.end(); ++it) { const LLUUID &id = *it; LLRemoteParcelInfoProcessor::getInstance()->removeObserver(id, this); } mParcelIDs.clear(); } /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data) { if (mParent) { if (mForEvents) { mParent->displayEventParcelImage(parcel_data); } else { mParent->displayParcelDetails(parcel_data); } } mParcelIDs.erase(parcel_data.parcel_id); LLRemoteParcelInfoProcessor::getInstance()->removeObserver(parcel_data.parcel_id, this); } /*virtual*/ void setParcelID(const LLUUID& parcel_id) { if (!parcel_id.isNull()) { mParcelIDs.insert(parcel_id); LLRemoteParcelInfoProcessor::getInstance()->addObserver(parcel_id, this); LLRemoteParcelInfoProcessor::getInstance()->sendParcelInfoRequest(parcel_id); } } /*virtual*/ void setErrorStatus(S32 status, const std::string& reason) { LL_WARNS("Search") << "Can't complete remote parcel request. Http Status: " << status << ". Reason : " << reason << LL_ENDL; } private: std::set mParcelIDs; FSFloaterSearch* mParent; bool mForEvents; }; ///// Avatar Properties Observer ///// class FSSearchAvatarPropertiesObserver : public LLAvatarPropertiesObserver { public: FSSearchAvatarPropertiesObserver(FSFloaterSearch* floater) : LLAvatarPropertiesObserver(), mParent(floater) {} ~FSSearchAvatarPropertiesObserver() { // remove any in-flight observers std::set::iterator it; for (it = mAvatarIDs.begin(); it != mAvatarIDs.end(); ++it) { const LLUUID &id = *it; LLAvatarPropertiesProcessor::getInstance()->removeObserver(id, this); } mAvatarIDs.clear(); } void processProperties(void* data, EAvatarProcessorType type) { if (!data) return; if (APT_PROPERTIES == type) { LLAvatarData* avatar_data = static_cast(data); if (avatar_data) { mParent->displayAvatarDetails(avatar_data); LLAvatarPropertiesProcessor::getInstance()->removeObserver(avatar_data->avatar_id, this); } } else if (APT_PROPERTIES_LEGACY == type) { LLAvatarData avatar_data(*static_cast(data)); mParent->displayAvatarDetails(&avatar_data); LLAvatarPropertiesProcessor::getInstance()->removeObserver(avatar_data.avatar_id, this); } if (APT_CLASSIFIED_INFO == type) { LLAvatarClassifiedInfo* c_info = static_cast(data); if (c_info) { mParent->displayClassifiedDetails(c_info); LLAvatarPropertiesProcessor::getInstance()->removeObserver(c_info->classified_id, this); std::string url = gAgent.getRegionCapability("SearchStatRequest"); if (!url.empty()) { LL_INFOS("Search") << "Classified stat request via capability" << LL_ENDL; LLSD body; body["classified_id"] = c_info->classified_id; LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, body, boost::bind(&LLPanelProfileClassified::handleSearchStatResponse, c_info->classified_id, _1)); } } } } private: std::set mAvatarIDs; FSFloaterSearch* mParent; }; ///// Group Info Observer ///// class FSSearchGroupInfoObserver : public LLGroupMgrObserver { public: FSSearchGroupInfoObserver(const LLUUID& group_id, FSFloaterSearch* parent) : LLGroupMgrObserver(group_id), mParent(parent) { LLGroupMgr* groupmgr = LLGroupMgr::getInstance(); if (!group_id.isNull() && groupmgr) { groupmgr->addObserver(this); mID = group_id; groupmgr->sendGroupPropertiesRequest(group_id); } } ~FSSearchGroupInfoObserver() { LLGroupMgr::getInstance()->removeObserver(this); } void changed(LLGroupChange gc) { if (gc == GC_PROPERTIES) { LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(mID); mParent->displayGroupDetails(group_data); LLGroupMgr::getInstance()->removeObserver(this); } } private: FSFloaterSearch* mParent; LLUUID mID; }; ///// Silly Classified Clickthrough Class ///// class FSDispatchClassifiedClickThrough : public LLDispatchHandler { public: virtual bool operator()( const LLDispatcher* dispatcher, const std::string& key, const LLUUID& invoice, const sparam_t& strings) { if (strings.size() != 4) return false; LLUUID classified_id(strings[0]); S32 teleport_clicks = atoi(strings[1].c_str()); S32 map_clicks = atoi(strings[2].c_str()); S32 profile_clicks = atoi(strings[3].c_str()); LLPanelProfileClassified::setClickThrough( classified_id, teleport_clicks, map_clicks, profile_clicks, false); return true; } }; static FSDispatchClassifiedClickThrough sClassifiedClickThrough; SearchQuery::SearchQuery() : category("category", "") , query("query") {} //////////////////////////////////////// // The floater itself // //////////////////////////////////////// FSFloaterSearch::FSFloaterSearch(const Params& key) : LLFloater(key) { mRemoteParcelObserver = new FSSearchRemoteParcelInfoObserver(this, false); mRemoteParcelEventLocationObserver = new FSSearchRemoteParcelInfoObserver(this, true); mAvatarPropertiesObserver = new FSSearchAvatarPropertiesObserver(this); mEventNotifierConnection = gEventNotifier.setNewEventCallback(boost::bind(&FSFloaterSearch::displayEventDetails, this, boost::placeholders::_1)); } FSFloaterSearch::~FSFloaterSearch() { mEventNotifierConnection.disconnect(); delete mRemoteParcelObserver; delete mRemoteParcelEventLocationObserver; delete mAvatarPropertiesObserver; gGenericDispatcher.addHandler("classifiedclickthrough", nullptr); } // virtual void FSFloaterSearch::onOpen(const LLSD& key) { Params p(key); mPanelWeb->loadURL(p.search); if (key.has("query")) { mTabContainer->selectTabPanel(mPanelWeb); } else if (key.has("tab") && key["tab"].asString() == "groups") { mTabContainer->selectTabPanel(mPanelGroups); } FSSearchPanelBase* current_panel = dynamic_cast(mTabContainer->getCurrentPanel()); if (current_panel) { current_panel->focusDefaultElement(); } } //virtual void FSFloaterSearch::onClose(bool app_quitting) { if (mTabContainer) { gSavedSettings.setS32("FSLastSearchTab", mTabContainer->getCurrentPanelIndex()); } } bool FSFloaterSearch::postBuild() { childSetAction("people_profile_btn", boost::bind(&FSFloaterSearch::onBtnPeopleProfile, this)); childSetAction("people_message_btn", boost::bind(&FSFloaterSearch::onBtnPeopleIM, this)); childSetAction("people_friend_btn", boost::bind(&FSFloaterSearch::onBtnPeopleFriend, this)); childSetAction("group_profile_btn", boost::bind(&FSFloaterSearch::onBtnGroupProfile, this)); childSetAction("group_message_btn", boost::bind(&FSFloaterSearch::onBtnGroupChat, this)); childSetAction("group_join_btn", boost::bind(&FSFloaterSearch::onBtnGroupJoin, this)); childSetAction("event_reminder_btn", boost::bind(&FSFloaterSearch::onBtnEventReminder, this)); childSetAction("teleport_btn", boost::bind(&FSFloaterSearch::onBtnTeleport, this)); childSetAction("map_btn", boost::bind(&FSFloaterSearch::onBtnMap, this)); resetVerbs(); mPanelPeople = findChild("panel_ls_people"); mPanelGroups = findChild("panel_ls_groups"); mPanelPlaces = findChild("panel_ls_places"); mPanelEvents = findChild("panel_ls_events"); mPanelLand = findChild("panel_ls_land"); mPanelClassifieds = findChild("panel_ls_classifieds"); mPanelWeb = findChild("panel_ls_web"); mDetailsPanel = getChild("panel_ls_details"); mDetailTitle = getChild("title"); mDetailDesc = getChild("desc"); mDetailAux1 = getChild("aux1"); mDetailAux2 = getChild("aux2"); mDetailLocation = getChild("location"); mDetailSnapshot = getChild("snapshot"); mDetailSnapshotParcel = getChild("snapshot_parcel"); mDetailMaturity = getChild("maturity_icon"); mTabContainer = getChild("ls_tabs"); mTabContainer->setCommitCallback(boost::bind(&FSFloaterSearch::onTabChange, this)); flushDetails(); mDetailsPanel->setVisible(false); mHasSelection = false; if (!mTabContainer->selectTab(gSavedSettings.getS32("FSLastSearchTab"))) { mTabContainer->selectFirstTab(); } return TRUE; } void FSFloaterSearch::onTabChange() { LL_INFOS() << "onTabChange()()" << LL_ENDL; flushDetails(); LLPanel* active_panel = mTabContainer->getCurrentPanel(); if (active_panel == mPanelWeb) { mDetailsPanel->setVisible(false); mPanelWeb->resetFocusOnLoad(); } else if (active_panel == mPanelPeople) { mDetailsPanel->setVisible(mHasSelection); } if (active_panel == mPanelPeople || active_panel == mPanelGroups) { mDetailSnapshotParcel->setVisible(FALSE); mDetailSnapshot->setVisible(TRUE); } else if (active_panel == mPanelPlaces || active_panel == mPanelLand || active_panel == mPanelEvents || active_panel == mPanelClassifieds) { mDetailSnapshot->setVisible(FALSE); mDetailSnapshotParcel->setVisible(TRUE); } } //static template T* FSFloaterSearch::getSearchPanel(const std::string& panel_name) { FSFloaterSearch* search_instance = LLFloaterReg::findTypedInstance("search"); if (search_instance && search_instance->mTabContainer) { return dynamic_cast(search_instance->mTabContainer->getPanelByName(panel_name)); } else { return nullptr; } } void FSFloaterSearch::onSelectedItem(const LLUUID& selected_item, ESearchCategory type) { LL_INFOS() << "onSelectedItem()" << LL_ENDL; if (!selected_item.isNull()) { mSelectedID = selected_item; resetVerbs(); flushDetails(); switch (type) { case SC_AVATAR: { LLAvatarPropertiesProcessor::getInstance()->addObserver(selected_item, mAvatarPropertiesObserver); LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(selected_item); } break; case SC_GROUP: mGroupPropertiesRequest = new FSSearchGroupInfoObserver(selected_item, this); break; case SC_PLACE: mRemoteParcelObserver->setParcelID(selected_item); break; case SC_CLASSIFIED: LLAvatarPropertiesProcessor::getInstance()->addObserver(selected_item, mAvatarPropertiesObserver); LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(selected_item); gGenericDispatcher.addHandler("classifiedclickthrough", &sClassifiedClickThrough); break; } setLoadingProgress(true); } } void FSFloaterSearch::onSelectedEvent(const S32 selected_event) { LL_INFOS() << "onSelectedEvent()()" << LL_ENDL; resetVerbs(); flushDetails(); gMessageSystem->newMessageFast(_PREHASH_EventInfoRequest); gMessageSystem->nextBlockFast(_PREHASH_AgentData); gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgentID); gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); gMessageSystem->nextBlockFast(_PREHASH_EventData); gMessageSystem->addU32Fast(_PREHASH_EventID, selected_event); gAgent.sendReliableMessage(); } void FSFloaterSearch::displayParcelDetails(const LLParcelData& parcel_data) { S32 region_x; S32 region_y; S32 region_z; region_x = ll_round(parcel_data.global_x) % REGION_WIDTH_UNITS; region_y = ll_round(parcel_data.global_y) % REGION_WIDTH_UNITS; region_z = ll_round(parcel_data.global_z); // HACK: Flag 0x2 == adult region, // Flag 0x1 == mature region, otherwise assume PG if (parcel_data.flags & 0x2) { mDetailMaturity->setValue("Parcel_R_Dark"); } else if (parcel_data.flags & 0x1) { mDetailMaturity->setValue("Parcel_M_Dark"); } else { mDetailMaturity->setValue("Parcel_PG_Dark"); } LLStringUtil::format_map_t map; map["DWELL"] = llformat("%.0f", (F64)parcel_data.dwell); map["AREA"] = llformat("%d m²", parcel_data.actual_area); map["LOCATION"] = llformat("%s (%d, %d, %d)", parcel_data.sim_name.c_str(), region_x, region_y, region_z); mParcelGlobal = LLVector3d(parcel_data.global_x, parcel_data.global_y, parcel_data.global_z); mDetailsPanel->setVisible(mTabContainer->getCurrentPanel()->getName() == "panel_ls_places" || mTabContainer->getCurrentPanel()->getName() == "panel_ls_land"); mHasSelection = true; mDetailMaturity->setVisible(true); mDetailTitle->setValue(parcel_data.name); mDetailDesc->setValue(parcel_data.desc); mDetailAux1->setValue(getString("string.traffic", map)); mDetailAux2->setValue(getString("string.area", map)); mDetailLocation->setValue(getString("string.location", map)); mDetailSnapshotParcel->setValue(parcel_data.snapshot_id); childSetVisible("teleport_btn", true); childSetVisible("map_btn", true); setLoadingProgress(false); } void FSFloaterSearch::displayAvatarDetails(LLAvatarData* avatar_data) { if (avatar_data) { LLStringUtil::format_map_t map; map["AGE"] = LLDateUtil::ageFromDate(avatar_data->born_on, LLDate::now()); if (avatar_data->partner_id.notNull()) { map["PARTNER"] = LLSLURL("agent", avatar_data->partner_id, "inspect").getSLURLString(); mDetailAux2->setValue(getString("string.partner", map)); } mDetailsPanel->setVisible(mTabContainer->getCurrentPanel()->getName() == "panel_ls_people"); mHasSelection = true; mDetailTitle->setValue(LLTrans::getString("LoadingData")); mDetailDesc->setValue(avatar_data->about_text); mDetailSnapshot->setValue(avatar_data->image_id); mDetailAux1->setValue(avatar_data->hide_age ? "" : getString("string.age", map)); LLAvatarNameCache::get(avatar_data->avatar_id, boost::bind(&FSFloaterSearch::avatarNameUpdatedCallback,this, _1, _2)); childSetVisible("people_profile_btn", true); childSetVisible("people_message_btn", true); childSetVisible("people_friend_btn", true); getChildView("people_friend_btn")->setEnabled(!LLAvatarActions::isFriend(avatar_data->avatar_id)); } } void FSFloaterSearch::displayGroupDetails(LLGroupMgrGroupData*& group_data) { if (group_data) { LLStringUtil::format_map_t map; map["MEMBER_COUNT"] = llformat("%d",group_data->mMemberCount); map["FOUNDER"] = LLSLURL("agent", group_data->mFounderID, "inspect").getSLURLString(); mDetailsPanel->setVisible(mTabContainer->getCurrentPanel()->getName() == "panel_ls_groups"); mHasSelection = true; mDetailTitle->setValue(LLTrans::getString("LoadingData")); mDetailDesc->setValue(group_data->mCharter); mDetailSnapshot->setValue(group_data->mInsigniaID); mDetailAux1->setValue(getString("string.members", map)); mDetailAux2->setValue(getString("string.founder", map)); LLGroupData agent_gdatap; bool is_member = gAgent.getGroupData(getSelectedID(),agent_gdatap) || gAgent.isGodlike(); bool join_btn_enabled = !is_member && group_data->mOpenEnrollment; childSetVisible("group_profile_btn", true); childSetVisible("group_message_btn", true); childSetVisible("group_join_btn", true); getChildView("group_join_btn")->setEnabled(join_btn_enabled); getChildView("group_message_btn")->setEnabled(is_member); gCacheName->getGroup(getSelectedID(), boost::bind(&FSFloaterSearch::groupNameUpdatedCallback, this, _1, _2, _3)); } } void FSFloaterSearch::displayClassifiedDetails(LLAvatarClassifiedInfo*& c_info) { if (c_info) { if (c_info->flags & CLASSIFIED_FLAG_MATURE) { mDetailMaturity->setValue("Parcel_M_Dark"); } else { mDetailMaturity->setValue("Parcel_PG_Dark"); } LLStringUtil::format_map_t map; map["LISTING_PRICE"] = llformat("L$%d", c_info->price_for_listing); map["SLURL"] = LLSLURL("parcel", c_info->parcel_id, "about").getSLURLString(); mDetailsPanel->setVisible(mTabContainer->getCurrentPanel()->getName() == "panel_ls_classifieds"); mHasSelection = true; mDetailMaturity->setVisible(true); mParcelGlobal = c_info->pos_global; mDetailTitle->setValue(c_info->name); mDetailDesc->setValue(c_info->description); mDetailSnapshotParcel->setValue(c_info->snapshot_id); mDetailAux1->setValue(getString("string.listing_price", map)); mDetailLocation->setValue(getString("string.slurl", map)); childSetVisible("teleport_btn", true); childSetVisible("map_btn", true); setLoadingProgress(false); } } bool FSFloaterSearch::displayEventDetails(LLEventStruct event) { if (event.flags == EVENT_FLAG_ADULT) { mDetailMaturity->setValue("Parcel_R_Dark"); } else if (event.flags == EVENT_FLAG_MATURE) { mDetailMaturity->setValue("Parcel_M_Dark"); } else { mDetailMaturity->setValue("Parcel_PG_Dark"); } S32 region_x; S32 region_y; S32 region_z; region_x = (S64)ll_round(event.globalPos.mdV[VX]) % REGION_WIDTH_UNITS; region_y = (S64)ll_round(event.globalPos.mdV[VY]) % REGION_WIDTH_UNITS; region_z = (S32)ll_round(event.globalPos.mdV[VZ]); LLStringUtil::format_map_t map; map["DURATION"] = llformat("%d:%.2d", event.duration / 60, event.duration % 60); map["LOCATION"] = llformat("%s (%d, %d, %d)", event.simName.c_str(), region_x, region_y, region_z); if (event.cover > 0) { map["COVERCHARGE"] = llformat("L$%d", event.cover); mDetailAux2->setValue(getString("string.covercharge", map)); } mParcelGlobal = event.globalPos; mEventID = event.eventId; mDetailsPanel->setVisible(mTabContainer->getCurrentPanel()->getName() == "panel_ls_events"); mHasSelection = true; mDetailMaturity->setVisible(true); mDetailTitle->setValue(event.eventName); mDetailDesc->setValue(event.desc); mDetailAux1->setValue(getString("string.duration", map)); mDetailLocation->setValue(getString("string.location", map)); mDetailSnapshotParcel->setValue(LLUUID::null); childSetVisible("teleport_btn", true); childSetVisible("map_btn", true); childSetVisible("event_reminder_btn", true); LLWorldMapMessage::getInstance()->sendNamedRegionRequest(event.simName, boost::bind(&FSFloaterSearch::regionHandleCallback, this, _1, event.globalPos), "", false); return true; } void FSFloaterSearch::regionHandleCallback(U64 region_handle, LLVector3d pos_global) { std::string url = gAgent.getRegionCapability("RemoteParcelRequest"); if (!url.empty()) { auto region_origin = from_region_handle(region_handle); LLVector3 pos_region(LLVector3(pos_global - region_origin)); LLRemoteParcelInfoProcessor::getInstance()->requestRegionParcelInfo(url, LLUUID::null, pos_region, pos_global, mRemoteParcelEventLocationObserver->getObserverHandle()); } else { setLoadingProgress(false); } } void FSFloaterSearch::displayEventParcelImage(const LLParcelData& parcel_data) { mDetailSnapshotParcel->setValue(parcel_data.snapshot_id); setLoadingProgress(false); } void FSFloaterSearch::avatarNameUpdatedCallback(const LLUUID& id, const LLAvatarName& av_name) { if (id == getSelectedID()) { mDetailTitle->setValue(av_name.getCompleteName()); setLoadingProgress(false); } // Otherwise possibly a request for an older selection, ignore it. } void FSFloaterSearch::groupNameUpdatedCallback(const LLUUID& id, const std::string& name, bool is_group) { if (id == getSelectedID()) { mDetailTitle->setValue( LLSD(name) ); setLoadingProgress(false); } // Otherwise possibly a request for an older selection, ignore it. } void FSFloaterSearch::setLoadingProgress(bool started) { LLLoadingIndicator* indicator = getChild("loading"); indicator->setVisible(started); if (started) { indicator->start(); } else { indicator->stop(); } } void FSFloaterSearch::resetVerbs() { childSetVisible("people_profile_btn", false); childSetVisible("people_message_btn", false); childSetVisible("people_friend_btn", false); childSetVisible("group_profile_btn", false); childSetVisible("group_message_btn", false); childSetVisible("group_join_btn", false); childSetVisible("event_reminder_btn", false); childSetVisible("teleport_btn", false); childSetVisible("map_btn", false); } void FSFloaterSearch::flushDetails() { LL_INFOS() << "flushDetails()" << LL_ENDL; mDetailTitle->setValue(""); mDetailDesc->setValue(""); mDetailAux1->setValue(""); mDetailAux2->setValue(""); mDetailLocation->setValue(""); mDetailSnapshot->setValue(LLSD()); mDetailMaturity->setVisible(false); mParcelGlobal.setZero(); } void FSFloaterSearch::onBtnPeopleProfile() { LLAvatarActions::showProfile(getSelectedID()); } void FSFloaterSearch::onBtnPeopleIM() { LLAvatarActions::startIM(getSelectedID()); } void FSFloaterSearch::onBtnPeopleFriend() { LLAvatarActions::requestFriendshipDialog(getSelectedID()); } void FSFloaterSearch::onBtnGroupProfile() { LLGroupActions::show(getSelectedID()); } void FSFloaterSearch::onBtnGroupChat() { LLGroupActions::startIM(getSelectedID()); } void FSFloaterSearch::onBtnGroupJoin() { LLGroupActions::join(getSelectedID()); } void FSFloaterSearch::onBtnTeleport() { if (!mParcelGlobal.isExactlyZero()) { gAgent.teleportViaLocationLookAt(mParcelGlobal); LLFloaterWorldMap::getInstance()->trackLocation(mParcelGlobal); /// What should we do when when we teleport? The default (1) is to close the floater, /// the user may elect to minimize the floater (2), or to do nothing (any other setting) static LLCachedControl teleport_action(gSavedSettings, "FSLegacySearchActionOnTeleport"); if (teleport_action == 1) { closeFloater(); } else if (teleport_action == 2) { setMinimized(TRUE); } } } void FSFloaterSearch::onBtnMap() { if (!mParcelGlobal.isExactlyZero()) { LLFloaterWorldMap::getInstance()->trackLocation(mParcelGlobal); LLFloaterReg::showInstance("world_map", "center"); } } void FSFloaterSearch::onBtnEventReminder() { gEventNotifier.add(mEventID); } //////////////////////////////////////// // People Search Panel // //////////////////////////////////////// static LLPanelInjector t_panel_fs_search_people("panel_ls_people"); FSPanelSearchPeople::FSPanelSearchPeople() : FSSearchPanelBase() , mQueryID(nullptr) , mStartSearch(0) , mResultsReceived(0) , mResultsContent() , mAvatarNameCallbackConnection() { } FSPanelSearchPeople::~FSPanelSearchPeople() { if (mAvatarNameCallbackConnection.connected()) { mAvatarNameCallbackConnection.disconnect(); } } bool FSPanelSearchPeople::postBuild() { mSearchComboBox = findChild("people_edit"); mSearchResults = findChild("search_results_people"); if (mSearchComboBox) { mSearchComboBox->setCommitCallback(boost::bind(&FSPanelSearchPeople::onBtnFind, this)); fillSearchComboBox(mSearchComboBox); } if (mSearchResults) { mSearchResults->setCommitCallback(boost::bind(&FSPanelSearchPeople::onSelectItem, this)); mSearchResults->setEnabled(FALSE); mSearchResults->setCommentText(LLTrans::getString("no_results")); mSearchResults->setContextMenu(LLScrollListCtrl::MENU_AVATAR); } childSetAction("people_next", boost::bind(&FSPanelSearchPeople::onBtnNext, this)); childSetAction("people_back", boost::bind(&FSPanelSearchPeople::onBtnBack, this)); getChildView("people_next")->setEnabled(FALSE); getChildView("people_back")->setEnabled(FALSE); return TRUE; } void FSPanelSearchPeople::focusDefaultElement() { mSearchComboBox->focusTextEntry(); } void FSPanelSearchPeople::find() { std::string text = mSearchComboBox->getSimple(); boost::trim(text); if (text.size() <= MIN_SEARCH_STRING_SIZE) { mSearchResults->setCommentText(LLTrans::getString("search_short")); return; } if (LLUUID::validate(text)) { LLUUID id(text); mSearchResults->deleteAllItems(); mSearchResults->setCommentText(LLTrans::getString("searching")); mResultsReceived = 0; mNumResultsReturned = 0; if (mAvatarNameCallbackConnection.connected()) { mAvatarNameCallbackConnection.disconnect(); } mAvatarNameCallbackConnection = LLAvatarNameCache::get(id, boost::bind(&FSPanelSearchPeople::onAvatarNameCallback, this, _1, _2)); return; } LLStringUtil::replaceChar(text, '.', ' '); mResultsReceived = 0; if (mQueryID.notNull()) { mQueryID.setNull(); } mQueryID.generate(); if (mStartSearch < 0) { mStartSearch = 0; } gMessageSystem->newMessage("DirFindQuery"); gMessageSystem->nextBlock("AgentData"); gMessageSystem->addUUID("AgentID", gAgentID); gMessageSystem->addUUID("SessionID", gAgentSessionID); gMessageSystem->nextBlock("QueryData"); gMessageSystem->addUUID("QueryID", getQueryID()); gMessageSystem->addString("QueryText", text); gMessageSystem->addU32("QueryFlags", DFQ_PEOPLE); gMessageSystem->addS32("QueryStart", mStartSearch); gAgent.sendReliableMessage(); LL_INFOS("Search") << "Firing off search request: " << getQueryID() << LL_ENDL; mSearchResults->deleteAllItems(); mSearchResults->setCommentText(LLTrans::getString("searching")); mNumResultsReturned = 0; } void FSPanelSearchPeople::onBtnFind() { std::chrono::time_point now = std::chrono::system_clock::now(); auto elapsed = now - lastRequestTime; U64 elapsedMS = std::chrono::duration_cast(elapsed).count(); if(elapsedMS < REQUEST_MIN_ELAPSED_TIME) return; lastRequestTime = now; std::string text = mSearchComboBox->getSimple(); if (!text.empty()) { LLSearchHistory::getInstance()->addEntry(text); } resetSearch(); find(); } void FSPanelSearchPeople::onBtnNext() { mStartSearch += RESULT_PAGE_SIZE; getChildView("people_back")->setEnabled(TRUE); find(); } void FSPanelSearchPeople::onBtnBack() { mStartSearch -= RESULT_PAGE_SIZE; getChildView("people_back")->setEnabled(mStartSearch > 0); find(); } void FSPanelSearchPeople::resetSearch() { mStartSearch = 0; getChildView("people_back")->setEnabled(FALSE); getChildView("people_next")->setEnabled(FALSE); } S32 FSPanelSearchPeople::showNextButton(S32 rows) { bool show_next_button = (mResultsReceived > RESULT_PAGE_SIZE); getChildView("people_next")->setEnabled(show_next_button); if (show_next_button) { rows -= (mResultsReceived - RESULT_PAGE_SIZE); } return rows; } void FSPanelSearchPeople::onSelectItem() { if (!mSearchResults) { return; } FSFloaterSearch* search_instance = LLFloaterReg::findTypedInstance("search"); if (search_instance) { search_instance->FSFloaterSearch::onSelectedItem(mSearchResults->getSelectedValue(), FSFloaterSearch::SC_AVATAR); } } // static void FSPanelSearchPeople::processSearchReply(LLMessageSystem* msg, void**) { LLUUID query_id; std::string first_name; std::string last_name; LLUUID agent_id; msg->getUUIDFast(_PREHASH_QueryData, _PREHASH_QueryID, query_id); msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); // This result is not for us. if (agent_id != gAgentID) { return; } LL_INFOS("Search") << "received search results - QueryID: " << query_id << " AgentID: " << agent_id << LL_ENDL; FSPanelSearchPeople* self = FSFloaterSearch::getSearchPanel("panel_ls_people"); // floater is closed or these are not results from our last request if (!self || query_id != self->getQueryID()) { return; } LLScrollListCtrl* search_results = self->getChild("search_results_people"); if (self->mNumResultsReturned++ == 0) { search_results->deleteAllItems(); } // Check for status messages if (msg->getNumberOfBlocks("StatusData")) { U32 status; msg->getU32("StatusData", "Status", status); if (status & STATUS_SEARCH_PLACES_FOUNDNONE) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("people_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); return; } else if (status & STATUS_SEARCH_PLACES_SHORTSTRING) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_short")); return; } else if (status & STATUS_SEARCH_PLACES_BANNEDWORD) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_banned")); return; } else if (status & STATUS_SEARCH_PLACES_SEARCHDISABLED) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_disabled")); return; } } bool found_one = false; S32 num_new_rows = msg->getNumberOfBlocksFast(_PREHASH_QueryReplies); if (num_new_rows == 0 && self->mResultsReceived == 0) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("people_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } self->mResultsReceived += num_new_rows; num_new_rows = self->showNextButton(num_new_rows); for (S32 i = 0; i < num_new_rows; i++) { msg->getStringFast( _PREHASH_QueryReplies, _PREHASH_FirstName, first_name, i); msg->getStringFast( _PREHASH_QueryReplies, _PREHASH_LastName, last_name, i); msg->getUUIDFast( _PREHASH_QueryReplies, _PREHASH_AgentID, agent_id, i); //msg->getU8Fast( _PREHASH_QueryReplies, _PREHASH_Online, online, i); if (agent_id.isNull()) { LL_INFOS("Search") << "Null result returned for QueryID: " << query_id << LL_ENDL; LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("people_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } else { LL_DEBUGS("Search") << "Got: " << first_name << " " << last_name << " AgentID: " << agent_id << LL_ENDL; search_results->setEnabled(TRUE); found_one = true; std::string avatar_name; avatar_name = LLCacheName::buildFullName(first_name, last_name); LLSD content; LLSD element; element["id"] = agent_id; element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "icon_avatar_offline.tga"; element["columns"][1]["column"] = "username"; element["columns"][1]["value"] = avatar_name; content["name"] = avatar_name; search_results->addElement(element, ADD_BOTTOM); self->mResultsContent[agent_id.asString()] = content; } } if (found_one) { search_results->selectFirstItem(); search_results->setFocus(TRUE); self->onSelectItem(); } } void FSPanelSearchPeople::onAvatarNameCallback(const LLUUID& id, const LLAvatarName& av_name) { if (mAvatarNameCallbackConnection.connected()) { mAvatarNameCallbackConnection.disconnect(); } LLScrollListCtrl* search_results = getChild("search_results_people"); if (av_name.getAccountName() != "(?\?\?).(?\?\?)") { LLSD content; LLSD data; data["id"] = id; data["columns"][0]["column"] = "icon"; data["columns"][0]["type"] = "icon"; data["columns"][0]["value"] = "icon_avatar_offline.tga"; data["columns"][1]["name"] = "username"; data["columns"][1]["value"] = av_name.getUserName(); content["name"] = av_name.getUserName(); search_results->addElement(data); mResultsContent[id.asString()] = content; mResultsReceived = 1; mNumResultsReturned = 1; search_results->setEnabled(TRUE); search_results->selectFirstItem(); search_results->setFocus(TRUE); onSelectItem(); } else { LLStringUtil::format_map_t map; map["[TEXT]"] = getChild("people_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } } //////////////////////////////////////// // Groups Search Panel // //////////////////////////////////////// static LLPanelInjector t_panel_fs_search_groups("panel_ls_groups"); FSPanelSearchGroups::FSPanelSearchGroups() : FSSearchPanelBase() , mQueryID(nullptr) , mStartSearch(0) , mResultsReceived(0) , mResultsContent() { } FSPanelSearchGroups::~FSPanelSearchGroups() { } bool FSPanelSearchGroups::postBuild() { mSearchComboBox = findChild("groups_edit"); mSearchResults = findChild("search_results_groups"); if (mSearchComboBox) { mSearchComboBox->setCommitCallback(boost::bind(&FSPanelSearchGroups::onBtnFind, this)); fillSearchComboBox(mSearchComboBox); } if (mSearchResults) { mSearchResults->setCommitCallback(boost::bind(&FSPanelSearchGroups::onSelectItem, this)); mSearchResults->setEnabled(FALSE); mSearchResults->setCommentText(LLTrans::getString("no_results")); } childSetAction("groups_next", boost::bind(&FSPanelSearchGroups::onBtnNext, this)); childSetAction("groups_back", boost::bind(&FSPanelSearchGroups::onBtnBack, this)); getChildView("groups_next")->setEnabled(FALSE); getChildView("groups_back")->setEnabled(FALSE); lastRequestTime = std::chrono::system_clock::now(); return TRUE; } void FSPanelSearchGroups::focusDefaultElement() { mSearchComboBox->focusTextEntry(); } void FSPanelSearchGroups::find() { std::string text = filterShortWords(mSearchComboBox->getSimple()); if (text.size() == 0) { mSearchResults->setCommentText(LLTrans::getString("search_short")); return; } static LLUICachedControl inc_pg("ShowPGSims", 1); static LLUICachedControl inc_mature("ShowMatureSims", 0); static LLUICachedControl inc_adult("ShowAdultSims", 0); if (!(inc_pg || inc_mature || inc_adult)) { LLNotificationsUtil::add("NoContentToSearch"); return; } U32 scope = 0; if (gAgent.wantsPGOnly()) { scope |= DFQ_PG_SIMS_ONLY; } bool adult_enabled = gAgent.canAccessAdult(); bool mature_enabled = gAgent.canAccessMature(); if (inc_pg) { scope |= DFQ_INC_PG; } if (inc_mature && mature_enabled) { scope |= DFQ_INC_MATURE; } if (inc_adult && adult_enabled) { scope |= DFQ_INC_ADULT; } scope |= DFQ_GROUPS; mResultsReceived = 0; if (mQueryID.notNull()) { mQueryID.setNull(); } mQueryID.generate(); if (mStartSearch < 0) { mStartSearch = 0; } gMessageSystem->newMessage("DirFindQuery"); gMessageSystem->nextBlock("AgentData"); gMessageSystem->addUUID("AgentID", gAgentID); gMessageSystem->addUUID("SessionID", gAgentSessionID); gMessageSystem->nextBlock("QueryData"); gMessageSystem->addUUID("QueryID", getQueryID()); gMessageSystem->addString("QueryText", text); gMessageSystem->addU32("QueryFlags", scope); gMessageSystem->addS32("QueryStart", mStartSearch); gAgent.sendReliableMessage(); LL_DEBUGS("Search") << "Firing off search request: " << getQueryID() << LL_ENDL; mSearchResults->deleteAllItems(); mSearchResults->setCommentText(LLTrans::getString("searching")); mNumResultsReturned = 0; } void FSPanelSearchGroups::onBtnFind() { std::chrono::time_point now = std::chrono::system_clock::now(); auto elapsed = now - lastRequestTime; U64 elapsedMS = std::chrono::duration_cast(elapsed).count(); if(elapsedMS < REQUEST_MIN_ELAPSED_TIME) return; lastRequestTime = now; std::string text = mSearchComboBox->getSimple(); if (!text.empty()) { LLSearchHistory::getInstance()->addEntry(text); } resetSearch(); find(); } void FSPanelSearchGroups::onBtnNext() { mStartSearch += RESULT_PAGE_SIZE; getChildView("groups_back")->setEnabled(TRUE); find(); } void FSPanelSearchGroups::onBtnBack() { mStartSearch -= RESULT_PAGE_SIZE; getChildView("groups_back")->setEnabled(mStartSearch > 0); find(); } void FSPanelSearchGroups::resetSearch() { mStartSearch = 0; getChildView("groups_back")->setEnabled(FALSE); getChildView("groups_next")->setEnabled(FALSE); } S32 FSPanelSearchGroups::showNextButton(S32 rows) { bool show_next_button = (mResultsReceived > RESULT_PAGE_SIZE); getChildView("groups_next")->setEnabled(show_next_button); if (show_next_button) { rows -= (mResultsReceived - RESULT_PAGE_SIZE); } return rows; } void FSPanelSearchGroups::onSelectItem() { if (!mSearchResults) { return; } FSFloaterSearch* search_instance = LLFloaterReg::findTypedInstance("search"); if (search_instance) { search_instance->FSFloaterSearch::onSelectedItem(mSearchResults->getSelectedValue(), FSFloaterSearch::SC_GROUP); } } // static void FSPanelSearchGroups::processSearchReply(LLMessageSystem* msg, void**) { LLUUID query_id; LLUUID group_id; LLUUID agent_id; std::string group_name; S32 members; F32 search_order; msg->getUUIDFast( _PREHASH_QueryData, _PREHASH_QueryID, query_id); msg->getUUIDFast( _PREHASH_AgentData, _PREHASH_AgentID, agent_id); // Not for us if (agent_id != gAgentID) { return; } LL_DEBUGS("Search") << "received directory request - QueryID: " << query_id << " AgentID: " << agent_id << LL_ENDL; FSPanelSearchGroups* self = FSFloaterSearch::getSearchPanel("panel_ls_groups"); // floater is closed or these are not results from our last request if (!self || query_id != self->mQueryID) { return; } LLScrollListCtrl* search_results = self->getChild("search_results_groups"); // Clear "Searching" label on first results if (self->mNumResultsReturned++ == 0) { search_results->deleteAllItems(); } // Check for status messages if (msg->getNumberOfBlocks("StatusData")) { U32 status; msg->getU32("StatusData", "Status", status); if (status & STATUS_SEARCH_PLACES_FOUNDNONE) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("groups_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); return; } else if(status & STATUS_SEARCH_PLACES_SHORTSTRING) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_short")); return; } else if (status & STATUS_SEARCH_PLACES_BANNEDWORD) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_banned")); return; } else if (status & STATUS_SEARCH_PLACES_SEARCHDISABLED) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_disabled")); return; } } bool found_one = false; S32 num_new_rows = msg->getNumberOfBlocksFast(_PREHASH_QueryReplies); if (num_new_rows == 0 && self->mResultsReceived == 0) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("groups_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } self->mResultsReceived += num_new_rows; num_new_rows = self->showNextButton(num_new_rows); for (S32 i = 0; i < num_new_rows; i++) { msg->getUUIDFast( _PREHASH_QueryReplies, _PREHASH_GroupID, group_id, i); msg->getStringFast( _PREHASH_QueryReplies, _PREHASH_GroupName, group_name, i); msg->getS32Fast( _PREHASH_QueryReplies, _PREHASH_Members, members, i); msg->getF32Fast( _PREHASH_QueryReplies, _PREHASH_SearchOrder, search_order,i); if (group_id.isNull()) { LL_DEBUGS("Search") << "No results returned for QueryID: " << query_id << LL_ENDL; LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("groups_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } else { LL_DEBUGS("Search") << "Got: " << group_name << " GroupID: " << group_id << LL_ENDL; search_results->setEnabled(TRUE); found_one = true; LLSD content; LLSD element; element["id"] = group_id; element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "Icon_Group"; element["columns"][1]["column"] = "group_name"; element["columns"][1]["value"] = group_name; element["columns"][2]["column"] = "members"; element["columns"][2]["value"] = members; element["columns"][3]["column"] = "score"; element["columns"][3]["value"] = search_order; content["name"] = group_name; search_results->addElement(element, ADD_BOTTOM); self->mResultsContent[group_id.asString()] = content; } } if (found_one) { search_results->selectFirstItem(); search_results->setFocus(TRUE); self->onSelectItem(); } } //////////////////////////////////////// // Places Search Panel // //////////////////////////////////////// static LLPanelInjector t_panel_fs_search_places("panel_ls_places"); FSPanelSearchPlaces::FSPanelSearchPlaces() : FSSearchPanelBase() , mQueryID(nullptr) , mStartSearch(0) , mResultsReceived(0) , mResultsContent() { mCommitCallbackRegistrar.add("CommitSearch", boost::bind(&FSPanelSearchPlaces::find, this)); } FSPanelSearchPlaces::~FSPanelSearchPlaces() { } bool FSPanelSearchPlaces::postBuild() { mSearchComboBox = findChild("places_edit"); mSearchResults = findChild("search_results_places"); mPlacesCategory = findChild("places_category"); if (mSearchComboBox) { mSearchComboBox->setCommitCallback(boost::bind(&FSPanelSearchPlaces::onBtnFind, this)); fillSearchComboBox(mSearchComboBox); } if (mSearchResults) { mSearchResults->setCommitCallback(boost::bind(&FSPanelSearchPlaces::onSelectItem, this)); mSearchResults->setEnabled(FALSE); mSearchResults->setCommentText(LLTrans::getString("no_results")); } if (mPlacesCategory) { mPlacesCategory->add(LLTrans::getString("all_categories"), LLSD("any")); mPlacesCategory->addSeparator(); for (int category = LLParcel::C_LINDEN; category < LLParcel::C_COUNT; category++) { LLParcel::ECategory eCategory = (LLParcel::ECategory)category; mPlacesCategory->add(LLTrans::getString(LLParcel::getCategoryUIString(eCategory)), LLParcel::getCategoryString(eCategory)); } } childSetAction("places_next", boost::bind(&FSPanelSearchPlaces::onBtnNext, this)); childSetAction("places_back", boost::bind(&FSPanelSearchPlaces::onBtnBack, this)); getChildView("places_next")->setEnabled(FALSE); getChildView("places_back")->setEnabled(FALSE); return TRUE; } void FSPanelSearchPlaces::focusDefaultElement() { mSearchComboBox->focusTextEntry(); } void FSPanelSearchPlaces::find() { std::string text = filterShortWords(mSearchComboBox->getSimple()); if (text.empty()) { mSearchResults->setCommentText(LLTrans::getString("search_short")); return; } static LLUICachedControl inc_pg("ShowPGSims", 1); static LLUICachedControl inc_mature("ShowMatureSims", 0); static LLUICachedControl inc_adult("ShowAdultSims", 0); if (!(inc_pg || inc_mature || inc_adult)) { LLNotificationsUtil::add("NoContentToSearch"); return; } S8 category; std::string category_string = mPlacesCategory->getSelectedValue(); if (category_string == "any") { category = LLParcel::C_ANY; } else { category = LLParcel::getCategoryFromString(category_string); } U32 scope = 0; if (gAgent.wantsPGOnly()) { scope |= DFQ_PG_SIMS_ONLY; } bool adult_enabled = gAgent.canAccessAdult(); bool mature_enabled = gAgent.canAccessMature(); if (inc_pg) { scope |= DFQ_INC_PG; } if (inc_mature && mature_enabled) { scope |= DFQ_INC_MATURE; } if (inc_adult && adult_enabled) { scope |= DFQ_INC_ADULT; } scope |= DFQ_DWELL_SORT; mResultsReceived = 0; if (mQueryID.notNull()) { mQueryID.setNull(); } mQueryID.generate(); if (mStartSearch < 0) { mStartSearch = 0; } gMessageSystem->newMessage("DirPlacesQuery"); gMessageSystem->nextBlock("AgentData"); gMessageSystem->addUUID("AgentID", gAgentID); gMessageSystem->addUUID("SessionID", gAgentSessionID); gMessageSystem->nextBlock("QueryData"); gMessageSystem->addUUID("QueryID", getQueryID()); gMessageSystem->addString("QueryText", text); gMessageSystem->addU32("QueryFlags", scope); gMessageSystem->addS8("Category", category); // TODO: Search filter by region name. gMessageSystem->addString("SimName", ""); gMessageSystem->addS32("QueryStart", mStartSearch); gAgent.sendReliableMessage(); LL_INFOS("Search") << "Firing off places search request: " << getQueryID() << LL_ENDL; mSearchResults->deleteAllItems(); mSearchResults->setCommentText(LLTrans::getString("searching")); mNumResultsReturned = 0; } void FSPanelSearchPlaces::onBtnFind() { std::chrono::time_point now = std::chrono::system_clock::now(); auto elapsed = now - lastRequestTime; U64 elapsedMS = std::chrono::duration_cast(elapsed).count(); if(elapsedMS < REQUEST_MIN_ELAPSED_TIME) return; lastRequestTime = now; std::string text = mSearchComboBox->getSimple(); if (!text.empty()) { LLSearchHistory::getInstance()->addEntry(text); } resetSearch(); find(); } void FSPanelSearchPlaces::onBtnNext() { mStartSearch += RESULT_PAGE_SIZE; getChildView("places_back")->setEnabled(TRUE); find(); } void FSPanelSearchPlaces::onBtnBack() { mStartSearch -= RESULT_PAGE_SIZE; getChildView("places_back")->setEnabled(mStartSearch > 0); find(); } void FSPanelSearchPlaces::resetSearch() { mStartSearch = 0; getChildView("places_back")->setEnabled(FALSE); getChildView("places_next")->setEnabled(FALSE); } S32 FSPanelSearchPlaces::showNextButton(S32 rows) { bool show_next_button = (mResultsReceived > RESULT_PAGE_SIZE); getChildView("places_next")->setEnabled(show_next_button); if (show_next_button) { rows -= (mResultsReceived - RESULT_PAGE_SIZE); } return rows; } void FSPanelSearchPlaces::onSelectItem() { if (!mSearchResults) { return; } FSFloaterSearch* search_instance = LLFloaterReg::findTypedInstance("search"); if (search_instance) { search_instance->FSFloaterSearch::onSelectedItem(mSearchResults->getSelectedValue(), FSFloaterSearch::SC_PLACE); } } // static void FSPanelSearchPlaces::processSearchReply(LLMessageSystem* msg, void**) { LLUUID agent_id; LLUUID query_id; LLUUID parcel_id; std::string name; bool for_sale; bool auction; F32 dwell; msg->getUUID("AgentData", "AgentID", agent_id); msg->getUUID("QueryData", "QueryID", query_id); // Not for us if (agent_id != gAgentID) { return; } LL_DEBUGS("Search") << "received directory request - QueryID: " << query_id << " AgentID: " << agent_id << LL_ENDL; FSPanelSearchPlaces* self = FSFloaterSearch::getSearchPanel("panel_ls_places"); // floater is closed or these are not results from our last request if (!self || query_id != self->getQueryID()) { return; } LLScrollListCtrl* search_results = self->getChild("search_results_places"); // Clear "Searching" label on first results if (self->mNumResultsReturned++ == 0) { search_results->deleteAllItems(); } // Check for status messages if (msg->getNumberOfBlocks("StatusData")) { U32 status; msg->getU32("StatusData", "Status", status); if (status & STATUS_SEARCH_PLACES_FOUNDNONE) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("places_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); return; } else if(status & STATUS_SEARCH_PLACES_SHORTSTRING) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_short")); return; } else if (status & STATUS_SEARCH_PLACES_BANNEDWORD) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_banned")); return; } else if (status & STATUS_SEARCH_PLACES_SEARCHDISABLED) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_disabled")); return; } else if (status & STATUS_SEARCH_PLACES_ESTATEEMPTY) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_disabled")); return; } } bool found_one = false; S32 num_new_rows = msg->getNumberOfBlocks("QueryReplies"); if (num_new_rows == 0 && self->mResultsReceived == 0) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("places_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } self->mResultsReceived += num_new_rows; num_new_rows = self->showNextButton(num_new_rows); for (S32 i = 0; i < num_new_rows; i++) { msg->getUUID( "QueryReplies", "ParcelID", parcel_id, i); msg->getString( "QueryReplies", "Name", name, i); msg->getBOOL( "QueryReplies", "ForSale", for_sale,i); msg->getBOOL( "QueryReplies", "Auction", auction, i); msg->getF32( "QueryReplies", "Dwell", dwell, i); if (parcel_id.isNull()) { LL_DEBUGS("Search") << "Null result returned for QueryID: " << query_id << LL_ENDL; LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("places_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } else { LL_DEBUGS("Search") << "Got: " << name << " ParcelID: " << parcel_id << LL_ENDL; search_results->setEnabled(TRUE); found_one = true; LLSD content; LLSD element; element["id"] = parcel_id; if (auction) { element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "Icon_Auction"; } else if (for_sale) { element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "Icon_For_Sale"; } else { element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "Icon_Place"; } element["columns"][1]["column"] = "place_name"; element["columns"][1]["value"] = name; content["name"] = name; std::string buffer = llformat("%.0f", (F64)dwell); element["columns"][2]["column"] = "dwell"; element["columns"][2]["value"] = buffer; search_results->addElement(element, ADD_BOTTOM); self->mResultsContent[parcel_id.asString()] = content; } } if (found_one) { search_results->selectFirstItem(); search_results->setFocus(TRUE); self->onSelectItem(); } } //////////////////////////////////////// // Land Search Panel // //////////////////////////////////////// static LLPanelInjector t_panel_fs_search_land("panel_ls_land"); FSPanelSearchLand::FSPanelSearchLand() : FSSearchPanelBase() , mQueryID(nullptr) , mStartSearch(0) , mResultsReceived(0) , mResultsContent() { mCommitCallbackRegistrar.add("CommitSearch", boost::bind(&FSPanelSearchLand::find, this)); } FSPanelSearchLand::~FSPanelSearchLand() { } bool FSPanelSearchLand::postBuild() { mSearchResults = getChild("search_results_land"); mPriceEditor = findChild("price_edit"); mAreaEditor = findChild("area_edit"); if (mSearchResults) { mSearchResults->setCommitCallback(boost::bind(&FSPanelSearchLand::onSelectItem, this)); mSearchResults->setEnabled(FALSE); mSearchResults->setCommentText(LLTrans::getString("no_results")); } if (mPriceEditor) { mPriceEditor->setCommitOnFocusLost(false); mPriceEditor->setCommitCallback(boost::bind(&FSPanelSearchLand::onBtnFind, this)); } if (mAreaEditor) { mAreaEditor->setCommitOnFocusLost(false); mAreaEditor->setCommitCallback(boost::bind(&FSPanelSearchLand::find, this)); } childSetAction("land_find", boost::bind(&FSPanelSearchLand::onBtnFind, this)); childSetAction("land_next", boost::bind(&FSPanelSearchLand::onBtnNext, this)); childSetAction("land_back", boost::bind(&FSPanelSearchLand::onBtnBack, this)); getChildView("land_next")->setEnabled(FALSE); getChildView("land_back")->setEnabled(FALSE); return TRUE; } void FSPanelSearchLand::find() { static LLUICachedControl inc_pg("ShowPGLand", 1); static LLUICachedControl inc_mature("ShowMatureLand", 0); static LLUICachedControl inc_adult("ShowAdultLand", 0); static LLUICachedControl limit_price("FindLandPrice", 1); static LLUICachedControl limit_area("FindLandArea", 1); if (!(inc_pg || inc_mature || inc_adult)) { LLNotificationsUtil::add("NoContentToSearch"); return; } U32 category = ST_ALL; const std::string& selection = findChild("land_category")->getSelectedValue().asString(); if (!selection.empty()) { if (selection == "Auction") { category = ST_AUCTION; } else if (selection == "Mainland") { category = ST_MAINLAND; } else if (selection == "Estate") { category = ST_ESTATE; } } U32 scope = 0; if (gAgent.wantsPGOnly()) { scope |= DFQ_PG_SIMS_ONLY; } bool mature_enabled = gAgent.canAccessMature(); bool adult_enabled = gAgent.canAccessAdult(); if (inc_pg) { scope |= DFQ_INC_PG; } if (inc_mature && mature_enabled) { scope |= DFQ_INC_MATURE; } if (inc_adult && adult_enabled) { scope |= DFQ_INC_ADULT; } const std::string& sort = findChild("land_sort_combo")->getSelectedValue().asString(); if (!sort.empty()) { if (sort == "Name") { scope |= DFQ_NAME_SORT; } else if (sort == "Price") { scope |= DFQ_PRICE_SORT; } else if (sort == "PPM") { scope |= DFQ_PER_METER_SORT; } else if (sort == "Area") { scope |= DFQ_AREA_SORT; } } else { scope |= DFQ_PRICE_SORT; } if (childGetValue("ascending_check").asBoolean()) { scope |= DFQ_SORT_ASC; } if (limit_price) { scope |= DFQ_LIMIT_BY_PRICE; } if (limit_area) { scope |= DFQ_LIMIT_BY_AREA; } S32 price = childGetValue("edit_price").asInteger(); S32 area = childGetValue("edit_area").asInteger(); mResultsReceived = 0; if (mQueryID.notNull()) { mQueryID.setNull(); } mQueryID.generate(); if (mStartSearch < 0) { mStartSearch = 0; } gMessageSystem->newMessage("DirLandQuery"); gMessageSystem->nextBlock("AgentData"); gMessageSystem->addUUID("AgentID", gAgentID); gMessageSystem->addUUID("SessionID", gAgentSessionID); gMessageSystem->nextBlock("QueryData"); gMessageSystem->addUUID("QueryID", getQueryID()); gMessageSystem->addU32("QueryFlags", scope); gMessageSystem->addU32("SearchType", category); gMessageSystem->addS32("Price", price); gMessageSystem->addS32("Area", area); gMessageSystem->addS32("QueryStart", mStartSearch); gAgent.sendReliableMessage(); LL_DEBUGS("Search") << "Firing off places search request: " << getQueryID() << category << LL_ENDL; mSearchResults->deleteAllItems(); mSearchResults->setCommentText(LLTrans::getString("searching")); mNumResultsReturned = 0; } void FSPanelSearchLand::onBtnFind() { std::chrono::time_point now = std::chrono::system_clock::now(); auto elapsed = now - lastRequestTime; U64 elapsedMS = std::chrono::duration_cast(elapsed).count(); if(elapsedMS < REQUEST_MIN_ELAPSED_TIME) return; lastRequestTime = now; resetSearch(); find(); } void FSPanelSearchLand::onBtnNext() { mStartSearch += RESULT_PAGE_SIZE; getChildView("land_back")->setEnabled(TRUE); find(); } void FSPanelSearchLand::onBtnBack() { mStartSearch -= RESULT_PAGE_SIZE; getChildView("land_back")->setEnabled(mStartSearch > 0); find(); } void FSPanelSearchLand::resetSearch() { mStartSearch = 0; getChildView("land_back")->setEnabled(FALSE); getChildView("land_next")->setEnabled(FALSE); } S32 FSPanelSearchLand::showNextButton(S32 rows) { bool show_next_button = (mResultsReceived > RESULT_PAGE_SIZE); getChildView("land_next")->setEnabled(show_next_button); if (show_next_button) { rows -= (mResultsReceived - RESULT_PAGE_SIZE); } return rows; } void FSPanelSearchLand::onSelectItem() { if (!mSearchResults) { return; } FSFloaterSearch* search_instance = LLFloaterReg::findTypedInstance("search"); if (search_instance) { search_instance->FSFloaterSearch::onSelectedItem(mSearchResults->getSelectedValue(), FSFloaterSearch::SC_PLACE); } } // static void FSPanelSearchLand::processSearchReply(LLMessageSystem* msg, void**) { LLUUID agent_id; LLUUID query_id; LLUUID parcel_id; std::string name; std::string land_sku; std::string land_type; bool auction; bool for_sale; S32 price; S32 area; msg->getUUID("AgentData", "AgentID", agent_id); msg->getUUID("QueryData", "QueryID", query_id); // Not for us if (agent_id != gAgentID) { return; } LL_DEBUGS("Search") << "received directory request - QueryID: " << query_id << " AgentID: " << agent_id << LL_ENDL; FSPanelSearchLand* self = FSFloaterSearch::getSearchPanel("panel_ls_land"); // floater is closed or these are not results from our last request if (!self || query_id != self->mQueryID) { return; } LLScrollListCtrl* search_results = self->getChild("search_results_land"); // clear "Searching" label on first results if (self->mNumResultsReturned++ == 0) { search_results->deleteAllItems(); } static LLUICachedControl use_price("FindLandPrice", 1); static LLUICachedControl use_area("FindLandArea", 1); S32 limit_price = self->childGetValue("edit_price").asInteger(); S32 limit_area = self->childGetValue("edit_area").asInteger(); bool found_one = false; S32 num_new_rows = msg->getNumberOfBlocks("QueryReplies"); if (num_new_rows == 0 && self->mResultsReceived == 0) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("events_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } self->mResultsReceived += num_new_rows; S32 not_auction = 0; for (S32 i = 0; i < num_new_rows; i++) { msg->getUUID( "QueryReplies", "ParcelID", parcel_id, i); msg->getString( "QueryReplies", "Name", name, i); msg->getBOOL( "QueryReplies", "Auction", auction, i); msg->getBOOL( "QueryReplies", "ForSale", for_sale, i); msg->getS32( "QueryReplies", "SalePrice", price, i); msg->getS32( "QueryReplies", "ActualArea", area, i); if (parcel_id.isNull()) { LL_DEBUGS("Search") << "Null result returned for QueryID: " << query_id << LL_ENDL; search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("no_results")); } else { LL_DEBUGS("Search") << "Got: " << name << " ClassifiedID: " << parcel_id << LL_ENDL; search_results->setEnabled(TRUE); found_one = true; if (msg->getSizeFast(_PREHASH_QueryReplies, i, _PREHASH_ProductSKU) > 0) { msg->getStringFast( _PREHASH_QueryReplies, _PREHASH_ProductSKU, land_sku, i); land_type = LLProductInfoRequestManager::instance().getDescriptionForSku(land_sku); } else { land_sku.clear(); land_type = LLTrans::getString("land_type_unknown"); } if (parcel_id.isNull()) { continue; } if (use_price && (price > limit_price)) { continue; } if (use_area && (area < limit_area)) { continue; } LLSD content; LLSD element; element["id"] = parcel_id; if (auction) { element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "Icon_Auction"; } else if (for_sale) { element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "Icon_For_Sale"; } else { element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "Icon_Place"; } element["columns"][1]["column"] = "land_name"; element["columns"][1]["value"] = name; content["place_name"] = name; std::string buffer = "Auction"; if (!auction) { buffer = llformat("%d", price); not_auction++; } element["columns"][2]["column"] = "price"; element["columns"][2]["value"] = price; element["columns"][3]["column"] = "area"; element["columns"][3]["value"] = area; if (!auction) { F32 ppm; if (area > 0) { ppm = (F32)price / (F32)area; } else { ppm = 0.f; } std::string ppm_buffer = llformat("%.1f", ppm); element["columns"][4]["column"] = "ppm"; element["columns"][4]["value"] = ppm_buffer; } else { element["columns"][4]["column"] = "ppm"; element["columns"][4]["value"] = "1.0"; } element["columns"][5]["column"] = "land_type"; element["columns"][5]["value"] = land_type; search_results->addElement(element, ADD_BOTTOM); self->mResultsContent[parcel_id.asString()] = content; } // We test against non-auction properties because they don't count towards the page limit. self->showNextButton(not_auction); } if (found_one) { search_results->selectFirstItem(); search_results->setFocus(TRUE); self->onSelectItem(); } } //////////////////////////////////////// // Classifieds Search Panel // //////////////////////////////////////// static LLPanelInjector t_panel_fs_search_classifieds("panel_ls_classifieds"); FSPanelSearchClassifieds::FSPanelSearchClassifieds() : FSSearchPanelBase() , mQueryID(nullptr) , mStartSearch(0) , mResultsReceived(0) , mResultsContent() { mCommitCallbackRegistrar.add("CommitSearch", boost::bind(&FSPanelSearchClassifieds::find, this)); } FSPanelSearchClassifieds::~FSPanelSearchClassifieds() { } bool FSPanelSearchClassifieds::postBuild() { mSearchComboBox = findChild("classifieds_edit"); mSearchResults = getChild("search_results_classifieds"); if (mSearchComboBox) { mSearchComboBox->setCommitCallback(boost::bind(&FSPanelSearchClassifieds::onBtnFind, this)); fillSearchComboBox(mSearchComboBox); } if (mSearchResults) { mSearchResults->setCommitCallback(boost::bind(&FSPanelSearchClassifieds::onSelectItem, this)); mSearchResults->setEnabled(FALSE); mSearchResults->setCommentText(LLTrans::getString("no_results")); } mClassifiedsCategory = getChild("classifieds_category"); if (mClassifiedsCategory) { LLClassifiedInfo::cat_map::iterator iter; mClassifiedsCategory->add(LLTrans::getString("all_categories"), LLSD(0)); mClassifiedsCategory->addSeparator(); for (iter = LLClassifiedInfo::sCategories.begin(); iter != LLClassifiedInfo::sCategories.end(); iter++) { mClassifiedsCategory->add(LLTrans::getString(iter->second), LLSD((S32)iter->first)); } } childSetAction("classifieds_next", boost::bind(&FSPanelSearchClassifieds::onBtnNext, this)); childSetAction("classifieds_back", boost::bind(&FSPanelSearchClassifieds::onBtnBack, this)); getChildView("classifieds_next")->setEnabled(FALSE); getChildView("classifieds_back")->setEnabled(FALSE); return TRUE; } void FSPanelSearchClassifieds::focusDefaultElement() { mSearchComboBox->focusTextEntry(); } void FSPanelSearchClassifieds::find() { std::string text = filterShortWords(mSearchComboBox->getSimple()); if (text.size() == 0) { mSearchResults->setCommentText(LLTrans::getString("search_short")); return; } static LLUICachedControl inc_pg("ShowPGClassifieds", 1); static LLUICachedControl inc_mature("ShowMatureClassifieds", 0); static LLUICachedControl inc_adult("ShowAdultClassifieds", 0); if (!(inc_pg || inc_mature || inc_adult)) { LLNotificationsUtil::add("NoContentToSearch"); return; } U32 category = mClassifiedsCategory->getValue().asInteger(); bool auto_renew = FALSE; U32 flags = pack_classified_flags_request(auto_renew, inc_pg, inc_mature, inc_adult); mResultsReceived = 0; if (mQueryID.notNull()) { mQueryID.setNull(); } mQueryID.generate(); if (mStartSearch < 0) { mStartSearch = 0; } gMessageSystem->newMessageFast(_PREHASH_DirClassifiedQuery); gMessageSystem->nextBlockFast(_PREHASH_AgentData); gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgentID); gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); gMessageSystem->nextBlockFast(_PREHASH_QueryData); gMessageSystem->addUUIDFast(_PREHASH_QueryID, getQueryID()); gMessageSystem->addStringFast(_PREHASH_QueryText, text); gMessageSystem->addU32Fast(_PREHASH_QueryFlags, flags); gMessageSystem->addU32Fast(_PREHASH_Category, category); gMessageSystem->addS32Fast(_PREHASH_QueryStart, mStartSearch); gAgent.sendReliableMessage(); LL_DEBUGS("Search") << "Firing off classified ad search request: " << getQueryID() << LL_ENDL; mSearchResults->deleteAllItems(); mSearchResults->setCommentText(LLTrans::getString("searching")); mNumResultsReturned = 0; } void FSPanelSearchClassifieds::onBtnFind() { std::chrono::time_point now = std::chrono::system_clock::now(); auto elapsed = now - lastRequestTime; U64 elapsedMS = std::chrono::duration_cast(elapsed).count(); if(elapsedMS < REQUEST_MIN_ELAPSED_TIME) return; lastRequestTime = now; std::string text = mSearchComboBox->getSimple(); if (!text.empty()) { LLSearchHistory::getInstance()->addEntry(text); } resetSearch(); find(); } void FSPanelSearchClassifieds::onBtnNext() { mStartSearch += RESULT_PAGE_SIZE; getChildView("classifieds_back")->setEnabled(TRUE); find(); } void FSPanelSearchClassifieds::onBtnBack() { mStartSearch -= RESULT_PAGE_SIZE; getChildView("classifieds_back")->setEnabled(mStartSearch > 0); find(); } void FSPanelSearchClassifieds::resetSearch() { mStartSearch = 0; getChildView("classifieds_back")->setEnabled(FALSE); getChildView("classifieds_next")->setEnabled(FALSE); } S32 FSPanelSearchClassifieds::showNextButton(S32 rows) { bool show_next_button = (mResultsReceived > RESULT_PAGE_SIZE); getChildView("classifieds_next")->setEnabled(show_next_button); if (show_next_button) { rows -= (mResultsReceived - RESULT_PAGE_SIZE); } return rows; } void FSPanelSearchClassifieds::onSelectItem() { if (!mSearchResults) { return; } FSFloaterSearch* search_instance = LLFloaterReg::findTypedInstance("search"); if (search_instance) { search_instance->FSFloaterSearch::onSelectedItem(mSearchResults->getSelectedValue(), FSFloaterSearch::SC_CLASSIFIED); } } // static void FSPanelSearchClassifieds::processSearchReply(LLMessageSystem* msg, void**) { LLUUID agent_id; LLUUID query_id; LLUUID classified_id; std::string name; U32 creation_date; U32 expiration_date; S32 price_for_listing; msg->getUUID("AgentData", "AgentID", agent_id); msg->getUUID("QueryData", "QueryID", query_id); // Not for us if (agent_id != gAgentID) { return; } LL_DEBUGS("Search") << "received directory request - QueryID: " << query_id << " AgentID: " << agent_id << LL_ENDL; FSPanelSearchClassifieds* self = FSFloaterSearch::getSearchPanel("panel_ls_classifieds"); if (msg->getNumberOfBlocks("StatusData")) { U32 status; msg->getU32("StatusData", "Status", status); if (status & STATUS_SEARCH_CLASSIFIEDS_BANNEDWORD) { LLNotificationsUtil::add("SearchWordBanned"); } } // floater is closed or these are not results from our last request if (!self || query_id != self->mQueryID) { return; } LLScrollListCtrl* search_results = self->getChild("search_results_classifieds"); // Clear "Searching" label on first results if (self->mNumResultsReturned++ == 0) { search_results->deleteAllItems(); } // Check for status messages if (msg->getNumberOfBlocks("StatusData")) { U32 status; msg->getU32("StatusData", "Status", status); if (status & STATUS_SEARCH_PLACES_FOUNDNONE) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("classifieds_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); return; } else if(status & STATUS_SEARCH_PLACES_SHORTSTRING) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_short")); return; } else if (status & STATUS_SEARCH_CLASSIFIEDS_BANNEDWORD) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_banned")); return; } else if (status & STATUS_SEARCH_PLACES_SEARCHDISABLED) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_disabled")); return; } } bool found_one = false; S32 num_new_rows = msg->getNumberOfBlocks("QueryReplies"); if (num_new_rows == 0 && self->mResultsReceived == 0) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("classifieds_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } self->mResultsReceived += num_new_rows; num_new_rows = self->showNextButton(num_new_rows); for (S32 i = 0; i < num_new_rows; i++) { msg->getUUID( "QueryReplies", "ClassifiedID", classified_id, i); msg->getString( "QueryReplies", "Name", name, i); msg->getU32( "QueryReplies", "CreationDate", creation_date, i); msg->getU32( "QueryReplies", "ExpirationDate", expiration_date,i); msg->getS32( "QueryReplies", "PriceForListing", price_for_listing,i); if (classified_id.isNull()) { LL_DEBUGS("Search") << "Null result returned for QueryID: " << query_id << LL_ENDL; LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("classifieds_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } else { LL_DEBUGS("Search") << "Got: " << name << " ClassifiedID: " << classified_id << LL_ENDL; search_results->setEnabled(TRUE); found_one = true; LLSD content; LLSD element; element["id"] = classified_id; element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "icon_top_pick.tga"; element["columns"][1]["column"] = "classified_name"; element["columns"][1]["value"] = name; element["columns"][2]["column"] = "price"; element["columns"][2]["value"] = price_for_listing; content["name"] = name; search_results->addElement(element, ADD_BOTTOM); self->mResultsContent[classified_id.asString()] = content; } } if (found_one) { search_results->selectFirstItem(); search_results->setFocus(TRUE); self->onSelectItem(); } } //////////////////////////////////////// // Events Search Panel // //////////////////////////////////////// static LLPanelInjector t_panel_fs_search_events("panel_ls_events"); FSPanelSearchEvents::FSPanelSearchEvents() : FSSearchPanelBase() , mQueryID(nullptr) , mResultsReceived(0) , mStartSearch(0) , mDay(0) , mResultsContent() { mCommitCallbackRegistrar.add("CommitSearch", boost::bind(&FSPanelSearchEvents::find, this)); } FSPanelSearchEvents::~FSPanelSearchEvents() { } bool FSPanelSearchEvents::postBuild() { mSearchComboBox = findChild("events_edit"); mSearchResults = getChild("search_results_events"); mEventsMode = findChild("events_search_mode"); if (mSearchComboBox) { mSearchComboBox->setCommitCallback(boost::bind(&FSPanelSearchEvents::onBtnFind, this)); fillSearchComboBox(mSearchComboBox); } if (mSearchResults) { mSearchResults->setCommitCallback(boost::bind(&FSPanelSearchEvents::onSelectItem, this)); mSearchResults->setEnabled(FALSE); mSearchResults->setCommentText(LLTrans::getString("no_results")); } if (mEventsMode) { mEventsMode->setCommitCallback(boost::bind(&FSPanelSearchEvents::onSearchModeChanged, this)); mEventsMode->selectFirstItem(); } childSetAction("events_next", boost::bind(&FSPanelSearchEvents::onBtnNext, this)); childSetAction("events_back", boost::bind(&FSPanelSearchEvents::onBtnBack, this)); childSetAction("events_tomorrow", boost::bind(&FSPanelSearchEvents::onBtnTomorrow, this)); childSetAction("events_yesterday", boost::bind(&FSPanelSearchEvents::onBtnYesterday, this)); childSetAction("events_today", boost::bind(&FSPanelSearchEvents::onBtnToday, this)); getChildView("events_next")->setEnabled(FALSE); getChildView("events_back")->setEnabled(FALSE); getChildView("events_tomorrow")->setEnabled(FALSE); getChildView("events_yesterday")->setEnabled(FALSE); getChildView("events_today")->setEnabled(FALSE); setDay(0); return TRUE; } void FSPanelSearchEvents::focusDefaultElement() { mSearchComboBox->focusTextEntry(); } void FSPanelSearchEvents::find() { std::string text = filterShortWords(mSearchComboBox->getSimple()); static LLUICachedControl inc_pg("ShowPGEvents", 1); static LLUICachedControl inc_mature("ShowMatureEvents", 0); static LLUICachedControl inc_adult("ShowAdultEvents", 0); if (!(inc_pg || inc_mature || inc_adult)) { LLNotificationsUtil::add("NoContentToSearch"); return; } U32 category = findChild("events_category")->getSelectedValue().asInteger(); U32 scope = DFQ_DATE_EVENTS; if (gAgent.wantsPGOnly()) { scope |= DFQ_PG_SIMS_ONLY; } bool mature_enabled = gAgent.canAccessMature(); bool adult_enabled = gAgent.canAccessAdult(); if (inc_pg) { scope |= DFQ_INC_PG; } if (inc_mature && mature_enabled) { scope |= DFQ_INC_MATURE; } if (inc_adult && adult_enabled) { scope |= DFQ_INC_ADULT; } std::ostringstream string; if ("current" == childGetValue("events_search_mode").asString()) { string << "u|"; } else { string << mDay << "|"; } string << category << "|"; string << text; mResultsReceived = 0; if (mQueryID.notNull()) { mQueryID.setNull(); } mQueryID.generate(); if (mStartSearch < 0) { mStartSearch = 0; } gMessageSystem->newMessage("DirFindQuery"); gMessageSystem->nextBlock("AgentData"); gMessageSystem->addUUID("AgentID", gAgentID); gMessageSystem->addUUID("SessionID", gAgentSessionID); gMessageSystem->nextBlock("QueryData"); gMessageSystem->addUUID("QueryID", getQueryID()); gMessageSystem->addString("QueryText", string.str()); gMessageSystem->addU32("QueryFlags", scope); gMessageSystem->addS32("QueryStart", mStartSearch); gAgent.sendReliableMessage(); LL_INFOS("Search") << "Firing off search request: " << getQueryID() << " Search Text: " << string.str() << LL_ENDL; mSearchResults->deleteAllItems(); mSearchResults->setCommentText(LLTrans::getString("searching")); mNumResultsReturned = 0; } void FSPanelSearchEvents::onBtnFind() { std::chrono::time_point now = std::chrono::system_clock::now(); auto elapsed = now - lastRequestTime; U64 elapsedMS = std::chrono::duration_cast(elapsed).count(); if(elapsedMS < REQUEST_MIN_ELAPSED_TIME) return; lastRequestTime = now; std::string text = mSearchComboBox->getSimple(); if (!text.empty()) { LLSearchHistory::getInstance()->addEntry(text); } resetSearch(); find(); } void FSPanelSearchEvents::onBtnNext() { mStartSearch += RESULT_PAGE_SIZE; getChildView("events_back")->setEnabled(TRUE); find(); } void FSPanelSearchEvents::onBtnBack() { mStartSearch -= RESULT_PAGE_SIZE; getChildView("events_back")->setEnabled(mStartSearch > 0); find(); } void FSPanelSearchEvents::onBtnTomorrow() { resetSearch(); setDay(mDay + 1); find(); } void FSPanelSearchEvents::onBtnYesterday() { resetSearch(); setDay(mDay - 1); find(); } void FSPanelSearchEvents::onBtnToday() { resetSearch(); setDay(0); find(); } void FSPanelSearchEvents::resetSearch() { mStartSearch = 0; getChildView("events_back")->setEnabled(FALSE); getChildView("events_next")->setEnabled(FALSE); } void FSPanelSearchEvents::onSearchModeChanged() { if (mEventsMode->getValue().asString() == "current") { getChildView("events_yesterday")->setEnabled(FALSE); getChildView("events_tomorrow")->setEnabled(FALSE); getChildView("events_today")->setEnabled(FALSE); } else { getChildView("events_yesterday")->setEnabled(TRUE); getChildView("events_tomorrow")->setEnabled(TRUE); getChildView("events_today")->setEnabled(TRUE); } } void FSPanelSearchEvents::setDay(S32 day) { mDay = day; struct tm* internal_time; time_t utc = time_corrected(); utc += day * 24 * 60 * 60; internal_time = utc_to_pacific_time(utc, is_daylight_savings()); std::string buffer = llformat("%d/%d", 1 + internal_time->tm_mon, internal_time->tm_mday); childSetValue("events_date", buffer); } S32 FSPanelSearchEvents::showNextButton(S32 rows) { bool show_next_button = (mResultsReceived > RESULT_PAGE_SIZE); getChildView("events_next")->setEnabled(show_next_button); if (show_next_button) { rows -= (mResultsReceived - RESULT_PAGE_SIZE); } return rows; } void FSPanelSearchEvents::onSelectItem() { if (!mSearchResults) { return; } S32 event_id = mSearchResults->getSelectedValue(); FSFloaterSearch* search_instance = LLFloaterReg::findTypedInstance("search"); if (search_instance) { search_instance->FSFloaterSearch::onSelectedEvent(event_id); } } // static void FSPanelSearchEvents::processSearchReply(LLMessageSystem* msg, void**) { LLUUID agent_id; LLUUID query_id; LLUUID owner_id; std::string name; std::string date; msg->getUUID("AgentData", "AgentID", agent_id); msg->getUUID("QueryData", "QueryID", query_id); // Not for us if (agent_id != gAgentID) { return; } LL_DEBUGS("Search") << "received directory request - QueryID: " << query_id << " AgentID: " << agent_id << LL_ENDL; FSPanelSearchEvents* self = FSFloaterSearch::getSearchPanel("panel_ls_events"); // floater is closed or these are not results from our last request if (!self || query_id != self->mQueryID) { return; } LLScrollListCtrl* search_results = self->getChild("search_results_events"); // Clear "Searching" label on first results if (self->mNumResultsReturned++ == 0) { search_results->deleteAllItems(); } // Check for status messages if (msg->getNumberOfBlocks("StatusData")) { U32 status; msg->getU32("StatusData", "Status", status); if (status & STATUS_SEARCH_EVENTS_FOUNDNONE) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("events_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); return; } else if(status & STATUS_SEARCH_EVENTS_SHORTSTRING) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_short")); return; } else if (status & STATUS_SEARCH_EVENTS_BANNEDWORD) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_banned")); return; } else if (status & STATUS_SEARCH_EVENTS_SEARCHDISABLED) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_disabled")); return; } else if (status & STATUS_SEARCH_EVENTS_NODATEOFFSET) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_no_date_offset")); return; } else if (status & STATUS_SEARCH_EVENTS_NOCATEGORY) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_no_events_category")); return; } else if (status & STATUS_SEARCH_EVENTS_NOQUERY) { search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("search_no_query")); return; } } S32 num_new_rows = msg->getNumberOfBlocks("QueryReplies"); if (num_new_rows == 0 && self->mResultsReceived == 0) { LLStringUtil::format_map_t map; map["[TEXT]"] = self->getChild("events_edit")->getValue().asString(); search_results->setEnabled(FALSE); search_results->setCommentText(LLTrans::getString("not_found", map)); } self->mResultsReceived += num_new_rows; num_new_rows = self->showNextButton(num_new_rows); static LLUICachedControl inc_pg("ShowPGEvents", 1); static LLUICachedControl inc_mature("ShowMatureEvents", 0); static LLUICachedControl inc_adult("ShowAdultEvents", 0); bool found_one = false; for (S32 i = 0; i < num_new_rows; i++) { U32 event_id; U32 unix_time; U32 event_flags; msg->getUUID( "QueryReplies", "OwnerID", owner_id, i); msg->getString( "QueryReplies", "Name", name, i); msg->getU32( "QueryReplies", "EventID", event_id, i); msg->getString( "QueryReplies", "Date", date, i); msg->getU32( "QueryReplies", "UnixTime", unix_time, i); msg->getU32( "QueryReplies", "EventFlags", event_flags,i); // Skip empty events... if (owner_id.isNull()) { LL_INFOS("Search") << "Skipped " << event_id << " because of a nullptr owner result" << LL_ENDL; continue; } // Skips events that don't match our scope... if (((event_flags & (EVENT_FLAG_ADULT | EVENT_FLAG_MATURE)) == EVENT_FLAG_NONE) && !inc_pg) { LL_INFOS("Search") << "Skipped " << event_id << " because it was out of scope" << LL_ENDL; continue; } if ((event_flags & EVENT_FLAG_MATURE) && !inc_mature) { LL_INFOS("Search") << "Skipped " << event_id << " because it was out of scope" << LL_ENDL; continue; } if ((event_flags & EVENT_FLAG_ADULT) && !inc_adult) { LL_INFOS("Search") << "Skipped " << event_id << " because it was out of scope" << LL_ENDL; continue; } search_results->setEnabled(TRUE); found_one = true; LLSD content; LLSD element; element["id"] = llformat("%u", event_id); if (event_flags == EVENT_FLAG_ADULT) { element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "Icon_Legacy_Event_Adult"; } else if (event_flags == EVENT_FLAG_MATURE) { element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "Icon_Legacy_Event_Mature"; } else { element["columns"][0]["column"] = "icon"; element["columns"][0]["type"] = "icon"; element["columns"][0]["value"] = "Icon_Legacy_Event_PG"; } element["columns"][1]["column"] = "name"; element["columns"][1]["value"] = name; element["columns"][2]["column"] = "date"; element["columns"][2]["value"] = date; element["columns"][3]["column"] = "time"; element["columns"][3]["value"] = llformat("%u", unix_time); content["name"] = name; content["event_id"] = (S32)event_id; search_results->addElement(element, ADD_BOTTOM); std::string event = llformat("%u", event_id); self->mResultsContent[event] = content; } if (found_one) { search_results->selectFirstItem(); search_results->setFocus(TRUE); self->onSelectItem(); } } //////////////////////////////////////// // WebSearch Panel // //////////////////////////////////////// static LLPanelInjector t_panel_fs_search_web("panel_ls_web"); FSPanelSearchWeb::FSPanelSearchWeb() : FSSearchPanelBase() , mWebBrowser(nullptr) , mResetFocusOnLoad(false) { // Second Life grids use a different URL format now mCategoryPaths = LLSD::emptyMap(); if (LLGridManager::getInstance()->isInSecondlife()) { // declare a map that transforms a category name into // the parameter list that is used to search that category mCategoryPaths["people"] = "collection_chosen=people"; mCategoryPaths["places"] = "collection_chosen=places"; mCategoryPaths["events"] = "collection_chosen=events"; mCategoryPaths["groups"] = "collection_chosen=groups"; mCategoryPaths["destinations"] = "collection_chosen=destinations"; mCategoryPaths["classifieds"] = "search_type=classified"; mCategoryPaths["wiki"] = "search/wiki"; // not sure if this is still a thing in the new search mCategoryPaths["all"] = mCategoryPaths["people"].asString() + "&" + mCategoryPaths["places"].asString() + "&" + mCategoryPaths["events"].asString() + "&" + mCategoryPaths["groups"].asString() + "&" + mCategoryPaths["destinations"].asString(); } // OpenSim currently still uses the old URL format else { // declare a map that transforms a category name into // the URL suffix that is used to search that category mCategoryPaths["all"] = "search"; mCategoryPaths["people"] = "search/people"; mCategoryPaths["places"] = "search/places"; mCategoryPaths["events"] = "search/events"; mCategoryPaths["groups"] = "search/groups"; mCategoryPaths["wiki"] = "search/wiki"; mCategoryPaths["destinations"] = "destinations"; mCategoryPaths["classifieds"] = "classifieds"; } } bool FSPanelSearchWeb::postBuild() { mWebBrowser = getChild("search_browser"); return TRUE; } void FSPanelSearchWeb::loadURL(const SearchQuery &p) { if (!mWebBrowser || !p.validateBlock()) { return; } // CATEGORY is no longer used as part of the path on Second Life grids LLSD subs = LLSD().with("CATEGORY", ""); // on OpenSim grids it probably is currently still being used, so keep the old behavior if (!LLGridManager::getInstance()->isInSecondlife()) { // work out the subdir to use based on the requested category LLSD subs = LLSD().with("CATEGORY", (mCategoryPaths.has(p.category.getValue()) ? mCategoryPaths[p.category.getValue()].asString() : mCategoryPaths["all"].asString())); } // add the search query string subs["QUERY"] = LLURI::escape(p.query.getValue()); // add the permissions token that login.cgi gave us // We use "search_token", and fallback to "auth_token" if not present. LLSD search_token = LLLoginInstance::getInstance()->getResponse("search_token"); if (search_token.asString().empty()) { search_token = LLLoginInstance::getInstance()->getResponse("auth_token"); } subs["AUTH_TOKEN"] = search_token.asString(); // add the user's preferred maturity (can be changed via prefs) std::string maturity; // on Second Life grids, the maturity level is now a "&maturity" parameter that's not in the provided search URL if (LLGridManager::getInstance()->isInSecondlife()) { if (gAgent.prefersAdult()) { maturity = "gma"; // PG,Mature,Adult } else if (gAgent.prefersMature()) { maturity = "gm"; // PG,Mature } else { maturity = "g"; // PG } // not used on the SL search anymore, so clear out the respective parameter subs["MATURITY"] = ""; } // OpenSim probably still uses the old maturity variant, so keep the old behavior here else { if (gAgent.prefersAdult()) { maturity = "42"; // PG,Mature,Adult } else if (gAgent.prefersMature()) { maturity = "21"; // PG,Mature } else { maturity = "13"; // PG } subs["MATURITY"] = maturity; } // add the user's god status subs["GODLIKE"] = gAgent.isGodlike() ? "1" : "0"; // Get the search URL and expand all of the substitutions // (also adds things like [LANGUAGE], [VERSION], [OS], etc.) // add the maturity and category variables to the new Second Life search URL //std::string url = gAgent.getRegion() != nullptr ? gAgent.getRegion()->getSearchServerURL() : gSavedSettings.getString(LLGridManager::getInstance()->isInOpenSim() ? "OpenSimSearchURL" : "SearchURL"); std::string url = gSavedSettings.getString("SearchURL"); if (LLGridManager::getInstance()->isInSecondlife()) { url.append("&maturity=" + maturity + "&" + mCategoryPaths[p.category.getValue()].asString()); } url = LLWeb::expandURLSubstitutions(url, subs); // Finally, load the URL in the webpanel mWebBrowser->navigateTo(url, HTTP_CONTENT_TEXT_HTML); } void FSPanelSearchWeb::focusDefaultElement() { mWebBrowser->setFocus(TRUE); } void FSPanelSearchWeb::draw() { if (mResetFocusOnLoad) { focusDefaultElement(); mResetFocusOnLoad = false; } FSSearchPanelBase::draw(); } //////////////////////////////////////// // Local functions // //////////////////////////////////////// std::string filterShortWords(std::string query_string) { if (query_string.length() < 1) { return ""; } std::string final_query; bool filtered = false; boost::char_separator sep(" "); boost::tokenizer > tokens(query_string, sep); boost::tokenizer >::iterator iter = tokens.begin(); boost::tokenizer >::iterator last = tokens.end(); boost::tokenizer >::iterator temp; for (; iter != last; ++iter) { if ((*iter).length() > MIN_SEARCH_STRING_SIZE) { final_query.append((*iter)); temp = iter; ++temp; if (temp != last) { final_query.append(" "); } } else { filtered = true; } } if (filtered) { LLSD args = LLSD().with("FINALQUERY", final_query); LLNotificationsUtil::add("SeachFilteredOnShortWords", args); } return final_query; } void fillSearchComboBox(LLSearchComboBox* search_combo) { if (search_combo == nullptr) { return; } LLSearchHistory::getInstance()->load(); LLSearchHistory::search_history_list_t search_list = LLSearchHistory::getInstance()->getSearchHistoryList(); LLSearchHistory::search_history_list_t::const_iterator it = search_list.begin(); for ( ; search_list.end() != it; ++it) { LLSearchHistory::LLSearchHistoryItem item = *it; search_combo->add(item.search_query); } }