diff options
Diffstat (limited to 'indra/newview/fsfloatersearch.cpp')
-rw-r--r-- | indra/newview/fsfloatersearch.cpp | 3212 |
1 files changed, 3212 insertions, 0 deletions
diff --git a/indra/newview/fsfloatersearch.cpp b/indra/newview/fsfloatersearch.cpp new file mode 100644 index 0000000000..7e0a873452 --- /dev/null +++ b/indra/newview/fsfloatersearch.cpp @@ -0,0 +1,3212 @@ +/** + * @file fsfloatersearch.cpp + * @brief Firestorm Search Floater + * + * $LicenseInfo:firstyear=2012&license=fsviewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (C) 2012, Cinder Roxley <cinder.roxley@phoenixviewer.com> + * + * 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 <boost/tokenizer.hpp> +#include <boost/algorithm/string.hpp> +#include <string> + +#include <chrono> + +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<std::chrono::system_clock> 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<LLUUID>::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<LLUUID> 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<LLUUID>::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<LLAvatarData*>(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<LLAvatarLegacyData*>(data)); + mParent->displayAvatarDetails(&avatar_data); + LLAvatarPropertiesProcessor::getInstance()->removeObserver(avatar_data.avatar_id, this); + } + if (APT_CLASSIFIED_INFO == type) + { + LLAvatarClassifiedInfo* c_info = static_cast<LLAvatarClassifiedInfo*>(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<LLUUID> 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<FSSearchPanelBase*>(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<FSPanelSearchPeople>("panel_ls_people"); + mPanelGroups = findChild<FSPanelSearchGroups>("panel_ls_groups"); + mPanelPlaces = findChild<FSPanelSearchPlaces>("panel_ls_places"); + mPanelEvents = findChild<FSPanelSearchEvents>("panel_ls_events"); + mPanelLand = findChild<FSPanelSearchLand>("panel_ls_land"); + mPanelClassifieds = findChild<FSPanelSearchClassifieds>("panel_ls_classifieds"); + mPanelWeb = findChild<FSPanelSearchWeb>("panel_ls_web"); + + mDetailsPanel = getChild<LLPanel>("panel_ls_details"); + mDetailTitle = getChild<LLTextEditor>("title"); + mDetailDesc = getChild<LLTextEditor>("desc"); + mDetailAux1 = getChild<LLTextEditor>("aux1"); + mDetailAux2 = getChild<LLTextEditor>("aux2"); + mDetailLocation = getChild<LLTextEditor>("location"); + mDetailSnapshot = getChild<LLTextureCtrl>("snapshot"); + mDetailSnapshotParcel = getChild<LLTextureCtrl>("snapshot_parcel"); + mDetailMaturity = getChild<LLIconCtrl>("maturity_icon"); + mTabContainer = getChild<LLTabContainer>("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 <class T> +T* FSFloaterSearch::getSearchPanel(const std::string& panel_name) +{ + FSFloaterSearch* search_instance = LLFloaterReg::findTypedInstance<FSFloaterSearch>("search"); + if (search_instance && search_instance->mTabContainer) + { + return dynamic_cast<T*>(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<LLLoadingIndicator>("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); + /// <FS:CR> 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<U32> 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<FSPanelSearchPeople> 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<LLSearchComboBox>("people_edit"); + mSearchResults = findChild<LLScrollListCtrl>("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<std::chrono::system_clock> now = std::chrono::system_clock::now(); + auto elapsed = now - lastRequestTime; + U64 elapsedMS = std::chrono::duration_cast<std::chrono::milliseconds>(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<FSFloaterSearch>("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<FSPanelSearchPeople>("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<LLScrollListCtrl>("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<LLUICtrl>("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<LLUICtrl>("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<LLUICtrl>("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<LLScrollListCtrl>("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<LLUICtrl>("people_edit")->getValue().asString(); + search_results->setEnabled(FALSE); + search_results->setCommentText(LLTrans::getString("not_found", map)); + } +} + +//////////////////////////////////////// +// Groups Search Panel // +//////////////////////////////////////// + +static LLPanelInjector<FSPanelSearchGroups> 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<LLSearchComboBox>("groups_edit"); + mSearchResults = findChild<LLScrollListCtrl>("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<bool> inc_pg("ShowPGSims", 1); + static LLUICachedControl<bool> inc_mature("ShowMatureSims", 0); + static LLUICachedControl<bool> 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<std::chrono::system_clock> now = std::chrono::system_clock::now(); + auto elapsed = now - lastRequestTime; + U64 elapsedMS = std::chrono::duration_cast<std::chrono::milliseconds>(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<FSFloaterSearch>("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<FSPanelSearchGroups>("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<LLScrollListCtrl>("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<LLUICtrl>("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<LLUICtrl>("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<LLUICtrl>("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<FSPanelSearchPlaces> 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<LLSearchComboBox>("places_edit"); + mSearchResults = findChild<LLScrollListCtrl>("search_results_places"); + mPlacesCategory = findChild<LLComboBox>("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<bool> inc_pg("ShowPGSims", 1); + static LLUICachedControl<bool> inc_mature("ShowMatureSims", 0); + static LLUICachedControl<bool> 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<std::chrono::system_clock> now = std::chrono::system_clock::now(); + auto elapsed = now - lastRequestTime; + U64 elapsedMS = std::chrono::duration_cast<std::chrono::milliseconds>(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<FSFloaterSearch>("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<FSPanelSearchPlaces>("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<LLScrollListCtrl>("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<LLUICtrl>("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<LLUICtrl>("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<LLUICtrl>("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<FSPanelSearchLand> 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<LLScrollListCtrl>("search_results_land"); + mPriceEditor = findChild<LLLineEditor>("price_edit"); + mAreaEditor = findChild<LLLineEditor>("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<bool> inc_pg("ShowPGLand", 1); + static LLUICachedControl<bool> inc_mature("ShowMatureLand", 0); + static LLUICachedControl<bool> inc_adult("ShowAdultLand", 0); + static LLUICachedControl<bool> limit_price("FindLandPrice", 1); + static LLUICachedControl<bool> limit_area("FindLandArea", 1); + if (!(inc_pg || inc_mature || inc_adult)) + { + LLNotificationsUtil::add("NoContentToSearch"); + return; + } + + U32 category = ST_ALL; + const std::string& selection = findChild<LLComboBox>("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<LLComboBox>("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<std::chrono::system_clock> now = std::chrono::system_clock::now(); + auto elapsed = now - lastRequestTime; + U64 elapsedMS = std::chrono::duration_cast<std::chrono::milliseconds>(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<FSFloaterSearch>("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<FSPanelSearchLand>("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<LLScrollListCtrl>("search_results_land"); + // clear "Searching" label on first results + if (self->mNumResultsReturned++ == 0) + { + search_results->deleteAllItems(); + } + + static LLUICachedControl<bool> use_price("FindLandPrice", 1); + static LLUICachedControl<bool> 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<LLUICtrl>("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<FSPanelSearchClassifieds> 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<LLSearchComboBox>("classifieds_edit"); + mSearchResults = getChild<LLScrollListCtrl>("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<LLComboBox>("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<bool> inc_pg("ShowPGClassifieds", 1); + static LLUICachedControl<bool> inc_mature("ShowMatureClassifieds", 0); + static LLUICachedControl<bool> 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<std::chrono::system_clock> now = std::chrono::system_clock::now(); + auto elapsed = now - lastRequestTime; + U64 elapsedMS = std::chrono::duration_cast<std::chrono::milliseconds>(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<FSFloaterSearch>("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<FSPanelSearchClassifieds>("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<LLScrollListCtrl>("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<LLUICtrl>("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<LLUICtrl>("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<LLUICtrl>("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<FSPanelSearchEvents> 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<LLSearchComboBox>("events_edit"); + mSearchResults = getChild<LLScrollListCtrl>("search_results_events"); + mEventsMode = findChild<LLRadioGroup>("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<bool> inc_pg("ShowPGEvents", 1); + static LLUICachedControl<bool> inc_mature("ShowMatureEvents", 0); + static LLUICachedControl<bool> inc_adult("ShowAdultEvents", 0); + if (!(inc_pg || inc_mature || inc_adult)) + { + LLNotificationsUtil::add("NoContentToSearch"); + return; + } + + U32 category = findChild<LLComboBox>("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<std::chrono::system_clock> now = std::chrono::system_clock::now(); + auto elapsed = now - lastRequestTime; + U64 elapsedMS = std::chrono::duration_cast<std::chrono::milliseconds>(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<FSFloaterSearch>("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<FSPanelSearchEvents>("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<LLScrollListCtrl>("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<LLUICtrl>("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<LLUICtrl>("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<bool> inc_pg("ShowPGEvents", 1); + static LLUICachedControl<bool> inc_mature("ShowMatureEvents", 0); + static LLUICachedControl<bool> 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<FSPanelSearchWeb> 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<LLMediaCtrl>("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<char> sep(" "); + boost::tokenizer<boost::char_separator<char> > tokens(query_string, sep); + boost::tokenizer<boost::char_separator<char> >::iterator iter = tokens.begin(); + boost::tokenizer<boost::char_separator<char> >::iterator last = tokens.end(); + boost::tokenizer<boost::char_separator<char> >::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); + } +} |