Forked from
Alchemy Viewer / Alchemy Viewer
15033 commits behind the upstream repository.
-
Nat Goodspeed authored
This changeset is meant to exemplify how to convert a "namespace" class whose methods are static -- and whose data are module-static -- to an LLSingleton. LLVersionInfo has no initClass() or cleanupClass() methods, but the general idea is the same. * Derive the class from LLSingleton<T>: class LLSomeSingleton: public LLSingleton<LLSomeSingleton> { ... }; * Add LLSINGLETON(LLSomeSingleton); in the private section of the class. This usage implies a separate LLSomeSingleton::LLSomeSingleton() definition, as described in indra/llcommon/llsingleton.h. * Move module-scope data in the .cpp file to non-static class members. Change any sVariableName to mVariableName to avoid being outright misleading. * Make static class methods non-static. Remove '//static' comments from method definitions as needed. * For LLVersionInfo specifically, the 'const std::string&' return type was replaced with 'std::string'. Returning a reference to a static or a member, const or otherwise, is an anti-pattern: the interface constrains the implementation, prohibiting possibly later returning a temporary (an expression). * For LLVersionInfo specifically, 'const S32' return type was replaced with simple 'S32'. 'const' is just noise in that usage. * Simple member initialization (e.g. the original initializer expressions for static variables) can be done with member{ value } initializers (no examples here though). * Delete initClass() method. * LLSingleton's forté is of course lazy initialization. It might work to simply delete any calls to initClass(). But if there are side effects that must happen at that moment, replace LLSomeSingleton::initClass() with (void)LLSomeSingleton::instance(); * Most initClass() initialization can be done in the constructor, as would normally be the case. * Initialization that might cause a circular LLSingleton reference should be moved to initSingleton(). Override 'void initSingleton();' should be private. * For LLVersionInfo specifically, certain initialization that used to be lazily performed was made unconditional, due to its low cost. * For LLVersionInfo specifically, certain initialization involved calling methods that have become non-static. This was moved to initSingleton() because, in a constructor body, 'this' does not yet point to the enclosing class. * Delete cleanupClass() method. * There is already a generic LLSingletonBase::deleteAll() call in LLAppViewer::cleanup(). It might work to let this new LLSingleton be cleaned up with all the rest. But if there are side effects that must happen at that moment, replace LLSomeSingleton::cleanupClass() with LLSomeSingleton::deleteSingleton(). That said, much of the benefit of converting to LLSingleton is deleteAll()'s guarantee that cross-LLSingleton dependencies will be properly honored: we're trying to migrate the code base away from the present fragile manual cleanup sequence. * Most cleanupClass() cleanup can be done in the destructor, as would normally be the case. * Cleanup that might throw an exception should be moved to cleanupSingleton(). Override 'void cleanupSingleton();' should be private. * Within LLSomeSingleton methods, remove any existing LLSomeSingleton::methodName() qualification: simple methodName() is better. * In the rest of the code base, convert most LLSomeSingleton::methodName() references to LLSomeSingleton::instance().methodName(). (Prefer instance() to getInstance() because a reference does not admit the possibility of NULL.) * Of course, LLSomeSingleton::ENUM_VALUE can remain unchanged. In general, for many successive references to an LLSingleton instance, it can be useful to capture the instance() as in: auto& versionInfo{LLVersionInfo::instance()}; // ... versionInfo.getVersion() ... We did not do that here only to simplify the code review. The STRINGIZE(expression) macro encapsulates: std::ostringstream out; out << expression; return out.str(); We used that in a couple places. For LLVersionInfo specifically, lllogininstance_test.cpp used to dummy out a couple specific static methods. It's harder to dummy out LLSingleton::instance() references, so we add the real class to that test.
Nat Goodspeed authoredThis changeset is meant to exemplify how to convert a "namespace" class whose methods are static -- and whose data are module-static -- to an LLSingleton. LLVersionInfo has no initClass() or cleanupClass() methods, but the general idea is the same. * Derive the class from LLSingleton<T>: class LLSomeSingleton: public LLSingleton<LLSomeSingleton> { ... }; * Add LLSINGLETON(LLSomeSingleton); in the private section of the class. This usage implies a separate LLSomeSingleton::LLSomeSingleton() definition, as described in indra/llcommon/llsingleton.h. * Move module-scope data in the .cpp file to non-static class members. Change any sVariableName to mVariableName to avoid being outright misleading. * Make static class methods non-static. Remove '//static' comments from method definitions as needed. * For LLVersionInfo specifically, the 'const std::string&' return type was replaced with 'std::string'. Returning a reference to a static or a member, const or otherwise, is an anti-pattern: the interface constrains the implementation, prohibiting possibly later returning a temporary (an expression). * For LLVersionInfo specifically, 'const S32' return type was replaced with simple 'S32'. 'const' is just noise in that usage. * Simple member initialization (e.g. the original initializer expressions for static variables) can be done with member{ value } initializers (no examples here though). * Delete initClass() method. * LLSingleton's forté is of course lazy initialization. It might work to simply delete any calls to initClass(). But if there are side effects that must happen at that moment, replace LLSomeSingleton::initClass() with (void)LLSomeSingleton::instance(); * Most initClass() initialization can be done in the constructor, as would normally be the case. * Initialization that might cause a circular LLSingleton reference should be moved to initSingleton(). Override 'void initSingleton();' should be private. * For LLVersionInfo specifically, certain initialization that used to be lazily performed was made unconditional, due to its low cost. * For LLVersionInfo specifically, certain initialization involved calling methods that have become non-static. This was moved to initSingleton() because, in a constructor body, 'this' does not yet point to the enclosing class. * Delete cleanupClass() method. * There is already a generic LLSingletonBase::deleteAll() call in LLAppViewer::cleanup(). It might work to let this new LLSingleton be cleaned up with all the rest. But if there are side effects that must happen at that moment, replace LLSomeSingleton::cleanupClass() with LLSomeSingleton::deleteSingleton(). That said, much of the benefit of converting to LLSingleton is deleteAll()'s guarantee that cross-LLSingleton dependencies will be properly honored: we're trying to migrate the code base away from the present fragile manual cleanup sequence. * Most cleanupClass() cleanup can be done in the destructor, as would normally be the case. * Cleanup that might throw an exception should be moved to cleanupSingleton(). Override 'void cleanupSingleton();' should be private. * Within LLSomeSingleton methods, remove any existing LLSomeSingleton::methodName() qualification: simple methodName() is better. * In the rest of the code base, convert most LLSomeSingleton::methodName() references to LLSomeSingleton::instance().methodName(). (Prefer instance() to getInstance() because a reference does not admit the possibility of NULL.) * Of course, LLSomeSingleton::ENUM_VALUE can remain unchanged. In general, for many successive references to an LLSingleton instance, it can be useful to capture the instance() as in: auto& versionInfo{LLVersionInfo::instance()}; // ... versionInfo.getVersion() ... We did not do that here only to simplify the code review. The STRINGIZE(expression) macro encapsulates: std::ostringstream out; out << expression; return out.str(); We used that in a couple places. For LLVersionInfo specifically, lllogininstance_test.cpp used to dummy out a couple specific static methods. It's harder to dummy out LLSingleton::instance() references, so we add the real class to that test.
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
llcurrencyuimanager.cpp 14.67 KiB
/**
* @file llcurrencyuimanager.cpp
* @brief LLCurrencyUIManager class implementation
*
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, 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 "lluictrlfactory.h"
#include "lltextbox.h"
#include "lllineeditor.h"
#include "llresmgr.h" // for LLLocale
#include "lltrans.h"
#include "llviewercontrol.h"
#include "llversioninfo.h"
#include "llcurrencyuimanager.h"
// viewer includes
#include "llagent.h"
#include "llconfirmationmanager.h"
#include "llframetimer.h"
#include "lllineeditor.h"
#include "llviewchildren.h"
#include "llxmlrpctransaction.h"
#include "llviewernetwork.h"
#include "llpanel.h"
const F64 CURRENCY_ESTIMATE_FREQUENCY = 2.0;
// how long of a pause in typing a currency buy amount before an
// esimate is fetched from the server
class LLCurrencyUIManager::Impl
{
public:
Impl(LLPanel& dialog);
virtual ~Impl();
LLPanel& mPanel;
bool mHidden;
bool mError;
std::string mErrorMessage;
std::string mErrorURI;
std::string mZeroMessage;
// user's choices
S32 mUserCurrencyBuy;
bool mUserEnteredCurrencyBuy;
// from website
// pre-viewer 2.0, the server returned estimates as an
// integer US cents value, e.g., "1000" for $10.00
// post-viewer 2.0, the server may also return estimates
// as a string with currency embedded, e.g., "10.00 Euros"
bool mUSDCurrencyEstimated;
S32 mUSDCurrencyEstimatedCost;
bool mLocalCurrencyEstimated;
std::string mLocalCurrencyEstimatedCost;
bool mSupportsInternationalBilling;
std::string mSiteConfirm;
bool mBought;
enum TransactionType
{
TransactionNone,
TransactionCurrency,
TransactionBuy
};
TransactionType mTransactionType;
LLXMLRPCTransaction* mTransaction;
bool mCurrencyChanged;
LLFrameTimer mCurrencyKeyTimer;
void updateCurrencyInfo();
void finishCurrencyInfo();
void startCurrencyBuy(const std::string& password);
void finishCurrencyBuy();
void clearEstimate();
bool hasEstimate() const;
std::string getLocalEstimate() const;
void startTransaction(TransactionType type,
const char* method, LLXMLRPCValue params);
bool checkTransaction();
// return true if update needed
void setError(const std::string& message, const std::string& uri);
void clearError();
bool considerUpdateCurrency();
// return true if update needed
void currencyKey(S32);
static void onCurrencyKey(LLLineEditor* caller, void* data);
void prepare();
void updateUI();
};
// is potentially not fully constructed.
LLCurrencyUIManager::Impl::Impl(LLPanel& dialog)
: mPanel(dialog),
mHidden(false),
mError(false),
mUserCurrencyBuy(2000), // note, this is a default, real value set in llfloaterbuycurrency.cpp
mUserEnteredCurrencyBuy(false),
mSupportsInternationalBilling(false),
mBought(false),
mTransactionType(TransactionNone), mTransaction(0),
mCurrencyChanged(false)
{
clearEstimate();
}
LLCurrencyUIManager::Impl::~Impl()
{
delete mTransaction;
}
void LLCurrencyUIManager::Impl::updateCurrencyInfo()
{
clearEstimate();
mBought = false;
mCurrencyChanged = false;
if (mUserCurrencyBuy == 0)
{
mLocalCurrencyEstimated = true;
return;
}
LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct();
keywordArgs.appendString("agentId", gAgent.getID().asString());
keywordArgs.appendString(
"secureSessionId",
gAgent.getSecureSessionID().asString());
keywordArgs.appendString("language", LLUI::getLanguage());
keywordArgs.appendInt("currencyBuy", mUserCurrencyBuy);
keywordArgs.appendString("viewerChannel", LLVersionInfo::instance().getChannel());
keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::instance().getMajor());
keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::instance().getMinor());
keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::instance().getPatch());
keywordArgs.appendInt("viewerBuildVersion", LLVersionInfo::instance().getBuild());
LLXMLRPCValue params = LLXMLRPCValue::createArray();
params.append(keywordArgs);
startTransaction(TransactionCurrency, "getCurrencyQuote", params);
}
void LLCurrencyUIManager::Impl::finishCurrencyInfo()
{
LLXMLRPCValue result = mTransaction->responseValue();
bool success = result["success"].asBool();
if (!success)
{
setError(
result["errorMessage"].asString(),
result["errorURI"].asString()
);
return;
}
LLXMLRPCValue currency = result["currency"];
// old XML-RPC server: estimatedCost = value in US cents
mUSDCurrencyEstimated = currency["estimatedCost"].isValid();
if (mUSDCurrencyEstimated)
{
mUSDCurrencyEstimatedCost = currency["estimatedCost"].asInt();
}
// newer XML-RPC server: estimatedLocalCost = local currency string
mLocalCurrencyEstimated = currency["estimatedLocalCost"].isValid();
if (mLocalCurrencyEstimated)
{
mLocalCurrencyEstimatedCost = currency["estimatedLocalCost"].asString();
mSupportsInternationalBilling = true;
}
S32 newCurrencyBuy = currency["currencyBuy"].asInt();
if (newCurrencyBuy != mUserCurrencyBuy)
{
mUserCurrencyBuy = newCurrencyBuy;
mUserEnteredCurrencyBuy = false;
}
mSiteConfirm = result["confirm"].asString();
}
void LLCurrencyUIManager::Impl::startCurrencyBuy(const std::string& password)
{
LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct();
keywordArgs.appendString("agentId", gAgent.getID().asString());
keywordArgs.appendString(
"secureSessionId",
gAgent.getSecureSessionID().asString());
keywordArgs.appendString("language", LLUI::getLanguage());
keywordArgs.appendInt("currencyBuy", mUserCurrencyBuy);
if (mUSDCurrencyEstimated)
{
keywordArgs.appendInt("estimatedCost", mUSDCurrencyEstimatedCost);
}
if (mLocalCurrencyEstimated)
{
keywordArgs.appendString("estimatedLocalCost", mLocalCurrencyEstimatedCost);
}
keywordArgs.appendString("confirm", mSiteConfirm);
if (!password.empty())
{
keywordArgs.appendString("password", password);
}
keywordArgs.appendString("viewerChannel", LLVersionInfo::instance().getChannel());
keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::instance().getMajor());
keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::instance().getMinor());
keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::instance().getPatch());
keywordArgs.appendInt("viewerBuildVersion", LLVersionInfo::instance().getBuild());
LLXMLRPCValue params = LLXMLRPCValue::createArray();
params.append(keywordArgs);
startTransaction(TransactionBuy, "buyCurrency", params);
clearEstimate();
mCurrencyChanged = false;
}
void LLCurrencyUIManager::Impl::finishCurrencyBuy()
{
LLXMLRPCValue result = mTransaction->responseValue();
bool success = result["success"].asBool();
if (!success)
{
setError(
result["errorMessage"].asString(),
result["errorURI"].asString()
);
}
else
{
mUserCurrencyBuy = 0;
mUserEnteredCurrencyBuy = false;
mBought = true;
}
}
void LLCurrencyUIManager::Impl::startTransaction(TransactionType type,
const char* method, LLXMLRPCValue params)
{
static std::string transactionURI;
if (transactionURI.empty())
{
transactionURI = LLGridManager::getInstance()->getHelperURI() + "currency.php";
}
delete mTransaction;
mTransactionType = type;
mTransaction = new LLXMLRPCTransaction(
transactionURI,
method,
params,
false /* don't use gzip */
);
clearError();
}
void LLCurrencyUIManager::Impl::clearEstimate()
{
mUSDCurrencyEstimated = false;
mUSDCurrencyEstimatedCost = 0;
mLocalCurrencyEstimated = false;
mLocalCurrencyEstimatedCost = "0";
}
bool LLCurrencyUIManager::Impl::hasEstimate() const
{
return (mUSDCurrencyEstimated || mLocalCurrencyEstimated);
}
std::string LLCurrencyUIManager::Impl::getLocalEstimate() const
{
if (mLocalCurrencyEstimated)
{
// we have the new-style local currency string
return mLocalCurrencyEstimatedCost;
}
if (mUSDCurrencyEstimated)
{
// we have the old-style USD-specific value
LLStringUtil::format_map_t args;
{
LLLocale locale_override(LLStringUtil::getLocale());
args["[AMOUNT]"] = llformat("%#.2f", mUSDCurrencyEstimatedCost / 100.0);
}
return LLTrans::getString("LocalEstimateUSD", args);
}
return "";
}
bool LLCurrencyUIManager::Impl::checkTransaction()
{
if (!mTransaction)
{
return false;
}
if (!mTransaction->process())
{
return false;
}
if (mTransaction->status(NULL) != LLXMLRPCTransaction::StatusComplete)
{
setError(mTransaction->statusMessage(), mTransaction->statusURI());
}
else {
switch (mTransactionType)
{
case TransactionCurrency: finishCurrencyInfo(); break;
case TransactionBuy: finishCurrencyBuy(); break;
default: ;
}
}
delete mTransaction;
mTransaction = NULL;
mTransactionType = TransactionNone;
return true;
}
void LLCurrencyUIManager::Impl::setError(
const std::string& message, const std::string& uri)
{
mError = true;
mErrorMessage = message;
mErrorURI = uri;
}
void LLCurrencyUIManager::Impl::clearError()
{
mError = false;
mErrorMessage.clear();
mErrorURI.clear();
}
bool LLCurrencyUIManager::Impl::considerUpdateCurrency()
{
if (mCurrencyChanged
&& !mTransaction
&& mCurrencyKeyTimer.getElapsedTimeF32() >= CURRENCY_ESTIMATE_FREQUENCY)
{
updateCurrencyInfo();
return true;
}
return false;
}
void LLCurrencyUIManager::Impl::currencyKey(S32 value)
{
mUserEnteredCurrencyBuy = true;
mCurrencyKeyTimer.reset();
if (mUserCurrencyBuy == value)
{
return;
}
mUserCurrencyBuy = value;
if (hasEstimate()) {
clearEstimate();
//cannot just simply refresh the whole UI, as the edit field will
// get reset and the cursor will change...
mPanel.getChildView("currency_est")->setVisible(FALSE);
mPanel.getChildView("getting_data")->setVisible(TRUE);
}
mCurrencyChanged = true;
}
// static
void LLCurrencyUIManager::Impl::onCurrencyKey(
LLLineEditor* caller, void* data)
{
S32 value = atoi(caller->getText().c_str());
LLCurrencyUIManager::Impl* self = (LLCurrencyUIManager::Impl*)data;
self->currencyKey(value);
}
void LLCurrencyUIManager::Impl::prepare()
{
LLLineEditor* lindenAmount = mPanel.getChild<LLLineEditor>("currency_amt");
if (lindenAmount)
{
lindenAmount->setPrevalidate(LLTextValidate::validateNonNegativeS32);
lindenAmount->setKeystrokeCallback(onCurrencyKey, this);
}
}
void LLCurrencyUIManager::Impl::updateUI()
{
if (mHidden)
{
mPanel.getChildView("currency_action")->setVisible(FALSE);
mPanel.getChildView("currency_amt")->setVisible(FALSE);
mPanel.getChildView("currency_est")->setVisible(FALSE);
return;
}
mPanel.getChildView("currency_action")->setVisible(TRUE);
LLLineEditor* lindenAmount = mPanel.getChild<LLLineEditor>("currency_amt");
if (lindenAmount)
{
lindenAmount->setVisible(true);
lindenAmount->setLabel(mZeroMessage);
if (!mUserEnteredCurrencyBuy)
{
if (!mZeroMessage.empty() && mUserCurrencyBuy == 0)
{
lindenAmount->setText(LLStringUtil::null);
}
else
{
lindenAmount->setText(llformat("%d", mUserCurrencyBuy));
}
lindenAmount->selectAll();
}
}
mPanel.getChild<LLUICtrl>("currency_est")->setTextArg("[LOCALAMOUNT]", getLocalEstimate());
mPanel.getChildView("currency_est")->setVisible( hasEstimate() && mUserCurrencyBuy > 0);
mPanel.getChildView("currency_links")->setVisible( mSupportsInternationalBilling);
mPanel.getChildView("exchange_rate_note")->setVisible( mSupportsInternationalBilling);
if (mPanel.getChildView("buy_btn")->getEnabled()
||mPanel.getChildView("currency_est")->getVisible()
|| mPanel.getChildView("error_web")->getVisible())
{
mPanel.getChildView("getting_data")->setVisible(FALSE);
}
}
LLCurrencyUIManager::LLCurrencyUIManager(LLPanel& dialog)
: impl(* new Impl(dialog))
{
}
LLCurrencyUIManager::~LLCurrencyUIManager()
{
delete &impl;
}
void LLCurrencyUIManager::setAmount(int amount, bool noEstimate)
{
impl.mUserCurrencyBuy = amount;
impl.mUserEnteredCurrencyBuy = false;
impl.updateUI();
impl.mCurrencyChanged = !noEstimate;
}
int LLCurrencyUIManager::getAmount()
{
return impl.mUserCurrencyBuy;
}
void LLCurrencyUIManager::setZeroMessage(const std::string& message)
{
impl.mZeroMessage = message;
}
void LLCurrencyUIManager::setUSDEstimate(int amount)
{
impl.mUSDCurrencyEstimatedCost = amount;
impl.mUSDCurrencyEstimated = true;
impl.updateUI();
impl.mCurrencyChanged = false;
}
int LLCurrencyUIManager::getUSDEstimate()
{
return impl.mUSDCurrencyEstimated ? impl.mUSDCurrencyEstimatedCost : 0;
}
void LLCurrencyUIManager::setLocalEstimate(const std::string &amount)
{
impl.mLocalCurrencyEstimatedCost = amount;
impl.mLocalCurrencyEstimated = true;
impl.updateUI();
impl.mCurrencyChanged = false;
}
std::string LLCurrencyUIManager::getLocalEstimate() const
{
return impl.getLocalEstimate();
}
void LLCurrencyUIManager::prepare()
{
impl.prepare();
}
void LLCurrencyUIManager::updateUI(bool show)
{
impl.mHidden = !show;
impl.updateUI();
}
bool LLCurrencyUIManager::process()
{
bool changed = false;
changed |= impl.checkTransaction();
changed |= impl.considerUpdateCurrency();
return changed;
}
void LLCurrencyUIManager::buy(const std::string& buy_msg)
{
if (!canBuy())
{
return;
}
LLUIString msg = buy_msg;
msg.setArg("[LINDENS]", llformat("%d", impl.mUserCurrencyBuy));
msg.setArg("[LOCALAMOUNT]", getLocalEstimate());
LLConfirmationManager::confirm(impl.mSiteConfirm,
msg,
impl,
&LLCurrencyUIManager::Impl::startCurrencyBuy);
}
bool LLCurrencyUIManager::inProcess()
{
return impl.mTransactionType != Impl::TransactionNone;
}
bool LLCurrencyUIManager::canCancel()
{
return impl.mTransactionType != Impl::TransactionBuy;
}
bool LLCurrencyUIManager::canBuy()
{
return impl.mTransactionType == Impl::TransactionNone
&& impl.hasEstimate()
&& impl.mUserCurrencyBuy > 0;
}
bool LLCurrencyUIManager::buying()
{
return impl.mTransactionType == Impl::TransactionBuy;
}
bool LLCurrencyUIManager::bought()
{
return impl.mBought;
}
bool LLCurrencyUIManager::hasError()
{
return impl.mError;
}
std::string LLCurrencyUIManager::errorMessage()
{
return impl.mErrorMessage;
}
std::string LLCurrencyUIManager::errorURI()
{
return impl.mErrorURI;
}