llpanelclassified.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056
  1. /**
  2. * @file llpanelclassified.cpp
  3. * @brief LLPanelClassified class implementation
  4. *
  5. * $LicenseInfo:firstyear=2005&license=viewergpl$
  6. *
  7. * Copyright (c) 2005-2009, Linden Research, Inc.
  8. *
  9. * Second Life Viewer Source Code
  10. * The source code in this file ("Source Code") is provided by Linden Lab
  11. * to you under the terms of the GNU General Public License, version 2.0
  12. * ("GPL"), unless you have obtained a separate licensing agreement
  13. * ("Other License"), formally executed by you and Linden Lab. Terms of
  14. * the GPL can be found in doc/GPL-license.txt in this distribution, or
  15. * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
  16. *
  17. * There are special exceptions to the terms and conditions of the GPL as
  18. * it is applied to this Source Code. View the full text of the exception
  19. * in the file doc/FLOSS-exception.txt in this software distribution, or
  20. * online at
  21. * http://secondlifegrid.net/programs/open_source/licensing/flossexception
  22. *
  23. * By copying, modifying or distributing this software, you acknowledge
  24. * that you have read and understood your obligations described above,
  25. * and agree to abide by those obligations.
  26. *
  27. * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
  28. * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
  29. * COMPLETENESS OR PERFORMANCE.
  30. * $/LicenseInfo$
  31. */
  32. // Display of a classified used both for the global view in the
  33. // Find directory, and also for each individual user's classified in their
  34. // profile.
  35. #include "llviewerprecompiledheaders.h"
  36. #include "llpanelclassified.h"
  37. #include "llalertdialog.h"
  38. #include "llbutton.h"
  39. #include "llcheckboxctrl.h"
  40. #include "llclassifiedflags.h"
  41. #include "llcombobox.h"
  42. #include "llcorehttputil.h"
  43. #include "lldir.h"
  44. #include "lldispatcher.h"
  45. #include "lllineeditor.h"
  46. #include "llparcel.h"
  47. #include "llsdserialize.h"
  48. #include "lltabcontainer.h"
  49. #include "lltextbox.h"
  50. #include "lluictrlfactory.h"
  51. #include "llagent.h"
  52. #include "llappviewer.h" // For abortQuit()
  53. #include "llfloateravatarinfo.h"
  54. #include "llfloaterclassified.h"
  55. #include "llfloaterworldmap.h"
  56. #include "lltexturectrl.h"
  57. #include "llurldispatcher.h" // For classified detail click teleports
  58. #include "llviewercontrol.h"
  59. #include "llviewermessage.h" // For gGenericDispatcher
  60. #include "llviewerparcelmgr.h"
  61. #include "llviewerregion.h"
  62. #include "llviewertexteditor.h"
  63. #include "llworldmap.h"
  64. constexpr S32 MINIMUM_PRICE_FOR_LISTING = 50; // L$
  65. constexpr S32 MATURE_CONTENT = 1;
  66. constexpr S32 PG_CONTENT = 2;
  67. constexpr S32 DECLINE_TO_STATE = 0;
  68. ///////////////////////////////////////////////////////////////////////////////
  69. // LLClassifiedInfo static class
  70. ///////////////////////////////////////////////////////////////////////////////
  71. LLClassifiedInfo::map_t LLClassifiedInfo::sCategories;
  72. //static
  73. void LLClassifiedInfo::loadCategories(const LLSD& options)
  74. {
  75. std::string name;
  76. for (LLSD::array_const_iterator it = options.beginArray(),
  77. end = options.endArray();
  78. it != end; ++it)
  79. {
  80. const LLSD& entry = *it;
  81. if (entry.has("name") && entry.has("category_id"))
  82. {
  83. U32 id = entry["category_id"].asInteger();
  84. sCategories[id] = entry["category_name"].asString();
  85. }
  86. }
  87. }
  88. ///////////////////////////////////////////////////////////////////////////////
  89. // LLPanelClassified class
  90. ///////////////////////////////////////////////////////////////////////////////
  91. //static
  92. LLPanelClassified::panel_list_t LLPanelClassified::sInstances;
  93. // "classifiedclickthrough"
  94. // strings[0] = classified_id
  95. // strings[1] = teleport_clicks
  96. // strings[2] = map_clicks
  97. // strings[3] = profile_clicks
  98. class LLDispatchClassifiedClickThrough final : public LLDispatchHandler
  99. {
  100. public:
  101. bool operator()(const LLDispatcher* dispatcher, const std::string& key,
  102. const LLUUID& invoice, const sparam_t& strings) override
  103. {
  104. if (strings.size() != 4)
  105. {
  106. return false;
  107. }
  108. LLUUID classified_id(strings[0]);
  109. S32 teleport_clicks = atoi(strings[1].c_str());
  110. S32 map_clicks = atoi(strings[2].c_str());
  111. S32 profile_clicks = atoi(strings[3].c_str());
  112. LLPanelClassified::setClickThrough(classified_id, teleport_clicks,
  113. map_clicks, profile_clicks, false);
  114. return true;
  115. }
  116. };
  117. static LLDispatchClassifiedClickThrough sClassifiedClickThrough;
  118. LLPanelClassified::LLPanelClassified(bool in_finder, bool from_search)
  119. : LLPanel("Classified Panel"),
  120. LLAvatarPropertiesObserver(LLUUID::null, APT_CLASSIFIED_INFO),
  121. mInFinder(in_finder),
  122. mFromSearch(from_search),
  123. mDirty(false),
  124. mForceClose(false),
  125. mLocationChanged(false),
  126. mPriceForListing(0),
  127. mDataRequested(false),
  128. mPaidFor(false),
  129. mAutoRenewCheck(NULL),
  130. mUpdateBtn(NULL),
  131. mTeleportBtn(NULL),
  132. mMapBtn(NULL),
  133. mProfileBtn(NULL),
  134. mInfoText(NULL),
  135. mSetBtn(NULL),
  136. mClickThroughText(NULL),
  137. mTeleportClicksOld(0),
  138. mMapClicksOld(0),
  139. mProfileClicksOld(0),
  140. mTeleportClicksNew(0),
  141. mMapClicksNew(0),
  142. mProfileClicksNew(0)
  143. {
  144. sInstances.insert(this);
  145. std::string file = mInFinder ? "panel_classified.xml"
  146. : "panel_avatar_classified.xml";
  147. LLUICtrlFactory::getInstance()->buildPanel(this, file);
  148. LLAvatarProperties::addObserver(this);
  149. // Register dispatcher
  150. gGenericDispatcher.addHandler("classifiedclickthrough",
  151. &sClassifiedClickThrough);
  152. }
  153. LLPanelClassified::~LLPanelClassified()
  154. {
  155. LLAvatarProperties::removeObserver(this);
  156. sInstances.erase(this);
  157. }
  158. void LLPanelClassified::reset()
  159. {
  160. mClassifiedID.setNull();
  161. mCreatorID.setNull();
  162. mParcelID.setNull();
  163. // Do not request data, this is not valid
  164. mDataRequested = true;
  165. mDirty = false;
  166. mPaidFor = false;
  167. mPosGlobal.clear();
  168. clearCtrls();
  169. resetDirty();
  170. }
  171. bool LLPanelClassified::postBuild()
  172. {
  173. mSnapshotCtrl = getChild<LLTextureCtrl>("snapshot_ctrl");
  174. mSnapshotCtrl->setCommitCallback(onCommitAny);
  175. mSnapshotCtrl->setCallbackUserData(this);
  176. mSnapshotSize = mSnapshotCtrl->getRect();
  177. mNameEditor = getChild<LLLineEditor>("given_name_editor");
  178. mNameEditor->setMaxTextLength(DB_PARCEL_NAME_LEN);
  179. mNameEditor->setCommitOnFocusLost(true);
  180. mNameEditor->setFocusReceivedCallback(focusReceived, this);
  181. mNameEditor->setCommitCallback(onCommitAny);
  182. mNameEditor->setCallbackUserData(this);
  183. mNameEditor->setPrevalidate(LLLineEditor::prevalidateASCII);
  184. mDescEditor = getChild<LLTextEditor>("desc_editor");
  185. mDescEditor->setCommitOnFocusLost(true);
  186. mDescEditor->setFocusReceivedCallback(focusReceived, this);
  187. mDescEditor->setCommitCallback(onCommitAny);
  188. mDescEditor->setCallbackUserData(this);
  189. mDescEditor->setTabsToNextField(true);
  190. mLocationEditor = getChild<LLLineEditor>("location_editor");
  191. mSetBtn = getChild<LLButton>("set_location_btn");
  192. mSetBtn->setClickedCallback(onClickSet);
  193. mSetBtn->setCallbackUserData(this);
  194. mTeleportBtn = getChild<LLButton>("classified_teleport_btn");
  195. mTeleportBtn->setClickedCallback(onClickTeleport);
  196. mTeleportBtn->setCallbackUserData(this);
  197. mMapBtn = getChild<LLButton>("classified_map_btn");
  198. mMapBtn->setClickedCallback(onClickMap);
  199. mMapBtn->setCallbackUserData(this);
  200. if (mInFinder)
  201. {
  202. mProfileBtn = getChild<LLButton>("classified_profile_btn");
  203. mProfileBtn->setClickedCallback(onClickProfile);
  204. mProfileBtn->setCallbackUserData(this);
  205. }
  206. mCategoryCombo = getChild<LLComboBox>("classified_category_combo");
  207. for (LLClassifiedInfo::map_t::iterator
  208. it = LLClassifiedInfo::sCategories.begin(),
  209. end = LLClassifiedInfo::sCategories.end();
  210. it != end; ++it)
  211. {
  212. mCategoryCombo->add(it->second, (void*)((intptr_t)it->first));
  213. }
  214. mCategoryCombo->setCurrentByIndex(0);
  215. mCategoryCombo->setCommitCallback(onCommitAny);
  216. mCategoryCombo->setCallbackUserData(this);
  217. mMatureCombo = getChild<LLComboBox>("classified_mature_check");
  218. mMatureCombo->setCurrentByIndex(0);
  219. mMatureCombo->setCommitCallback(onCommitAny);
  220. mMatureCombo->setCallbackUserData(this);
  221. if (gAgent.wantsPGOnly())
  222. {
  223. // Teens do not get to set mature flag. JC
  224. mMatureCombo->setVisible(false);
  225. mMatureCombo->setCurrentByIndex(PG_CONTENT);
  226. }
  227. if (!mInFinder)
  228. {
  229. mAutoRenewCheck = getChild<LLCheckBoxCtrl>("auto_renew_check");
  230. mAutoRenewCheck->setCommitCallback(onCommitAny);
  231. mAutoRenewCheck->setCallbackUserData(this);
  232. }
  233. mUpdateBtn = getChild<LLButton>("classified_update_btn");
  234. mUpdateBtn->setClickedCallback(onClickUpdate);
  235. mUpdateBtn->setCallbackUserData(this);
  236. if (!mInFinder)
  237. {
  238. mClickThroughText = getChild<LLTextBox>("click_through_text");
  239. }
  240. resetDirty();
  241. return true;
  242. }
  243. bool LLPanelClassified::titleIsValid()
  244. {
  245. // Disallow leading spaces, punctuation, etc that screw up sort order.
  246. const std::string& name = mNameEditor->getText();
  247. if (name.empty())
  248. {
  249. gNotifications.add("BlankClassifiedName");
  250. return false;
  251. }
  252. if (!isalnum(name[0]))
  253. {
  254. gNotifications.add("ClassifiedMustBeAlphanumeric");
  255. return false;
  256. }
  257. return true;
  258. }
  259. void LLPanelClassified::apply()
  260. {
  261. // Apply is used for automatically saving results, so only do that if there
  262. // is a difference, and this is a save not create.
  263. if (checkDirty() && mPaidFor)
  264. {
  265. sendClassifiedInfoUpdate();
  266. }
  267. }
  268. bool LLPanelClassified::saveCallback(const LLSD& notification,
  269. const LLSD& response)
  270. {
  271. S32 option = LLNotification::getSelectedOption(notification, response);
  272. switch (option)
  273. {
  274. case 0: // Save
  275. {
  276. sendClassifiedInfoUpdate();
  277. // fall through to close
  278. }
  279. case 1: // Do not Save
  280. {
  281. mForceClose = true;
  282. // Close containing floater
  283. if (gFloaterViewp && gFloaterViewp->getParentFloater(this))
  284. {
  285. gFloaterViewp->getParentFloater(this)->close();
  286. }
  287. break;
  288. }
  289. default: // Cancel
  290. {
  291. gAppViewerp->abortQuit();
  292. }
  293. }
  294. return false;
  295. }
  296. bool LLPanelClassified::canClose()
  297. {
  298. if (mForceClose || !checkDirty())
  299. {
  300. return true;
  301. }
  302. LLSD args;
  303. args["NAME"] = mNameEditor->getText();
  304. gNotifications.add("ClassifiedSave", args, LLSD(),
  305. boost::bind(&LLPanelClassified::saveCallback, this,
  306. _1, _2));
  307. return false;
  308. }
  309. // Fill in some reasonable defaults for a new classified.
  310. void LLPanelClassified::initNewClassified()
  311. {
  312. // TODO: do not generate this on the client.
  313. mClassifiedID.generate();
  314. mCreatorID = gAgentID;
  315. mPosGlobal = gAgent.getPositionGlobal();
  316. mPaidFor = false;
  317. // Try to fill in the current parcel
  318. LLParcel* parcel = gViewerParcelMgr.getAgentParcel();
  319. if (parcel)
  320. {
  321. mNameEditor->setText(parcel->getName());
  322. mSnapshotCtrl->setImageAssetID(parcel->getSnapshotID());
  323. mCategoryCombo->setCurrentByIndex(0);
  324. }
  325. mUpdateBtn->setLabel(getString("publish_txt"));
  326. // Simulate clicking the "location" button
  327. LLPanelClassified::onClickSet(this);
  328. }
  329. //static
  330. void LLPanelClassified::setClickThrough(const LLUUID& classified_id,
  331. S32 teleport, S32 map, S32 profile,
  332. bool from_new_table)
  333. {
  334. for (panel_list_t::iterator it = sInstances.begin(), end = sInstances.end();
  335. it != end; ++it)
  336. {
  337. LLPanelClassified* self = *it;
  338. // For top picks, must match pick id
  339. if (self->mClassifiedID != classified_id)
  340. {
  341. continue;
  342. }
  343. // We need to check to see if the data came from the new stat_table
  344. // or the old classified table. We also need to cache the data from
  345. // the two separate sources so as to display the aggregate totals.
  346. if (from_new_table)
  347. {
  348. self->mTeleportClicksNew = teleport;
  349. self->mMapClicksNew = map;
  350. self->mProfileClicksNew = profile;
  351. }
  352. else
  353. {
  354. self->mTeleportClicksOld = teleport;
  355. self->mMapClicksOld = map;
  356. self->mProfileClicksOld = profile;
  357. }
  358. if (self->mClickThroughText)
  359. {
  360. std::string msg =
  361. llformat("Clicks: %d teleport, %d map, %d profile",
  362. self->mTeleportClicksNew + self->mTeleportClicksOld,
  363. self->mMapClicksNew + self->mMapClicksOld,
  364. self->mProfileClicksNew + self->mProfileClicksOld);
  365. self->mClickThroughText->setText(msg);
  366. }
  367. }
  368. }
  369. // Schedules the panel to request data from the server next time it is drawn.
  370. void LLPanelClassified::markForServerRequest()
  371. {
  372. mDataRequested = false;
  373. }
  374. std::string LLPanelClassified::getClassifiedName()
  375. {
  376. return mNameEditor->getText();
  377. }
  378. void LLPanelClassified::sendClassifiedInfoRequest()
  379. {
  380. if (mClassifiedID == mRequestedID)
  381. {
  382. // Nothing to do.
  383. return;
  384. }
  385. LLAvatarProperties::sendClassifiedInfoRequest(mClassifiedID);
  386. mRequestedID = mClassifiedID;
  387. mDataRequested = true;
  388. // While we are at it let's get the stats from the new table if that
  389. // capability exists.
  390. const std::string& url = gAgent.getRegionCapability("SearchStatRequest");
  391. if (url.empty())
  392. {
  393. return;
  394. }
  395. llinfos << "Classified stat request via capability. Classified Id: "
  396. << mClassifiedID << llendl;
  397. LLSD body;
  398. body["classified_id"] = mClassifiedID;
  399. LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, body,
  400. boost::bind(&LLPanelClassified::handleSearchStatResponse,
  401. mClassifiedID,
  402. _1));
  403. }
  404. //static
  405. void LLPanelClassified::handleSearchStatResponse(LLUUID id, LLSD result)
  406. {
  407. if (!result.isMap())
  408. {
  409. llwarns << "Malformed response for classified: " << id << llendl;
  410. return;
  411. }
  412. S32 teleport = result["teleport_clicks"].asInteger();
  413. S32 map = result["map_clicks"].asInteger();
  414. S32 profile = result["profile_clicks"].asInteger();
  415. S32 search_teleport = result["search_teleport_clicks"].asInteger();
  416. S32 search_map = result["search_map_clicks"].asInteger();
  417. S32 search_profile = result["search_profile_clicks"].asInteger();
  418. LLPanelClassified::setClickThrough(id, teleport + search_teleport,
  419. map + search_map,
  420. profile + search_profile, true);
  421. }
  422. void LLPanelClassified::sendClassifiedInfoUpdate()
  423. {
  424. // If we do not have a classified id yet, we will need to generate one,
  425. // otherwise we will keep overwriting classified_id 00000 in the database.
  426. if (mClassifiedID.isNull())
  427. {
  428. // *TODO: do not do this on the client.
  429. mClassifiedID.generate();
  430. }
  431. LLAvatarClassifiedInfo data;
  432. data.mClassifiedId = mClassifiedID;
  433. data.mCategory = mCategoryCombo->getCurrentIndex() + 1;
  434. data.mName = mNameEditor->getText();
  435. data.mDesc = mDescEditor->getText();
  436. data.mParcelId = mParcelID;
  437. data.mSnapshotId = mSnapshotCtrl->getImageAssetID();
  438. data.mPosGlobal = mPosGlobal;
  439. data.mListingPrice = mPriceForListing;
  440. bool auto_renew = mAutoRenewCheck && mAutoRenewCheck->get();
  441. bool mature = mMatureCombo->getCurrentIndex() == MATURE_CONTENT;
  442. // These flags do not matter here.
  443. constexpr bool adult_enabled = false;
  444. constexpr bool is_pg = false;
  445. data.mFlags = pack_classified_flags_request(auto_renew, is_pg, mature,
  446. adult_enabled);
  447. LLAvatarProperties::sendClassifiedInfoUpdate(data);
  448. mDirty = false;
  449. }
  450. //virtual
  451. void LLPanelClassified::processProperties(S32 type, void* data)
  452. {
  453. if (type != APT_CLASSIFIED_INFO || mClassifiedID.isNull())
  454. {
  455. return; // Bad info, or we have not yet been assigned a classified.
  456. }
  457. LLAvatarClassifiedInfo* info = (LLAvatarClassifiedInfo*)data;
  458. if (info->mClassifiedId != mClassifiedID)
  459. {
  460. return; // Not for us.
  461. }
  462. mCreatorID = info->mAvatarId;
  463. mParcelID = info->mParcelId;
  464. // "Location text" is actually the original name that the owner gave to
  465. // the parcel, and the location.
  466. std::string location_text = info->mParcelName;
  467. if (!location_text.empty())
  468. {
  469. location_text.append(", ");
  470. }
  471. mSimName = info->mSimName;
  472. mPosGlobal = info->mPosGlobal;
  473. S32 region_x = ll_roundp((F32)mPosGlobal.mdV[VX]) % REGION_WIDTH_UNITS;
  474. S32 region_y = ll_roundp((F32)mPosGlobal.mdV[VY]) % REGION_WIDTH_UNITS;
  475. S32 region_z = ll_roundp((F32)mPosGlobal.mdV[VZ]);
  476. std::string buffer = llformat("%s (%d, %d, %d)", mSimName.c_str(),
  477. region_x, region_y, region_z);
  478. location_text.append(buffer);
  479. mLocationEditor->setText(location_text);
  480. mLocationChanged = false;
  481. mPriceForListing = info->mListingPrice;
  482. mNameEditor->setText(info->mName);
  483. mDescEditor->setText(info->mDesc);
  484. mSnapshotCtrl->setImageAssetID(info->mSnapshotId);
  485. mCategoryCombo->setCurrentByIndex(info->mCategory - 1);
  486. if (is_cf_mature(info->mFlags))
  487. {
  488. mMatureCombo->setCurrentByIndex(MATURE_CONTENT);
  489. }
  490. else
  491. {
  492. mMatureCombo->setCurrentByIndex(PG_CONTENT);
  493. }
  494. if (mAutoRenewCheck)
  495. {
  496. mAutoRenewCheck->set(is_cf_auto_renew(info->mFlags));
  497. }
  498. time_t tim = info->mCreationDate;
  499. tm* now = localtime(&tim);
  500. std::string datestr;
  501. timeStructToFormattedString(now,
  502. gSavedSettings.getString("ShortDateFormat"),
  503. datestr);
  504. LLStringUtil::format_map_t string_args;
  505. string_args["[DATE]"] = datestr;
  506. string_args["[AMT]"] = llformat("%d", mPriceForListing);
  507. if (getChild<LLTextBox>("classified_info_text", true, false))
  508. {
  509. childSetText("classified_info_text",
  510. getString("ad_placed_paid", string_args));
  511. }
  512. // If we got data from the database, we know the listing is paid for.
  513. mPaidFor = true;
  514. mUpdateBtn->setLabel(getString("update_txt"));
  515. resetDirty();
  516. }
  517. void LLPanelClassified::draw()
  518. {
  519. refresh();
  520. LLPanel::draw();
  521. }
  522. void LLPanelClassified::refresh()
  523. {
  524. if (!mDataRequested)
  525. {
  526. sendClassifiedInfoRequest();
  527. }
  528. // Check for god mode
  529. bool godlike = gAgent.isGodlike();
  530. bool is_self = (gAgentID == mCreatorID);
  531. // Set button visibility/enablement appropriately
  532. if (mInFinder)
  533. {
  534. // End user does not need to see price twice, or date posted.
  535. mSnapshotCtrl->setEnabled(godlike);
  536. if (godlike)
  537. {
  538. // Make it smaller, so text is more legible
  539. mSnapshotCtrl->reshape(360, 270);
  540. }
  541. else
  542. {
  543. mSnapshotCtrl->setOrigin(mSnapshotSize.mLeft,
  544. mSnapshotSize.mBottom);
  545. mSnapshotCtrl->reshape(mSnapshotSize.getWidth(),
  546. mSnapshotSize.getHeight());
  547. }
  548. mNameEditor->setEnabled(godlike);
  549. mDescEditor->setEnabled(godlike);
  550. mCategoryCombo->setEnabled(godlike);
  551. mCategoryCombo->setVisible(godlike);
  552. mMatureCombo->setEnabled(godlike);
  553. mMatureCombo->setVisible(godlike);
  554. // Jesse (who is the only one who uses this, as far as we can tell
  555. // Says that he does not want a set location button - he has used it
  556. // accidently in the past.
  557. mSetBtn->setVisible(false);
  558. mSetBtn->setEnabled(false);
  559. mUpdateBtn->setEnabled(godlike);
  560. mUpdateBtn->setVisible(godlike);
  561. }
  562. else
  563. {
  564. mSnapshotCtrl->setEnabled(is_self);
  565. mNameEditor->setEnabled(is_self);
  566. mDescEditor->setEnabled(is_self);
  567. //mPriceEditor->setEnabled(is_self);
  568. mCategoryCombo->setEnabled(is_self);
  569. mMatureCombo->setEnabled(is_self);
  570. if (is_self && mMatureCombo->getCurrentIndex() == 0)
  571. {
  572. // It is a new panel. PG regions should have PG classifieds and
  573. // Adult should have Mature.
  574. setDefaultAccessCombo();
  575. }
  576. if (mAutoRenewCheck)
  577. {
  578. mAutoRenewCheck->setEnabled(is_self);
  579. mAutoRenewCheck->setVisible(is_self);
  580. }
  581. mClickThroughText->setEnabled(is_self);
  582. mClickThroughText->setVisible(is_self);
  583. mSetBtn->setVisible(is_self);
  584. mSetBtn->setEnabled(is_self);
  585. mUpdateBtn->setEnabled(is_self && checkDirty());
  586. mUpdateBtn->setVisible(is_self);
  587. }
  588. }
  589. //static
  590. void LLPanelClassified::onClickUpdate(void* data)
  591. {
  592. LLPanelClassified* self = (LLPanelClassified*)data;
  593. if (!self) return;
  594. // Disallow leading spaces, punctuation, etc that screw up sort order.
  595. if (!self->titleIsValid())
  596. {
  597. return;
  598. }
  599. // If user has not set mature, do not allow publish
  600. if (self->mMatureCombo->getCurrentIndex() == DECLINE_TO_STATE)
  601. {
  602. // Tell user about it
  603. gNotifications.add("SetClassifiedMature", LLSD(), LLSD(),
  604. boost::bind(&LLPanelClassified::confirmMature, self,
  605. _1, _2));
  606. }
  607. else
  608. {
  609. // Mature content flag is set, proceed
  610. self->gotMature();
  611. }
  612. }
  613. // Callback from a dialog indicating response to mature notification
  614. bool LLPanelClassified::confirmMature(const LLSD& notification,
  615. const LLSD& response)
  616. {
  617. S32 option = LLNotification::getSelectedOption(notification, response);
  618. switch (option)
  619. {
  620. case 0: // 0 == Yes
  621. mMatureCombo->setCurrentByIndex(MATURE_CONTENT);
  622. break;
  623. case 1: // 1 == No
  624. mMatureCombo->setCurrentByIndex(PG_CONTENT);
  625. break;
  626. default: // 2 == Cancel
  627. return false;
  628. }
  629. // If we got here it means they set a valid value
  630. gotMature();
  631. return false;
  632. }
  633. // Called after we have determined whether this classified has
  634. // mature content or not.
  635. void LLPanelClassified::gotMature()
  636. {
  637. // If already paid for, just do the update
  638. if (mPaidFor)
  639. {
  640. LLNotification::Params params("PublishClassified");
  641. params.functor(boost::bind(&LLPanelClassified::confirmPublish,
  642. this, _1, _2));
  643. gNotifications.forceResponse(params, 0);
  644. }
  645. else
  646. {
  647. // Ask the user how much they want to pay
  648. LLFloaterPriceForListing::show(callbackGotPriceForListing, this);
  649. }
  650. }
  651. //static
  652. void LLPanelClassified::callbackGotPriceForListing(S32 option,
  653. std::string text,
  654. void* data)
  655. {
  656. LLPanelClassified* self = (LLPanelClassified*)data;
  657. // Only do something if user hits publish
  658. if (option != 0) return;
  659. S32 price_for_listing = strtol(text.c_str(), NULL, 10);
  660. if (price_for_listing < MINIMUM_PRICE_FOR_LISTING)
  661. {
  662. LLSD args;
  663. std::string price_text = llformat("%d", MINIMUM_PRICE_FOR_LISTING);
  664. args["MIN_PRICE"] = price_text;
  665. gNotifications.add("MinClassifiedPrice", args);
  666. return;
  667. }
  668. // Price is acceptable, put it in the dialog for later read by update send
  669. self->mPriceForListing = price_for_listing;
  670. LLSD args;
  671. args["AMOUNT"] = llformat("%d", price_for_listing);
  672. gNotifications.add("PublishClassified", args, LLSD(),
  673. boost::bind(&LLPanelClassified::confirmPublish, self,
  674. _1, _2));
  675. }
  676. void LLPanelClassified::resetDirty()
  677. {
  678. // Tell all the widgets to reset their dirty state since the ad was just
  679. // saved
  680. mSnapshotCtrl->resetDirty();
  681. mNameEditor->resetDirty();
  682. mDescEditor->resetDirty();
  683. mLocationEditor->resetDirty();
  684. mLocationChanged = false;
  685. mCategoryCombo->resetDirty();
  686. mMatureCombo->resetDirty();
  687. if (mAutoRenewCheck)
  688. {
  689. mAutoRenewCheck->resetDirty();
  690. }
  691. }
  692. bool LLPanelClassified::confirmPublish(const LLSD& notification,
  693. const LLSD& response)
  694. {
  695. S32 option = LLNotification::getSelectedOption(notification, response);
  696. // Option 0 = publish
  697. if (option != 0) return false;
  698. sendClassifiedInfoUpdate();
  699. // *HACK: assume that top picks are always in a browser and non-finder
  700. // classifieds are always in a tab container.
  701. if (!mInFinder)
  702. {
  703. LLTabContainer* tab = (LLTabContainer*)getParent();
  704. tab->setCurrentTabName(mNameEditor->getText());
  705. }
  706. #if 0 // *TODO: enable this
  707. else
  708. {
  709. LLPanelDirClassifieds* panelp = (LLPanelDirClassifieds*)getParent();
  710. panelp->renameClassified(mClassifiedID, mNameEditor->getText());
  711. }
  712. #endif
  713. resetDirty();
  714. return false;
  715. }
  716. //static
  717. void LLPanelClassified::onClickTeleport(void* data)
  718. {
  719. LLPanelClassified* self = (LLPanelClassified*)data;
  720. if (self && !self->mPosGlobal.isExactlyZero())
  721. {
  722. gAgent.teleportViaLocation(self->mPosGlobal);
  723. if (gFloaterWorldMapp)
  724. {
  725. gFloaterWorldMapp->trackLocation(self->mPosGlobal);
  726. }
  727. self->sendClassifiedClickMessage("teleport");
  728. }
  729. }
  730. //static
  731. void LLPanelClassified::onClickMap(void* data)
  732. {
  733. LLPanelClassified* self = (LLPanelClassified*)data;
  734. if (self)
  735. {
  736. if (gFloaterWorldMapp)
  737. {
  738. gFloaterWorldMapp->trackLocation(self->mPosGlobal);
  739. }
  740. LLFloaterWorldMap::show(NULL, true);
  741. self->sendClassifiedClickMessage("map");
  742. }
  743. }
  744. //static
  745. void LLPanelClassified::onClickProfile(void* data)
  746. {
  747. LLPanelClassified* self = (LLPanelClassified*)data;
  748. if (self)
  749. {
  750. LLFloaterAvatarInfo::showFromDirectory(self->mCreatorID);
  751. self->sendClassifiedClickMessage("profile");
  752. }
  753. }
  754. #if 0
  755. //static
  756. void LLPanelClassified::onClickLandmark(void* data)
  757. {
  758. LLPanelClassified* self = (LLPanelClassified*)data;
  759. create_landmark(self->mNameEditor->getText(), "", self->mPosGlobal);
  760. }
  761. #endif
  762. //static
  763. void LLPanelClassified::onClickSet(void* data)
  764. {
  765. LLPanelClassified* self = (LLPanelClassified*)data;
  766. // Save location for later.
  767. self->mPosGlobal = gAgent.getPositionGlobal();
  768. std::string location_text;
  769. std::string regionName = "(will update after publish)";
  770. LLViewerRegion* regionp = gAgent.getRegion();
  771. if (regionp)
  772. {
  773. regionName = regionp->getName();
  774. }
  775. location_text.assign(regionName);
  776. location_text.append(", ");
  777. S32 region_x = ll_roundp((F32)self->mPosGlobal.mdV[VX]) %
  778. REGION_WIDTH_UNITS;
  779. S32 region_y = ll_roundp((F32)self->mPosGlobal.mdV[VY]) %
  780. REGION_WIDTH_UNITS;
  781. S32 region_z = ll_roundp((F32)self->mPosGlobal.mdV[VZ]);
  782. location_text.append(self->mSimName);
  783. location_text.append(llformat(" (%d, %d, %d)", region_x, region_y,
  784. region_z));
  785. self->mLocationEditor->setText(location_text);
  786. self->mLocationChanged = true;
  787. self->setDefaultAccessCombo();
  788. // Set this to null so it updates on the next save.
  789. self->mParcelID.setNull();
  790. onCommitAny(NULL, data);
  791. }
  792. bool LLPanelClassified::checkDirty()
  793. {
  794. mDirty = mLocationChanged || mSnapshotCtrl->isDirty() ||
  795. mNameEditor->isDirty() || mDescEditor->isDirty() ||
  796. mLocationEditor->isDirty() || mCategoryCombo->isDirty() ||
  797. mMatureCombo->isDirty() ||
  798. (mAutoRenewCheck && mAutoRenewCheck->isDirty());
  799. return mDirty;
  800. }
  801. //static
  802. void LLPanelClassified::onCommitAny(LLUICtrl* ctrl, void* data)
  803. {
  804. LLPanelClassified* self = (LLPanelClassified*)data;
  805. if (self)
  806. {
  807. self->checkDirty();
  808. }
  809. }
  810. //static
  811. void LLPanelClassified::focusReceived(LLFocusableElement* ctrl, void* data)
  812. {
  813. // Allow the data to be saved
  814. onCommitAny((LLUICtrl*)ctrl, data);
  815. }
  816. void LLPanelClassified::sendClassifiedClickMessage(const std::string& type)
  817. {
  818. // You are allowed to click on your own ads to reassure yourself that the
  819. // system is working.
  820. LLSD body;
  821. body["type"] = type;
  822. body["from_search"] = mFromSearch;
  823. body["classified_id"] = mClassifiedID;
  824. body["parcel_id"] = mParcelID;
  825. body["dest_pos_global"] = mPosGlobal.getValue();
  826. body["region_name"] = mSimName;
  827. const std::string& url = gAgent.getRegionCapability("SearchStatTracking");
  828. if (url.empty())
  829. {
  830. return;
  831. }
  832. llinfos << "Sending classified click message via capability" << llendl;
  833. LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body,
  834. "Tracking click report sent.",
  835. "Failed to send tracking click report.");
  836. }
  837. ////////////////////////////////////////////////////////////////////////////////////////////
  838. LLFloaterPriceForListing::LLFloaterPriceForListing()
  839. : LLFloater("classified price"),
  840. mCallback(NULL),
  841. mUserData(NULL)
  842. {
  843. }
  844. //virtual
  845. bool LLFloaterPriceForListing::postBuild()
  846. {
  847. LLLineEditor* edit = getChild<LLLineEditor>("price_edit");
  848. if (edit)
  849. {
  850. edit->setPrevalidate(LLLineEditor::prevalidateNonNegativeS32);
  851. std::string min_price = llformat("%d", MINIMUM_PRICE_FOR_LISTING);
  852. edit->setText(min_price);
  853. edit->selectAll();
  854. edit->setFocus(true);
  855. }
  856. childSetAction("set_price_btn", onClickSetPrice, this);
  857. childSetAction("cancel_btn", onClickCancel, this);
  858. setDefaultBtn("set_price_btn");
  859. return true;
  860. }
  861. //static
  862. void LLFloaterPriceForListing::show(void (*callback)(S32, std::string, void*),
  863. void* userdata)
  864. {
  865. LLFloaterPriceForListing* self = new LLFloaterPriceForListing();
  866. // Builds and adds to gFloaterViewp
  867. LLUICtrlFactory::getInstance()->buildFloater(self,
  868. "floater_price_for_listing.xml");
  869. self->center();
  870. self->mCallback = callback;
  871. self->mUserData = userdata;
  872. }
  873. //static
  874. void LLFloaterPriceForListing::onClickSetPrice(void* data)
  875. {
  876. buttonCore(0, data);
  877. }
  878. //static
  879. void LLFloaterPriceForListing::onClickCancel(void* data)
  880. {
  881. buttonCore(1, data);
  882. }
  883. //static
  884. void LLFloaterPriceForListing::buttonCore(S32 button, void* data)
  885. {
  886. LLFloaterPriceForListing* self = (LLFloaterPriceForListing*)data;
  887. if (self && self->mCallback)
  888. {
  889. std::string text = self->childGetText("price_edit");
  890. self->mCallback(button, text, self->mUserData);
  891. self->close();
  892. }
  893. }
  894. void LLPanelClassified::setDefaultAccessCombo()
  895. {
  896. LLViewerRegion* regionp = gAgent.getRegion();
  897. if (!regionp)
  898. {
  899. return;
  900. }
  901. U8 access = regionp->getSimAccess();
  902. if (access == SIM_ACCESS_PG)
  903. {
  904. mMatureCombo->setCurrentByIndex(PG_CONTENT);
  905. }
  906. else if (access == SIM_ACCESS_ADULT)
  907. {
  908. mMatureCombo->setCurrentByIndex(MATURE_CONTENT);
  909. }
  910. }