Also cleaned up slfloatermediafilter to act as a proper singleton. Removes childSetUserData entirely.
1013 lines
28 KiB
C++
1013 lines
28 KiB
C++
/**
|
|
* @file llpanelclassified.cpp
|
|
* @brief LLPanelClassified class implementation
|
|
*
|
|
* $LicenseInfo:firstyear=2005&license=viewergpl$
|
|
*
|
|
* Copyright (c) 2005-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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$
|
|
*/
|
|
|
|
// Display of a classified used both for the global view in the
|
|
// Find directory, and also for each individual user's classified in their
|
|
// profile.
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "llpanelclassified.h"
|
|
|
|
#include "lldir.h"
|
|
#include "lldispatcher.h"
|
|
#include "llnotifications.h"
|
|
#include "llnotificationsutil.h"
|
|
#include "llparcel.h"
|
|
#include "message.h"
|
|
|
|
#include "llagent.h"
|
|
#include "llavataractions.h"
|
|
#include "llbutton.h"
|
|
#include "llcheckboxctrl.h"
|
|
#include "llclassifiedflags.h"
|
|
#include "llclassifiedstatsresponder.h"
|
|
#include "llcommandhandler.h" // for classified HTML detail page click tracking
|
|
#include "lllineeditor.h"
|
|
#include "llfloaterclassified.h"
|
|
#include "lltextbox.h"
|
|
#include "llcombobox.h"
|
|
#include "llviewertexteditor.h"
|
|
#include "lltexturectrl.h"
|
|
#include "llurldispatcher.h" // for classified HTML detail click teleports
|
|
#include "lluictrlfactory.h"
|
|
#include "llviewerparcelmgr.h"
|
|
#include "llviewerwindow.h"
|
|
#include "llworldmap.h"
|
|
#include "llfloaterworldmap.h"
|
|
#include "llviewergenericmessage.h" // send_generic_message
|
|
#include "llviewerregion.h"
|
|
#include "llviewerwindow.h" // for window width, height
|
|
#include "llappviewer.h" // abortQuit()
|
|
|
|
#include "hippogridmanager.h"
|
|
|
|
// [RLVa:KB]
|
|
#include "rlvhandler.h"
|
|
// [/RLVa:KB]
|
|
|
|
const S32 MINIMUM_PRICE_FOR_LISTING = 50; // L$
|
|
const S32 MATURE_UNDEFINED = -1;
|
|
const S32 MATURE_CONTENT = 1;
|
|
const S32 PG_CONTENT = 2;
|
|
const S32 DECLINE_TO_STATE = 0;
|
|
|
|
//static
|
|
std::list<LLPanelClassified*> LLPanelClassified::sAllPanels;
|
|
|
|
// "classifiedclickthrough"
|
|
// strings[0] = classified_id
|
|
// strings[1] = teleport_clicks
|
|
// strings[2] = map_clicks
|
|
// strings[3] = profile_clicks
|
|
class LLDispatchClassifiedClickThrough : public LLDispatchHandler
|
|
{
|
|
public:
|
|
virtual bool operator()(
|
|
const LLDispatcher* dispatcher,
|
|
const std::string& key,
|
|
const LLUUID& invoice,
|
|
const sparam_t& strings)
|
|
{
|
|
if (strings.size() != 4) return false;
|
|
LLUUID classified_id(strings[0]);
|
|
S32 teleport_clicks = atoi(strings[1].c_str());
|
|
S32 map_clicks = atoi(strings[2].c_str());
|
|
S32 profile_clicks = atoi(strings[3].c_str());
|
|
LLPanelClassified::setClickThrough(classified_id, teleport_clicks,
|
|
map_clicks,
|
|
profile_clicks,
|
|
false);
|
|
return true;
|
|
}
|
|
};
|
|
static LLDispatchClassifiedClickThrough sClassifiedClickThrough;
|
|
|
|
|
|
/* Re-expose this if we need to have classified ad HTML detail
|
|
pages. JC
|
|
|
|
// We need to count classified teleport clicks from the search HTML detail pages,
|
|
// so we need have a teleport that also sends a click count message.
|
|
class LLClassifiedTeleportHandler : public LLCommandHandler
|
|
{
|
|
public:
|
|
// don't allow from external browsers because it moves you immediately
|
|
LLClassifiedTeleportHandler() : LLCommandHandler("classifiedteleport", true) { }
|
|
|
|
bool handle(const LLSD& tokens, const LLSD& queryMap)
|
|
{
|
|
// Need at least classified id and region name, so 2 params
|
|
if (tokens.size() < 2) return false;
|
|
LLUUID classified_id = tokens[0].asUUID();
|
|
if (classified_id.isNull()) return false;
|
|
// *HACK: construct a SLURL to do the teleport
|
|
std::string url("secondlife:///app/teleport/");
|
|
// skip the uuid we took off above, rebuild URL
|
|
// separated by slashes.
|
|
for (S32 i = 1; i < tokens.size(); ++i)
|
|
{
|
|
url += tokens[i].asString();
|
|
url += "/";
|
|
}
|
|
llinfos << "classified teleport to " << url << llendl;
|
|
// *TODO: separately track old search, sidebar, and new search
|
|
// Right now detail HTML pages count as new search.
|
|
const bool from_search = true;
|
|
LLPanelClassified::sendClassifiedClickMessage(classified_id, "teleport", from_search);
|
|
// Invoke teleport
|
|
LLMediaCtrl* web = NULL;
|
|
const bool trusted_browser = true;
|
|
return LLURLDispatcher::dispatch(url, web, trusted_browser);
|
|
}
|
|
};
|
|
// Creating the object registers with the dispatcher.
|
|
LLClassifiedTeleportHandler gClassifiedTeleportHandler;
|
|
*/
|
|
|
|
LLPanelClassified::LLPanelClassified(bool in_finder, bool from_search)
|
|
: LLPanel(std::string("Classified Panel")),
|
|
mInFinder(in_finder),
|
|
mFromSearch(from_search),
|
|
mDirty(false),
|
|
mForceClose(false),
|
|
mLocationChanged(false),
|
|
mClassifiedID(),
|
|
mCreatorID(),
|
|
mPriceForListing(0),
|
|
mDataRequested(FALSE),
|
|
mPaidFor(FALSE),
|
|
mPosGlobal(),
|
|
mSnapshotCtrl(NULL),
|
|
mNameEditor(NULL),
|
|
mDescEditor(NULL),
|
|
mLocationEditor(NULL),
|
|
mCategoryCombo(NULL),
|
|
mMatureCombo(NULL),
|
|
mAutoRenewCheck(NULL),
|
|
mUpdateBtn(NULL),
|
|
mTeleportBtn(NULL),
|
|
mMapBtn(NULL),
|
|
mProfileBtn(NULL),
|
|
mInfoText(NULL),
|
|
mSetBtn(NULL),
|
|
mClickThroughText(NULL),
|
|
mTeleportClicksOld(0),
|
|
mMapClicksOld(0),
|
|
mProfileClicksOld(0),
|
|
mTeleportClicksNew(0),
|
|
mMapClicksNew(0),
|
|
mProfileClicksNew(0)
|
|
|
|
{
|
|
sAllPanels.push_back(this);
|
|
|
|
std::string classified_def_file;
|
|
if (mInFinder)
|
|
{
|
|
LLUICtrlFactory::getInstance()->buildPanel(this, "panel_classified.xml");
|
|
}
|
|
else
|
|
{
|
|
LLUICtrlFactory::getInstance()->buildPanel(this, "panel_avatar_classified.xml");
|
|
}
|
|
|
|
// Register dispatcher
|
|
gGenericDispatcher.addHandler("classifiedclickthrough",
|
|
&sClassifiedClickThrough);
|
|
}
|
|
|
|
|
|
LLPanelClassified::~LLPanelClassified()
|
|
{
|
|
if(mCreatorID.notNull())
|
|
{
|
|
LLAvatarPropertiesProcessor::getInstance()->removeObserver(mCreatorID, this);
|
|
}
|
|
sAllPanels.remove(this);
|
|
}
|
|
|
|
|
|
void LLPanelClassified::reset()
|
|
{
|
|
if(mCreatorID.notNull())
|
|
{
|
|
LLAvatarPropertiesProcessor::getInstance()->removeObserver(mCreatorID, this);
|
|
}
|
|
|
|
mClassifiedID.setNull();
|
|
mCreatorID.setNull();
|
|
mParcelID.setNull();
|
|
|
|
// Don't request data, this isn't valid
|
|
mDataRequested = TRUE;
|
|
|
|
mDirty = false;
|
|
mPaidFor = FALSE;
|
|
|
|
mPosGlobal.clearVec();
|
|
|
|
clearCtrls();
|
|
resetDirty();
|
|
}
|
|
|
|
|
|
BOOL LLPanelClassified::postBuild()
|
|
{
|
|
mSnapshotCtrl = getChild<LLTextureCtrl>("snapshot_ctrl");
|
|
mSnapshotCtrl->setCommitCallback(boost::bind(&LLPanelClassified::checkDirty, this));
|
|
mSnapshotSize = mSnapshotCtrl->getRect();
|
|
|
|
mNameEditor = getChild<LLLineEditor>("given_name_editor");
|
|
mNameEditor->setMaxTextLength(DB_PARCEL_NAME_LEN);
|
|
mNameEditor->setCommitOnFocusLost(TRUE);
|
|
mNameEditor->setFocusReceivedCallback(boost::bind(&LLPanelClassified::checkDirty, this));
|
|
mNameEditor->setCommitCallback(boost::bind(&LLPanelClassified::checkDirty, this));
|
|
mNameEditor->setPrevalidate( LLLineEditor::prevalidateASCII );
|
|
|
|
mDescEditor = getChild<LLTextEditor>("desc_editor");
|
|
mDescEditor->setCommitOnFocusLost(TRUE);
|
|
mDescEditor->setFocusReceivedCallback(boost::bind(&LLPanelClassified::checkDirty, this));
|
|
mDescEditor->setCommitCallback(boost::bind(&LLPanelClassified::checkDirty, this));
|
|
mDescEditor->setTabsToNextField(TRUE);
|
|
|
|
mLocationEditor = getChild<LLLineEditor>("location_editor");
|
|
|
|
mSetBtn = getChild<LLButton>( "set_location_btn");
|
|
mSetBtn->setCommitCallback(boost::bind(&LLPanelClassified::onClickSet, this));
|
|
|
|
mTeleportBtn = getChild<LLButton>( "classified_teleport_btn");
|
|
mTeleportBtn->setCommitCallback(boost::bind(&LLPanelClassified::onClickTeleport, this));
|
|
|
|
mMapBtn = getChild<LLButton>( "classified_map_btn");
|
|
mMapBtn->setCommitCallback(boost::bind(&LLPanelClassified::onClickMap, this));
|
|
|
|
if(mInFinder)
|
|
{
|
|
mProfileBtn = getChild<LLButton>( "classified_profile_btn");
|
|
mProfileBtn->setCommitCallback(boost::bind(&LLPanelClassified::onClickProfile, this));
|
|
}
|
|
|
|
mCategoryCombo = getChild<LLComboBox>( "classified_category_combo");
|
|
LLClassifiedInfo::cat_map::iterator iter;
|
|
for (iter = LLClassifiedInfo::sCategories.begin();
|
|
iter != LLClassifiedInfo::sCategories.end();
|
|
iter++)
|
|
{
|
|
mCategoryCombo->add(iter->second, (void *)((intptr_t)iter->first), ADD_BOTTOM);
|
|
}
|
|
mCategoryCombo->setCurrentByIndex(0);
|
|
mCategoryCombo->setCommitCallback(boost::bind(&LLPanelClassified::checkDirty, this));
|
|
|
|
mMatureCombo = getChild<LLComboBox>( "classified_mature_check");
|
|
mMatureCombo->setCurrentByIndex(0);
|
|
mMatureCombo->setCommitCallback(boost::bind(&LLPanelClassified::checkDirty, this));
|
|
if (gAgent.wantsPGOnly())
|
|
{
|
|
// Teens don't get to set mature flag. JC
|
|
mMatureCombo->setVisible(FALSE);
|
|
mMatureCombo->setCurrentByIndex(PG_CONTENT);
|
|
}
|
|
|
|
if (!mInFinder)
|
|
{
|
|
mAutoRenewCheck = getChild<LLCheckBoxCtrl>( "auto_renew_check");
|
|
mAutoRenewCheck->setCommitCallback(boost::bind(&LLPanelClassified::checkDirty, this));
|
|
}
|
|
|
|
mUpdateBtn = getChild<LLButton>("classified_update_btn");
|
|
mUpdateBtn->setCommitCallback(boost::bind(&LLPanelClassified::onClickUpdate, this));
|
|
|
|
if (!mInFinder)
|
|
{
|
|
mClickThroughText = getChild<LLTextBox>("click_through_text");
|
|
}
|
|
|
|
resetDirty();
|
|
return TRUE;
|
|
}
|
|
|
|
void LLPanelClassified::processProperties(void* data, EAvatarProcessorType type)
|
|
{
|
|
if(APT_CLASSIFIED_INFO == type)
|
|
{
|
|
lldebugs << "processClassifiedInfoReply()" << llendl;
|
|
|
|
LLAvatarClassifiedInfo* c_info = static_cast<LLAvatarClassifiedInfo*>(data);
|
|
if(c_info && mClassifiedID == c_info->classified_id)
|
|
{
|
|
LLAvatarPropertiesProcessor::getInstance()->removeObserver(LLUUID::null, this);
|
|
|
|
// "Location text" is actually the original
|
|
// name that owner gave the parcel, and the location.
|
|
std::string location_text = c_info->parcel_name;
|
|
|
|
if (!location_text.empty())
|
|
location_text.append(", ");
|
|
|
|
S32 region_x = llround((F32)c_info->pos_global.mdV[VX]) % REGION_WIDTH_UNITS;
|
|
S32 region_y = llround((F32)c_info->pos_global.mdV[VY]) % REGION_WIDTH_UNITS;
|
|
S32 region_z = llround((F32)c_info->pos_global.mdV[VZ]);
|
|
|
|
std::string buffer = llformat("%s (%d, %d, %d)", c_info->sim_name.c_str(), region_x, region_y, region_z);
|
|
location_text.append(buffer);
|
|
|
|
//BOOL enabled = is_cf_enabled(flags);
|
|
|
|
time_t tim = c_info->creation_date;
|
|
tm *now=localtime(&tim);
|
|
|
|
|
|
// Found the panel, now fill in the information
|
|
mClassifiedID = c_info->classified_id;
|
|
mCreatorID = c_info->creator_id;
|
|
mParcelID = c_info->parcel_id;
|
|
mPriceForListing = c_info->price_for_listing;
|
|
mSimName = c_info->sim_name;
|
|
mPosGlobal = c_info->pos_global;
|
|
|
|
// Update UI controls
|
|
mNameEditor->setText(c_info->name);
|
|
mDescEditor->setText(c_info->description);
|
|
mSnapshotCtrl->setImageAssetID(c_info->snapshot_id);
|
|
mLocationEditor->setText(location_text);
|
|
mLocationChanged = false;
|
|
|
|
mCategoryCombo->setCurrentByIndex(c_info->category - 1);
|
|
|
|
mMatureCombo->setCurrentByIndex(is_cf_mature(c_info->flags) ? MATURE_CONTENT : PG_CONTENT);
|
|
|
|
if (mAutoRenewCheck)
|
|
{
|
|
mAutoRenewCheck->set(is_cf_auto_renew(c_info->flags));
|
|
}
|
|
|
|
std::string datestr;
|
|
timeStructToFormattedString(now, gSavedSettings.getString("ShortDateFormat"), datestr);
|
|
LLStringUtil::format_map_t string_args;
|
|
string_args["[DATE]"] = datestr;
|
|
string_args["[CURRENCY]"] = gHippoGridManager->getConnectedGrid()->getCurrencySymbol();
|
|
string_args["[AMT]"] = llformat("%d", c_info->price_for_listing);
|
|
childSetText("classified_info_text", getString("ad_placed_paid", string_args));
|
|
|
|
// If we got data from the database, we know the listing is paid for.
|
|
mPaidFor = TRUE;
|
|
|
|
mUpdateBtn->setLabel(getString("update_txt"));
|
|
|
|
resetDirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOL LLPanelClassified::titleIsValid()
|
|
{
|
|
// Disallow leading spaces, punctuation, etc. that screw up
|
|
// sort order.
|
|
const std::string& name = mNameEditor->getText();
|
|
if (name.empty())
|
|
{
|
|
LLNotificationsUtil::add("BlankClassifiedName");
|
|
return FALSE;
|
|
}
|
|
if (!isalnum(name[0]))
|
|
{
|
|
LLNotificationsUtil::add("ClassifiedMustBeAlphanumeric");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void LLPanelClassified::apply()
|
|
{
|
|
// Apply is used for automatically saving results, so only
|
|
// do that if there is a difference, and this is a save not create.
|
|
if (checkDirty() && mPaidFor)
|
|
{
|
|
sendClassifiedInfoUpdate();
|
|
}
|
|
}
|
|
|
|
bool LLPanelClassified::saveCallback(const LLSD& notification, const LLSD& response)
|
|
{
|
|
S32 option = LLNotification::getSelectedOption(notification, response);
|
|
|
|
switch(option)
|
|
{
|
|
case 0: // Save
|
|
sendClassifiedInfoUpdate();
|
|
// fall through to close
|
|
|
|
case 1: // Don't Save
|
|
{
|
|
mForceClose = true;
|
|
// Close containing floater
|
|
LLFloater* parent_floater = gFloaterView->getParentFloater(this);
|
|
if (parent_floater)
|
|
{
|
|
parent_floater->close();
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 2: // Cancel
|
|
default:
|
|
LLAppViewer::instance()->abortQuit();
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
BOOL LLPanelClassified::canClose()
|
|
{
|
|
if (mForceClose || !checkDirty())
|
|
return TRUE;
|
|
|
|
LLSD args;
|
|
args["NAME"] = mNameEditor->getText();
|
|
LLNotificationsUtil::add("ClassifiedSave", args, LLSD(), boost::bind(&LLPanelClassified::saveCallback, this, _1, _2));
|
|
return FALSE;
|
|
}
|
|
|
|
// Fill in some reasonable defaults for a new classified.
|
|
void LLPanelClassified::initNewClassified()
|
|
{
|
|
// TODO: Don't generate this on the client.
|
|
mClassifiedID.generate();
|
|
|
|
mCreatorID = gAgent.getID();
|
|
|
|
mPosGlobal = gAgent.getPositionGlobal();
|
|
|
|
mPaidFor = FALSE;
|
|
|
|
// Try to fill in the current parcel
|
|
LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
|
|
if (parcel)
|
|
{
|
|
mNameEditor->setText(parcel->getName());
|
|
//mDescEditor->setText(parcel->getDesc());
|
|
mSnapshotCtrl->setImageAssetID(parcel->getSnapshotID());
|
|
//mPriceEditor->setText("0");
|
|
mCategoryCombo->setCurrentByIndex(0);
|
|
}
|
|
|
|
mUpdateBtn->setLabel(getString("publish_txt"));
|
|
|
|
// simulate clicking the "location" button
|
|
onClickSet();
|
|
}
|
|
|
|
|
|
void LLPanelClassified::setClassifiedID(const LLUUID& id)
|
|
{
|
|
mClassifiedID = id;
|
|
}
|
|
|
|
//static
|
|
void LLPanelClassified::setClickThrough(const LLUUID& classified_id,
|
|
S32 teleport,
|
|
S32 map,
|
|
S32 profile,
|
|
bool from_new_table)
|
|
{
|
|
for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter)
|
|
{
|
|
LLPanelClassified* self = *iter;
|
|
// For top picks, must match pick id
|
|
if (self->mClassifiedID != classified_id)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// We need to check to see if the data came from the new stat_table
|
|
// or the old classified table. We also need to cache the data from
|
|
// the two separate sources so as to display the aggregate totals.
|
|
|
|
if (from_new_table)
|
|
{
|
|
self->mTeleportClicksNew = teleport;
|
|
self->mMapClicksNew = map;
|
|
self->mProfileClicksNew = profile;
|
|
}
|
|
else
|
|
{
|
|
self->mTeleportClicksOld = teleport;
|
|
self->mMapClicksOld = map;
|
|
self->mProfileClicksOld = profile;
|
|
}
|
|
|
|
if (self->mClickThroughText)
|
|
{
|
|
std::string msg = llformat("Clicks: %d teleport, %d map, %d profile",
|
|
self->mTeleportClicksNew + self->mTeleportClicksOld,
|
|
self->mMapClicksNew + self->mMapClicksOld,
|
|
self->mProfileClicksNew + self->mProfileClicksOld);
|
|
self->mClickThroughText->setText(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Schedules the panel to request data
|
|
// from the server next time it is drawn.
|
|
void LLPanelClassified::markForServerRequest()
|
|
{
|
|
mDataRequested = FALSE;
|
|
}
|
|
|
|
|
|
std::string LLPanelClassified::getClassifiedName()
|
|
{
|
|
return mNameEditor->getText();
|
|
}
|
|
|
|
|
|
void LLPanelClassified::sendClassifiedInfoRequest()
|
|
{
|
|
if (mClassifiedID != mRequestedID)
|
|
{
|
|
LLAvatarPropertiesProcessor::getInstance()->addObserver(LLUUID::null, this);
|
|
LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(mClassifiedID);
|
|
|
|
mDataRequested = TRUE;
|
|
|
|
mRequestedID = mClassifiedID;
|
|
|
|
// While we're at it let's get the stats from the new table if that
|
|
// capability exists.
|
|
std::string url = gAgent.getRegion()->getCapability("SearchStatRequest");
|
|
LLSD body;
|
|
body["classified_id"] = mClassifiedID;
|
|
|
|
if (!url.empty())
|
|
{
|
|
llinfos << "Classified stat request via capability" << llendl;
|
|
LLHTTPClient::post(url, body, new LLClassifiedStatsResponder(((LLView*)this)->getHandle(), mClassifiedID));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLPanelClassified::sendClassifiedInfoUpdate()
|
|
{
|
|
LLAvatarClassifiedInfo c_data;
|
|
|
|
// If we don't have a classified id yet, we'll need to generate one,
|
|
// otherwise we'll keep overwriting classified_id 00000 in the database.
|
|
if (mClassifiedID.isNull())
|
|
{
|
|
// TODO: Don't do this on the client.
|
|
mClassifiedID.generate();
|
|
}
|
|
|
|
c_data.agent_id = gAgent.getID();
|
|
c_data.classified_id = mClassifiedID;
|
|
c_data.category = mCategoryCombo->getCurrentIndex() + 1;
|
|
c_data.name = mNameEditor->getText();
|
|
c_data.description = mDescEditor->getText();
|
|
c_data.parcel_id = mParcelID;
|
|
c_data.snapshot_id = mSnapshotCtrl->getImageAssetID();
|
|
c_data.pos_global = mPosGlobal;
|
|
BOOL auto_renew = mAutoRenewCheck && mAutoRenewCheck->get();
|
|
c_data.flags = pack_classified_flags_request(auto_renew, false, mMatureCombo->getCurrentIndex() == MATURE_CONTENT, false);
|
|
c_data.price_for_listing = mPriceForListing;
|
|
c_data.parent_estate = 0; //probably not required.
|
|
|
|
LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoUpdate(&c_data);
|
|
|
|
mDirty = false;
|
|
}
|
|
|
|
void LLPanelClassified::draw()
|
|
{
|
|
refresh();
|
|
|
|
LLPanel::draw();
|
|
}
|
|
|
|
|
|
void LLPanelClassified::refresh()
|
|
{
|
|
if (!mDataRequested)
|
|
{
|
|
sendClassifiedInfoRequest();
|
|
}
|
|
|
|
// Check for god mode
|
|
BOOL godlike = gAgent.isGodlike();
|
|
BOOL is_self = (gAgent.getID() == mCreatorID);
|
|
|
|
// Set button visibility/enablement appropriately
|
|
if (mInFinder)
|
|
{
|
|
|
|
// End user doesn't ned to see price twice, or date posted.
|
|
|
|
mSnapshotCtrl->setEnabled(godlike);
|
|
if(godlike)
|
|
{
|
|
//make it smaller, so text is more legible
|
|
mSnapshotCtrl->setOrigin(20, 175);
|
|
mSnapshotCtrl->reshape(300, 200);
|
|
}
|
|
else
|
|
{
|
|
mSnapshotCtrl->setOrigin(mSnapshotSize.mLeft, mSnapshotSize.mBottom);
|
|
mSnapshotCtrl->reshape(mSnapshotSize.getWidth(), mSnapshotSize.getHeight());
|
|
//normal
|
|
}
|
|
mNameEditor->setEnabled(godlike);
|
|
mDescEditor->setEnabled(godlike);
|
|
mCategoryCombo->setEnabled(godlike);
|
|
mCategoryCombo->setVisible(godlike);
|
|
|
|
mMatureCombo->setEnabled(godlike);
|
|
mMatureCombo->setVisible(godlike);
|
|
|
|
// Jesse (who is the only one who uses this, as far as we can tell
|
|
// Says that he does not want a set location button - he has used it
|
|
// accidently in the past.
|
|
mSetBtn->setVisible(FALSE);
|
|
mSetBtn->setEnabled(FALSE);
|
|
|
|
mUpdateBtn->setEnabled(godlike);
|
|
mUpdateBtn->setVisible(godlike);
|
|
}
|
|
else
|
|
{
|
|
mSnapshotCtrl->setEnabled(is_self);
|
|
mNameEditor->setEnabled(is_self);
|
|
mDescEditor->setEnabled(is_self);
|
|
//mPriceEditor->setEnabled(is_self);
|
|
mCategoryCombo->setEnabled(is_self);
|
|
mMatureCombo->setEnabled(is_self);
|
|
|
|
if( is_self )
|
|
{
|
|
if( mMatureCombo->getCurrentIndex() == 0 )
|
|
{
|
|
// It's a new panel.
|
|
// PG regions should have PG classifieds. AO should have mature.
|
|
|
|
setDefaultAccessCombo();
|
|
}
|
|
}
|
|
|
|
if (mAutoRenewCheck)
|
|
{
|
|
mAutoRenewCheck->setEnabled(is_self);
|
|
mAutoRenewCheck->setVisible(is_self);
|
|
}
|
|
|
|
mClickThroughText->setEnabled(is_self);
|
|
mClickThroughText->setVisible(is_self);
|
|
|
|
mSetBtn->setVisible(is_self);
|
|
//mSetBtn->setEnabled(is_self);
|
|
// [RLVa:KB] - Checked: 2009-07-04 (RLVa-1.0.0a)
|
|
mSetBtn->setEnabled(is_self && (!gRlvHandler.hasBehaviour(RLV_BHVR_SHOWLOC)) );
|
|
// [/RLVa:KB]
|
|
|
|
mUpdateBtn->setEnabled(is_self && checkDirty());
|
|
mUpdateBtn->setVisible(is_self);
|
|
}
|
|
}
|
|
|
|
void LLPanelClassified::onClickUpdate()
|
|
{
|
|
// Disallow leading spaces, punctuation, etc. that screw up
|
|
// sort order.
|
|
if (!titleIsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If user has not set mature, do not allow publish
|
|
if (mMatureCombo->getCurrentIndex() == DECLINE_TO_STATE)
|
|
{
|
|
// Tell user about it
|
|
LLNotificationsUtil::add("SetClassifiedMature",
|
|
LLSD(),
|
|
LLSD(),
|
|
boost::bind(&LLPanelClassified::confirmMature, this, _1, _2));
|
|
return;
|
|
}
|
|
|
|
// Mature content flag is set, proceed
|
|
gotMature();
|
|
}
|
|
|
|
// Callback from a dialog indicating response to mature notification
|
|
bool LLPanelClassified::confirmMature(const LLSD& notification, const LLSD& response)
|
|
{
|
|
S32 option = LLNotification::getSelectedOption(notification, response);
|
|
|
|
// 0 == Yes
|
|
// 1 == No
|
|
// 2 == Cancel
|
|
switch(option)
|
|
{
|
|
case 0:
|
|
mMatureCombo->setCurrentByIndex(MATURE_CONTENT);
|
|
break;
|
|
case 1:
|
|
mMatureCombo->setCurrentByIndex(PG_CONTENT);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
// If we got here it means they set a valid value
|
|
gotMature();
|
|
return false;
|
|
}
|
|
|
|
// Called after we have determined whether this classified has
|
|
// mature content or not.
|
|
void LLPanelClassified::gotMature()
|
|
{
|
|
// if already paid for, just do the update
|
|
if (mPaidFor)
|
|
{
|
|
LLNotification::Params params("PublishClassified");
|
|
params.functor(boost::bind(&LLPanelClassified::confirmPublish, this, _1, _2));
|
|
LLNotifications::instance().forceResponse(params, 0);
|
|
}
|
|
else
|
|
{
|
|
// Ask the user how much they want to pay
|
|
new LLFloaterPriceForListing(boost::bind(&LLPanelClassified::callbackGotPriceForListing, this, _1));
|
|
}
|
|
}
|
|
|
|
void LLPanelClassified::callbackGotPriceForListing(const std::string& text)
|
|
{
|
|
S32 price_for_listing = strtol(text.c_str(), NULL, 10);
|
|
if (price_for_listing < MINIMUM_PRICE_FOR_LISTING)
|
|
{
|
|
LLSD args;
|
|
args["MIN_PRICE"] = llformat("%d", MINIMUM_PRICE_FOR_LISTING);
|
|
LLNotificationsUtil::add("MinClassifiedPrice", args);
|
|
return;
|
|
}
|
|
|
|
// price is acceptable, put it in the dialog for later read by
|
|
// update send
|
|
mPriceForListing = price_for_listing;
|
|
|
|
LLSD args;
|
|
args["AMOUNT"] = llformat("%d", price_for_listing);
|
|
args["CURRENCY"] = gHippoGridManager->getConnectedGrid()->getCurrencySymbol();
|
|
LLNotificationsUtil::add("PublishClassified", args, LLSD(),
|
|
boost::bind(&LLPanelClassified::confirmPublish, this, _1, _2));
|
|
}
|
|
|
|
void LLPanelClassified::resetDirty()
|
|
{
|
|
// Tell all the widgets to reset their dirty state since the ad was just saved
|
|
if (mSnapshotCtrl)
|
|
mSnapshotCtrl->resetDirty();
|
|
if (mNameEditor)
|
|
mNameEditor->resetDirty();
|
|
if (mDescEditor)
|
|
mDescEditor->resetDirty();
|
|
if (mLocationEditor)
|
|
mLocationEditor->resetDirty();
|
|
mLocationChanged = false;
|
|
if (mCategoryCombo)
|
|
mCategoryCombo->resetDirty();
|
|
if (mMatureCombo)
|
|
mMatureCombo->resetDirty();
|
|
if (mAutoRenewCheck)
|
|
mAutoRenewCheck->resetDirty();
|
|
}
|
|
|
|
// invoked from callbackConfirmPublish
|
|
bool LLPanelClassified::confirmPublish(const LLSD& notification, const LLSD& response)
|
|
{
|
|
S32 option = LLNotification::getSelectedOption(notification, response);
|
|
// Option 0 = publish
|
|
if (option != 0) return false;
|
|
|
|
sendClassifiedInfoUpdate();
|
|
|
|
// Big hack - assume that top picks are always in a browser,
|
|
// and non-finder-classifieds are always in a tab container.
|
|
if (mInFinder)
|
|
{
|
|
// TODO: enable this
|
|
//LLPanelDirClassifieds* panel = (LLPanelDirClassifieds*)getParent();
|
|
//panel->renameClassified(mClassifiedID, mNameEditor->getText());
|
|
}
|
|
else
|
|
{
|
|
LLTabContainer* tab = (LLTabContainer*)getParent();
|
|
tab->setCurrentTabName(mNameEditor->getText());
|
|
}
|
|
|
|
resetDirty();
|
|
return false;
|
|
}
|
|
|
|
void LLPanelClassified::onClickTeleport()
|
|
{
|
|
if (!mPosGlobal.isExactlyZero())
|
|
{
|
|
gAgent.teleportViaLocation(mPosGlobal);
|
|
gFloaterWorldMap->trackLocation(mPosGlobal);
|
|
|
|
sendClassifiedClickMessage("teleport");
|
|
}
|
|
}
|
|
|
|
void LLPanelClassified::onClickMap()
|
|
{
|
|
gFloaterWorldMap->trackLocation(mPosGlobal);
|
|
LLFloaterWorldMap::show(true);
|
|
|
|
sendClassifiedClickMessage("map");
|
|
}
|
|
|
|
void LLPanelClassified::onClickProfile()
|
|
{
|
|
LLAvatarActions::showProfile(mCreatorID);
|
|
sendClassifiedClickMessage("profile");
|
|
}
|
|
|
|
/*
|
|
void LLPanelClassified::onClickLandmark()
|
|
{
|
|
create_landmark(mNameEditor->getText(), "", mPosGlobal);
|
|
}
|
|
*/
|
|
|
|
void LLPanelClassified::onClickSet()
|
|
{
|
|
// [RLVa:KB] - Checked: 2009-07-04 (RLVa-1.0.0a)
|
|
if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWLOC))
|
|
{
|
|
return;
|
|
}
|
|
// [/RLVa:KB]
|
|
|
|
// Save location for later.
|
|
mPosGlobal = gAgent.getPositionGlobal();
|
|
|
|
std::string location_text;
|
|
std::string regionName = "(will update after publish)";
|
|
LLViewerRegion* pRegion = gAgent.getRegion();
|
|
if (pRegion)
|
|
{
|
|
regionName = pRegion->getName();
|
|
}
|
|
location_text.assign(regionName);
|
|
location_text.append(", ");
|
|
|
|
S32 region_x = llround((F32)mPosGlobal.mdV[VX]) % REGION_WIDTH_UNITS;
|
|
S32 region_y = llround((F32)mPosGlobal.mdV[VY]) % REGION_WIDTH_UNITS;
|
|
S32 region_z = llround((F32)mPosGlobal.mdV[VZ]);
|
|
|
|
location_text.append(mSimName);
|
|
location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z));
|
|
|
|
mLocationEditor->setText(location_text);
|
|
mLocationChanged = true;
|
|
|
|
setDefaultAccessCombo();
|
|
|
|
// Set this to null so it updates on the next save.
|
|
mParcelID.setNull();
|
|
|
|
checkDirty();
|
|
}
|
|
|
|
|
|
BOOL LLPanelClassified::checkDirty()
|
|
{
|
|
mDirty = FALSE;
|
|
if ( mSnapshotCtrl ) mDirty |= mSnapshotCtrl->isDirty();
|
|
if ( mNameEditor ) mDirty |= mNameEditor->isDirty();
|
|
if ( mDescEditor ) mDirty |= mDescEditor->isDirty();
|
|
if ( mLocationEditor ) mDirty |= mLocationEditor->isDirty();
|
|
if ( mLocationChanged ) mDirty |= TRUE;
|
|
if ( mCategoryCombo ) mDirty |= mCategoryCombo->isDirty();
|
|
if ( mMatureCombo ) mDirty |= mMatureCombo->isDirty();
|
|
if ( mAutoRenewCheck ) mDirty |= mAutoRenewCheck->isDirty();
|
|
|
|
return mDirty;
|
|
}
|
|
|
|
|
|
void LLPanelClassified::sendClassifiedClickMessage(const std::string& type)
|
|
{
|
|
// You're allowed to click on your own ads to reassure yourself
|
|
// that the system is working.
|
|
LLSD body;
|
|
body["type"] = type;
|
|
body["from_search"] = mFromSearch;
|
|
body["classified_id"] = mClassifiedID;
|
|
body["parcel_id"] = mParcelID;
|
|
body["dest_pos_global"] = mPosGlobal.getValue();
|
|
body["region_name"] = mSimName;
|
|
|
|
std::string url = gAgent.getRegion()->getCapability("SearchStatTracking");
|
|
llinfos << "LLPanelClassified::sendClassifiedClickMessage via capability" << llendl;
|
|
LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
LLFloaterPriceForListing::LLFloaterPriceForListing(const signal_t::slot_type& cb)
|
|
: LLFloater(std::string("PriceForListing")),
|
|
mSignal(new signal_t)
|
|
{
|
|
// Builds and adds to gFloaterView
|
|
LLUICtrlFactory::getInstance()->buildFloater(this, "floater_price_for_listing.xml");
|
|
center();
|
|
|
|
mSignal->connect(cb);
|
|
}
|
|
|
|
//virtual
|
|
LLFloaterPriceForListing::~LLFloaterPriceForListing()
|
|
{
|
|
delete mSignal;
|
|
}
|
|
|
|
//virtual
|
|
BOOL LLFloaterPriceForListing::postBuild()
|
|
{
|
|
LLLineEditor* edit = getChild<LLLineEditor>("price_edit");
|
|
if (edit)
|
|
{
|
|
edit->setPrevalidate(LLLineEditor::prevalidateNonNegativeS32);
|
|
std::string min_price = llformat("%d", MINIMUM_PRICE_FOR_LISTING);
|
|
edit->setText(min_price);
|
|
edit->selectAll();
|
|
edit->setFocus(TRUE);
|
|
}
|
|
|
|
getChild<LLUICtrl>("set_price_btn")->setCommitCallback(boost::bind(&LLFloaterPriceForListing::buttonCore, this));
|
|
|
|
getChild<LLUICtrl>("cancel_btn")->setCommitCallback(boost::bind(&LLFloater::close, this, false));
|
|
|
|
setDefaultBtn("set_price_btn");
|
|
return TRUE;
|
|
}
|
|
|
|
void LLFloaterPriceForListing::buttonCore()
|
|
{
|
|
(*mSignal)(childGetText("price_edit"));
|
|
close();
|
|
}
|
|
|
|
void LLPanelClassified::setDefaultAccessCombo()
|
|
{
|
|
// PG regions should have PG classifieds. AO should have mature.
|
|
|
|
LLViewerRegion *regionp = gAgent.getRegion();
|
|
|
|
switch( regionp->getSimAccess() )
|
|
{
|
|
case SIM_ACCESS_PG:
|
|
mMatureCombo->setCurrentByIndex(PG_CONTENT);
|
|
break;
|
|
case SIM_ACCESS_ADULT:
|
|
mMatureCombo->setCurrentByIndex(MATURE_CONTENT);
|
|
break;
|
|
default:
|
|
// You are free to move about the cabin.
|
|
break;
|
|
}
|
|
}
|