Basic Summary: Issue 743: [Chat UI] Option to italicize actions (/me) in chat - Adds debug LiruItalicizeActions, and a checkbox to Adv. Chat->Chat UI preferences Issue 737: [Frosting] Annoyance Removal (Red beacon after teleport using LM's) - Adds debug ClearBeaconAfterTeleport, checkbox under System->General Issue 639: [Frosting] The agent isn't identified properly in chat - Oh what a silly issue this was, it's as though whoever wrote this didn't care. Fixes issue where names in logs do not match names in chat due to display name system Fixes the issue in which Unnamed objects got named by a hardcoded string under certain circumstances. Issue 813: [Frosting] When only accepting from friends, do not display incoming chat notification for nonfriends - Also broke the setting out, separating it from the voice calls friend only setting - Adds InstantMessagesFriendsOnly debug setting and checkbox in Adv. Chat->Chat/IM Issue 764: Copy SLURL from Map returns correct region but wrong coordinates. Satisfied the longstanding issue of inflexible autoresponse options. - Autoresponse now has its own tab in Adv. Chat preferences: Busy, Muted, nonfriends, and anyone (or just friends) can have separate responses, along with items of your choosing. - Prevent doubling up with the first repeated autoresponse due to typing message and normal message. Translator Summary: Adv. Chat->Chat UI->"Italicize action messages (/me)" System->General->"Clear red destination beacon after teleporting" Drop Targets for floater_ao.xml, panel_avatar.xml, panel_group_notices.xml, and panel_preferences_ascent_system.xml Adv. Chat->Chat/IM->"Only accept IMs from Friends" Please clean up the Busy Mode Response elements from panel_preferences_im.xml strings.xml now has "IM_autoresponse_minutes" Adv. Chat (panel_preferences_ascent_chat.xml) now has a new panel "Autoresponse", please clean up the old Autoresponse elements from Chat/IM tab and translate this panel. Developer Summary: Adds EChatStyle to LLChat, used for identifying what style a piece of chat is. Update settings_per_account.xml - Reorganized the ascent specific section. - Removes a few old and unused settings Better organize settings_per_account_ascent.xml - TODO: Actually get this include system working and remove the Ascent specific section in settings_per_account.xml Modernize LLDropTarget and make it more flexible and stand alone - The Text of drop targets is now a child of the target itself, meaning the necessity of having a static instance to the parent is eliminated - Drop targets are now one element in UI XML. - Drop targets now have fill_parent option which allows the target to spread over the parent, while the text, tool_tip, and border stays in place - If Drop Targets have a control_name, it is from the per account settings group, since Items must be in the inventory of the account in question. - All drop targets now use the common LLDropTarget class instead of their own. - LLGroupDropTarget is now derived from LLDropTarget and has its own tag group_drop_target. Cleaned up the focus functions we use to focus the input bar, setInputFocus exists for their purpose. Updated our llim* code to line up better with upstream and conform to styling. Polished LLTracker and LLFloaterWorldMap a bit Cleaned/Updated up LLStyleMap a bit. Optimized autoresponse code: - wildcards are now replaced via boost::algorithm::replace_all - Autoresponse and related chat enhancements are now performed inside their case, instead of by the large if block above.
1094 lines
29 KiB
C++
1094 lines
29 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 "llalertdialog.h"
|
|
#include "llbutton.h"
|
|
#include "llcheckboxctrl.h"
|
|
#include "llclassifiedflags.h"
|
|
#include "llclassifiedstatsresponder.h"
|
|
#include "llcommandhandler.h" // for classified HTML detail page click tracking
|
|
#include "llviewercontrol.h"
|
|
#include "lllineeditor.h"
|
|
#include "llfloateravatarinfo.h"
|
|
#include "llfloaterclassified.h"
|
|
#include "lltextbox.h"
|
|
#include "llcombobox.h"
|
|
#include "llviewertexteditor.h"
|
|
#include "lltexturectrl.h"
|
|
#include "lluiconstants.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(onCommitAny);
|
|
mSnapshotCtrl->setCallbackUserData(this);
|
|
mSnapshotSize = mSnapshotCtrl->getRect();
|
|
|
|
mNameEditor = getChild<LLLineEditor>("given_name_editor");
|
|
mNameEditor->setMaxTextLength(DB_PARCEL_NAME_LEN);
|
|
mNameEditor->setCommitOnFocusLost(TRUE);
|
|
mNameEditor->setFocusReceivedCallback(boost::bind(focusReceived, _1, this));
|
|
mNameEditor->setCommitCallback(onCommitAny);
|
|
mNameEditor->setCallbackUserData(this);
|
|
mNameEditor->setPrevalidate( LLLineEditor::prevalidateASCII );
|
|
|
|
mDescEditor = getChild<LLTextEditor>("desc_editor");
|
|
mDescEditor->setCommitOnFocusLost(TRUE);
|
|
mDescEditor->setFocusReceivedCallback(boost::bind(focusReceived, _1, this));
|
|
mDescEditor->setCommitCallback(onCommitAny);
|
|
mDescEditor->setCallbackUserData(this);
|
|
mDescEditor->setTabsToNextField(TRUE);
|
|
|
|
mLocationEditor = getChild<LLLineEditor>("location_editor");
|
|
|
|
mSetBtn = getChild<LLButton>( "set_location_btn");
|
|
mSetBtn->setClickedCallback(boost::bind(&LLPanelClassified::onClickSet, this));
|
|
|
|
mTeleportBtn = getChild<LLButton>( "classified_teleport_btn");
|
|
mTeleportBtn->setClickedCallback(boost::bind(&LLPanelClassified::onClickTeleport, this));
|
|
|
|
mMapBtn = getChild<LLButton>( "classified_map_btn");
|
|
mMapBtn->setClickedCallback(boost::bind(&LLPanelClassified::onClickMap, this));
|
|
|
|
if(mInFinder)
|
|
{
|
|
mProfileBtn = getChild<LLButton>( "classified_profile_btn");
|
|
mProfileBtn->setClickedCallback(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(onCommitAny);
|
|
mCategoryCombo->setCallbackUserData(this);
|
|
|
|
mMatureCombo = getChild<LLComboBox>( "classified_mature_check");
|
|
mMatureCombo->setCurrentByIndex(0);
|
|
mMatureCombo->setCommitCallback(onCommitAny);
|
|
mMatureCombo->setCallbackUserData(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(onCommitAny);
|
|
mAutoRenewCheck->setCallbackUserData(this);
|
|
}
|
|
|
|
mUpdateBtn = getChild<LLButton>("classified_update_btn");
|
|
mUpdateBtn->setClickedCallback(boost::bind(&LLPanelClassified::onClickUpdate, this));
|
|
mUpdateBtn->setCallbackUserData(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();
|
|
|
|
// I don't know if a second call is deliberate or a bad merge, so I'm leaving it here.
|
|
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
|
|
LLPanelClassified::onClickSet(this);
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
// static
|
|
void LLPanelClassified::onClickUpdate(void* data)
|
|
{
|
|
LLPanelClassified* self = (LLPanelClassified*)data;
|
|
|
|
if(self == NULL) return;
|
|
|
|
// Disallow leading spaces, punctuation, etc. that screw up
|
|
// sort order.
|
|
if ( ! self->titleIsValid() )
|
|
{
|
|
return;
|
|
};
|
|
|
|
// If user has not set mature, do not allow publish
|
|
if(self->mMatureCombo->getCurrentIndex() == DECLINE_TO_STATE)
|
|
{
|
|
// Tell user about it
|
|
LLNotificationsUtil::add("SetClassifiedMature",
|
|
LLSD(),
|
|
LLSD(),
|
|
boost::bind(&LLPanelClassified::confirmMature, self, _1, _2));
|
|
return;
|
|
}
|
|
|
|
// Mature content flag is set, proceed
|
|
self->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
|
|
LLFloaterPriceForListing::show( callbackGotPriceForListing, this );
|
|
}
|
|
}
|
|
|
|
// static
|
|
void LLPanelClassified::callbackGotPriceForListing(S32 option, std::string text, void* data)
|
|
{
|
|
LLPanelClassified* self = (LLPanelClassified*)data;
|
|
|
|
// Only do something if user hits publish
|
|
if (option != 0) return;
|
|
|
|
S32 price_for_listing = strtol(text.c_str(), NULL, 10);
|
|
if (price_for_listing < MINIMUM_PRICE_FOR_LISTING)
|
|
{
|
|
LLSD args;
|
|
std::string price_text = llformat("%d", MINIMUM_PRICE_FOR_LISTING);
|
|
args["MIN_PRICE"] = price_text;
|
|
|
|
LLNotificationsUtil::add("MinClassifiedPrice", args);
|
|
return;
|
|
}
|
|
|
|
// price is acceptable, put it in the dialog for later read by
|
|
// update send
|
|
self->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, self, _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;
|
|
}
|
|
|
|
|
|
// static
|
|
void LLPanelClassified::onClickTeleport(void* data)
|
|
{
|
|
LLPanelClassified* self = (LLPanelClassified*)data;
|
|
|
|
if (!self->mPosGlobal.isExactlyZero())
|
|
{
|
|
gAgent.teleportViaLocation(self->mPosGlobal);
|
|
gFloaterWorldMap->trackLocation(self->mPosGlobal);
|
|
|
|
self->sendClassifiedClickMessage("teleport");
|
|
}
|
|
}
|
|
|
|
|
|
// static
|
|
void LLPanelClassified::onClickMap(void* data)
|
|
{
|
|
LLPanelClassified* self = (LLPanelClassified*)data;
|
|
gFloaterWorldMap->trackLocation(self->mPosGlobal);
|
|
LLFloaterWorldMap::show(true);
|
|
|
|
self->sendClassifiedClickMessage("map");
|
|
}
|
|
|
|
// static
|
|
void LLPanelClassified::onClickProfile(void* data)
|
|
{
|
|
LLPanelClassified* self = (LLPanelClassified*)data;
|
|
LLFloaterAvatarInfo::showFromDirectory(self->mCreatorID);
|
|
self->sendClassifiedClickMessage("profile");
|
|
}
|
|
|
|
// static
|
|
/*
|
|
void LLPanelClassified::onClickLandmark(void* data)
|
|
{
|
|
LLPanelClassified* self = (LLPanelClassified*)data;
|
|
create_landmark(self->mNameEditor->getText(), "", self->mPosGlobal);
|
|
}
|
|
*/
|
|
|
|
// static
|
|
void LLPanelClassified::onClickSet(void* data)
|
|
{
|
|
// [RLVa:KB] - Checked: 2009-07-04 (RLVa-1.0.0a)
|
|
if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWLOC))
|
|
{
|
|
return;
|
|
}
|
|
// [/RLVa:KB]
|
|
LLPanelClassified* self = (LLPanelClassified*)data;
|
|
|
|
// Save location for later.
|
|
self->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)self->mPosGlobal.mdV[VX]) % REGION_WIDTH_UNITS;
|
|
S32 region_y = llround((F32)self->mPosGlobal.mdV[VY]) % REGION_WIDTH_UNITS;
|
|
S32 region_z = llround((F32)self->mPosGlobal.mdV[VZ]);
|
|
|
|
location_text.append(self->mSimName);
|
|
location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z));
|
|
|
|
self->mLocationEditor->setText(location_text);
|
|
self->mLocationChanged = true;
|
|
|
|
self->setDefaultAccessCombo();
|
|
|
|
// Set this to null so it updates on the next save.
|
|
self->mParcelID.setNull();
|
|
|
|
onCommitAny(NULL, data);
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
// static
|
|
void LLPanelClassified::onCommitAny(LLUICtrl* ctrl, void* data)
|
|
{
|
|
LLPanelClassified* self = (LLPanelClassified*)data;
|
|
if (self)
|
|
{
|
|
self->checkDirty();
|
|
}
|
|
}
|
|
|
|
// static
|
|
void LLPanelClassified::focusReceived(LLFocusableElement* ctrl, void* data)
|
|
{
|
|
// allow the data to be saved
|
|
onCommitAny((LLUICtrl*)ctrl, data);
|
|
}
|
|
|
|
|
|
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()
|
|
: LLFloater(std::string("PriceForListing")),
|
|
mCallback(NULL),
|
|
mUserData(NULL)
|
|
{ }
|
|
|
|
//virtual
|
|
LLFloaterPriceForListing::~LLFloaterPriceForListing()
|
|
{ }
|
|
|
|
//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);
|
|
}
|
|
|
|
childSetAction("set_price_btn", onClickSetPrice, this);
|
|
|
|
childSetAction("cancel_btn", onClickCancel, this);
|
|
|
|
setDefaultBtn("set_price_btn");
|
|
return TRUE;
|
|
}
|
|
|
|
//static
|
|
void LLFloaterPriceForListing::show( void (*callback)(S32, std::string, void*), void* userdata)
|
|
{
|
|
LLFloaterPriceForListing *self = new LLFloaterPriceForListing();
|
|
|
|
// Builds and adds to gFloaterView
|
|
LLUICtrlFactory::getInstance()->buildFloater(self, "floater_price_for_listing.xml");
|
|
self->center();
|
|
|
|
self->mCallback = callback;
|
|
self->mUserData = userdata;
|
|
}
|
|
|
|
//static
|
|
void LLFloaterPriceForListing::onClickSetPrice(void* data)
|
|
{
|
|
buttonCore(0, data);
|
|
}
|
|
|
|
//static
|
|
void LLFloaterPriceForListing::onClickCancel(void* data)
|
|
{
|
|
buttonCore(1, data);
|
|
}
|
|
|
|
//static
|
|
void LLFloaterPriceForListing::buttonCore(S32 button, void* data)
|
|
{
|
|
LLFloaterPriceForListing* self = (LLFloaterPriceForListing*)data;
|
|
|
|
if (self->mCallback)
|
|
{
|
|
std::string text = self->childGetText("price_edit");
|
|
self->mCallback(button, text, self->mUserData);
|
|
self->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;
|
|
}
|
|
}
|