/** 
 * @file llpaneldirbrowser.cpp
 * @brief LLPanelDirBrowser class implementation
 *
 * $LicenseInfo:firstyear=2001&license=viewergpl$
 * 
 * Copyright (c) 2001-2008, Linden Research, Inc.
 * 
 * Second Life Viewer Source Code
 * The source code in this file ("Source Code") is provided by Linden Lab
 * to you under the terms of the GNU General Public License, version 2.0
 * ("GPL"), unless you have obtained a separate licensing agreement
 * ("Other License"), formally executed by you and Linden Lab.  Terms of
 * the GPL can be found in doc/GPL-license.txt in this distribution, or
 * online at http://secondlife.com/developers/opensource/gplv2
 * 
 * There are special exceptions to the terms and conditions of the GPL as
 * it is applied to this Source Code. View the full text of the exception
 * in the file doc/FLOSS-exception.txt in this software distribution, or
 * online at http://secondlife.com/developers/opensource/flossexception
 * 
 * By copying, modifying or distributing this software, you acknowledge
 * that you have read and understood your obligations described above,
 * and agree to abide by those obligations.
 * 
 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
 * COMPLETENESS OR PERFORMANCE.
 * $/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 "llfontgl.h"
#include "llqueryflags.h"
#include "message.h"

// viewer project includes
#include "llagent.h"
#include "llbutton.h"
#include "llcheckboxctrl.h"
#include "llcombobox.h"
#include "llfloateravatarinfo.h"
#include "llfloaterdirectory.h" 
#include "lllineeditor.h"
#include "llmenucommands.h"
#include "llmenugl.h"
#include "llpanelavatar.h"
#include "llpanelevent.h"
#include "llpanelgroup.h"
#include "llpanelclassified.h"
#include "llpanelpick.h"
#include "llpanelplace.h"
#include "llpaneldirland.h"
#include "llscrolllistctrl.h"
#include "lltextbox.h"
#include "lluiconstants.h"
#include "llviewercontrol.h"
#include "llviewerimagelist.h"
#include "llviewermessage.h"
#include "llvieweruictrlfactory.h"


//
// Globals
//

LLMap< const LLUUID, LLPanelDirBrowser* > gDirBrowserInstances;

LLPanelDirBrowser::LLPanelDirBrowser(const std::string& name, LLFloaterDirectory* floater)
:	LLPanel(name),
	mSearchID(),
	mWantSelectID(),
	mCurrentSortColumn("name"),
	mCurrentSortAscending(TRUE),
	mSearchStart(0),
	mResultsPerPage(100),
	mResultsReceived(0),
	mMinSearchChars(1),
	mResultsContents(),
	mHaveSearchResults(FALSE),
	mDidAutoSelect(TRUE),
	mLastResultTimer(),
	mFloaterDirectory(floater)
{
	//updateResultCount();
}

BOOL LLPanelDirBrowser::postBuild()
{
	childSetCommitCallback("results", onCommitList, this);

	childSetAction("< Prev", onClickPrev, this);
	childHide("< Prev");

	childSetAction("Next >", onClickNext, this);
	childHide("Next >");

	return TRUE;
}

LLPanelDirBrowser::~LLPanelDirBrowser()
{
	// Children all cleaned up by default view destructor.

	gDirBrowserInstances.removeData(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 (getVisible() && 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;
	childShow("< Prev");

	performQuery();
}


// virtual
void LLPanelDirBrowser::prevPage()
{
	mSearchStart -= mResultsPerPage;
	childSetVisible("< Prev", mSearchStart > 0);

	performQuery();
}


void LLPanelDirBrowser::resetSearchStart()
{
	mSearchStart = 0;
	childHide("Next >");
	childHide("< Prev");
}

// protected
void LLPanelDirBrowser::updateResultCount()
{
	LLCtrlListInterface *list = childGetListInterface("results");
	if (!list) return;

	S32 result_count = list->getItemCount();
	LLString result_text;

	if (!mHaveSearchResults) result_count = 0;

	if (childIsVisible("Next >")) 
	{
		// 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->addSimpleElement("None found.");
			list->operateOnAll(LLCtrlListInterface::OP_DESELECT);
		}
	}
	else
	{
		childEnable("results");
	}
}

// static
void LLPanelDirBrowser::onClickPrev(void* data)
{
	LLPanelDirBrowser* self = (LLPanelDirBrowser*)data;
	self->prevPage();
}


// static
void LLPanelDirBrowser::onClickNext(void* data)
{
	LLPanelDirBrowser* self = (LLPanelDirBrowser*)data;
	self->nextPage();
}


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);
		}
	}
}

U32 LLPanelDirBrowser::getSelectedEventID() const
{
	if (mFloaterDirectory)
	{
		if (mFloaterDirectory->mPanelEventp)
		{
			return mFloaterDirectory->mPanelEventp->getEventID();
		}
	}
	return 0;
}

void LLPanelDirBrowser::getSelectedInfo(LLUUID* id, S32 *type)
{
	LLCtrlListInterface *list = childGetListInterface("results");
	if (!list) return;

	LLSD id_sd = childGetValue("results");

	*id = id_sd.asUUID();

	LLString 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;
	}

	LLString 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"];
	}

	//LLString 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->setAvatarID(id.asUUID(), "", ONLINE_STATUS_NO);
		}
		break;
	case EVENT_CODE:
		{
			U32 event_id = (U32)id.asInteger();
			showEvent(event_id);
		}
		break;
	case GROUP_CODE:
		if (mFloaterDirectory && mFloaterDirectory->mPanelGroupHolderp)
		{
			mFloaterDirectory->mPanelGroupHolderp->setVisible(TRUE);
		}
		if (mFloaterDirectory && mFloaterDirectory->mPanelGroupp)
		{
			mFloaterDirectory->mPanelGroupp->setVisible(TRUE);
			mFloaterDirectory->mPanelGroupp->setGroupID(id.asUUID());
		}
		break;
	case CLASSIFIED_CODE:
		if (mFloaterDirectory && mFloaterDirectory->mPanelClassifiedp)
		{
			mFloaterDirectory->mPanelClassifiedp->setVisible(TRUE);
			mFloaterDirectory->mPanelClassifiedp->setClassifiedID(id.asUUID());
			mFloaterDirectory->mPanelClassifiedp->sendClassifiedInfoRequest();
		}
		break;
	case FOR_SALE_CODE:
	case AUCTION_CODE:
		if (mFloaterDirectory && mFloaterDirectory->mPanelPlaceSmallp)
		{
			mFloaterDirectory->mPanelPlaceSmallp->setVisible(TRUE);
			mFloaterDirectory->mPanelPlaceSmallp->resetLocation();
			mFloaterDirectory->mPanelPlaceSmallp->setParcelID(id.asUUID());
		}
		break;
	case PLACE_CODE:
	case POPULAR_CODE:
		if (mFloaterDirectory && mFloaterDirectory->mPanelPlacep)
		{
			mFloaterDirectory->mPanelPlacep->setVisible(TRUE);
			mFloaterDirectory->mPanelPlacep->resetLocation();
			mFloaterDirectory->mPanelPlacep->setParcelID(id.asUUID());
		}
		break;
	default:
		{
			llwarns << "Unknown event type!" << llendl;
		}
		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;
	char   first_name[DB_FIRST_NAME_BUF_SIZE];	/* Flawfinder: ignore */
	char   last_name[DB_LAST_NAME_BUF_SIZE];	/* Flawfinder: ignore */
	LLUUID agent_id;

	msg->getUUIDFast(_PREHASH_QueryData,_PREHASH_QueryID, query_id);

	LLPanelDirBrowser* self;
	self = gDirBrowserInstances.getIfThere(query_id);
	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, DB_FIRST_NAME_BUF_SIZE, first_name, i);
		msg->getStringFast(_PREHASH_QueryReplies,_PREHASH_LastName,	DB_LAST_NAME_BUF_SIZE, 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, DB_GROUP_NAME_BUF_SIZE, group, i);
		// msg->getS32Fast(   _PREHASH_QueryReplies,_PREHASH_Reputation, reputation, i);

		if (agent_id.isNull())
		{
			continue;
		}

		LLSD content;

		LLSD row;
		row["id"] = agent_id;

		LLUUID image_id;
		// We don't show online status in the finder anymore,
		// so just use the 'offline' icon as the generic 'person' icon
		image_id.set( gViewerArt.getString("icon_avatar_offline.tga") );
		row["columns"][0]["column"] = "icon";
		row["columns"][0]["type"] = "icon";
		row["columns"][0]["value"] = image_id;

		content["type"] = AVATAR_CODE;

		LLString fullname = LLString(first_name) + " " + LLString(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;
	char	name[MAX_STRING];		/*Flawfinder: ignore*/
	BOOL	is_for_sale;
	BOOL	is_auction;
	F32		dwell;

	msg->getUUID("AgentData", "AgentID", agent_id);
	msg->getUUID("QueryData", "QueryID", query_id );

	LLPanelDirBrowser* self;
	self = gDirBrowserInstances.getIfThere(query_id);
	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 i;
	S32 count = msg->getNumberOfBlocks("QueryReplies");
	self->mResultsReceived += count;

	count = self->showNextButton(count);

	for (i = 0; i < count ; i++)
	{
		msg->getUUID("QueryReplies", "ParcelID", parcel_id, i);
		msg->getString("QueryReplies", "Name", MAX_STRING, 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;

		LLString buffer = llformat("%.0f", (F64)dwell);
		row["columns"][3]["column"] = "dwell";
		row["columns"][3]["value"] = buffer;
		row["columns"][3]["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::processDirPopularReply(LLMessageSystem *msg, void**)
{
	LLUUID	agent_id;
	LLUUID	query_id;
	LLUUID	parcel_id;
	char	name[MAX_STRING];		/*Flawfinder: ignore*/
	F32		dwell;

	msg->getUUID("AgentData", "AgentID", agent_id);
	msg->getUUID("QueryData", "QueryID", query_id );

	LLPanelDirBrowser* self;
	self = gDirBrowserInstances.getIfThere(query_id);
	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 i;
	S32 count = msg->getNumberOfBlocks("QueryReplies");
	self->mResultsReceived += count;
	for (i = 0; i < count; i++)
	{
		msg->getUUID(	"QueryReplies", "ParcelID", parcel_id, i);
		msg->getString(	"QueryReplies", "Name", MAX_STRING, name, i);
		msg->getF32(	"QueryReplies", "Dwell", dwell, i);

		if (parcel_id.isNull())
		{
			continue;
		}

		LLSD content;
		content["type"] = POPULAR_CODE;
		content["name"] = name;

		LLSD row;
		row["id"] = parcel_id;

		LLUUID image_id;
		image_id.set( gViewerArt.getString("icon_popular.tga") );
		row["columns"][0]["column"] = "icon";
		row["columns"][0]["type"] = "icon";
		row["columns"][0]["value"] = image_id;

		row["columns"][1]["column"] = "name";
		row["columns"][1]["value"] = name;
		row["columns"][1]["font"] = "SANSSERIF";

		LLString buffer = llformat("%.0f", 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;
	char	name[MAX_STRING];			/*Flawfinder: ignore*/
	char	date[MAX_STRING];		/*Flawfinder: ignore*/
	BOOL	show_mature = gSavedSettings.getBOOL("ShowMatureEvents");

	msg->getUUID("AgentData", "AgentID", agent_id);
	msg->getUUID("QueryData", "QueryID", query_id );

	LLPanelDirBrowser* self;
	self = gDirBrowserInstances.getIfThere(query_id);
	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->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", MAX_STRING, name, i);
		msg->getU32("QueryReplies", "EventID", event_id, i);
		msg->getString("QueryReplies", "Date", MAX_STRING, 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?
			llwarns << "skipped event due to owner_id null, event_id " << event_id << llendl;
			continue;
		}
		// Skip mature events if not showing
		if (!show_mature
			&& (event_flags & EVENT_FLAG_MATURE))
		{
			llwarns << "Skipped due to maturity, 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_MATURE)
		{
			image_id.set( gViewerArt.getString("icon_event_mature.tga") );
			row["columns"][0]["column"] = "icon";
			row["columns"][0]["type"] = "icon";
			row["columns"][0]["value"] = image_id;
		}
		else
		{
			image_id.set( gViewerArt.getString("icon_event.tga") );
			row["columns"][0]["column"] = "icon";
			row["columns"][0]["type"] = "icon";
			row["columns"][0]["value"] = image_id;
		}

		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_SORTED);

		LLString 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;
	char	group_name[DB_GROUP_NAME_BUF_SIZE];		/*Flawfinder: ignore*/
	S32     members;
	F32     search_order;

	msg->getUUIDFast(_PREHASH_QueryData,_PREHASH_QueryID, query_id );

	LLPanelDirBrowser* self;
	self = gDirBrowserInstances.getIfThere(query_id);
	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,	DB_GROUP_NAME_BUF_SIZE,		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;
		image_id.set( gViewerArt.getString("icon_group.tga") );
		row["columns"][0]["column"] = "icon";
		row["columns"][0]["type"] = "icon";
		row["columns"][0]["value"] = image_id;

		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())
	{
		llwarns << "Message for wrong agent " << agent_id
			<< " in processDirClassifiedReply" << llendl;
		return;
	}

	msg->getUUID("QueryData", "QueryID", query_id);
	LLPanelDirBrowser* self = gDirBrowserInstances.getIfThere(query_id);
	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();
	}

	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;
		char name[DB_PARCEL_NAME_SIZE];		/*Flawfinder: ignore*/
		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", DB_PARCEL_NAME_SIZE, 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;
	char	name[MAX_STRING];		/*Flawfinder: ignore*/
	BOOL	auction;
	BOOL	for_sale;
	S32		sale_price;
	S32		actual_area;

	msg->getUUID("AgentData", "AgentID", agent_id);
	msg->getUUID("QueryData", "QueryID", query_id );

	LLPanelDirBrowser* browser;
	browser = gDirBrowserInstances.getIfThere(query_id);
	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", MAX_STRING, 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 (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;

		LLString buffer = "Auction";
		if (!auction)
		{
			buffer = llformat("%d", sale_price);
			non_auction_count++;
		}
		row["columns"][3]["column"] = "price";
		row["columns"][3]["value"] = buffer;
		row["columns"][3]["font"] = "SANSSERIFSMALL";

		buffer = llformat("%d", actual_area);
		row["columns"][4]["column"] = "area";
		row["columns"][4]["value"] = buffer;
		row["columns"][4]["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"][5]["column"] = "per_meter";
			row["columns"][5]["value"] = buffer;
			row["columns"][5]["font"] = "SANSSERIFSMALL";
		}
		else
		{
			// Auctions start at L$1 per meter
			row["columns"][5]["column"] = "per_meter";
			row["columns"][5]["value"] = "1.0";
			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);

	// Empty string will sort by current sort options.
	list->sortByColumn("",FALSE);
	self->updateResultCount();

	// Poke the result received timer
	self->mLastResultTimer.reset();
	self->mDidAutoSelect = FALSE;
}

void LLPanelDirBrowser::addClassified(LLCtrlListInterface *list, const LLUUID& pick_id, const char* name, const U32 creation_date, const S32 price_for_listing)
{
	LLString type = llformat("%d", CLASSIFIED_CODE);

	LLSD row;
	row["id"] = pick_id;

	LLUUID image_id;
	image_id.set( gViewerArt.getString("icon_top_pick.tga") );
	row["columns"][0]["column"] = "icon";
	row["columns"][0]["type"] = "icon";
	row["columns"][0]["value"] = image_id;

	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 LLString& name, S32 *type)
{
	LLSD row;
	row["id"] = parcel_id;
	LLUUID image_id;

	// Icon and type
	if(is_auction)
	{
		image_id.set( gViewerArt.getString("icon_auction.tga") );
		row["columns"][0]["column"] = "icon";
		row["columns"][0]["type"] = "icon";
		row["columns"][0]["value"] = image_id;

		*type = AUCTION_CODE;
	}
	else if (is_for_sale)
	{
		image_id.set( gViewerArt.getString("icon_for_sale.tga") );
		row["columns"][0]["column"] = "icon";
		row["columns"][0]["type"] = "icon";
		row["columns"][0]["value"] = image_id;

		*type = FOR_SALE_CODE;
	}
	else
	{
		image_id.set( gViewerArt.getString("icon_place.tga") );
		row["columns"][0]["column"] = "icon";
		row["columns"][0]["type"] = "icon";
		row["columns"][0]["value"] = image_id;

		*type = PLACE_CODE;
	}

	row["columns"][2]["column"] = "name";
	row["columns"][2]["value"] = name;
	row["columns"][2]["font"] = "SANSSERIF";

	return row;
}

void LLPanelDirBrowser::newClassified()
{
	LLCtrlListInterface *list = childGetListInterface("results");
	if (!list) return;

	if (mFloaterDirectory->mPanelClassifiedp)
	{
		// Clear the panel on the right
		mFloaterDirectory->mPanelClassifiedp->reset();

		// Set up the classified with the info we've created
		// and a sane default position.
		mFloaterDirectory->mPanelClassifiedp->initNewClassified();

		// We need the ID to select in the list.
		LLUUID classified_id = mFloaterDirectory->mPanelClassifiedp->getClassifiedID();

		// Put it in the list on the left
		addClassified(list, classified_id, mFloaterDirectory->mPanelClassifiedp->getClassifiedName().c_str(),0,0);

		// Select it.
		list->setCurrentByID(classified_id);

		// Make the right panel visible (should already be)
		mFloaterDirectory->mPanelClassifiedp->setVisible(TRUE);
	}
}

void LLPanelDirBrowser::renameClassified(const LLUUID& classified_id, const char* name)
{
	// TomY What, really?
	/*LLScrollListItem* row;
	for (row = mResultsList->getFirstData(); row; row = mResultsList->getNextData())
	{
		if (row->getUUID() == classified_id)
		{
			const LLScrollListCell* column;
			LLScrollListText* text;

			// icon
			// type
			column = row->getColumn(2);	// name (visible)
			text = (LLScrollListText*)column;
			text->setText(name);

			column = row->getColumn(3);	// name (invisible)
			text = (LLScrollListText*)column;
			text->setText(name);
		}
	}*/
}


void LLPanelDirBrowser::setupNewSearch()
{
	LLCtrlListInterface *list = childGetListInterface("results");
	if (!list) return;

	gDirBrowserInstances.removeData(mSearchID);
	// Make a new query ID
	mSearchID.generate();

	gDirBrowserInstances.addData(mSearchID, this);

	// ready the list for results
	list->operateOnAll(LLCtrlListInterface::OP_DELETE);
	list->addSimpleElement("Searching...");
	childDisable("results");

	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();

	LLFloaterDirectory::sOldSearchCount++;
}


// static
void LLPanelDirBrowser::sendDirFindQuery(
	LLMessageSystem* msg,
	const LLUUID& query_id,
	const LLString& 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::addHelpText(const char* text)
{
	LLCtrlListInterface *list = childGetListInterface("results");
	if (!list) return;

	list->addSimpleElement(text);
	childDisable("results");
}


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);
		childSetVisible("Next >", show_next);
		if (show_next)
		{
			rows -= (mResultsReceived - mResultsPerPage);
		}
	}
	else
	{
		// Hide page buttons
		childHide("Next >");
		childHide("< Prev");
	}
	return rows;
}