summaryrefslogtreecommitdiff
path: root/indra/newview/llpaneldirbrowser.cpp
diff options
context:
space:
mode:
authorMaxim Nikolenko <maximnproductengine@lindenlab.com>2025-10-31 13:52:19 +0200
committerGitHub <noreply@github.com>2025-10-31 13:52:19 +0200
commite2a741dfd2db44df1de5ad3b23de2a9aba28db2b (patch)
tree588037645c4c75eddfcf47fe3ed3ec7030de5ca3 /indra/newview/llpaneldirbrowser.cpp
parentbd8fe9d63d8891501ee66a570724f17e4abcacfa (diff)
parent84b2861b9e99c5099ee82a12a566119b3de9417e (diff)
Merge pull request #4900 from secondlife/maxim/2025.08-legacy-search
Restore Legacy Search
Diffstat (limited to 'indra/newview/llpaneldirbrowser.cpp')
-rw-r--r--indra/newview/llpaneldirbrowser.cpp1169
1 files changed, 1169 insertions, 0 deletions
diff --git a/indra/newview/llpaneldirbrowser.cpp b/indra/newview/llpaneldirbrowser.cpp
new file mode 100644
index 0000000000..8468520c11
--- /dev/null
+++ b/indra/newview/llpaneldirbrowser.cpp
@@ -0,0 +1,1169 @@
+/**
+ * @file llpaneldirbrowser.cpp
+ * @brief Base class for panels in the legacy Search directory.
+ *
+ * $LicenseInfo:firstyear=2025&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2025, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+// Base class for the various search panels/results browsers
+// in the Find floater. For example, Find > Popular Places
+// is derived from this.
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llpaneldirbrowser.h"
+
+// linden library includes
+#include "lldir.h"
+#include "lleventflags.h"
+#include "llqueryflags.h"
+#include "message.h"
+
+// viewer project includes
+#include "llagent.h"
+#include "llbutton.h"
+#include "llcheckboxctrl.h"
+#include "llcombobox.h"
+#include "lllineeditor.h"
+#include "llnotificationsutil.h"
+#include "llpanelavatar.h"
+#include "llpanelgroup.h"
+#include "llpanelclassified.h"
+#include "llpaneldirevents.h"
+#include "llpaneldirland.h"
+#include "llproductinforequest.h"
+#include "llscrolllistctrl.h"
+#include "lltextbox.h"
+#include "lltrans.h"
+#include "llviewercontrol.h"
+#include "llfloaterdirectory.h"
+#include "llpanelprofile.h"
+#include "llpanelplaces.h"
+#include "llpaneleventinfo.h"
+
+
+std::map<LLUUID, LLPanelDirBrowser*> gDirBrowserInstances;
+
+LLPanelDirBrowser::LLPanelDirBrowser()
+: LLPanel(),
+ mSearchID(),
+ mWantSelectID(),
+ mCurrentSortColumn("name"),
+ mCurrentSortAscending(true),
+ mSearchStart(0),
+ mResultsPerPage(RESULTS_PER_PAGE_DEFAULT),
+ mResultsReceived(0),
+ mMinSearchChars(1),
+ mResultsContents(),
+ mHaveSearchResults(false),
+ mDidAutoSelect(true),
+ mLastResultTimer(),
+ mFloaterDirectory(nullptr)
+{
+}
+
+bool LLPanelDirBrowser::postBuild()
+{
+ childSetCommitCallback("results", onCommitList, this);
+
+ mPrevPageBtn = getChild<LLButton>("prev_btn");
+ mNextPageBtn = getChild<LLButton>("next_btn");
+
+ mPrevPageBtn->setClickedCallback([this](LLUICtrl*, const LLSD&){ prevPage(); });
+ mPrevPageBtn->setVisible(false);
+
+ mNextPageBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { nextPage(); });
+ mNextPageBtn->setVisible(false);
+
+ return true;
+}
+
+LLPanelDirBrowser::~LLPanelDirBrowser()
+{
+ // Children all cleaned up by default view destructor.
+ gDirBrowserInstances.erase(mSearchID);
+}
+
+// virtual
+void LLPanelDirBrowser::draw()
+{
+ // HACK: If the results panel has data, we want to select the first
+ // item. Unfortunately, we don't know when the find is actually done,
+ // so only do this if it's been some time since the last packet of
+ // results was received.
+ if (mLastResultTimer.getElapsedTimeF32() > 0.5)
+ {
+ if (!mDidAutoSelect &&
+ !childHasFocus("results"))
+ {
+ LLCtrlListInterface *list = childGetListInterface("results");
+ if (list)
+ {
+ if (list->getCanSelect())
+ {
+ list->selectFirstItem(); // select first item by default
+ childSetFocus("results", true);
+ }
+ // Request specific data from the server
+ onCommitList(NULL, this);
+ }
+ }
+ mDidAutoSelect = true;
+ }
+
+ LLPanel::draw();
+}
+
+
+// virtual
+void LLPanelDirBrowser::nextPage()
+{
+ mSearchStart += mResultsPerPage;
+ mPrevPageBtn->setVisible(true);
+
+ performQuery();
+}
+
+
+// virtual
+void LLPanelDirBrowser::prevPage()
+{
+ mSearchStart -= mResultsPerPage;
+ mPrevPageBtn->setVisible(mSearchStart > 0);
+
+ performQuery();
+}
+
+
+void LLPanelDirBrowser::resetSearchStart()
+{
+ mSearchStart = 0;
+ mNextPageBtn->setVisible(false);
+ mPrevPageBtn->setVisible(false);
+}
+
+// protected
+void LLPanelDirBrowser::updateResultCount()
+{
+ LLScrollListCtrl* list = getChild<LLScrollListCtrl>("results");
+
+ S32 result_count = list->getItemCount();
+ std::string result_text;
+
+ if (!mHaveSearchResults) result_count = 0;
+
+ if (mNextPageBtn && mNextPageBtn->getVisible())
+ {
+ // Item count be off by a few if bogus items sent from database
+ // Just use the number of results per page. JC
+ result_text = llformat(">%d found", mResultsPerPage);
+ }
+ else
+ {
+ result_text = llformat("%d found", result_count);
+ }
+
+ childSetValue("result_text", result_text);
+
+ if (result_count == 0)
+ {
+ // add none found response
+ if (list->getItemCount() == 0)
+ {
+ list->setCommentText(std::string("None found.")); // *TODO: Translate
+ list->operateOnAll(LLCtrlListInterface::OP_DESELECT);
+ }
+ }
+ else
+ {
+ childEnable("results");
+ }
+}
+
+// static
+std::string LLPanelDirBrowser::filterShortWords(const std::string source_string, int shortest_word_length, bool& was_filtered)
+{
+ // degenerate case
+ if ( source_string.length() < 1 )
+ return "";
+
+ std::stringstream codec( source_string );
+ std::string each_word;
+ std::vector< std::string > all_words;
+
+ while( codec >> each_word )
+ all_words.push_back( each_word );
+
+ std::ostringstream dest_string( "" );
+
+ was_filtered = false;
+
+ std::vector< std::string >::iterator iter = all_words.begin();
+ while( iter != all_words.end() )
+ {
+ if ( (int)(*iter).length() >= shortest_word_length )
+ {
+ dest_string << *iter;
+ dest_string << " ";
+ }
+ else
+ {
+ was_filtered = true;
+ }
+
+ ++iter;
+ };
+
+ return dest_string.str();
+}
+
+void LLPanelDirBrowser::selectByUUID(const LLUUID& id)
+{
+ LLCtrlListInterface *list = childGetListInterface("results");
+ if (!list) return;
+ bool found = list->setCurrentByID(id);
+ if (found)
+ {
+ // we got it, don't wait for network
+ // Don't bother looking for this in the draw loop.
+ mWantSelectID.setNull();
+ // Make sure UI updates.
+ onCommitList(NULL, this);
+ }
+ else
+ {
+ // waiting for this item from the network
+ mWantSelectID = id;
+ }
+}
+
+void LLPanelDirBrowser::selectEventByID(S32 event_id)
+{
+ if (mFloaterDirectory)
+ {
+ if (mFloaterDirectory->mPanelEventp)
+ {
+ mFloaterDirectory->mPanelEventp->setVisible(true);
+ mFloaterDirectory->mPanelEventp->setEventID(event_id);
+ }
+ }
+}
+
+void LLPanelDirBrowser::getSelectedInfo(LLUUID* id, S32 *type)
+{
+ LLCtrlListInterface *list = childGetListInterface("results");
+ if (!list) return;
+
+ LLSD id_sd = childGetValue("results");
+
+ *id = id_sd.asUUID();
+
+ std::string id_str = id_sd.asString();
+ *type = mResultsContents[id_str]["type"];
+}
+
+
+// static
+void LLPanelDirBrowser::onCommitList(LLUICtrl* ctrl, void* data)
+{
+ LLPanelDirBrowser* self = (LLPanelDirBrowser*)data;
+ LLCtrlListInterface *list = self->childGetListInterface("results");
+ if (!list) return;
+
+ // Start with everyone invisible
+ if (self->mFloaterDirectory)
+ {
+ self->mFloaterDirectory->hideAllDetailPanels();
+ }
+
+ if (FALSE == list->getCanSelect())
+ {
+ return;
+ }
+
+ std::string id_str = self->childGetValue("results").asString();
+ if (id_str.empty())
+ {
+ return;
+ }
+
+ LLSD item_id = list->getCurrentID();
+ S32 type = self->mResultsContents[id_str]["type"];
+ if (type == EVENT_CODE)
+ {
+ // all but events use the UUID above
+ item_id = self->mResultsContents[id_str]["event_id"];
+ }
+ //std::string name = self->mResultsContents[id_str]["name"].asString();
+ self->showDetailPanel(type, item_id);
+}
+
+void LLPanelDirBrowser::showDetailPanel(S32 type, LLSD id)
+{
+ switch(type)
+ {
+ case AVATAR_CODE:
+ if (mFloaterDirectory && mFloaterDirectory->mPanelAvatarp)
+ {
+ mFloaterDirectory->mPanelAvatarp->setVisible(true);
+ mFloaterDirectory->mPanelAvatarp->onOpen(id);
+ mFloaterDirectory->mPanelAvatarp->updateData();
+ }
+ break;
+ case GROUP_CODE:
+ if (mFloaterDirectory && mFloaterDirectory->mPanelGroupp)
+ {
+ mFloaterDirectory->mPanelGroupp->setVisible(true);
+ mFloaterDirectory->mPanelGroupp->onOpen(LLSD().with("group_id", id));
+ }
+ break;
+ case PLACE_CODE:
+ case FOR_SALE_CODE:
+ case AUCTION_CODE:
+ if (mFloaterDirectory && mFloaterDirectory->mPanelPlacep)
+ {
+ mFloaterDirectory->mPanelPlacep->setVisible(true);
+ LLSD key;
+ key["type"] = "remote_place";
+ key["id"]= id;
+ mFloaterDirectory->mPanelPlacep->onOpen(key);
+ }
+ break;
+ case CLASSIFIED_CODE:
+ if (mFloaterDirectory && mFloaterDirectory->mPanelClassifiedp)
+ {
+ mFloaterDirectory->mPanelClassifiedp->setVisible(true);
+ LLSD key;
+ key["classified_id"] = id;
+ key["from_search"] = true;
+ mFloaterDirectory->mPanelClassifiedp->onOpen(key);
+ }
+ break;
+ case EVENT_CODE:
+ {
+ U32 event_id = (U32)id.asInteger();
+ showEvent(event_id);
+ }
+ break;
+ default:
+ {
+ LL_WARNS() << "Unknown event type!" << LL_ENDL;
+ }
+ break;
+ }
+}
+
+
+void LLPanelDirBrowser::showEvent(const U32 event_id)
+{
+ // Start with everyone invisible
+ if (mFloaterDirectory)
+ {
+ mFloaterDirectory->hideAllDetailPanels();
+ if (mFloaterDirectory->mPanelEventp)
+ {
+ mFloaterDirectory->mPanelEventp->setVisible(true);
+ mFloaterDirectory->mPanelEventp->setEventID(event_id);
+ }
+ }
+}
+
+void LLPanelDirBrowser::processDirPeopleReply(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);
+
+ LLPanelDirBrowser* self = get_if_there(gDirBrowserInstances, query_id, (LLPanelDirBrowser*)NULL);
+ if (!self)
+ {
+ // data from an old query
+ return;
+ }
+
+ self->mHaveSearchResults = TRUE;
+
+ LLCtrlListInterface *list = self->childGetListInterface("results");
+ if (!list) return;
+
+ if (!list->getCanSelect())
+ {
+ list->operateOnAll(LLCtrlListInterface::OP_DELETE);
+ self->mResultsContents = LLSD();
+ }
+
+ S32 rows = msg->getNumberOfBlocksFast(_PREHASH_QueryReplies);
+ self->mResultsReceived += rows;
+
+ rows = self->showNextButton(rows);
+
+ for (S32 i = 0; i < 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);
+ // unused
+ // msg->getStringFast(_PREHASH_QueryReplies,_PREHASH_Group, group, i);
+ // msg->getS32Fast( _PREHASH_QueryReplies,_PREHASH_Reputation, reputation, i);
+
+ if (agent_id.isNull())
+ {
+ continue;
+ }
+
+ LLSD content;
+
+ LLSD row;
+ row["id"] = agent_id;
+
+ // We don't show online status in the finder anymore,
+ // so just use the 'offline' icon as the generic 'person' icon
+ row["columns"][0]["column"] = "icon";
+ row["columns"][0]["type"] = "icon";
+ row["columns"][0]["value"] = "icon_avatar_offline.tga";
+
+ content["type"] = AVATAR_CODE;
+
+ std::string fullname = first_name + " " + last_name;
+ row["columns"][1]["column"] = "name";
+ row["columns"][1]["value"] = fullname;
+ row["columns"][1]["font"] = "SANSSERIF";
+
+ content["name"] = fullname;
+
+ list->addElement(row);
+ self->mResultsContents[agent_id.asString()] = content;
+ }
+
+ list->sortByColumn(self->mCurrentSortColumn, self->mCurrentSortAscending);
+ self->updateResultCount();
+
+ // Poke the result received timer
+ self->mLastResultTimer.reset();
+ self->mDidAutoSelect = false;
+}
+
+
+void LLPanelDirBrowser::processDirPlacesReply(LLMessageSystem* msg, void**)
+{
+ LLUUID agent_id;
+ LLUUID query_id;
+ LLUUID parcel_id;
+ std::string name;
+ bool is_for_sale = false;
+ bool is_auction = false;
+ F32 dwell;
+
+ msg->getUUID("AgentData", "AgentID", agent_id);
+ msg->getUUID("QueryData", "QueryID", query_id );
+
+ if (msg->getNumberOfBlocks("StatusData"))
+ {
+ U32 status;
+ msg->getU32("StatusData", "Status", status);
+ if (status & STATUS_SEARCH_PLACES_BANNEDWORD)
+ {
+ LLNotificationsUtil::add("SearchWordBanned");
+ }
+ }
+
+ LLPanelDirBrowser* self = get_if_there(gDirBrowserInstances, query_id, (LLPanelDirBrowser*)NULL);
+ if (!self)
+ {
+ // data from an old query
+ return;
+ }
+
+ self->mHaveSearchResults = TRUE;
+
+ LLCtrlListInterface *list = self->childGetListInterface("results");
+ if (!list) return;
+
+ if (!list->getCanSelect())
+ {
+ list->operateOnAll(LLCtrlListInterface::OP_DELETE);
+ self->mResultsContents = LLSD();
+ }
+
+ S32 count = msg->getNumberOfBlocks("QueryReplies");
+ self->mResultsReceived += count;
+
+ count = self->showNextButton(count);
+
+ for (S32 i = 0; i < count ; i++)
+ {
+ msg->getUUID("QueryReplies", "ParcelID", parcel_id, i);
+ msg->getString("QueryReplies", "Name", name, i);
+ msg->getBOOL("QueryReplies", "ForSale", is_for_sale, i);
+ msg->getBOOL("QueryReplies", "Auction", is_auction, i);
+ msg->getF32("QueryReplies", "Dwell", dwell, i);
+
+ if (parcel_id.isNull())
+ {
+ continue;
+ }
+
+ LLSD content;
+ S32 type;
+
+ LLSD row = self->createLandSale(parcel_id, is_auction, is_for_sale, name, &type);
+
+ content["type"] = type;
+ content["name"] = name;
+
+ std::string buffer = llformat("%.0f", (F64)dwell);
+ row["columns"][2]["column"] = "dwell";
+ row["columns"][2]["value"] = buffer;
+ row["columns"][2]["font"] = "SansSerifSmall";
+
+ list->addElement(row);
+ self->mResultsContents[parcel_id.asString()] = content;
+ }
+
+ list->sortByColumn(self->mCurrentSortColumn, self->mCurrentSortAscending);
+ self->updateResultCount();
+
+ // Poke the result received timer
+ self->mLastResultTimer.reset();
+ self->mDidAutoSelect = false;
+}
+
+
+void LLPanelDirBrowser::processDirEventsReply(LLMessageSystem* msg, void**)
+{
+ LLUUID agent_id;
+ LLUUID query_id;
+ LLUUID owner_id;
+ std::string name;
+ std::string date;
+ bool show_pg = gSavedSettings.getBOOL("ShowPGEvents");
+ bool show_mature = gSavedSettings.getBOOL("ShowMatureEvents");
+ bool show_adult = gSavedSettings.getBOOL("ShowAdultEvents");
+
+ msg->getUUID("AgentData", "AgentID", agent_id);
+ msg->getUUID("QueryData", "QueryID", query_id );
+
+ LLPanelDirBrowser* self = get_if_there(gDirBrowserInstances, query_id, (LLPanelDirBrowser*)NULL);
+ if (!self)
+ {
+ return;
+ }
+
+ if (msg->getNumberOfBlocks("StatusData"))
+ {
+ U32 status;
+ msg->getU32("StatusData", "Status", status);
+ if (status & STATUS_SEARCH_EVENTS_BANNEDWORD)
+ {
+ LLNotificationsUtil::add("SearchWordBanned");
+ }
+ }
+
+ self->mHaveSearchResults = TRUE;
+
+ LLCtrlListInterface *list = self->childGetListInterface("results");
+ if (!list) return;
+
+ if (!list->getCanSelect())
+ {
+ list->operateOnAll(LLCtrlListInterface::OP_DELETE);
+ self->mResultsContents = LLSD();
+ }
+
+ S32 rows = msg->getNumberOfBlocks("QueryReplies");
+ self->mResultsReceived += rows;
+
+ rows = self->showNextButton(rows);
+
+ for (S32 i = 0; i < 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())
+ {
+ //RN: should this check event_id instead?
+ LL_WARNS() << "skipped event due to owner_id null, event_id " << event_id << LL_ENDL;
+ continue;
+ }
+
+ // skip events that don't match the flags
+ // there's no PG flag, so we make sure neither adult nor mature is set
+ if (((event_flags & (EVENT_FLAG_ADULT | EVENT_FLAG_MATURE)) == EVENT_FLAG_NONE) && !show_pg)
+ {
+ //llwarns << "Skipped pg event because we're not showing pg, event_id " << event_id << llendl;
+ continue;
+ }
+
+ if ((event_flags & EVENT_FLAG_MATURE) && !show_mature)
+ {
+ //llwarns << "Skipped mature event because we're not showing mature, event_id " << event_id << llendl;
+ continue;
+ }
+
+ if ((event_flags & EVENT_FLAG_ADULT) && !show_adult)
+ {
+ //llwarns << "Skipped adult event because we're not showing adult, event_id " << event_id << llendl;
+ continue;
+ }
+
+ LLSD content;
+
+ content["type"] = EVENT_CODE;
+ content["name"] = name;
+ content["event_id"] = (S32)event_id;
+
+ LLSD row;
+ row["id"] = llformat("%u", event_id);
+
+ // Column 0 - event icon
+ LLUUID image_id;
+ if (event_flags == EVENT_FLAG_ADULT)
+ {
+ row["columns"][0]["column"] = "icon";
+ row["columns"][0]["type"] = "icon";
+ row["columns"][0]["value"] = "icon_event_adult.tga";
+ }
+ else if (event_flags == EVENT_FLAG_MATURE)
+ {
+ row["columns"][0]["column"] = "icon";
+ row["columns"][0]["type"] = "icon";
+ row["columns"][0]["value"] = "icon_event_mature.tga";
+ }
+ else
+ {
+ row["columns"][0]["column"] = "icon";
+ row["columns"][0]["type"] = "icon";
+ row["columns"][0]["value"] = "icon_event.tga";
+ }
+
+ row["columns"][1]["column"] = "name";
+ row["columns"][1]["value"] = name;
+ row["columns"][1]["font"] = "SANSSERIF";
+
+ row["columns"][2]["column"] = "date";
+ row["columns"][2]["value"] = date;
+ row["columns"][2]["font"] = "SansSerifSmall";
+
+ row["columns"][3]["column"] = "time";
+ row["columns"][3]["value"] = llformat("%u", unix_time);
+ row["columns"][3]["font"] = "SansSerifSmall";
+
+ list->addElement(row, ADD_TOP /*ADD_SORTED*/);
+
+ std::string id_str = llformat("%u", event_id);
+ self->mResultsContents[id_str] = content;
+ }
+
+ list->sortByColumn(self->mCurrentSortColumn, self->mCurrentSortAscending);
+ self->updateResultCount();
+
+ // Poke the result received timer
+ self->mLastResultTimer.reset();
+ self->mDidAutoSelect = false;
+}
+
+
+// static
+void LLPanelDirBrowser::processDirGroupsReply(LLMessageSystem* msg, void**)
+{
+ S32 i;
+
+ LLUUID query_id;
+ LLUUID group_id;
+ std::string group_name;
+ S32 members;
+ F32 search_order;
+
+ msg->getUUIDFast(_PREHASH_QueryData,_PREHASH_QueryID, query_id );
+
+ LLPanelDirBrowser* self = get_if_there(gDirBrowserInstances, query_id, (LLPanelDirBrowser*)NULL);
+ if (!self)
+ {
+ return;
+ }
+
+ self->mHaveSearchResults = TRUE;
+
+ LLCtrlListInterface *list = self->childGetListInterface("results");
+ if (!list) return;
+
+ if (!list->getCanSelect())
+ {
+ list->operateOnAll(LLCtrlListInterface::OP_DELETE);
+ self->mResultsContents = LLSD();
+ }
+
+ S32 rows = msg->getNumberOfBlocksFast(_PREHASH_QueryReplies);
+ self->mResultsReceived += rows;
+
+ rows = self->showNextButton(rows);
+
+ for (i = 0; i < 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())
+ {
+ continue;
+ }
+
+ LLSD content;
+ content["type"] = GROUP_CODE;
+ content["name"] = group_name;
+
+ LLSD row;
+ row["id"] = group_id;
+
+ LLUUID image_id;
+ row["columns"][0]["column"] = "icon";
+ row["columns"][0]["type"] = "icon";
+ row["columns"][0]["value"] = "icon_group.tga";
+
+ row["columns"][1]["column"] = "name";
+ row["columns"][1]["value"] = group_name;
+ row["columns"][1]["font"] = "SANSSERIF";
+
+ row["columns"][2]["column"] = "members";
+ row["columns"][2]["value"] = members;
+ row["columns"][2]["font"] = "SansSerifSmall";
+
+ row["columns"][3]["column"] = "score";
+ row["columns"][3]["value"] = search_order;
+
+ list->addElement(row);
+ self->mResultsContents[group_id.asString()] = content;
+ }
+ list->sortByColumn(self->mCurrentSortColumn, self->mCurrentSortAscending);
+ self->updateResultCount();
+
+ // Poke the result received timer
+ self->mLastResultTimer.reset();
+ self->mDidAutoSelect = false;
+}
+
+
+// static
+void LLPanelDirBrowser::processDirClassifiedReply(LLMessageSystem* msg, void**)
+{
+ S32 i;
+ S32 num_new_rows;
+
+ LLUUID agent_id;
+ LLUUID query_id;
+
+ msg->getUUID("AgentData", "AgentID", agent_id);
+ if (agent_id != gAgent.getID())
+ {
+ LL_WARNS() << "Message for wrong agent " << agent_id
+ << " in processDirClassifiedReply" << LL_ENDL;
+ return;
+ }
+
+ msg->getUUID("QueryData", "QueryID", query_id);
+ LLPanelDirBrowser* self = get_if_there(gDirBrowserInstances, query_id, (LLPanelDirBrowser*)NULL);
+ if (!self)
+ {
+ return;
+ }
+
+ if (msg->getNumberOfBlocks("StatusData"))
+ {
+ U32 status;
+ msg->getU32("StatusData", "Status", status);
+ if (status & STATUS_SEARCH_CLASSIFIEDS_BANNEDWORD)
+ {
+ LLNotificationsUtil::add("SearchWordBanned");
+ }
+ }
+
+ self->mHaveSearchResults = TRUE;
+
+ LLCtrlListInterface *list = self->childGetListInterface("results");
+ if (!list) return;
+
+ if (!list->getCanSelect())
+ {
+ list->operateOnAll(LLCtrlListInterface::OP_DELETE);
+ self->mResultsContents = LLSD();
+ }
+
+ num_new_rows = msg->getNumberOfBlocksFast(_PREHASH_QueryReplies);
+ self->mResultsReceived += num_new_rows;
+
+ num_new_rows = self->showNextButton(num_new_rows);
+ for (i = 0; i < num_new_rows; i++)
+ {
+ LLUUID classified_id;
+ std::string name;
+ U32 creation_date = 0; // unix timestamp
+ U32 expiration_date = 0; // future use
+ S32 price_for_listing = 0;
+ 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.notNull() )
+ {
+ self->addClassified(list, classified_id, name, creation_date, price_for_listing);
+
+ LLSD content;
+ content["type"] = CLASSIFIED_CODE;
+ content["name"] = name;
+ self->mResultsContents[classified_id.asString()] = content;
+ }
+ }
+ // The server does the initial sort, by price paid per listing and date. JC
+ self->updateResultCount();
+
+ // Poke the result received timer
+ self->mLastResultTimer.reset();
+ self->mDidAutoSelect = false;
+}
+
+void LLPanelDirBrowser::processDirLandReply(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 = false;
+ bool for_sale = false;
+ S32 sale_price;
+ S32 actual_area;
+
+ msg->getUUID("AgentData", "AgentID", agent_id);
+ msg->getUUID("QueryData", "QueryID", query_id );
+
+ LLPanelDirBrowser* browser = get_if_there(gDirBrowserInstances, query_id, (LLPanelDirBrowser*)NULL);
+ if (!browser)
+ {
+ // data from an old query
+ return;
+ }
+
+ // Only handled by LLPanelDirLand
+ LLPanelDirLand* self = (LLPanelDirLand*)browser;
+
+ self->mHaveSearchResults = TRUE;
+
+ LLCtrlListInterface *list = self->childGetListInterface("results");
+ if (!list) return;
+
+ if (!list->getCanSelect())
+ {
+ list->operateOnAll(LLCtrlListInterface::OP_DELETE);
+ self->mResultsContents = LLSD();
+ }
+
+ bool use_price = gSavedSettings.getBOOL("FindLandPrice");
+ S32 limit_price = self->childGetValue("priceedit").asInteger();
+
+ bool use_area = gSavedSettings.getBOOL("FindLandArea");
+ S32 limit_area = self->childGetValue("areaedit").asInteger();
+
+ S32 i;
+ S32 count = msg->getNumberOfBlocks("QueryReplies");
+ self->mResultsReceived += count;
+
+ S32 non_auction_count = 0;
+ for (i = 0; i < count; 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", sale_price, i);
+ msg->getS32("QueryReplies", "ActualArea", actual_area, i);
+
+ 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 && (sale_price > limit_price)) continue;
+
+ if (use_area && (actual_area < limit_area)) continue;
+
+ LLSD content;
+ S32 type;
+
+ LLSD row = self->createLandSale(parcel_id, auction, for_sale, name, &type);
+
+ content["type"] = type;
+ content["name"] = name;
+ content["landtype"] = land_type;
+
+ std::string buffer = "Auction";
+ if (!auction)
+ {
+ buffer = llformat("%d", sale_price);
+ non_auction_count++;
+ }
+ row["columns"][2]["column"] = "price";
+ row["columns"][2]["value"] = buffer;
+ row["columns"][2]["font"] = "SansSerifSmall";
+
+ buffer = llformat("%d", actual_area);
+ row["columns"][3]["column"] = "area";
+ row["columns"][3]["value"] = buffer;
+ row["columns"][3]["font"] = "SansSerifSmall";
+
+ if (!auction)
+ {
+ F32 price_per_meter;
+ if (actual_area > 0)
+ {
+ price_per_meter = (F32)sale_price / (F32)actual_area;
+ }
+ else
+ {
+ price_per_meter = 0.f;
+ }
+ // Prices are usually L$1 - L$10 / meter
+ buffer = llformat("%.1f", price_per_meter);
+ row["columns"][4]["column"] = "per_meter";
+ row["columns"][4]["value"] = buffer;
+ row["columns"][4]["font"] = "SansSerifSmall";
+ }
+ else
+ {
+ // Auctions start at L$1 per meter
+ row["columns"][4]["column"] = "per_meter";
+ row["columns"][4]["value"] = "1.0";
+ row["columns"][4]["font"] = "SansSerifSmall";
+ }
+
+ row["columns"][5]["column"] = "landtype";
+ row["columns"][5]["value"] = land_type;
+ row["columns"][5]["font"] = "SansSerifSmall";
+
+ list->addElement(row);
+ self->mResultsContents[parcel_id.asString()] = content;
+ }
+
+ // All auction results are shown on the first page
+ // But they don't count towards the 100 / page limit
+ // So figure out the next button here, when we know how many aren't auctions
+ count = self->showNextButton(non_auction_count);
+
+ self->updateResultCount();
+
+ // Poke the result received timer
+ self->mLastResultTimer.reset();
+ self->mDidAutoSelect = false;
+}
+
+void LLPanelDirBrowser::addClassified(LLCtrlListInterface *list, const LLUUID& pick_id, const std::string& name, const U32 creation_date, const S32 price_for_listing)
+{
+ std::string type = llformat("%d", CLASSIFIED_CODE);
+
+ LLSD row;
+ row["id"] = pick_id;
+
+ row["columns"][0]["column"] = "icon";
+ row["columns"][0]["type"] = "icon";
+ row["columns"][0]["value"] = "icon_top_pick.tga";
+
+ row["columns"][1]["column"] = "name";
+ row["columns"][1]["value"] = name;
+ row["columns"][1]["font"] = "SANSSERIF";
+
+ row["columns"][2]["column"] = "price";
+ row["columns"][2]["value"] = price_for_listing;
+ row["columns"][2]["font"] = "SansSerifSmall";
+
+ list->addElement(row);
+}
+
+LLSD LLPanelDirBrowser::createLandSale(const LLUUID& parcel_id, bool is_auction, bool is_for_sale, const std::string& name, S32 *type)
+{
+ LLSD row;
+ row["id"] = parcel_id;
+ LLUUID image_id;
+
+ // Icon and type
+ if(is_auction)
+ {
+ row["columns"][0]["column"] = "icon";
+ row["columns"][0]["type"] = "icon";
+ row["columns"][0]["value"] = "icon_auction.tga";
+
+ *type = AUCTION_CODE;
+ }
+ else if (is_for_sale)
+ {
+ row["columns"][0]["column"] = "icon";
+ row["columns"][0]["type"] = "icon";
+ row["columns"][0]["value"] = "icon_for_sale.tga";
+
+ *type = FOR_SALE_CODE;
+ }
+ else
+ {
+ row["columns"][0]["column"] = "icon";
+ row["columns"][0]["type"] = "icon";
+ row["columns"][0]["value"] = "icon_place.tga";
+
+ *type = PLACE_CODE;
+ }
+
+ row["columns"][1]["column"] = "name";
+ row["columns"][1]["value"] = name;
+ row["columns"][1]["font"] = "SANSSERIF";
+
+ return row;
+}
+
+void LLPanelDirBrowser::setupNewSearch()
+{
+ LLScrollListCtrl* list = getChild<LLScrollListCtrl>("results");
+
+ gDirBrowserInstances.erase(mSearchID);
+ // Make a new query ID
+ mSearchID.generate();
+
+ gDirBrowserInstances.emplace(mSearchID, this);
+
+ // ready the list for results
+ list->operateOnAll(LLCtrlListInterface::OP_DELETE);
+ list->setCommentText(LLTrans::getString("Searching"));
+ list->setEnabled(false);
+
+ mResultsReceived = 0;
+ mHaveSearchResults = FALSE;
+
+ // Set all panels to be invisible
+ mFloaterDirectory->hideAllDetailPanels();
+
+ updateResultCount();
+}
+
+
+// static
+// called from calssifieds, events, groups, land, people, and places
+void LLPanelDirBrowser::onClickSearchCore(void* userdata)
+{
+ LLPanelDirBrowser* self = (LLPanelDirBrowser*)userdata;
+ if (!self) return;
+
+ self->resetSearchStart();
+ self->performQuery();
+}
+
+
+// static
+void LLPanelDirBrowser::sendDirFindQuery(
+ LLMessageSystem* msg,
+ const LLUUID& query_id,
+ const std::string& text,
+ U32 flags,
+ S32 query_start)
+{
+ msg->newMessage("DirFindQuery");
+ msg->nextBlock("AgentData");
+ msg->addUUID("AgentID", gAgent.getID());
+ msg->addUUID("SessionID", gAgent.getSessionID());
+ msg->nextBlock("QueryData");
+ msg->addUUID("QueryID", query_id);
+ msg->addString("QueryText", text);
+ msg->addU32("QueryFlags", flags);
+ msg->addS32("QueryStart", query_start);
+ gAgent.sendReliableMessage();
+}
+
+
+void LLPanelDirBrowser::onKeystrokeName(LLLineEditor* line, void* data)
+{
+ LLPanelDirBrowser *self = (LLPanelDirBrowser*)data;
+ if (line->getLength() >= (S32)self->mMinSearchChars)
+ {
+ self->setDefaultBtn( "Search" );
+ self->childEnable("Search");
+ }
+ else
+ {
+ self->setDefaultBtn();
+ self->childDisable("Search");
+ }
+}
+
+// setup results when shown
+void LLPanelDirBrowser::onVisibilityChange(bool new_visibility)
+{
+ if (new_visibility)
+ {
+ onCommitList(NULL, this);
+ }
+ LLPanel::onVisibilityChange(new_visibility);
+}
+
+S32 LLPanelDirBrowser::showNextButton(S32 rows)
+{
+ // HACK: This hack doesn't work for llpaneldirfind (ALL)
+ // because other data is being returned as well.
+ if ( getName() != "find_all_old_panel")
+ {
+ // HACK: The (mResultsPerPage)+1th entry indicates there are 'more'
+ bool show_next = (mResultsReceived > mResultsPerPage);
+ mNextPageBtn->setVisible(show_next);
+ if (show_next)
+ {
+ rows -= (mResultsReceived - mResultsPerPage);
+ }
+ }
+ else
+ {
+ // Hide page buttons
+ mNextPageBtn->setVisible(false);
+ mPrevPageBtn->setVisible(false);
+ }
+ return rows;
+}