1598 lines
49 KiB
C++
1598 lines
49 KiB
C++
/**
|
|
* @file llpanelprofileclassifieds.cpp
|
|
* @brief LLPanelProfileClassifieds and related class implementations
|
|
*
|
|
* $LicenseInfo:firstyear=2022&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2022, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "llviewerprecompiledheaders.h"
|
|
|
|
#include "llpanelprofileclassifieds.h"
|
|
|
|
#include "llagent.h"
|
|
#include "llavataractions.h"
|
|
#include "llavatarpropertiesprocessor.h"
|
|
#include "llclassifiedflags.h"
|
|
#include "llcombobox.h"
|
|
#include "llcommandhandler.h" // for classified HTML detail page click tracking
|
|
#include "llcorehttputil.h"
|
|
#include "lldispatcher.h"
|
|
#include "llfloaterclassified.h"
|
|
#include "llfloaterreg.h"
|
|
#include "llfloatersidepanelcontainer.h"
|
|
#include "llfloaterworldmap.h"
|
|
#include "lliconctrl.h"
|
|
#include "lllineeditor.h"
|
|
#include "llnotifications.h"
|
|
#include "llnotificationsutil.h"
|
|
#include "llpanelavatar.h"
|
|
#include "llparcel.h"
|
|
#include "llregistry.h"
|
|
#include "llscrollcontainer.h"
|
|
#include "llstartup.h"
|
|
#include "llstatusbar.h"
|
|
#include "lltabcontainer.h"
|
|
#include "lltexteditor.h"
|
|
#include "lltexturectrl.h"
|
|
#include "lltrans.h"
|
|
#include "llviewergenericmessage.h" // send_generic_message
|
|
#include "llviewerparcelmgr.h"
|
|
#include "llviewerregion.h"
|
|
#include "llviewertexture.h"
|
|
|
|
#ifdef OPENSIM
|
|
#include "llviewernetwork.h"
|
|
#endif // OPENSIM
|
|
|
|
//*TODO: verify this limit
|
|
const S32 MAX_AVATAR_CLASSIFIEDS = 100;
|
|
|
|
const S32 MINIMUM_PRICE_FOR_LISTING = 50; // L$
|
|
const S32 DEFAULT_EDIT_CLASSIFIED_SCROLL_HEIGHT = 530;
|
|
|
|
//static
|
|
LLPanelProfileClassified::panel_list_t LLPanelProfileClassified::sAllPanels;
|
|
|
|
static LLPanelInjector<LLPanelProfileClassifieds> t_panel_profile_classifieds("panel_profile_classifieds");
|
|
static LLPanelInjector<LLPanelProfileClassified> t_panel_profile_classified("panel_profile_classified");
|
|
|
|
class LLClassifiedHandler : public LLCommandHandler, public LLAvatarPropertiesObserver
|
|
{
|
|
public:
|
|
// throttle calls from untrusted browsers
|
|
LLClassifiedHandler() : LLCommandHandler("classified", UNTRUSTED_THROTTLE) {}
|
|
|
|
std::set<LLUUID> mClassifiedIds;
|
|
std::string mRequestVerb;
|
|
|
|
virtual bool canHandleUntrusted(
|
|
const LLSD& params,
|
|
const LLSD& query_map,
|
|
LLMediaCtrl* web,
|
|
const std::string& nav_type)
|
|
{
|
|
if (params.size() < 1)
|
|
{
|
|
return true; // don't block, will fail later
|
|
}
|
|
|
|
if (nav_type == NAV_TYPE_CLICKED
|
|
|| nav_type == NAV_TYPE_EXTERNAL)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const std::string verb = params[0].asString();
|
|
if (verb == "create")
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web)
|
|
{
|
|
if (LLStartUp::getStartupState() < STATE_STARTED)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableClassifieds"))
|
|
{
|
|
LLNotificationsUtil::add("NoClassifieds", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit"));
|
|
return true;
|
|
}
|
|
|
|
// handle app/classified/create urls first
|
|
if (params.size() == 1 && params[0].asString() == "create")
|
|
{
|
|
LLAvatarActions::createClassified();
|
|
return true;
|
|
}
|
|
|
|
// then handle the general app/classified/{UUID}/{CMD} urls
|
|
if (params.size() < 2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// get the ID for the classified
|
|
LLUUID classified_id;
|
|
if (!classified_id.set(params[0], FALSE))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// show the classified in the side tray.
|
|
// need to ask the server for more info first though...
|
|
const std::string verb = params[1].asString();
|
|
if (verb == "about")
|
|
{
|
|
mRequestVerb = verb;
|
|
mClassifiedIds.insert(classified_id);
|
|
LLAvatarPropertiesProcessor::getInstance()->addObserver(LLUUID(), this);
|
|
LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(classified_id);
|
|
return true;
|
|
}
|
|
else if (verb == "edit")
|
|
{
|
|
LLAvatarActions::showClassified(gAgent.getID(), classified_id, true);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void openClassified(LLAvatarClassifiedInfo* c_info)
|
|
{
|
|
if (mRequestVerb == "about")
|
|
{
|
|
if (c_info->creator_id == gAgent.getID())
|
|
{
|
|
LLAvatarActions::showClassified(gAgent.getID(), c_info->classified_id, false);
|
|
}
|
|
else
|
|
{
|
|
LLSD params;
|
|
params["id"] = c_info->creator_id;
|
|
params["classified_id"] = c_info->classified_id;
|
|
params["classified_creator_id"] = c_info->creator_id;
|
|
params["classified_snapshot_id"] = c_info->snapshot_id;
|
|
params["classified_name"] = c_info->name;
|
|
params["classified_desc"] = c_info->description;
|
|
params["from_search"] = true;
|
|
|
|
LLFloaterClassified* floaterp = LLFloaterReg::getTypedInstance<LLFloaterClassified>("classified", params);
|
|
if (floaterp)
|
|
{
|
|
floaterp->openFloater(params);
|
|
floaterp->setVisibleAndFrontmost();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void processProperties(void* data, EAvatarProcessorType type)
|
|
{
|
|
if (APT_CLASSIFIED_INFO != type)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// is this the classified that we asked for?
|
|
LLAvatarClassifiedInfo* c_info = static_cast<LLAvatarClassifiedInfo*>(data);
|
|
if (!c_info || mClassifiedIds.find(c_info->classified_id) == mClassifiedIds.end())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// open the detail side tray for this classified
|
|
openClassified(c_info);
|
|
|
|
// remove our observer now that we're done
|
|
mClassifiedIds.erase(c_info->classified_id);
|
|
LLAvatarPropertiesProcessor::getInstance()->removeObserver(LLUUID(), this);
|
|
}
|
|
};
|
|
LLClassifiedHandler gClassifiedHandler;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLPanelProfileClassifieds
|
|
//-----------------------------------------------------------------------------
|
|
|
|
LLPanelProfileClassifieds::LLPanelProfileClassifieds()
|
|
: LLPanelProfilePropertiesProcessorTab()
|
|
, mClassifiedToSelectOnLoad(LLUUID::null)
|
|
, mClassifiedEditOnLoad(false)
|
|
, mSheduledClassifiedCreation(false)
|
|
, mRlvBehaviorCallbackConnection() // <FS:Ansariel> RLVa support
|
|
{
|
|
}
|
|
|
|
LLPanelProfileClassifieds::~LLPanelProfileClassifieds()
|
|
{
|
|
// <FS:Ansariel> RLVa support
|
|
if (mRlvBehaviorCallbackConnection.connected())
|
|
{
|
|
mRlvBehaviorCallbackConnection.disconnect();
|
|
}
|
|
// </FS:Ansariel>
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::onOpen(const LLSD& key)
|
|
{
|
|
LL_INFOS("profiles") << "Inside onOpen classifieds panel" << LL_ENDL;
|
|
|
|
LLPanelProfilePropertiesProcessorTab::onOpen(key);
|
|
|
|
resetData();
|
|
|
|
bool own_profile = getSelfProfile();
|
|
if (own_profile)
|
|
{
|
|
mNewButton->setVisible(TRUE);
|
|
mNewButton->setEnabled(FALSE);
|
|
|
|
mDeleteButton->setVisible(TRUE);
|
|
mDeleteButton->setEnabled(FALSE);
|
|
}
|
|
|
|
childSetVisible("buttons_header", own_profile);
|
|
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::selectClassified(const LLUUID& classified_id, bool edit)
|
|
{
|
|
if (getIsLoaded())
|
|
{
|
|
for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx)
|
|
{
|
|
LLPanelProfileClassified* classified_panel = dynamic_cast<LLPanelProfileClassified*>(mTabContainer->getPanelByIndex(tab_idx));
|
|
if (classified_panel)
|
|
{
|
|
if (classified_panel->getClassifiedId() == classified_id)
|
|
{
|
|
mTabContainer->selectTabPanel(classified_panel);
|
|
if (edit)
|
|
{
|
|
classified_panel->setEditMode(TRUE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mClassifiedToSelectOnLoad = classified_id;
|
|
mClassifiedEditOnLoad = edit;
|
|
}
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::createClassified()
|
|
{
|
|
if (getIsLoaded())
|
|
{
|
|
mNoItemsLabel->setVisible(FALSE);
|
|
LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create();
|
|
classified_panel->onOpen(LLSD());
|
|
mTabContainer->addTabPanel(
|
|
LLTabContainer::TabPanelParams().
|
|
panel(classified_panel).
|
|
select_tab(true).
|
|
label(classified_panel->getClassifiedName()));
|
|
updateButtons();
|
|
}
|
|
else
|
|
{
|
|
mSheduledClassifiedCreation = true;
|
|
}
|
|
}
|
|
|
|
BOOL LLPanelProfileClassifieds::postBuild()
|
|
{
|
|
mTabContainer = getChild<LLTabContainer>("tab_classifieds");
|
|
mNoItemsLabel = getChild<LLUICtrl>("classifieds_panel_text");
|
|
mNewButton = getChild<LLButton>("new_btn");
|
|
mDeleteButton = getChild<LLButton>("delete_btn");
|
|
|
|
mNewButton->setCommitCallback(boost::bind(&LLPanelProfileClassifieds::onClickNewBtn, this));
|
|
mDeleteButton->setCommitCallback(boost::bind(&LLPanelProfileClassifieds::onClickDelete, this));
|
|
|
|
// <FS:Ansariel> RLVa support
|
|
mRlvBehaviorCallbackConnection = gRlvHandler.setBehaviourCallback(boost::bind(&LLPanelProfileClassifieds::updateRlvRestrictions, this, _1, _2));
|
|
// </FS:Ansariel>
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::onClickNewBtn()
|
|
{
|
|
mNoItemsLabel->setVisible(FALSE);
|
|
LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create();
|
|
classified_panel->onOpen(LLSD());
|
|
mTabContainer->addTabPanel(
|
|
LLTabContainer::TabPanelParams().
|
|
panel(classified_panel).
|
|
select_tab(true).
|
|
label(classified_panel->getClassifiedName()));
|
|
updateButtons();
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::onClickDelete()
|
|
{
|
|
LLPanelProfileClassified* classified_panel = dynamic_cast<LLPanelProfileClassified*>(mTabContainer->getCurrentPanel());
|
|
if (classified_panel)
|
|
{
|
|
LLUUID classified_id = classified_panel->getClassifiedId();
|
|
LLSD args;
|
|
args["CLASSIFIED"] = classified_panel->getClassifiedName();
|
|
LLSD payload;
|
|
payload["classified_id"] = classified_id;
|
|
payload["tab_idx"] = mTabContainer->getCurrentPanelIndex();
|
|
LLNotificationsUtil::add("ProfileDeleteClassified", args, payload,
|
|
boost::bind(&LLPanelProfileClassifieds::callbackDeleteClassified, this, _1, _2));
|
|
}
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::callbackDeleteClassified(const LLSD& notification, const LLSD& response)
|
|
{
|
|
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
|
|
|
|
if (0 == option)
|
|
{
|
|
LLUUID classified_id = notification["payload"]["classified_id"].asUUID();
|
|
S32 tab_idx = notification["payload"]["tab_idx"].asInteger();
|
|
|
|
LLPanelProfileClassified* classified_panel = dynamic_cast<LLPanelProfileClassified*>(mTabContainer->getPanelByIndex(tab_idx));
|
|
if (classified_panel && classified_panel->getClassifiedId() == classified_id)
|
|
{
|
|
mTabContainer->removeTabPanel(classified_panel);
|
|
}
|
|
|
|
if (classified_id.notNull())
|
|
{
|
|
LLAvatarPropertiesProcessor::getInstance()->sendClassifiedDelete(classified_id);
|
|
}
|
|
|
|
updateButtons();
|
|
|
|
BOOL no_data = !mTabContainer->getTabCount();
|
|
mNoItemsLabel->setVisible(no_data);
|
|
}
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::processProperties(void* data, EAvatarProcessorType type)
|
|
{
|
|
if ((APT_CLASSIFIEDS == type) || (APT_CLASSIFIED_INFO == type))
|
|
{
|
|
LLUUID avatar_id = getAvatarId();
|
|
|
|
LLAvatarClassifieds* c_info = static_cast<LLAvatarClassifieds*>(data);
|
|
if (c_info && getAvatarId() == c_info->target_id)
|
|
{
|
|
// do not clear classified list in case we will receive two or more data packets.
|
|
// list has been cleared in updateData(). (fix for EXT-6436)
|
|
LLUUID selected_id = mClassifiedToSelectOnLoad;
|
|
bool has_selection = false;
|
|
|
|
LLAvatarClassifieds::classifieds_list_t::const_iterator it = c_info->classifieds_list.begin();
|
|
for (; c_info->classifieds_list.end() != it; ++it)
|
|
{
|
|
LLAvatarClassifieds::classified_data c_data = *it;
|
|
|
|
LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create();
|
|
|
|
LLSD params;
|
|
params["classified_creator_id"] = avatar_id;
|
|
params["classified_id"] = c_data.classified_id;
|
|
params["classified_name"] = c_data.name;
|
|
params["from_search"] = (selected_id == c_data.classified_id); //SLURL handling and stats tracking
|
|
params["edit"] = (selected_id == c_data.classified_id) && mClassifiedEditOnLoad;
|
|
classified_panel->onOpen(params);
|
|
|
|
mTabContainer->addTabPanel(
|
|
LLTabContainer::TabPanelParams().
|
|
panel(classified_panel).
|
|
select_tab(selected_id == c_data.classified_id).
|
|
label(c_data.name));
|
|
|
|
if (selected_id == c_data.classified_id)
|
|
{
|
|
has_selection = true;
|
|
}
|
|
}
|
|
|
|
if (mSheduledClassifiedCreation)
|
|
{
|
|
LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create();
|
|
classified_panel->onOpen(LLSD());
|
|
mTabContainer->addTabPanel(
|
|
LLTabContainer::TabPanelParams().
|
|
panel(classified_panel).
|
|
select_tab(!has_selection).
|
|
label(classified_panel->getClassifiedName()));
|
|
has_selection = true;
|
|
}
|
|
|
|
// reset 'do on load' values
|
|
mClassifiedToSelectOnLoad = LLUUID::null;
|
|
mClassifiedEditOnLoad = false;
|
|
mSheduledClassifiedCreation = false;
|
|
|
|
// set even if not visible, user might delete own
|
|
// calassified and this string will need to be shown
|
|
if (getSelfProfile())
|
|
{
|
|
mNoItemsLabel->setValue(LLTrans::getString("NoClassifiedsText"));
|
|
}
|
|
else
|
|
{
|
|
mNoItemsLabel->setValue(LLTrans::getString("NoAvatarClassifiedsText"));
|
|
}
|
|
|
|
bool has_data = mTabContainer->getTabCount() > 0;
|
|
mNoItemsLabel->setVisible(!has_data);
|
|
if (has_data && !has_selection)
|
|
{
|
|
mTabContainer->selectFirstTab();
|
|
}
|
|
|
|
setLoaded();
|
|
updateButtons();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::resetData()
|
|
{
|
|
resetLoading();
|
|
mTabContainer->deleteAllTabs();
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::updateButtons()
|
|
{
|
|
if (getSelfProfile())
|
|
{
|
|
// <FS:Ansariel> RLVa support
|
|
//mNewButton->setEnabled(canAddNewClassified());
|
|
mNewButton->setEnabled(canAddNewClassified() && !gRlvHandler.hasBehaviour(RLV_BHVR_SHOWLOC));
|
|
// </FS:Ansariel>
|
|
mDeleteButton->setEnabled(canDeleteClassified());
|
|
}
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::updateData()
|
|
{
|
|
// Send picks request only once
|
|
LLUUID avatar_id = getAvatarId();
|
|
if (!getStarted() && avatar_id.notNull())
|
|
{
|
|
setIsLoading();
|
|
mNoItemsLabel->setValue(LLTrans::getString("PicksClassifiedsLoadingText"));
|
|
mNoItemsLabel->setVisible(TRUE);
|
|
|
|
LLAvatarPropertiesProcessor::getInstance()->sendAvatarClassifiedsRequest(avatar_id);
|
|
}
|
|
}
|
|
|
|
bool LLPanelProfileClassifieds::hasNewClassifieds()
|
|
{
|
|
for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx)
|
|
{
|
|
LLPanelProfileClassified* classified_panel = dynamic_cast<LLPanelProfileClassified*>(mTabContainer->getPanelByIndex(tab_idx));
|
|
if (classified_panel && classified_panel->isNew())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LLPanelProfileClassifieds::hasUnsavedChanges()
|
|
{
|
|
for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx)
|
|
{
|
|
LLPanelProfileClassified* classified_panel = dynamic_cast<LLPanelProfileClassified*>(mTabContainer->getPanelByIndex(tab_idx));
|
|
if (classified_panel && classified_panel->isDirty()) // includes 'new'
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool LLPanelProfileClassifieds::canAddNewClassified()
|
|
{
|
|
return (mTabContainer->getTabCount() < MAX_AVATAR_CLASSIFIEDS);
|
|
}
|
|
|
|
bool LLPanelProfileClassifieds::canDeleteClassified()
|
|
{
|
|
return (mTabContainer->getTabCount() > 0);
|
|
}
|
|
|
|
void LLPanelProfileClassifieds::commitUnsavedChanges()
|
|
{
|
|
if (getIsLoaded())
|
|
{
|
|
for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx)
|
|
{
|
|
LLPanelProfileClassified* classified_panel = dynamic_cast<LLPanelProfileClassified*>(mTabContainer->getPanelByIndex(tab_idx));
|
|
if (classified_panel && classified_panel->isDirty() && !classified_panel->isNew())
|
|
{
|
|
classified_panel->doSave();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// <FS:Ansariel> RLVa support
|
|
void LLPanelProfileClassifieds::updateRlvRestrictions(ERlvBehaviour behavior, ERlvParamType type)
|
|
{
|
|
if (behavior == RLV_BHVR_SHOWLOC)
|
|
{
|
|
updateButtons();
|
|
}
|
|
}
|
|
// </FS:Ansariel> RLV support
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLDispatchClassifiedClickThrough
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// "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());
|
|
|
|
LLPanelProfileClassified::setClickThrough(
|
|
classified_id, teleport_clicks, map_clicks, profile_clicks, false);
|
|
|
|
return true;
|
|
}
|
|
};
|
|
static LLDispatchClassifiedClickThrough sClassifiedClickThrough;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLPanelProfileClassified
|
|
//-----------------------------------------------------------------------------
|
|
|
|
static const S32 CB_ITEM_MATURE = 0;
|
|
static const S32 CB_ITEM_PG = 1;
|
|
|
|
LLPanelProfileClassified::LLPanelProfileClassified()
|
|
: LLPanelProfilePropertiesProcessorTab()
|
|
, mInfoLoaded(false)
|
|
, mTeleportClicksOld(0)
|
|
, mMapClicksOld(0)
|
|
, mProfileClicksOld(0)
|
|
, mTeleportClicksNew(0)
|
|
, mMapClicksNew(0)
|
|
, mProfileClicksNew(0)
|
|
, mPriceForListing(0)
|
|
, mSnapshotCtrl(NULL)
|
|
, mPublishFloater(NULL)
|
|
, mIsNew(false)
|
|
, mIsNewWithErrors(false)
|
|
, mCanClose(false)
|
|
, mEditMode(false)
|
|
, mEditOnLoad(false)
|
|
{
|
|
sAllPanels.push_back(this);
|
|
}
|
|
|
|
LLPanelProfileClassified::~LLPanelProfileClassified()
|
|
{
|
|
sAllPanels.remove(this);
|
|
gGenericDispatcher.addHandler("classifiedclickthrough", NULL); // deregister our handler
|
|
|
|
// [SL:KB] - Patch : UI-ProfileGroupFloater | Checked: 2010-11-28 (Catznip-2.4.0g) | Added: Catznip-2.4.0g
|
|
if(getAvatarId().notNull())
|
|
{
|
|
LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this);
|
|
}
|
|
// [/SL:KB]
|
|
}
|
|
|
|
//static
|
|
LLPanelProfileClassified* LLPanelProfileClassified::create()
|
|
{
|
|
LLPanelProfileClassified* panel = new LLPanelProfileClassified();
|
|
panel->buildFromFile("panel_profile_classified.xml");
|
|
return panel;
|
|
}
|
|
|
|
BOOL LLPanelProfileClassified::postBuild()
|
|
{
|
|
mScrollContainer = getChild<LLScrollContainer>("profile_scroll");
|
|
mInfoPanel = getChild<LLView>("info_panel");
|
|
mInfoScroll = getChild<LLPanel>("info_scroll_content_panel");
|
|
mEditPanel = getChild<LLPanel>("edit_panel");
|
|
|
|
mSnapshotCtrl = getChild<LLTextureCtrl>("classified_snapshot");
|
|
mEditIcon = getChild<LLUICtrl>("edit_icon");
|
|
|
|
//info
|
|
mClassifiedNameText = getChild<LLUICtrl>("classified_name");
|
|
mClassifiedDescText = getChild<LLTextEditor>("classified_desc");
|
|
mLocationText = getChild<LLUICtrl>("classified_location");
|
|
mCategoryText = getChild<LLUICtrl>("category");
|
|
mContentTypeText = getChild<LLUICtrl>("content_type");
|
|
mContentTypeM = getChild<LLIconCtrl>("content_type_moderate");
|
|
mContentTypeG = getChild<LLIconCtrl>("content_type_general");
|
|
mPriceText = getChild<LLUICtrl>("price_for_listing");
|
|
mAutoRenewText = getChild<LLUICtrl>("auto_renew");
|
|
|
|
mMapButton = getChild<LLButton>("show_on_map_btn");
|
|
mTeleportButton = getChild<LLButton>("teleport_btn");
|
|
mEditButton = getChild<LLButton>("edit_btn");
|
|
|
|
//edit
|
|
mClassifiedNameEdit = getChild<LLLineEditor>("classified_name_edit");
|
|
mClassifiedDescEdit = getChild<LLTextEditor>("classified_desc_edit");
|
|
mLocationEdit = getChild<LLUICtrl>("classified_location_edit");
|
|
mCategoryCombo = getChild<LLComboBox>("category_edit");
|
|
mContentTypeCombo = getChild<LLComboBox>("content_type_edit");
|
|
mAutoRenewEdit = getChild<LLUICtrl>("auto_renew_edit");
|
|
|
|
mSaveButton = getChild<LLButton>("save_changes_btn");
|
|
mSetLocationButton = getChild<LLButton>("set_to_curr_location_btn");
|
|
mCancelButton = getChild<LLButton>("cancel_btn");
|
|
|
|
mUtilityBtnCnt = getChild<LLPanel>("util_buttons_lp");
|
|
mPublishBtnsCnt = getChild<LLPanel>("publish_layout_panel");
|
|
mCancelBtnCnt = getChild<LLPanel>("cancel_btn_lp");
|
|
mSaveBtnCnt = getChild<LLPanel>("save_btn_lp");
|
|
|
|
mSnapshotCtrl->setOnSelectCallback(boost::bind(&LLPanelProfileClassified::onTextureSelected, this));
|
|
mSnapshotCtrl->setMouseEnterCallback(boost::bind(&LLPanelProfileClassified::onTexturePickerMouseEnter, this));
|
|
mSnapshotCtrl->setMouseLeaveCallback(boost::bind(&LLPanelProfileClassified::onTexturePickerMouseLeave, this));
|
|
mEditIcon->setVisible(false);
|
|
|
|
mMapButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onMapClick, this));
|
|
mTeleportButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onTeleportClick, this));
|
|
mEditButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onEditClick, this));
|
|
mSaveButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onSaveClick, this));
|
|
mSetLocationButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onSetLocationClick, this));
|
|
mCancelButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onCancelClick, this));
|
|
|
|
LLClassifiedInfo::cat_map::iterator iter;
|
|
for (iter = LLClassifiedInfo::sCategories.begin();
|
|
iter != LLClassifiedInfo::sCategories.end();
|
|
iter++)
|
|
{
|
|
mCategoryCombo->add(LLTrans::getString(iter->second));
|
|
}
|
|
|
|
mClassifiedNameEdit->setKeystrokeCallback(boost::bind(&LLPanelProfileClassified::onChange, this), NULL);
|
|
mClassifiedDescEdit->setKeystrokeCallback(boost::bind(&LLPanelProfileClassified::onChange, this));
|
|
mCategoryCombo->setCommitCallback(boost::bind(&LLPanelProfileClassified::onChange, this));
|
|
mContentTypeCombo->setCommitCallback(boost::bind(&LLPanelProfileClassified::onChange, this));
|
|
mAutoRenewEdit->setCommitCallback(boost::bind(&LLPanelProfileClassified::onChange, this));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void LLPanelProfileClassified::onOpen(const LLSD& key)
|
|
{
|
|
mIsNew = key.isUndefined();
|
|
|
|
resetData();
|
|
resetControls();
|
|
scrollToTop();
|
|
|
|
// classified is not created yet
|
|
bool is_new = isNew() || isNewWithErrors();
|
|
|
|
if(is_new)
|
|
{
|
|
LLPanelProfilePropertiesProcessorTab::setAvatarId(gAgent.getID());
|
|
|
|
setPosGlobal(gAgent.getPositionGlobal());
|
|
|
|
LLUUID snapshot_id = LLUUID::null;
|
|
std::string desc;
|
|
LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
|
|
if(parcel)
|
|
{
|
|
desc = parcel->getDesc();
|
|
snapshot_id = parcel->getSnapshotID();
|
|
}
|
|
|
|
std::string region_name = LLTrans::getString("ClassifiedUpdateAfterPublish");
|
|
LLViewerRegion* region = gAgent.getRegion();
|
|
if (region)
|
|
{
|
|
region_name = region->getName();
|
|
}
|
|
|
|
setClassifiedName(makeClassifiedName());
|
|
setDescription(desc);
|
|
setSnapshotId(snapshot_id);
|
|
setClassifiedLocation(createLocationText(getLocationNotice(), region_name, getPosGlobal()));
|
|
// server will set valid parcel id
|
|
setParcelId(LLUUID::null);
|
|
|
|
mSaveButton->setLabelArg("[LABEL]", getString("publish_label"));
|
|
|
|
setEditMode(TRUE);
|
|
enableSave(true);
|
|
enableEditing(true);
|
|
resetDirty();
|
|
setInfoLoaded(false);
|
|
}
|
|
else
|
|
{
|
|
LLUUID avatar_id = key["classified_creator_id"];
|
|
if(avatar_id.isNull())
|
|
{
|
|
return;
|
|
}
|
|
LLPanelProfilePropertiesProcessorTab::setAvatarId(avatar_id);
|
|
|
|
setClassifiedId(key["classified_id"]);
|
|
setClassifiedName(key["classified_name"]);
|
|
setFromSearch(key["from_search"]);
|
|
mEditOnLoad = key["edit"];
|
|
|
|
LL_INFOS() << "Opening classified [" << getClassifiedName() << "] (" << getClassifiedId() << ")" << LL_ENDL;
|
|
|
|
LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId());
|
|
|
|
gGenericDispatcher.addHandler("classifiedclickthrough", &sClassifiedClickThrough);
|
|
|
|
if (gAgent.getRegion())
|
|
{
|
|
// 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");
|
|
if (!url.empty())
|
|
{
|
|
LL_INFOS() << "Classified stat request via capability" << LL_ENDL;
|
|
LLSD body;
|
|
LLUUID classifiedId = getClassifiedId();
|
|
body["classified_id"] = classifiedId;
|
|
LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, body,
|
|
boost::bind(&LLPanelProfileClassified::handleSearchStatResponse, classifiedId, _1));
|
|
}
|
|
}
|
|
// Update classified click stats.
|
|
// *TODO: Should we do this when opening not from search?
|
|
if (!fromSearch() )
|
|
{
|
|
sendClickMessage("profile");
|
|
}
|
|
|
|
setInfoLoaded(false);
|
|
}
|
|
|
|
|
|
bool is_self = getSelfProfile();
|
|
getChildView("auto_renew_layout_panel")->setVisible(is_self);
|
|
getChildView("clickthrough_layout_panel")->setVisible(is_self);
|
|
|
|
updateButtons();
|
|
}
|
|
|
|
void LLPanelProfileClassified::processProperties(void* data, EAvatarProcessorType type)
|
|
{
|
|
if (APT_CLASSIFIED_INFO != type)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLAvatarClassifiedInfo* c_info = static_cast<LLAvatarClassifiedInfo*>(data);
|
|
if(c_info && getClassifiedId() == c_info->classified_id)
|
|
{
|
|
// see LLPanelProfileClassified::sendUpdate() for notes
|
|
if (mIsNewWithErrors)
|
|
{
|
|
// We just published it
|
|
setEditMode(FALSE);
|
|
}
|
|
mIsNewWithErrors = false;
|
|
mIsNew = false;
|
|
|
|
setClassifiedName(c_info->name);
|
|
setDescription(c_info->description);
|
|
setSnapshotId(c_info->snapshot_id);
|
|
setParcelId(c_info->parcel_id);
|
|
setPosGlobal(c_info->pos_global);
|
|
setSimName(c_info->sim_name);
|
|
|
|
setClassifiedLocation(createLocationText(c_info->parcel_name, c_info->sim_name, c_info->pos_global));
|
|
|
|
mCategoryText->setValue(LLClassifiedInfo::sCategories[c_info->category]);
|
|
// *HACK see LLPanelProfileClassified::sendUpdate()
|
|
setCategory(c_info->category - 1);
|
|
|
|
bool mature = is_cf_mature(c_info->flags);
|
|
setContentType(mature);
|
|
|
|
bool auto_renew = is_cf_auto_renew(c_info->flags);
|
|
std::string auto_renew_str = auto_renew ? getString("auto_renew_on") : getString("auto_renew_off");
|
|
mAutoRenewText->setValue(auto_renew_str);
|
|
mAutoRenewEdit->setValue(auto_renew);
|
|
|
|
static LLUIString price_str = getString("l$_price");
|
|
price_str.setArg("[PRICE]", llformat("%d", c_info->price_for_listing));
|
|
mPriceText->setValue(LLSD(price_str));
|
|
|
|
static std::string date_fmt = getString("date_fmt");
|
|
std::string date_str = date_fmt;
|
|
LLStringUtil::format(date_str, LLSD().with("datetime", (S32) c_info->creation_date));
|
|
getChild<LLUICtrl>("creation_date")->setValue(date_str);
|
|
|
|
resetDirty();
|
|
setInfoLoaded(true);
|
|
enableSave(false);
|
|
enableEditing(true);
|
|
|
|
// for just created classified - in case user opened edit panel before processProperties() callback
|
|
mSaveButton->setLabelArg("[LABEL]", getString("save_label"));
|
|
|
|
setLoaded();
|
|
updateButtons();
|
|
|
|
if (mEditOnLoad)
|
|
{
|
|
setEditMode(TRUE);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void LLPanelProfileClassified::setEditMode(BOOL edit_mode)
|
|
{
|
|
mEditMode = edit_mode;
|
|
|
|
mInfoPanel->setVisible(!edit_mode);
|
|
mEditPanel->setVisible(edit_mode);
|
|
|
|
// snapshot control is common between info and edit,
|
|
// enable it only when in edit mode
|
|
mSnapshotCtrl->setEnabled(edit_mode);
|
|
|
|
scrollToTop();
|
|
updateButtons();
|
|
updateInfoRect();
|
|
}
|
|
|
|
void LLPanelProfileClassified::updateButtons()
|
|
{
|
|
bool edit_mode = getEditMode();
|
|
mUtilityBtnCnt->setVisible(!edit_mode);
|
|
|
|
// cancel button should either delete unpublished
|
|
// classified or not be there at all
|
|
mCancelBtnCnt->setVisible(edit_mode && !mIsNew);
|
|
mPublishBtnsCnt->setVisible(edit_mode);
|
|
mSaveBtnCnt->setVisible(edit_mode);
|
|
mEditButton->setVisible(!edit_mode && getSelfProfile());
|
|
}
|
|
|
|
void LLPanelProfileClassified::updateInfoRect()
|
|
{
|
|
if (getEditMode())
|
|
{
|
|
// info_scroll_content_panel contains both info and edit panel
|
|
// info panel can be very large and scroll bar will carry over.
|
|
// Resize info panel to prevent scroll carry over when in edit mode.
|
|
mInfoScroll->reshape(mInfoScroll->getRect().getWidth(), DEFAULT_EDIT_CLASSIFIED_SCROLL_HEIGHT, FALSE);
|
|
}
|
|
else
|
|
{
|
|
// Adjust text height to make description scrollable.
|
|
S32 new_height = mClassifiedDescText->getTextBoundingRect().getHeight();
|
|
LLRect visible_rect = mClassifiedDescText->getVisibleDocumentRect();
|
|
S32 delta_height = new_height - visible_rect.getHeight() + 5;
|
|
|
|
LLRect rect = mInfoScroll->getRect();
|
|
mInfoScroll->reshape(rect.getWidth(), rect.getHeight() + delta_height, FALSE);
|
|
}
|
|
}
|
|
|
|
void LLPanelProfileClassified::enableEditing(bool enable)
|
|
{
|
|
mEditButton->setEnabled(enable);
|
|
mClassifiedNameEdit->setEnabled(enable);
|
|
mClassifiedDescEdit->setEnabled(enable);
|
|
mSetLocationButton->setEnabled(enable);
|
|
mCategoryCombo->setEnabled(enable);
|
|
mContentTypeCombo->setEnabled(enable);
|
|
mAutoRenewEdit->setEnabled(enable);
|
|
}
|
|
|
|
void LLPanelProfileClassified::resetControls()
|
|
{
|
|
updateButtons();
|
|
|
|
mCategoryCombo->setCurrentByIndex(0);
|
|
mContentTypeCombo->setCurrentByIndex(0);
|
|
mAutoRenewEdit->setValue(false);
|
|
// <FS:CR> FIRE-9814 - Don't hardcode a classified listing fee
|
|
//mPriceForListing = MINIMUM_PRICE_FOR_LISTING;
|
|
mPriceForListing = getClassifiedFee();
|
|
// </FS:CR>
|
|
}
|
|
|
|
// <FS:CR> FIRE-9814 - Don't hardcode a classified listing fee
|
|
S32 LLPanelProfileClassified::getClassifiedFee()
|
|
{
|
|
S32 fee = MINIMUM_PRICE_FOR_LISTING;
|
|
#ifdef OPENSIM
|
|
if (LLGridManager::getInstance()->isInOpenSim())
|
|
{
|
|
fee = LLGridManager::getInstance()->getClassifiedFee();
|
|
}
|
|
#endif // OPENSIM
|
|
return fee;
|
|
}
|
|
// </FS:CR>
|
|
|
|
void LLPanelProfileClassified::onEditClick()
|
|
{
|
|
setEditMode(TRUE);
|
|
}
|
|
|
|
void LLPanelProfileClassified::onCancelClick()
|
|
{
|
|
if (isNew())
|
|
{
|
|
mClassifiedNameEdit->setValue(mClassifiedNameText->getValue());
|
|
mClassifiedDescEdit->setValue(mClassifiedDescText->getValue());
|
|
mLocationEdit->setValue(mLocationText->getValue());
|
|
mCategoryCombo->setCurrentByIndex(0);
|
|
mContentTypeCombo->setCurrentByIndex(0);
|
|
mAutoRenewEdit->setValue(false);
|
|
// <FS:CR> FIRE-9814 - Don't hardcode a classified listing fee
|
|
//mPriceForListing = MINIMUM_PRICE_FOR_LISTING;
|
|
mPriceForListing = getClassifiedFee();
|
|
// </FS:CR>
|
|
}
|
|
else
|
|
{
|
|
// Reload data to undo changes to forms
|
|
LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId());
|
|
}
|
|
|
|
setInfoLoaded(false);
|
|
|
|
setEditMode(FALSE);
|
|
}
|
|
|
|
void LLPanelProfileClassified::onSaveClick()
|
|
{
|
|
mCanClose = false;
|
|
|
|
if(!isValidName())
|
|
{
|
|
notifyInvalidName();
|
|
return;
|
|
}
|
|
if(isNew() || isNewWithErrors())
|
|
{
|
|
if(gStatusBar->getBalance() < getPriceForListing())
|
|
{
|
|
LLNotificationsUtil::add("ClassifiedInsufficientFunds");
|
|
return;
|
|
}
|
|
|
|
mPublishFloater = LLFloaterReg::findTypedInstance<LLPublishClassifiedFloater>(
|
|
"publish_classified", LLSD());
|
|
|
|
if(!mPublishFloater)
|
|
{
|
|
mPublishFloater = LLFloaterReg::getTypedInstance<LLPublishClassifiedFloater>(
|
|
"publish_classified", LLSD());
|
|
|
|
mPublishFloater->setPublishClickedCallback(boost::bind
|
|
(&LLPanelProfileClassified::onPublishFloaterPublishClicked, this));
|
|
}
|
|
|
|
// set spinner value before it has focus or value wont be set
|
|
mPublishFloater->setPrice(getPriceForListing());
|
|
mPublishFloater->openFloater(mPublishFloater->getKey());
|
|
mPublishFloater->center();
|
|
}
|
|
else
|
|
{
|
|
doSave();
|
|
}
|
|
}
|
|
|
|
/*static*/
|
|
void LLPanelProfileClassified::handleSearchStatResponse(LLUUID classifiedId, LLSD result)
|
|
{
|
|
S32 teleport = result["teleport_clicks"].asInteger();
|
|
S32 map = result["map_clicks"].asInteger();
|
|
S32 profile = result["profile_clicks"].asInteger();
|
|
S32 search_teleport = result["search_teleport_clicks"].asInteger();
|
|
S32 search_map = result["search_map_clicks"].asInteger();
|
|
S32 search_profile = result["search_profile_clicks"].asInteger();
|
|
|
|
LLPanelProfileClassified::setClickThrough(classifiedId,
|
|
teleport + search_teleport,
|
|
map + search_map,
|
|
profile + search_profile,
|
|
true);
|
|
}
|
|
|
|
void LLPanelProfileClassified::resetData()
|
|
{
|
|
setClassifiedName(LLStringUtil::null);
|
|
setDescription(LLStringUtil::null);
|
|
setClassifiedLocation(LLStringUtil::null);
|
|
setClassifiedId(LLUUID::null);
|
|
setSnapshotId(LLUUID::null);
|
|
setPosGlobal(LLVector3d::zero);
|
|
setParcelId(LLUUID::null);
|
|
setSimName(LLStringUtil::null);
|
|
setFromSearch(false);
|
|
|
|
// reset click stats
|
|
mTeleportClicksOld = 0;
|
|
mMapClicksOld = 0;
|
|
mProfileClicksOld = 0;
|
|
mTeleportClicksNew = 0;
|
|
mMapClicksNew = 0;
|
|
mProfileClicksNew = 0;
|
|
|
|
// <FS:CR> FIRE-9814 - Don't hardcode a classified listing fee
|
|
//mPriceForListing = MINIMUM_PRICE_FOR_LISTING;
|
|
mPriceForListing = getClassifiedFee();
|
|
// </FS:CR>
|
|
|
|
mCategoryText->setValue(LLStringUtil::null);
|
|
mContentTypeText->setValue(LLStringUtil::null);
|
|
getChild<LLUICtrl>("click_through_text")->setValue(LLStringUtil::null);
|
|
mEditButton->setValue(LLStringUtil::null);
|
|
getChild<LLUICtrl>("creation_date")->setValue(LLStringUtil::null);
|
|
mContentTypeM->setVisible(FALSE);
|
|
mContentTypeG->setVisible(FALSE);
|
|
}
|
|
|
|
void LLPanelProfileClassified::setClassifiedName(const std::string& name)
|
|
{
|
|
mClassifiedNameText->setValue(name);
|
|
mClassifiedNameEdit->setValue(name);
|
|
}
|
|
|
|
std::string LLPanelProfileClassified::getClassifiedName()
|
|
{
|
|
return mClassifiedNameEdit->getValue().asString();
|
|
}
|
|
|
|
void LLPanelProfileClassified::setDescription(const std::string& desc)
|
|
{
|
|
mClassifiedDescText->setValue(desc);
|
|
mClassifiedDescEdit->setValue(desc);
|
|
|
|
updateInfoRect();
|
|
}
|
|
|
|
std::string LLPanelProfileClassified::getDescription()
|
|
{
|
|
return mClassifiedDescEdit->getValue().asString();
|
|
}
|
|
|
|
void LLPanelProfileClassified::setClassifiedLocation(const std::string& location)
|
|
{
|
|
mLocationText->setValue(location);
|
|
mLocationEdit->setValue(location);
|
|
}
|
|
|
|
std::string LLPanelProfileClassified::getClassifiedLocation()
|
|
{
|
|
return mLocationText->getValue().asString();
|
|
}
|
|
|
|
void LLPanelProfileClassified::setSnapshotId(const LLUUID& id)
|
|
{
|
|
mSnapshotCtrl->setValue(id);
|
|
}
|
|
|
|
LLUUID LLPanelProfileClassified::getSnapshotId()
|
|
{
|
|
return mSnapshotCtrl->getValue().asUUID();
|
|
}
|
|
|
|
// static
|
|
void LLPanelProfileClassified::setClickThrough(
|
|
const LLUUID& classified_id,
|
|
S32 teleport,
|
|
S32 map,
|
|
S32 profile,
|
|
bool from_new_table)
|
|
{
|
|
LL_INFOS() << "Click-through data for classified " << classified_id << " arrived: ["
|
|
<< teleport << ", " << map << ", " << profile << "] ("
|
|
<< (from_new_table ? "new" : "old") << ")" << LL_ENDL;
|
|
|
|
for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter)
|
|
{
|
|
LLPanelProfileClassified* self = *iter;
|
|
if (self->getClassifiedId() != classified_id)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// *HACK: Skip LLPanelProfileClassified instances: they don't display clicks data.
|
|
// Those instances should not be in the list at all.
|
|
if (typeid(*self) != typeid(LLPanelProfileClassified))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LL_INFOS() << "Updating classified info panel" << LL_ENDL;
|
|
|
|
// 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;
|
|
}
|
|
|
|
static LLUIString ct_str = self->getString("click_through_text_fmt");
|
|
|
|
ct_str.setArg("[TELEPORT]", llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld));
|
|
ct_str.setArg("[MAP]", llformat("%d", self->mMapClicksNew + self->mMapClicksOld));
|
|
ct_str.setArg("[PROFILE]", llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld));
|
|
|
|
self->getChild<LLUICtrl>("click_through_text")->setValue(ct_str.getString());
|
|
// *HACK: remove this when there is enough room for click stats in the info panel
|
|
self->getChildView("click_through_text")->setToolTip(ct_str.getString());
|
|
|
|
LL_INFOS() << "teleport: " << llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld)
|
|
<< ", map: " << llformat("%d", self->mMapClicksNew + self->mMapClicksOld)
|
|
<< ", profile: " << llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld)
|
|
<< LL_ENDL;
|
|
}
|
|
}
|
|
|
|
// static
|
|
std::string LLPanelProfileClassified::createLocationText(
|
|
const std::string& original_name,
|
|
const std::string& sim_name,
|
|
const LLVector3d& pos_global)
|
|
{
|
|
std::string location_text;
|
|
|
|
location_text.append(original_name);
|
|
|
|
if (!sim_name.empty())
|
|
{
|
|
if (!location_text.empty())
|
|
location_text.append(", ");
|
|
location_text.append(sim_name);
|
|
}
|
|
|
|
if (!location_text.empty())
|
|
location_text.append(" ");
|
|
|
|
if (!pos_global.isNull())
|
|
{
|
|
S32 region_x = ll_round((F32)pos_global.mdV[VX]) % REGION_WIDTH_UNITS;
|
|
S32 region_y = ll_round((F32)pos_global.mdV[VY]) % REGION_WIDTH_UNITS;
|
|
S32 region_z = ll_round((F32)pos_global.mdV[VZ]);
|
|
location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z));
|
|
}
|
|
|
|
return location_text;
|
|
}
|
|
|
|
void LLPanelProfileClassified::scrollToTop()
|
|
{
|
|
if (mScrollContainer)
|
|
{
|
|
mScrollContainer->goToTop();
|
|
}
|
|
}
|
|
|
|
//info
|
|
// static
|
|
// *TODO: move out of the panel
|
|
void LLPanelProfileClassified::sendClickMessage(
|
|
const std::string& type,
|
|
bool from_search,
|
|
const LLUUID& classified_id,
|
|
const LLUUID& parcel_id,
|
|
const LLVector3d& global_pos,
|
|
const std::string& sim_name)
|
|
{
|
|
if (gAgent.getRegion())
|
|
{
|
|
// 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"] = from_search;
|
|
body["classified_id"] = classified_id;
|
|
body["parcel_id"] = parcel_id;
|
|
body["dest_pos_global"] = global_pos.getValue();
|
|
body["region_name"] = sim_name;
|
|
|
|
std::string url = gAgent.getRegion()->getCapability("SearchStatTracking");
|
|
LL_INFOS() << "Sending click msg via capability (url=" << url << ")" << LL_ENDL;
|
|
LL_INFOS() << "body: [" << body << "]" << LL_ENDL;
|
|
LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body,
|
|
"SearchStatTracking Click report sent.", "SearchStatTracking Click report NOT sent.");
|
|
}
|
|
}
|
|
|
|
void LLPanelProfileClassified::sendClickMessage(const std::string& type)
|
|
{
|
|
sendClickMessage(
|
|
type,
|
|
fromSearch(),
|
|
getClassifiedId(),
|
|
getParcelId(),
|
|
getPosGlobal(),
|
|
getSimName());
|
|
}
|
|
|
|
void LLPanelProfileClassified::onMapClick()
|
|
{
|
|
sendClickMessage("map");
|
|
LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal());
|
|
LLFloaterReg::showInstance("world_map", "center");
|
|
}
|
|
|
|
void LLPanelProfileClassified::onTeleportClick()
|
|
{
|
|
if (!getPosGlobal().isExactlyZero())
|
|
{
|
|
sendClickMessage("teleport");
|
|
gAgent.teleportViaLocation(getPosGlobal());
|
|
LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal());
|
|
}
|
|
}
|
|
|
|
BOOL LLPanelProfileClassified::isDirty() const
|
|
{
|
|
if(mIsNew)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL dirty = false;
|
|
dirty |= mSnapshotCtrl->isDirty();
|
|
dirty |= mClassifiedNameEdit->isDirty();
|
|
dirty |= mClassifiedDescEdit->isDirty();
|
|
dirty |= mCategoryCombo->isDirty();
|
|
dirty |= mContentTypeCombo->isDirty();
|
|
dirty |= mAutoRenewEdit->isDirty();
|
|
|
|
return dirty;
|
|
}
|
|
|
|
void LLPanelProfileClassified::resetDirty()
|
|
{
|
|
mSnapshotCtrl->resetDirty();
|
|
mClassifiedNameEdit->resetDirty();
|
|
|
|
// call blockUndo() to really reset dirty(and make isDirty work as intended)
|
|
mClassifiedDescEdit->blockUndo();
|
|
mClassifiedDescEdit->resetDirty();
|
|
|
|
mCategoryCombo->resetDirty();
|
|
mContentTypeCombo->resetDirty();
|
|
mAutoRenewEdit->resetDirty();
|
|
}
|
|
|
|
bool LLPanelProfileClassified::canClose()
|
|
{
|
|
return mCanClose;
|
|
}
|
|
|
|
U32 LLPanelProfileClassified::getContentType()
|
|
{
|
|
return mContentTypeCombo->getCurrentIndex();
|
|
}
|
|
|
|
void LLPanelProfileClassified::setContentType(bool mature)
|
|
{
|
|
static std::string mature_str = getString("type_mature");
|
|
static std::string pg_str = getString("type_pg");
|
|
mContentTypeText->setValue(mature ? mature_str : pg_str);
|
|
mContentTypeM->setVisible(mature);
|
|
mContentTypeG->setVisible(!mature);
|
|
mContentTypeCombo->setCurrentByIndex(mature ? CB_ITEM_MATURE : CB_ITEM_PG);
|
|
mContentTypeCombo->resetDirty();
|
|
}
|
|
|
|
bool LLPanelProfileClassified::getAutoRenew()
|
|
{
|
|
return mAutoRenewEdit->getValue().asBoolean();
|
|
}
|
|
|
|
void LLPanelProfileClassified::sendUpdate()
|
|
{
|
|
LLAvatarClassifiedInfo c_data;
|
|
|
|
if(getClassifiedId().isNull())
|
|
{
|
|
setClassifiedId(LLUUID::generateNewID());
|
|
}
|
|
|
|
c_data.agent_id = gAgent.getID();
|
|
c_data.classified_id = getClassifiedId();
|
|
// *HACK
|
|
// Categories on server start with 1 while combo-box index starts with 0
|
|
c_data.category = getCategory() + 1;
|
|
c_data.name = getClassifiedName();
|
|
c_data.description = getDescription();
|
|
c_data.parcel_id = getParcelId();
|
|
c_data.snapshot_id = getSnapshotId();
|
|
c_data.pos_global = getPosGlobal();
|
|
c_data.flags = getFlags();
|
|
c_data.price_for_listing = getPriceForListing();
|
|
|
|
LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoUpdate(&c_data);
|
|
|
|
if(isNew())
|
|
{
|
|
// Lets assume there will be some error.
|
|
// Successful sendClassifiedInfoUpdate will trigger processProperties and
|
|
// let us know there was no error.
|
|
mIsNewWithErrors = true;
|
|
}
|
|
}
|
|
|
|
U32 LLPanelProfileClassified::getCategory()
|
|
{
|
|
return mCategoryCombo->getCurrentIndex();
|
|
}
|
|
|
|
void LLPanelProfileClassified::setCategory(U32 category)
|
|
{
|
|
mCategoryCombo->setCurrentByIndex(category);
|
|
mCategoryCombo->resetDirty();
|
|
}
|
|
|
|
U8 LLPanelProfileClassified::getFlags()
|
|
{
|
|
bool auto_renew = mAutoRenewEdit->getValue().asBoolean();
|
|
|
|
bool mature = mContentTypeCombo->getCurrentIndex() == CB_ITEM_MATURE;
|
|
|
|
return pack_classified_flags_request(auto_renew, false, mature, false);
|
|
}
|
|
|
|
void LLPanelProfileClassified::enableSave(bool enable)
|
|
{
|
|
mSaveButton->setEnabled(enable);
|
|
}
|
|
|
|
std::string LLPanelProfileClassified::makeClassifiedName()
|
|
{
|
|
std::string name;
|
|
|
|
LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
|
|
if(parcel)
|
|
{
|
|
name = parcel->getName();
|
|
}
|
|
|
|
if(!name.empty())
|
|
{
|
|
return name;
|
|
}
|
|
|
|
LLViewerRegion* region = gAgent.getRegion();
|
|
if(region)
|
|
{
|
|
name = region->getName();
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
void LLPanelProfileClassified::onSetLocationClick()
|
|
{
|
|
setPosGlobal(gAgent.getPositionGlobal());
|
|
setParcelId(LLUUID::null);
|
|
|
|
std::string region_name = LLTrans::getString("ClassifiedUpdateAfterPublish");
|
|
LLViewerRegion* region = gAgent.getRegion();
|
|
if (region)
|
|
{
|
|
region_name = region->getName();
|
|
}
|
|
|
|
setClassifiedLocation(createLocationText(getLocationNotice(), region_name, getPosGlobal()));
|
|
|
|
// mark classified as dirty
|
|
setValue(LLSD());
|
|
|
|
onChange();
|
|
}
|
|
|
|
void LLPanelProfileClassified::onChange()
|
|
{
|
|
enableSave(isDirty());
|
|
}
|
|
|
|
void LLPanelProfileClassified::doSave()
|
|
{
|
|
//*TODO: Fix all of this
|
|
|
|
mCanClose = true;
|
|
sendUpdate();
|
|
updateTabLabel(getClassifiedName());
|
|
resetDirty();
|
|
|
|
if (!canClose())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!isNew() && !isNewWithErrors())
|
|
{
|
|
setEditMode(FALSE);
|
|
return;
|
|
}
|
|
|
|
updateButtons();
|
|
}
|
|
|
|
void LLPanelProfileClassified::onPublishFloaterPublishClicked()
|
|
{
|
|
setPriceForListing(mPublishFloater->getPrice());
|
|
|
|
doSave();
|
|
}
|
|
|
|
std::string LLPanelProfileClassified::getLocationNotice()
|
|
{
|
|
static std::string location_notice = getString("location_notice");
|
|
return location_notice;
|
|
}
|
|
|
|
bool LLPanelProfileClassified::isValidName()
|
|
{
|
|
std::string name = getClassifiedName();
|
|
if (name.empty())
|
|
{
|
|
return false;
|
|
}
|
|
if (!isalnum(name[0]))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void LLPanelProfileClassified::notifyInvalidName()
|
|
{
|
|
std::string name = getClassifiedName();
|
|
if (name.empty())
|
|
{
|
|
LLNotificationsUtil::add("BlankClassifiedName");
|
|
}
|
|
else if (!isalnum(name[0]))
|
|
{
|
|
LLNotificationsUtil::add("ClassifiedMustBeAlphanumeric");
|
|
}
|
|
}
|
|
|
|
void LLPanelProfileClassified::onTexturePickerMouseEnter()
|
|
{
|
|
mEditIcon->setVisible(TRUE);
|
|
}
|
|
|
|
void LLPanelProfileClassified::onTexturePickerMouseLeave()
|
|
{
|
|
mEditIcon->setVisible(FALSE);
|
|
}
|
|
|
|
void LLPanelProfileClassified::onTextureSelected()
|
|
{
|
|
setSnapshotId(mSnapshotCtrl->getValue().asUUID());
|
|
onChange();
|
|
}
|
|
|
|
void LLPanelProfileClassified::updateTabLabel(const std::string& title)
|
|
{
|
|
setLabel(title);
|
|
LLTabContainer* parent = dynamic_cast<LLTabContainer*>(getParent());
|
|
if (parent)
|
|
{
|
|
parent->setCurrentTabName(title);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// LLPublishClassifiedFloater
|
|
//-----------------------------------------------------------------------------
|
|
|
|
LLPublishClassifiedFloater::LLPublishClassifiedFloater(const LLSD& key)
|
|
: LLFloater(key)
|
|
{
|
|
}
|
|
|
|
LLPublishClassifiedFloater::~LLPublishClassifiedFloater()
|
|
{
|
|
}
|
|
|
|
BOOL LLPublishClassifiedFloater::postBuild()
|
|
{
|
|
LLFloater::postBuild();
|
|
|
|
childSetAction("publish_btn", boost::bind(&LLFloater::closeFloater, this, false));
|
|
childSetAction("cancel_btn", boost::bind(&LLFloater::closeFloater, this, false));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void LLPublishClassifiedFloater::setPrice(S32 price)
|
|
{
|
|
getChild<LLUICtrl>("price_for_listing")->setValue(price);
|
|
}
|
|
|
|
S32 LLPublishClassifiedFloater::getPrice()
|
|
{
|
|
return getChild<LLUICtrl>("price_for_listing")->getValue().asInteger();
|
|
}
|
|
|
|
void LLPublishClassifiedFloater::setPublishClickedCallback(const commit_signal_t::slot_type& cb)
|
|
{
|
|
getChild<LLButton>("publish_btn")->setClickedCallback(cb);
|
|
}
|
|
|
|
void LLPublishClassifiedFloater::setCancelClickedCallback(const commit_signal_t::slot_type& cb)
|
|
{
|
|
getChild<LLButton>("cancel_btn")->setClickedCallback(cb);
|
|
}
|