PageRenderTime 106ms CodeModel.GetById 15ms app.highlight 83ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/newview/llpanelclassified.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 1169 lines | 870 code | 206 blank | 93 comment | 65 complexity | 7de975b060f3568cba8cda7657c0ee2a MD5 | raw file
   1/** 
   2 * @file llpanelclassified.cpp
   3 * @brief LLPanelClassified class implementation
   4 *
   5 * $LicenseInfo:firstyear=2005&license=viewerlgpl$
   6 * Second Life Viewer Source Code
   7 * Copyright (C) 2010, Linden Research, Inc.
   8 * 
   9 * This library is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU Lesser General Public
  11 * License as published by the Free Software Foundation;
  12 * version 2.1 of the License only.
  13 * 
  14 * This library is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17 * Lesser General Public License for more details.
  18 * 
  19 * You should have received a copy of the GNU Lesser General Public
  20 * License along with this library; if not, write to the Free Software
  21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  22 * 
  23 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  24 * $/LicenseInfo$
  25 */
  26
  27// Display of a classified used both for the global view in the
  28// Find directory, and also for each individual user's classified in their
  29// profile.
  30
  31#include "llviewerprecompiledheaders.h"
  32
  33#include "llpanelclassified.h"
  34
  35#include "lldispatcher.h"
  36#include "llfloaterreg.h"
  37#include "llhttpclient.h"
  38#include "llnotifications.h"
  39#include "llnotificationsutil.h"
  40#include "llparcel.h"
  41
  42#include "llagent.h"
  43#include "llclassifiedflags.h"
  44#include "llclassifiedstatsresponder.h"
  45#include "llcommandhandler.h" // for classified HTML detail page click tracking
  46#include "lliconctrl.h"
  47#include "lllineeditor.h"
  48#include "llcombobox.h"
  49#include "lltexturectrl.h"
  50#include "lltexteditor.h"
  51#include "llviewerparcelmgr.h"
  52#include "llfloaterworldmap.h"
  53#include "llviewergenericmessage.h"	// send_generic_message
  54#include "llviewerregion.h"
  55#include "lltrans.h"
  56#include "llscrollcontainer.h"
  57#include "llstatusbar.h"
  58
  59const S32 MINIMUM_PRICE_FOR_LISTING = 50;	// L$
  60
  61//static
  62LLPanelClassifiedInfo::panel_list_t LLPanelClassifiedInfo::sAllPanels;
  63
  64// "classifiedclickthrough"
  65// strings[0] = classified_id
  66// strings[1] = teleport_clicks
  67// strings[2] = map_clicks
  68// strings[3] = profile_clicks
  69class LLDispatchClassifiedClickThrough : public LLDispatchHandler
  70{
  71public:
  72	virtual bool operator()(
  73		const LLDispatcher* dispatcher,
  74		const std::string& key,
  75		const LLUUID& invoice,
  76		const sparam_t& strings)
  77	{
  78		if (strings.size() != 4) return false;
  79		LLUUID classified_id(strings[0]);
  80		S32 teleport_clicks = atoi(strings[1].c_str());
  81		S32 map_clicks = atoi(strings[2].c_str());
  82		S32 profile_clicks = atoi(strings[3].c_str());
  83
  84		LLPanelClassifiedInfo::setClickThrough(
  85			classified_id, teleport_clicks, map_clicks, profile_clicks, false);
  86
  87		return true;
  88	}
  89};
  90static LLDispatchClassifiedClickThrough sClassifiedClickThrough;
  91
  92// Just to debug errors. Can be thrown away later.
  93class LLClassifiedClickMessageResponder : public LLHTTPClient::Responder
  94{
  95	LOG_CLASS(LLClassifiedClickMessageResponder);
  96
  97public:
  98	// If we get back an error (not found, etc...), handle it here
  99	virtual void errorWithContent(
 100		U32 status,
 101		const std::string& reason,
 102		const LLSD& content)
 103	{
 104		llwarns << "Sending click message failed (" << status << "): [" << reason << "]" << llendl;
 105		llwarns << "Content: [" << content << "]" << llendl;
 106	}
 107};
 108
 109//////////////////////////////////////////////////////////////////////////
 110//////////////////////////////////////////////////////////////////////////
 111//////////////////////////////////////////////////////////////////////////
 112
 113LLPanelClassifiedInfo::LLPanelClassifiedInfo()
 114 : LLPanel()
 115 , mInfoLoaded(false)
 116 , mScrollingPanel(NULL)
 117 , mScrollContainer(NULL)
 118 , mScrollingPanelMinHeight(0)
 119 , mScrollingPanelWidth(0)
 120 , mSnapshotStreched(false)
 121 , mTeleportClicksOld(0)
 122 , mMapClicksOld(0)
 123 , mProfileClicksOld(0)
 124 , mTeleportClicksNew(0)
 125 , mMapClicksNew(0)
 126 , mProfileClicksNew(0)
 127 , mSnapshotCtrl(NULL)
 128{
 129	sAllPanels.push_back(this);
 130}
 131
 132LLPanelClassifiedInfo::~LLPanelClassifiedInfo()
 133{
 134	sAllPanels.remove(this);
 135}
 136
 137// static
 138LLPanelClassifiedInfo* LLPanelClassifiedInfo::create()
 139{
 140	LLPanelClassifiedInfo* panel = new LLPanelClassifiedInfo();
 141	panel->buildFromFile("panel_classified_info.xml");
 142	return panel;
 143}
 144
 145BOOL LLPanelClassifiedInfo::postBuild()
 146{
 147	childSetAction("back_btn", boost::bind(&LLPanelClassifiedInfo::onExit, this));
 148	childSetAction("show_on_map_btn", boost::bind(&LLPanelClassifiedInfo::onMapClick, this));
 149	childSetAction("teleport_btn", boost::bind(&LLPanelClassifiedInfo::onTeleportClick, this));
 150
 151	mScrollingPanel = getChild<LLPanel>("scroll_content_panel");
 152	mScrollContainer = getChild<LLScrollContainer>("profile_scroll");
 153
 154	mScrollingPanelMinHeight = mScrollContainer->getScrolledViewRect().getHeight();
 155	mScrollingPanelWidth = mScrollingPanel->getRect().getWidth();
 156
 157	mSnapshotCtrl = getChild<LLTextureCtrl>("classified_snapshot");
 158	mSnapshotRect = getDefaultSnapshotRect();
 159
 160	return TRUE;
 161}
 162
 163void LLPanelClassifiedInfo::setExitCallback(const commit_callback_t& cb)
 164{
 165	getChild<LLButton>("back_btn")->setClickedCallback(cb);
 166}
 167
 168void LLPanelClassifiedInfo::setEditClassifiedCallback(const commit_callback_t& cb)
 169{
 170	getChild<LLButton>("edit_btn")->setClickedCallback(cb);
 171}
 172
 173void LLPanelClassifiedInfo::reshape(S32 width, S32 height, BOOL called_from_parent /* = TRUE */)
 174{
 175	LLPanel::reshape(width, height, called_from_parent);
 176
 177	if (!mScrollContainer || !mScrollingPanel)
 178		return;
 179
 180	static LLUICachedControl<S32> scrollbar_size ("UIScrollbarSize", 0);
 181
 182	S32 scroll_height = mScrollContainer->getRect().getHeight();
 183	if (mScrollingPanelMinHeight >= scroll_height)
 184	{
 185		mScrollingPanel->reshape(mScrollingPanelWidth, mScrollingPanelMinHeight);
 186	}
 187	else
 188	{
 189		mScrollingPanel->reshape(mScrollingPanelWidth + scrollbar_size, scroll_height);
 190	}
 191
 192	mSnapshotRect = getDefaultSnapshotRect();
 193	stretchSnapshot();
 194}
 195
 196void LLPanelClassifiedInfo::onOpen(const LLSD& key)
 197{
 198	LLUUID avatar_id = key["classified_creator_id"];
 199	if(avatar_id.isNull())
 200	{
 201		return;
 202	}
 203
 204	if(getAvatarId().notNull())
 205	{
 206		LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this);
 207	}
 208
 209	setAvatarId(avatar_id);
 210
 211	resetData();
 212	resetControls();
 213	scrollToTop();
 214
 215	setClassifiedId(key["classified_id"]);
 216	setClassifiedName(key["classified_name"]);
 217	setDescription(key["classified_desc"]);
 218	setSnapshotId(key["classified_snapshot_id"]);
 219	setFromSearch(key["from_search"]);
 220
 221	llinfos << "Opening classified [" << getClassifiedName() << "] (" << getClassifiedId() << ")" << llendl;
 222
 223	LLAvatarPropertiesProcessor::getInstance()->addObserver(getAvatarId(), this);
 224	LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId());
 225	gGenericDispatcher.addHandler("classifiedclickthrough", &sClassifiedClickThrough);
 226
 227	// While we're at it let's get the stats from the new table if that
 228	// capability exists.
 229	std::string url = gAgent.getRegion()->getCapability("SearchStatRequest");
 230	if (!url.empty())
 231	{
 232		llinfos << "Classified stat request via capability" << llendl;
 233		LLSD body;
 234		body["classified_id"] = getClassifiedId();
 235		LLHTTPClient::post(url, body, new LLClassifiedStatsResponder(getClassifiedId()));
 236	}
 237
 238	// Update classified click stats.
 239	// *TODO: Should we do this when opening not from search?
 240	sendClickMessage("profile");
 241
 242	setInfoLoaded(false);
 243}
 244
 245void LLPanelClassifiedInfo::processProperties(void* data, EAvatarProcessorType type)
 246{
 247	if(APT_CLASSIFIED_INFO == type)
 248	{
 249		LLAvatarClassifiedInfo* c_info = static_cast<LLAvatarClassifiedInfo*>(data);
 250		if(c_info && getClassifiedId() == c_info->classified_id)
 251		{
 252			setClassifiedName(c_info->name);
 253			setDescription(c_info->description);
 254			setSnapshotId(c_info->snapshot_id);
 255			setParcelId(c_info->parcel_id);
 256			setPosGlobal(c_info->pos_global);
 257			setSimName(c_info->sim_name);
 258
 259			setClassifiedLocation(createLocationText(c_info->parcel_name, c_info->sim_name, c_info->pos_global));
 260			getChild<LLUICtrl>("category")->setValue(LLClassifiedInfo::sCategories[c_info->category]);
 261
 262			static std::string mature_str = getString("type_mature");
 263			static std::string pg_str = getString("type_pg");
 264			static LLUIString  price_str = getString("l$_price");
 265			static std::string date_fmt = getString("date_fmt");
 266
 267			bool mature = is_cf_mature(c_info->flags);
 268			getChild<LLUICtrl>("content_type")->setValue(mature ? mature_str : pg_str);
 269			getChild<LLIconCtrl>("content_type_moderate")->setVisible(mature);
 270			getChild<LLIconCtrl>("content_type_general")->setVisible(!mature);
 271
 272			std::string auto_renew_str = is_cf_auto_renew(c_info->flags) ? 
 273				getString("auto_renew_on") : getString("auto_renew_off");
 274			getChild<LLUICtrl>("auto_renew")->setValue(auto_renew_str);
 275
 276			price_str.setArg("[PRICE]", llformat("%d", c_info->price_for_listing));
 277			getChild<LLUICtrl>("price_for_listing")->setValue(LLSD(price_str));
 278
 279			std::string date_str = date_fmt;
 280			LLStringUtil::format(date_str, LLSD().with("datetime", (S32) c_info->creation_date));
 281			getChild<LLUICtrl>("creation_date")->setValue(date_str);
 282
 283			setInfoLoaded(true);
 284		}
 285	}
 286}
 287
 288void LLPanelClassifiedInfo::resetData()
 289{
 290	setClassifiedName(LLStringUtil::null);
 291	setDescription(LLStringUtil::null);
 292	setClassifiedLocation(LLStringUtil::null);
 293	setClassifiedId(LLUUID::null);
 294	setSnapshotId(LLUUID::null);
 295	setPosGlobal(LLVector3d::zero);
 296	setParcelId(LLUUID::null);
 297	setSimName(LLStringUtil::null);
 298	setFromSearch(false);
 299
 300	// reset click stats
 301	mTeleportClicksOld	= 0;
 302	mMapClicksOld		= 0;
 303	mProfileClicksOld	= 0;
 304	mTeleportClicksNew	= 0;
 305	mMapClicksNew		= 0;
 306	mProfileClicksNew	= 0;
 307
 308	getChild<LLUICtrl>("category")->setValue(LLStringUtil::null);
 309	getChild<LLUICtrl>("content_type")->setValue(LLStringUtil::null);
 310	getChild<LLUICtrl>("click_through_text")->setValue(LLStringUtil::null);
 311	getChild<LLUICtrl>("price_for_listing")->setValue(LLStringUtil::null);
 312	getChild<LLUICtrl>("auto_renew")->setValue(LLStringUtil::null);
 313	getChild<LLUICtrl>("creation_date")->setValue(LLStringUtil::null);
 314	getChild<LLUICtrl>("click_through_text")->setValue(LLStringUtil::null);
 315	getChild<LLIconCtrl>("content_type_moderate")->setVisible(FALSE);
 316	getChild<LLIconCtrl>("content_type_general")->setVisible(FALSE);
 317}
 318
 319void LLPanelClassifiedInfo::resetControls()
 320{
 321	bool is_self = getAvatarId() == gAgent.getID();
 322
 323	getChildView("edit_btn")->setEnabled(is_self);
 324	getChildView("edit_btn")->setVisible( is_self);
 325	getChildView("price_layout_panel")->setVisible( is_self);
 326	getChildView("clickthrough_layout_panel")->setVisible( is_self);
 327}
 328
 329void LLPanelClassifiedInfo::setClassifiedName(const std::string& name)
 330{
 331	getChild<LLUICtrl>("classified_name")->setValue(name);
 332}
 333
 334std::string LLPanelClassifiedInfo::getClassifiedName()
 335{
 336	return getChild<LLUICtrl>("classified_name")->getValue().asString();
 337}
 338
 339void LLPanelClassifiedInfo::setDescription(const std::string& desc)
 340{
 341	getChild<LLUICtrl>("classified_desc")->setValue(desc);
 342}
 343
 344std::string LLPanelClassifiedInfo::getDescription()
 345{
 346	return getChild<LLUICtrl>("classified_desc")->getValue().asString();
 347}
 348
 349void LLPanelClassifiedInfo::setClassifiedLocation(const std::string& location)
 350{
 351	getChild<LLUICtrl>("classified_location")->setValue(location);
 352}
 353
 354std::string LLPanelClassifiedInfo::getClassifiedLocation()
 355{
 356	return getChild<LLUICtrl>("classified_location")->getValue().asString();
 357}
 358
 359void LLPanelClassifiedInfo::setSnapshotId(const LLUUID& id)
 360{
 361	mSnapshotCtrl->setValue(id);
 362	mSnapshotStreched = false;
 363}
 364
 365void LLPanelClassifiedInfo::draw()
 366{
 367	LLPanel::draw();
 368
 369	// Stretch in draw because it takes some time to load a texture,
 370	// going to try to stretch snapshot until texture is loaded
 371	if(!mSnapshotStreched)
 372	{
 373		stretchSnapshot();
 374	}
 375}
 376
 377LLUUID LLPanelClassifiedInfo::getSnapshotId()
 378{
 379	return getChild<LLUICtrl>("classified_snapshot")->getValue().asUUID();
 380}
 381
 382// static
 383void LLPanelClassifiedInfo::setClickThrough(
 384	const LLUUID& classified_id,
 385	S32 teleport,
 386	S32 map,
 387	S32 profile,
 388	bool from_new_table)
 389{
 390	llinfos << "Click-through data for classified " << classified_id << " arrived: ["
 391			<< teleport << ", " << map << ", " << profile << "] ("
 392			<< (from_new_table ? "new" : "old") << ")" << llendl;
 393
 394	for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter)
 395	{
 396		LLPanelClassifiedInfo* self = *iter;
 397		if (self->getClassifiedId() != classified_id)
 398		{
 399			continue;
 400		}
 401
 402		// *HACK: Skip LLPanelClassifiedEdit instances: they don't display clicks data.
 403		// Those instances should not be in the list at all.
 404		if (typeid(*self) != typeid(LLPanelClassifiedInfo))
 405		{
 406			continue;
 407		}
 408
 409		llinfos << "Updating classified info panel" << llendl;
 410
 411		// We need to check to see if the data came from the new stat_table 
 412		// or the old classified table. We also need to cache the data from 
 413		// the two separate sources so as to display the aggregate totals.
 414
 415		if (from_new_table)
 416		{
 417			self->mTeleportClicksNew = teleport;
 418			self->mMapClicksNew = map;
 419			self->mProfileClicksNew = profile;
 420		}
 421		else
 422		{
 423			self->mTeleportClicksOld = teleport;
 424			self->mMapClicksOld = map;
 425			self->mProfileClicksOld = profile;
 426		}
 427
 428		static LLUIString ct_str = self->getString("click_through_text_fmt");
 429
 430		ct_str.setArg("[TELEPORT]",	llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld));
 431		ct_str.setArg("[MAP]",		llformat("%d", self->mMapClicksNew + self->mMapClicksOld));
 432		ct_str.setArg("[PROFILE]",	llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld));
 433
 434		self->getChild<LLUICtrl>("click_through_text")->setValue(ct_str.getString());
 435		// *HACK: remove this when there is enough room for click stats in the info panel
 436		self->getChildView("click_through_text")->setToolTip(ct_str.getString());  
 437
 438		llinfos << "teleport: " << llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld)
 439				<< ", map: "    << llformat("%d", self->mMapClicksNew + self->mMapClicksOld)
 440				<< ", profile: " << llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld)
 441				<< llendl;
 442	}
 443}
 444
 445// static
 446std::string LLPanelClassifiedInfo::createLocationText(
 447	const std::string& original_name, 
 448	const std::string& sim_name, 
 449	const LLVector3d& pos_global)
 450{
 451	std::string location_text;
 452	
 453	location_text.append(original_name);
 454
 455	if (!sim_name.empty())
 456	{
 457		if (!location_text.empty()) 
 458			location_text.append(", ");
 459		location_text.append(sim_name);
 460	}
 461
 462	if (!location_text.empty()) 
 463		location_text.append(" ");
 464
 465	if (!pos_global.isNull())
 466	{
 467		S32 region_x = llround((F32)pos_global.mdV[VX]) % REGION_WIDTH_UNITS;
 468		S32 region_y = llround((F32)pos_global.mdV[VY]) % REGION_WIDTH_UNITS;
 469		S32 region_z = llround((F32)pos_global.mdV[VZ]);
 470		location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z));
 471	}
 472
 473	return location_text;
 474}
 475
 476void LLPanelClassifiedInfo::stretchSnapshot()
 477{
 478	// *NOTE dzaporozhan
 479	// Could be moved to LLTextureCtrl
 480
 481	LLViewerFetchedTexture* texture = mSnapshotCtrl->getTexture();
 482
 483	if(!texture)
 484	{
 485		return;
 486	}
 487
 488	if(0 == texture->getOriginalWidth() || 0 == texture->getOriginalHeight())
 489	{
 490		// looks like texture is not loaded yet
 491		return;
 492	}
 493
 494	LLRect rc = mSnapshotRect;
 495	// *HACK dzaporozhan
 496	// LLTextureCtrl uses BTN_HEIGHT_SMALL as bottom for texture which causes
 497	// drawn texture to be smaller than expected. (see LLTextureCtrl::draw())
 498	// Lets increase texture height to force texture look as expected.
 499	rc.mBottom -= BTN_HEIGHT_SMALL;
 500
 501	F32 t_width = texture->getFullWidth();
 502	F32 t_height = texture->getFullHeight();
 503
 504	F32 ratio = llmin<F32>( (rc.getWidth() / t_width), (rc.getHeight() / t_height) );
 505
 506	t_width *= ratio;
 507	t_height *= ratio;
 508
 509	rc.setCenterAndSize(rc.getCenterX(), rc.getCenterY(), llfloor(t_width), llfloor(t_height));
 510	mSnapshotCtrl->setShape(rc);
 511
 512	mSnapshotStreched = true;
 513}
 514
 515LLRect LLPanelClassifiedInfo::getDefaultSnapshotRect()
 516{
 517	// Using scroll container makes getting default rect a hard task
 518	// because rect in postBuild() and in first reshape() is not the same.
 519	// Using snapshot_panel makes it easier to reshape snapshot.
 520	return getChild<LLUICtrl>("snapshot_panel")->getLocalRect();
 521}
 522
 523void LLPanelClassifiedInfo::scrollToTop()
 524{
 525	LLScrollContainer* scrollContainer = findChild<LLScrollContainer>("profile_scroll");
 526	if (scrollContainer)
 527		scrollContainer->goToTop();
 528}
 529
 530// static
 531// *TODO: move out of the panel
 532void LLPanelClassifiedInfo::sendClickMessage(
 533		const std::string& type,
 534		bool from_search,
 535		const LLUUID& classified_id,
 536		const LLUUID& parcel_id,
 537		const LLVector3d& global_pos,
 538		const std::string& sim_name)
 539{
 540	// You're allowed to click on your own ads to reassure yourself
 541	// that the system is working.
 542	LLSD body;
 543	body["type"]			= type;
 544	body["from_search"]		= from_search;
 545	body["classified_id"]	= classified_id;
 546	body["parcel_id"]		= parcel_id;
 547	body["dest_pos_global"]	= global_pos.getValue();
 548	body["region_name"]		= sim_name;
 549
 550	std::string url = gAgent.getRegion()->getCapability("SearchStatTracking");
 551	llinfos << "Sending click msg via capability (url=" << url << ")" << llendl;
 552	llinfos << "body: [" << body << "]" << llendl;
 553	LLHTTPClient::post(url, body, new LLClassifiedClickMessageResponder());
 554}
 555
 556void LLPanelClassifiedInfo::sendClickMessage(const std::string& type)
 557{
 558	sendClickMessage(
 559		type,
 560		fromSearch(),
 561		getClassifiedId(),
 562		getParcelId(),
 563		getPosGlobal(),
 564		getSimName());
 565}
 566
 567void LLPanelClassifiedInfo::onMapClick()
 568{
 569	sendClickMessage("map");
 570	LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal());
 571	LLFloaterReg::showInstance("world_map", "center");
 572}
 573
 574void LLPanelClassifiedInfo::onTeleportClick()
 575{
 576	if (!getPosGlobal().isExactlyZero())
 577	{
 578		sendClickMessage("teleport");
 579		gAgent.teleportViaLocation(getPosGlobal());
 580		LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal());
 581	}
 582}
 583
 584void LLPanelClassifiedInfo::onExit()
 585{
 586	LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this);
 587	gGenericDispatcher.addHandler("classifiedclickthrough", NULL); // deregister our handler
 588}
 589
 590//////////////////////////////////////////////////////////////////////////
 591//////////////////////////////////////////////////////////////////////////
 592//////////////////////////////////////////////////////////////////////////
 593
 594static const S32 CB_ITEM_MATURE = 0;
 595static const S32 CB_ITEM_PG	   = 1;
 596
 597LLPanelClassifiedEdit::LLPanelClassifiedEdit()
 598 : LLPanelClassifiedInfo()
 599 , mIsNew(false)
 600 , mIsNewWithErrors(false)
 601 , mCanClose(false)
 602 , mPublishFloater(NULL)
 603{
 604}
 605
 606LLPanelClassifiedEdit::~LLPanelClassifiedEdit()
 607{
 608}
 609
 610//static
 611LLPanelClassifiedEdit* LLPanelClassifiedEdit::create()
 612{
 613	LLPanelClassifiedEdit* panel = new LLPanelClassifiedEdit();
 614	panel->buildFromFile("panel_edit_classified.xml");
 615	return panel;
 616}
 617
 618BOOL LLPanelClassifiedEdit::postBuild()
 619{
 620	LLPanelClassifiedInfo::postBuild();
 621
 622	LLTextureCtrl* snapshot = getChild<LLTextureCtrl>("classified_snapshot");
 623	snapshot->setOnSelectCallback(boost::bind(&LLPanelClassifiedEdit::onChange, this));
 624
 625	LLUICtrl* edit_icon = getChild<LLUICtrl>("edit_icon");
 626	snapshot->setMouseEnterCallback(boost::bind(&LLPanelClassifiedEdit::onTexturePickerMouseEnter, this, edit_icon));
 627	snapshot->setMouseLeaveCallback(boost::bind(&LLPanelClassifiedEdit::onTexturePickerMouseLeave, this, edit_icon));
 628	edit_icon->setVisible(false);
 629
 630	LLLineEditor* line_edit = getChild<LLLineEditor>("classified_name");
 631	line_edit->setKeystrokeCallback(boost::bind(&LLPanelClassifiedEdit::onChange, this), NULL);
 632
 633	LLTextEditor* text_edit = getChild<LLTextEditor>("classified_desc");
 634	text_edit->setKeystrokeCallback(boost::bind(&LLPanelClassifiedEdit::onChange, this));
 635
 636	LLComboBox* combobox = getChild<LLComboBox>( "category");
 637	LLClassifiedInfo::cat_map::iterator iter;
 638	for (iter = LLClassifiedInfo::sCategories.begin();
 639		iter != LLClassifiedInfo::sCategories.end();
 640		iter++)
 641	{
 642		combobox->add(LLTrans::getString(iter->second));
 643	}
 644
 645	combobox->setCommitCallback(boost::bind(&LLPanelClassifiedEdit::onChange, this));
 646
 647	childSetCommitCallback("content_type", boost::bind(&LLPanelClassifiedEdit::onChange, this), NULL);
 648	childSetCommitCallback("price_for_listing", boost::bind(&LLPanelClassifiedEdit::onChange, this), NULL);
 649	childSetCommitCallback("auto_renew", boost::bind(&LLPanelClassifiedEdit::onChange, this), NULL);
 650
 651	childSetAction("save_changes_btn", boost::bind(&LLPanelClassifiedEdit::onSaveClick, this));
 652	childSetAction("set_to_curr_location_btn", boost::bind(&LLPanelClassifiedEdit::onSetLocationClick, this));
 653
 654	mSnapshotCtrl->setOnSelectCallback(boost::bind(&LLPanelClassifiedEdit::onTextureSelected, this));
 655
 656	return TRUE;
 657}
 658
 659void LLPanelClassifiedEdit::fillIn(const LLSD& key)
 660{
 661	setAvatarId(gAgent.getID());
 662
 663	if(key.isUndefined())
 664	{
 665		setPosGlobal(gAgent.getPositionGlobal());
 666
 667		LLUUID snapshot_id = LLUUID::null;
 668		std::string desc;
 669		LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
 670
 671		if(parcel)
 672		{
 673			desc = parcel->getDesc();
 674			snapshot_id = parcel->getSnapshotID();
 675		}
 676
 677		std::string region_name = LLTrans::getString("ClassifiedUpdateAfterPublish");
 678		LLViewerRegion* region = gAgent.getRegion();
 679		if (region)
 680		{
 681			region_name = region->getName();
 682		}
 683
 684		getChild<LLUICtrl>("classified_name")->setValue(makeClassifiedName());
 685		getChild<LLUICtrl>("classified_desc")->setValue(desc);
 686		setSnapshotId(snapshot_id);
 687		setClassifiedLocation(createLocationText(getLocationNotice(), region_name, getPosGlobal()));
 688		// server will set valid parcel id
 689		setParcelId(LLUUID::null);
 690	}
 691	else
 692	{
 693		setClassifiedId(key["classified_id"]);
 694		setClassifiedName(key["name"]);
 695		setDescription(key["desc"]);
 696		setSnapshotId(key["snapshot_id"]);
 697		setCategory((U32)key["category"].asInteger());
 698		setContentType((U32)key["content_type"].asInteger());
 699		setClassifiedLocation(key["location_text"]);
 700		getChild<LLUICtrl>("auto_renew")->setValue(key["auto_renew"]);
 701		getChild<LLUICtrl>("price_for_listing")->setValue(key["price_for_listing"].asInteger());
 702	}
 703}
 704
 705void LLPanelClassifiedEdit::onOpen(const LLSD& key)
 706{
 707	mIsNew = key.isUndefined();
 708	
 709	scrollToTop();
 710
 711	// classified is not created yet
 712	bool is_new = isNew() || isNewWithErrors();
 713
 714	if(is_new)
 715	{
 716		resetData();
 717		resetControls();
 718
 719		fillIn(key);
 720
 721		if(isNew())
 722		{
 723			LLAvatarPropertiesProcessor::getInstance()->addObserver(getAvatarId(), this);
 724		}
 725	}
 726	else
 727	{
 728		LLPanelClassifiedInfo::onOpen(key);
 729	}
 730
 731	std::string save_btn_label = is_new ? getString("publish_label") : getString("save_label");
 732	getChild<LLUICtrl>("save_changes_btn")->setLabelArg("[LABEL]", save_btn_label);
 733
 734	enableVerbs(is_new);
 735	enableEditing(is_new);
 736	showEditing(!is_new);
 737	resetDirty();
 738	setInfoLoaded(false);
 739}
 740
 741void LLPanelClassifiedEdit::processProperties(void* data, EAvatarProcessorType type)
 742{
 743	if(APT_CLASSIFIED_INFO == type)
 744	{
 745		LLAvatarClassifiedInfo* c_info = static_cast<LLAvatarClassifiedInfo*>(data);
 746		if(c_info && getClassifiedId() == c_info->classified_id)
 747		{
 748			// see LLPanelClassifiedEdit::sendUpdate() for notes
 749			mIsNewWithErrors = false;
 750			// for just created classified - panel will probably be closed when we get here.
 751			if(!getVisible())
 752			{
 753				return;
 754			}
 755
 756			enableEditing(true);
 757
 758			setClassifiedName(c_info->name);
 759			setDescription(c_info->description);
 760			setSnapshotId(c_info->snapshot_id);
 761			setPosGlobal(c_info->pos_global);
 762
 763			setClassifiedLocation(createLocationText(c_info->parcel_name, c_info->sim_name, c_info->pos_global));
 764			// *HACK see LLPanelClassifiedEdit::sendUpdate()
 765			setCategory(c_info->category - 1);
 766
 767			bool mature = is_cf_mature(c_info->flags);
 768			bool auto_renew = is_cf_auto_renew(c_info->flags);
 769
 770			setContentType(mature ? CB_ITEM_MATURE : CB_ITEM_PG);
 771			getChild<LLUICtrl>("auto_renew")->setValue(auto_renew);
 772			getChild<LLUICtrl>("price_for_listing")->setValue(c_info->price_for_listing);
 773			getChildView("price_for_listing")->setEnabled(isNew());
 774
 775			resetDirty();
 776			setInfoLoaded(true);
 777			enableVerbs(false);
 778
 779			// for just created classified - in case user opened edit panel before processProperties() callback 
 780			getChild<LLUICtrl>("save_changes_btn")->setLabelArg("[LABEL]", getString("save_label"));
 781		}
 782	}
 783}
 784
 785BOOL LLPanelClassifiedEdit::isDirty() const
 786{
 787	if(mIsNew) 
 788	{
 789		return TRUE;
 790	}
 791
 792	BOOL dirty = false;
 793
 794	dirty |= LLPanelClassifiedInfo::isDirty();
 795	dirty |= getChild<LLUICtrl>("classified_snapshot")->isDirty();
 796	dirty |= getChild<LLUICtrl>("classified_name")->isDirty();
 797	dirty |= getChild<LLUICtrl>("classified_desc")->isDirty();
 798	dirty |= getChild<LLUICtrl>("category")->isDirty();
 799	dirty |= getChild<LLUICtrl>("content_type")->isDirty();
 800	dirty |= getChild<LLUICtrl>("auto_renew")->isDirty();
 801	dirty |= getChild<LLUICtrl>("price_for_listing")->isDirty();
 802
 803	return dirty;
 804}
 805
 806void LLPanelClassifiedEdit::resetDirty()
 807{
 808	LLPanelClassifiedInfo::resetDirty();
 809	getChild<LLUICtrl>("classified_snapshot")->resetDirty();
 810	getChild<LLUICtrl>("classified_name")->resetDirty();
 811
 812	LLTextEditor* desc = getChild<LLTextEditor>("classified_desc");
 813	// call blockUndo() to really reset dirty(and make isDirty work as intended)
 814	desc->blockUndo();
 815	desc->resetDirty();
 816
 817	getChild<LLUICtrl>("category")->resetDirty();
 818	getChild<LLUICtrl>("content_type")->resetDirty();
 819	getChild<LLUICtrl>("auto_renew")->resetDirty();
 820	getChild<LLUICtrl>("price_for_listing")->resetDirty();
 821}
 822
 823void LLPanelClassifiedEdit::setSaveCallback(const commit_signal_t::slot_type& cb)
 824{
 825	mSaveButtonClickedSignal.connect(cb);
 826}
 827
 828void LLPanelClassifiedEdit::setCancelCallback(const commit_signal_t::slot_type& cb)
 829{
 830	getChild<LLButton>("cancel_btn")->setClickedCallback(cb);
 831}
 832
 833void LLPanelClassifiedEdit::resetControls()
 834{
 835	LLPanelClassifiedInfo::resetControls();
 836
 837	getChild<LLComboBox>("category")->setCurrentByIndex(0);
 838	getChild<LLComboBox>("content_type")->setCurrentByIndex(0);
 839	getChild<LLUICtrl>("auto_renew")->setValue(false);
 840	getChild<LLUICtrl>("price_for_listing")->setValue(MINIMUM_PRICE_FOR_LISTING);
 841	getChildView("price_for_listing")->setEnabled(TRUE);
 842}
 843
 844bool LLPanelClassifiedEdit::canClose()
 845{
 846	return mCanClose;
 847}
 848
 849void LLPanelClassifiedEdit::draw()
 850{
 851	LLPanel::draw();
 852
 853	// Need to re-stretch on every draw because LLTextureCtrl::onSelectCallback
 854	// does not trigger callbacks when user navigates through images.
 855	stretchSnapshot();
 856}
 857
 858void LLPanelClassifiedEdit::stretchSnapshot()
 859{
 860	LLPanelClassifiedInfo::stretchSnapshot();
 861
 862	getChild<LLUICtrl>("edit_icon")->setShape(mSnapshotCtrl->getRect());
 863}
 864
 865U32 LLPanelClassifiedEdit::getContentType()
 866{
 867	LLComboBox* ct_cb = getChild<LLComboBox>("content_type");
 868	return ct_cb->getCurrentIndex();
 869}
 870
 871void LLPanelClassifiedEdit::setContentType(U32 content_type)
 872{
 873	LLComboBox* ct_cb = getChild<LLComboBox>("content_type");
 874	ct_cb->setCurrentByIndex(content_type);
 875	ct_cb->resetDirty();
 876}
 877
 878bool LLPanelClassifiedEdit::getAutoRenew()
 879{
 880	return getChild<LLUICtrl>("auto_renew")->getValue().asBoolean();
 881}
 882
 883void LLPanelClassifiedEdit::sendUpdate()
 884{
 885	LLAvatarClassifiedInfo c_data;
 886
 887	if(getClassifiedId().isNull())
 888	{
 889		setClassifiedId(LLUUID::generateNewID());
 890	}
 891
 892	c_data.agent_id = gAgent.getID();
 893	c_data.classified_id = getClassifiedId();
 894	// *HACK 
 895	// Categories on server start with 1 while combo-box index starts with 0
 896	c_data.category = getCategory() + 1;
 897	c_data.name = getClassifiedName();
 898	c_data.description = getDescription();
 899	c_data.parcel_id = getParcelId();
 900	c_data.snapshot_id = getSnapshotId();
 901	c_data.pos_global = getPosGlobal();
 902	c_data.flags = getFlags();
 903	c_data.price_for_listing = getPriceForListing();
 904
 905	LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoUpdate(&c_data);
 906	
 907	if(isNew())
 908	{
 909		// Lets assume there will be some error.
 910		// Successful sendClassifiedInfoUpdate will trigger processProperties and
 911		// let us know there was no error.
 912		mIsNewWithErrors = true;
 913	}
 914}
 915
 916U32 LLPanelClassifiedEdit::getCategory()
 917{
 918	LLComboBox* cat_cb = getChild<LLComboBox>("category");
 919	return cat_cb->getCurrentIndex();
 920}
 921
 922void LLPanelClassifiedEdit::setCategory(U32 category)
 923{
 924	LLComboBox* cat_cb = getChild<LLComboBox>("category");
 925	cat_cb->setCurrentByIndex(category);
 926	cat_cb->resetDirty();
 927}
 928
 929U8 LLPanelClassifiedEdit::getFlags()
 930{
 931	bool auto_renew = getChild<LLUICtrl>("auto_renew")->getValue().asBoolean();
 932
 933	LLComboBox* content_cb = getChild<LLComboBox>("content_type");
 934	bool mature = content_cb->getCurrentIndex() == CB_ITEM_MATURE;
 935	
 936	return pack_classified_flags_request(auto_renew, false, mature, false);
 937}
 938
 939void LLPanelClassifiedEdit::enableVerbs(bool enable)
 940{
 941	getChildView("save_changes_btn")->setEnabled(enable);
 942}
 943
 944void LLPanelClassifiedEdit::enableEditing(bool enable)
 945{
 946	getChildView("classified_snapshot")->setEnabled(enable);
 947	getChildView("classified_name")->setEnabled(enable);
 948	getChildView("classified_desc")->setEnabled(enable);
 949	getChildView("set_to_curr_location_btn")->setEnabled(enable);
 950	getChildView("category")->setEnabled(enable);
 951	getChildView("content_type")->setEnabled(enable);
 952	getChildView("price_for_listing")->setEnabled(enable);
 953	getChildView("auto_renew")->setEnabled(enable);
 954}
 955
 956void LLPanelClassifiedEdit::showEditing(bool show)
 957{
 958	getChildView("price_for_listing_label")->setVisible( show);
 959	getChildView("price_for_listing")->setVisible( show);
 960}
 961
 962std::string LLPanelClassifiedEdit::makeClassifiedName()
 963{
 964	std::string name;
 965
 966	LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
 967	if(parcel)
 968	{
 969		name = parcel->getName();
 970	}
 971
 972	if(!name.empty())
 973	{
 974		return name;
 975	}
 976
 977	LLViewerRegion* region = gAgent.getRegion();
 978	if(region)
 979	{
 980		name = region->getName();
 981	}
 982
 983	return name;
 984}
 985
 986S32 LLPanelClassifiedEdit::getPriceForListing()
 987{
 988	return getChild<LLUICtrl>("price_for_listing")->getValue().asInteger();
 989}
 990
 991void LLPanelClassifiedEdit::setPriceForListing(S32 price)
 992{
 993	getChild<LLUICtrl>("price_for_listing")->setValue(price);
 994}
 995
 996void LLPanelClassifiedEdit::onSetLocationClick()
 997{
 998	setPosGlobal(gAgent.getPositionGlobal());
 999	setParcelId(LLUUID::null);
1000
1001	std::string region_name = LLTrans::getString("ClassifiedUpdateAfterPublish");
1002	LLViewerRegion* region = gAgent.getRegion();
1003	if (region)
1004	{
1005		region_name = region->getName();
1006	}
1007
1008	setClassifiedLocation(createLocationText(getLocationNotice(), region_name, getPosGlobal()));
1009
1010	// mark classified as dirty
1011	setValue(LLSD());
1012
1013	onChange();
1014}
1015
1016void LLPanelClassifiedEdit::onChange()
1017{
1018	enableVerbs(isDirty());
1019}
1020
1021void LLPanelClassifiedEdit::onSaveClick()
1022{
1023	mCanClose = false;
1024
1025	if(!isValidName())
1026	{
1027		notifyInvalidName();
1028		return;
1029	}
1030	if(isNew() || isNewWithErrors())
1031	{
1032		if(gStatusBar->getBalance() < getPriceForListing())
1033		{
1034			LLNotificationsUtil::add("ClassifiedInsufficientFunds");
1035			return;
1036		}
1037
1038		mPublishFloater = LLFloaterReg::findTypedInstance<LLPublishClassifiedFloater>(
1039			"publish_classified", LLSD());
1040
1041		if(!mPublishFloater)
1042		{
1043			mPublishFloater = LLFloaterReg::getTypedInstance<LLPublishClassifiedFloater>(
1044				"publish_classified", LLSD());
1045
1046			mPublishFloater->setPublishClickedCallback(boost::bind
1047				(&LLPanelClassifiedEdit::onPublishFloaterPublishClicked, this));
1048		}
1049
1050		// set spinner value before it has focus or value wont be set
1051		mPublishFloater->setPrice(getPriceForListing());
1052		mPublishFloater->openFloater(mPublishFloater->getKey());
1053		mPublishFloater->center();
1054	}
1055	else
1056	{
1057		doSave();
1058	}
1059}
1060
1061void LLPanelClassifiedEdit::doSave()
1062{
1063	mCanClose = true;
1064	sendUpdate();
1065	resetDirty();
1066
1067	mSaveButtonClickedSignal(this, LLSD());
1068}
1069
1070void LLPanelClassifiedEdit::onPublishFloaterPublishClicked()
1071{
1072	setPriceForListing(mPublishFloater->getPrice());
1073
1074	doSave();
1075}
1076
1077std::string LLPanelClassifiedEdit::getLocationNotice()
1078{
1079	static std::string location_notice = getString("location_notice");
1080	return location_notice;
1081}
1082
1083bool LLPanelClassifiedEdit::isValidName()
1084{
1085	std::string name = getClassifiedName();
1086	if (name.empty())
1087	{
1088		return false;
1089	}
1090	if (!isalnum(name[0]))
1091	{
1092		return false;
1093	}
1094
1095	return true;
1096}
1097
1098void LLPanelClassifiedEdit::notifyInvalidName()
1099{
1100	std::string name = getClassifiedName();
1101	if (name.empty())
1102	{
1103		LLNotificationsUtil::add("BlankClassifiedName");
1104	}
1105	else if (!isalnum(name[0]))
1106	{
1107		LLNotificationsUtil::add("ClassifiedMustBeAlphanumeric");
1108	}
1109}
1110
1111void LLPanelClassifiedEdit::onTexturePickerMouseEnter(LLUICtrl* ctrl)
1112{
1113	ctrl->setVisible(TRUE);
1114}
1115
1116void LLPanelClassifiedEdit::onTexturePickerMouseLeave(LLUICtrl* ctrl)
1117{
1118	ctrl->setVisible(FALSE);
1119}
1120
1121void LLPanelClassifiedEdit::onTextureSelected()
1122{
1123	setSnapshotId(mSnapshotCtrl->getValue().asUUID());
1124}
1125
1126//////////////////////////////////////////////////////////////////////////
1127//////////////////////////////////////////////////////////////////////////
1128//////////////////////////////////////////////////////////////////////////
1129
1130LLPublishClassifiedFloater::LLPublishClassifiedFloater(const LLSD& key)
1131 : LLFloater(key)
1132{
1133}
1134
1135LLPublishClassifiedFloater::~LLPublishClassifiedFloater()
1136{
1137}
1138
1139BOOL LLPublishClassifiedFloater::postBuild()
1140{
1141	LLFloater::postBuild();
1142
1143	childSetAction("publish_btn", boost::bind(&LLFloater::closeFloater, this, false));
1144	childSetAction("cancel_btn", boost::bind(&LLFloater::closeFloater, this, false));
1145
1146	return TRUE;
1147}
1148
1149void LLPublishClassifiedFloater::setPrice(S32 price)
1150{
1151	getChild<LLUICtrl>("price_for_listing")->setValue(price);
1152}
1153
1154S32 LLPublishClassifiedFloater::getPrice()
1155{
1156	return getChild<LLUICtrl>("price_for_listing")->getValue().asInteger();
1157}
1158
1159void LLPublishClassifiedFloater::setPublishClickedCallback(const commit_signal_t::slot_type& cb)
1160{
1161	getChild<LLButton>("publish_btn")->setClickedCallback(cb);
1162}
1163
1164void LLPublishClassifiedFloater::setCancelClickedCallback(const commit_signal_t::slot_type& cb)
1165{
1166	getChild<LLButton>("cancel_btn")->setClickedCallback(cb);
1167}
1168
1169//EOF