diff --git a/src/addresscombo.cpp b/src/addresscombo.cpp index f7cbeee..5ba4955 100644 --- a/src/addresscombo.cpp +++ b/src/addresscombo.cpp @@ -24,16 +24,16 @@ void AddressCombo::setCurrentText(const QString& text) { } } -void AddressCombo::addItem(const QString& text, double bal) { +void AddressCombo::addItem(const QString& text, CAmount bal) { QString txt = AddressBook::addLabelToAddress(text); - if (bal > 0) - txt = txt % "(" % Settings::getZECDisplayFormat(bal) % ")"; + if (bal.toqint64() > 0) + txt = txt % "(" % bal.toDecimalZECString() % ")"; QComboBox::addItem(txt); } -void AddressCombo::insertItem(int index, const QString& text, double bal) { +void AddressCombo::insertItem(int index, const QString& text, CAmount bal) { QString txt = AddressBook::addLabelToAddress(text) % - "(" % Settings::getZECDisplayFormat(bal) % ")"; + "(" % bal.toDecimalZECString() % ")"; QComboBox::insertItem(index, txt); } \ No newline at end of file diff --git a/src/addresscombo.h b/src/addresscombo.h index 8088906..ee1c8ed 100644 --- a/src/addresscombo.h +++ b/src/addresscombo.h @@ -1,6 +1,7 @@ #ifndef ADDRESSCOMBO_H #define ADDRESSCOMBO_H +#include "camount.h" #include "precompiled.h" class AddressCombo : public QComboBox @@ -12,8 +13,8 @@ public: QString itemText(int i); QString currentText(); - void addItem(const QString& itemText, double bal); - void insertItem(int index, const QString& text, double bal = 0.0); + void addItem(const QString& itemText, CAmount bal); + void insertItem(int index, const QString& text, CAmount bal = CAmount::fromqint64(0)); public slots: void setCurrentText(const QString& itemText); diff --git a/src/balancestablemodel.cpp b/src/balancestablemodel.cpp index 353e2e4..da02f78 100644 --- a/src/balancestablemodel.cpp +++ b/src/balancestablemodel.cpp @@ -1,6 +1,7 @@ #include "balancestablemodel.h" #include "addressbook.h" #include "settings.h" +#include "camount.h" BalancesTableModel::BalancesTableModel(QObject *parent) @@ -8,7 +9,7 @@ BalancesTableModel::BalancesTableModel(QObject *parent) } void BalancesTableModel::setNewData(const QList zaddrs, const QList taddrs, - const QMap balances, const QList outputs) + const QMap balances, const QList outputs) { loading = false; @@ -22,7 +23,7 @@ void BalancesTableModel::setNewData(const QList zaddrs, const QList>(); + modeldata = new QList>(); std::for_each(balances.keyBegin(), balances.keyEnd(), [=] (auto keyIt) { modeldata->push_back(std::make_tuple(keyIt, balances.value(keyIt))); }); @@ -30,13 +31,13 @@ void BalancesTableModel::setNewData(const QList zaddrs, const QListpush_back(std::make_tuple(zaddr, 0)); + modeldata->push_back(std::make_tuple(zaddr, CAmount::fromqint64(0))); } } for (auto taddr: taddrs) { if (!balances.contains(taddr)) { - modeldata->push_back(std::make_tuple(taddr, 0)); + modeldata->push_back(std::make_tuple(taddr, CAmount::fromqint64(0))); } } @@ -101,14 +102,14 @@ QVariant BalancesTableModel::data(const QModelIndex &index, int role) const if (role == Qt::DisplayRole) { switch (index.column()) { case 0: return AddressBook::addLabelToAddress(std::get<0>(modeldata->at(index.row()))); - case 1: return Settings::getZECDisplayFormat(std::get<1>(modeldata->at(index.row()))); + case 1: return std::get<1>(modeldata->at(index.row())).toDecimalZECString(); } } if(role == Qt::ToolTipRole) { switch (index.column()) { case 0: return AddressBook::addLabelToAddress(std::get<0>(modeldata->at(index.row()))); - case 1: return Settings::getUSDFromZecAmount(std::get<1>(modeldata->at(index.row()))); + case 1: return std::get<1>(modeldata->at(index.row())).toDecimalZECString(); } } diff --git a/src/balancestablemodel.h b/src/balancestablemodel.h index 6c1782b..de01eb0 100644 --- a/src/balancestablemodel.h +++ b/src/balancestablemodel.h @@ -3,6 +3,7 @@ #include "precompiled.h" #include "datamodel.h" +#include "camount.h" class BalancesTableModel : public QAbstractTableModel { @@ -10,7 +11,7 @@ public: BalancesTableModel(QObject* parent); ~BalancesTableModel(); - void setNewData(const QList zaddrs, const QList taddrs, const QMap balances, const QList outputs); + void setNewData(const QList zaddrs, const QList taddrs, const QMap balances, const QList outputs); int rowCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const; @@ -18,7 +19,7 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role) const; private: - QList>* modeldata = nullptr; + QList>* modeldata = nullptr; QList* unspentOutputs = nullptr; bool loading = true; diff --git a/src/camount.cpp b/src/camount.cpp new file mode 100644 index 0000000..c8cd723 --- /dev/null +++ b/src/camount.cpp @@ -0,0 +1,61 @@ +#include "camount.h" +#include "settings.h" + +#include "precompiled.h" + +const int NUMPLACES = 8; +const qint64 COIN = 100000000; + +double CAmount::toDecimalDouble() const { + return static_cast(this->amount) / COIN; +} + +QString CAmount::toDecimalString() const { + if (amount < 0) { + CAmount negative(-1 * this->amount); + return "-" + negative.toDecimalString(); + } + + int wholePart = amount / COIN; + int decimalPart = amount % COIN; + + QString r = QString::number(wholePart); + if (decimalPart > 0) { + QString decimalPartStr = QString::number(decimalPart); + QString leadingZeros = QString("0").repeated(NUMPLACES - decimalPartStr.length()); + + r = r + "." + leadingZeros + decimalPartStr; + } + + return r; +} + +QString CAmount::toDecimalUSDString() const { + double dblAmount = static_cast(this->amount) / COIN; + double price = Settings::getInstance()->getZECPrice(); + + return "$" + QLocale(QLocale::English).toString(dblAmount*price, 'f', 2); +} + +QString CAmount::toDecimalZECString() const { + return this->toDecimalString() % " " % Settings::getTokenName(); +} + +QString CAmount::toDecimalZECUSDString() const { + auto usdString = this->toDecimalUSDString(); + if (!usdString.isEmpty()) + return this->toDecimalZECString() % " (" % usdString % ")"; + else + return this->toDecimalZECString(); +} + +CAmount CAmount::fromDecimalString(QString decimalString) { + auto amtParts = decimalString.split("."); + qint64 r = amtParts[0].toULongLong() * COIN; + if (amtParts.length() == 2) { + auto trailingZeros = QString("0").repeated(NUMPLACES - amtParts[1].length()); + r += QString(amtParts[1] + trailingZeros).toULongLong(); + } + + return CAmount(r); +} \ No newline at end of file diff --git a/src/camount.h b/src/camount.h new file mode 100644 index 0000000..d81eef0 --- /dev/null +++ b/src/camount.h @@ -0,0 +1,47 @@ +#ifndef CAMOUNT_H +#define CAMOUNT_H + + +class CAmount { +private: + CAmount(qint64 amount) { + this->amount = amount; + } + + qint64 amount; + +public: + static CAmount fromDecimalString(QString decimalString); + static CAmount fromqint64(qint64 a) { + return CAmount(a); + } + + CAmount() : amount(0) {}; + CAmount(const CAmount&) = default; + ~CAmount() = default; + + double toDecimalDouble() const; + QString toDecimalString() const; + QString toDecimalUSDString() const; + QString toDecimalZECString() const; + QString toDecimalZECUSDString() const; + qint64 toqint64() const { return amount; }; + + CAmount operator+ (const CAmount& other) const { + return CAmount(this->amount + other.amount); + } + + CAmount operator- (const CAmount& other) const { + return CAmount(this->amount - other.amount); + } + + bool operator< (const CAmount& other) const { + return this->amount < other.amount; + } + + bool operator> (const CAmount& other) const { + return this->amount > other.amount; + } +}; + +#endif // CAMOUNT_H diff --git a/src/controller.cpp b/src/controller.cpp index f46d306..4e0c53f 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -3,6 +3,7 @@ #include "addressbook.h" #include "settings.h" #include "version.h" +#include "camount.h" #include "websockets.h" using json = nlohmann::json; @@ -91,18 +92,12 @@ void Controller::fillTxJsonParams(json& allRecepients, Tx tx) { // Construct the JSON params json rec = json::object(); rec["address"] = toAddr.addr.toStdString(); - rec["amount"] = toAddr.amount; + rec["amount"] = toAddr.amount.toqint64(); if (Settings::isZAddress(toAddr.addr) && !toAddr.memo.trimmed().isEmpty()) rec["memo"] = toAddr.memo.toStdString(); allRecepients.push_back(rec); } - - // // Add fees if custom fees are allowed. - // if (Settings::getInstance()->getAllowCustomFees()) { - // params.push_back(1); // minconf - // params.push_back(tx.fee); - // } } @@ -115,7 +110,7 @@ void Controller::noConnection() { main->ui->statusBar->showMessage(QObject::tr("No Connection"), 1000); // Clear balances table. - QMap emptyBalances; + QMap emptyBalances; QList emptyOutputs; QList emptyAddresses; balancesTableModel->setNewData(emptyAddresses, emptyAddresses, emptyBalances, emptyOutputs); @@ -254,20 +249,21 @@ void Controller::updateUI(bool anyUnconfirmed) { }; // Function to process reply of the listunspent and z_listunspent API calls, used below. -void Controller::processUnspent(const json& reply, QMap* balancesMap, QList* unspentOutputs) { +void Controller::processUnspent(const json& reply, QMap* balancesMap, QList* unspentOutputs) { auto processFn = [=](const json& array) { for (auto& it : array) { QString qsAddr = QString::fromStdString(it["address"]); int block = it["created_in_block"].get(); QString txid = QString::fromStdString(it["created_in_txid"]); - QString amount = Settings::getDecimalString(it["value"].get()); + CAmount amount = CAmount::fromqint64(it["value"].get()); bool spendable = it["unconfirmed_spent"].is_null() && it["spent"].is_null(); // TODO: Wait for 4 confirmations bool pending = !it["unconfirmed_spent"].is_null(); unspentOutputs->push_back(UnspentOutput{ qsAddr, txid, amount, block, spendable, pending }); if (spendable) { - (*balancesMap)[qsAddr] = (*balancesMap)[qsAddr] + it["value"].get(); + (*balancesMap)[qsAddr] = (*balancesMap)[qsAddr] + + CAmount::fromqint64(it["value"].get()); } } }; @@ -284,26 +280,26 @@ void Controller::refreshBalances() { // 1. Get the Balances zrpc->fetchBalance([=] (json reply) { - auto balT = reply["tbalance"].get(); - auto balZ = reply["zbalance"].get(); - auto balTotal = balT + balZ; + CAmount balT = CAmount::fromqint64(reply["tbalance"].get()); + CAmount balZ = CAmount::fromqint64(reply["zbalance"].get()); + CAmount balTotal = balT + balZ; AppDataModel::getInstance()->setBalances(balT, balZ); - ui->balSheilded ->setText(Settings::getZECDisplayFormat(balZ)); - ui->balTransparent->setText(Settings::getZECDisplayFormat(balT)); - ui->balTotal ->setText(Settings::getZECDisplayFormat(balTotal)); - + ui->balSheilded ->setText(balZ.toDecimalZECString()); + ui->balTransparent->setText(balT.toDecimalZECString()); + ui->balTotal ->setText(balTotal.toDecimalZECString()); - ui->balSheilded ->setToolTip(Settings::getZECDisplayFormat(balZ)); - ui->balTransparent->setToolTip(Settings::getZECDisplayFormat(balT)); - ui->balTotal ->setToolTip(Settings::getZECDisplayFormat(balTotal)); + auto price = Settings::getInstance()->getZECPrice(); + ui->balSheilded ->setToolTip(balZ.toDecimalZECUSDString()); + ui->balTransparent->setToolTip(balT.toDecimalZECUSDString()); + ui->balTotal ->setToolTip(balTotal.toDecimalZECUSDString()); }); // 2. Get the UTXOs // First, create a new UTXO list. It will be replacing the existing list when everything is processed. auto newUnspentOutputs = new QList(); - auto newBalances = new QMap(); + auto newBalances = new QMap(); // Call the Transparent and Z unspent APIs serially and then, once they're done, update the UI zrpc->fetchUnspent([=] (json reply) { @@ -334,7 +330,7 @@ void Controller::refreshTransactions() { for (auto& it : reply.get()) { QString address; - qint64 total_amount = 0; + CAmount total_amount = CAmount::fromqint64(0); QList items; // First, check if there's outgoing metadata @@ -342,7 +338,9 @@ void Controller::refreshTransactions() { for (auto o: it["outgoing_metadata"].get()) { QString address = QString::fromStdString(o["address"]); - qint64 amount = -1 * o["value"].get(); // Sent items are -ve + + // Sent items are -ve + CAmount amount = CAmount::fromqint64(-1 * o["value"].get()); QString memo; if (!o["memo"].is_null()) { @@ -350,7 +348,7 @@ void Controller::refreshTransactions() { } items.push_back(TransactionItemDetail{address, amount, memo}); - total_amount += amount; + total_amount = total_amount + amount; } if (items.length() == 1) { @@ -374,7 +372,7 @@ void Controller::refreshTransactions() { items.push_back(TransactionItemDetail{ address, - it["amount"].get(), + CAmount::fromqint64(it["amount"].get()), "" }); diff --git a/src/controller.h b/src/controller.h index d896b62..79f8b4f 100644 --- a/src/controller.h +++ b/src/controller.h @@ -3,6 +3,7 @@ #include "precompiled.h" +#include "camount.h" #include "datamodel.h" #include "balancestablemodel.h" #include "txtablemodel.h" @@ -20,14 +21,6 @@ struct WatchedTx { std::function error; }; -struct MigrationStatus { - bool available; // Whether the underlying zcashd supports migration? - bool enabled; - QString saplingAddress; - double unmigrated; - double migrated; - QList txids; -}; class Controller { @@ -78,7 +71,7 @@ private: void refreshTransactions(); - void processUnspent (const json& reply, QMap* newBalances, QList* newUnspentOutputs); + void processUnspent (const json& reply, QMap* newBalances, QList* newUnspentOutputs); void updateUI (bool anyUnconfirmed); void getInfoThenRefresh(bool force); diff --git a/src/datamodel.cpp b/src/datamodel.cpp index 60990c4..e043466 100644 --- a/src/datamodel.cpp +++ b/src/datamodel.cpp @@ -7,7 +7,7 @@ DataModel::DataModel() { QWriteLocker locker(lock); utxos = new QList(); - balances = new QMap(); + balances = new QMap(); usedAddresses = new QMap(); zaddresses = new QList(); taddresses = new QList(); @@ -44,7 +44,7 @@ void DataModel::replaceTaddresses(QList* newT) { taddresses = newT; } -void DataModel::replaceBalances(QMap* newBalances) { +void DataModel::replaceBalances(QMap* newBalances) { QWriteLocker locker(lock); Q_ASSERT(newBalances); diff --git a/src/datamodel.h b/src/datamodel.h index 00ad43c..1068d41 100644 --- a/src/datamodel.h +++ b/src/datamodel.h @@ -1,13 +1,14 @@ #ifndef DATAMODEL_H #define DATAMODEL_H +#include "camount.h" #include "precompiled.h" struct UnspentOutput { QString address; QString txid; - QString amount; + CAmount amount; int blockCreated; bool spendable; bool pending; @@ -19,7 +20,7 @@ class DataModel { public: void replaceZaddresses(QList* newZ); void replaceTaddresses(QList* newZ); - void replaceBalances(QMap* newBalances); + void replaceBalances(QMap* newBalances); void replaceUTXOs(QList* utxos); void markAddressUsed(QString address); @@ -30,7 +31,7 @@ public: const QList getAllZAddresses() { QReadLocker locker(lock); return *zaddresses; } const QList getAllTAddresses() { QReadLocker locker(lock); return *taddresses; } const QList getUTXOs() { QReadLocker locker(lock); return *utxos; } - const QMap getAllBalances() { QReadLocker locker(lock); return *balances; } + const QMap getAllBalances() { QReadLocker locker(lock); return *balances; } const QMap getUsedAddresses() { QReadLocker locker(lock); return *usedAddresses; } @@ -40,7 +41,7 @@ private: int latestBlock; QList* utxos = nullptr; - QMap* balances = nullptr; + QMap* balances = nullptr; QMap* usedAddresses = nullptr; QList* zaddresses = nullptr; QList* taddresses = nullptr; diff --git a/src/liteinterface.h b/src/liteinterface.h index 062c321..67cc782 100644 --- a/src/liteinterface.h +++ b/src/liteinterface.h @@ -3,6 +3,7 @@ #include "precompiled.h" +#include "camount.h" #include "connection.h" using json = nlohmann::json; @@ -11,7 +12,7 @@ using json = nlohmann::json; // into a struct with address, amount, memo struct TransactionItemDetail { QString address; - qint64 amount; + CAmount amount; QString memo; }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fbcfadc..a8ed3ce 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -438,7 +438,7 @@ void MainWindow::payZcashURI(QString uri, QString myAddr) { ui->Address1->setText(paymentInfo.addr); ui->Address1->setCursorPosition(0); - ui->Amount1->setText(Settings::getDecimalString(paymentInfo.amt.toDouble())); + ui->Amount1->setText(paymentInfo.amt); ui->MemoTxt1->setText(paymentInfo.memo); // And switch to the send tab. @@ -982,7 +982,7 @@ void MainWindow::setupReceiveTab() { } ui->rcvLabel->setText(label); - ui->rcvBal->setText(Settings::getZECUSDDisplayFormat(rpc->getModel()->getAllBalances().value(addr))); + ui->rcvBal->setText(rpc->getModel()->getAllBalances().value(addr).toDecimalZECUSDString()); ui->txtReceive->setPlainText(addr); ui->qrcodeDisplay->setQrcodeString(addr); if (rpc->getModel()->getUsedAddresses().value(addr, false)) { @@ -1073,7 +1073,7 @@ void MainWindow::updateTAddrCombo(bool checked) { // If the address is in the address book, add it. if (labels.contains(taddr) && !addrs.contains(taddr)) { addrs.insert(taddr); - ui->listReceiveAddresses->addItem(taddr, 0); + ui->listReceiveAddresses->addItem(taddr, CAmount::fromqint64(0)); } }); @@ -1084,7 +1084,7 @@ void MainWindow::updateTAddrCombo(bool checked) { if (!addrs.contains(addr)) { addrs.insert(addr); // Balance is zero since it has not been previously added - ui->listReceiveAddresses->addItem(addr, 0); + ui->listReceiveAddresses->addItem(addr, CAmount::fromqint64(0)); } } @@ -1101,7 +1101,7 @@ void MainWindow::updateTAddrCombo(bool checked) { // 5. Add a last, disabled item if there are remaining items if (allTaddrs.size() > addrs.size()) { auto num = QString::number(allTaddrs.size() - addrs.size()); - ui->listReceiveAddresses->addItem("-- " + num + " more --", 0); + ui->listReceiveAddresses->addItem("-- " + num + " more --", CAmount::fromqint64(0)); QStandardItemModel* model = qobject_cast(ui->listReceiveAddresses->model()); QStandardItem* item = model->findItems("--", Qt::MatchStartsWith)[0]; diff --git a/src/mainwindow.h b/src/mainwindow.h index 6250cea..6b1aafd 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -17,7 +17,7 @@ using json = nlohmann::json; // Struct used to hold destination info when sending a Tx. struct ToFields { QString addr; - qint64 amount; + CAmount amount; QString memo; }; @@ -25,7 +25,7 @@ struct ToFields { struct Tx { QString fromAddr; QList toAddrs; - qint64 fee; + CAmount fee; }; namespace Ui { diff --git a/src/requestdialog.cpp b/src/requestdialog.cpp index 352a757..cd88309 100644 --- a/src/requestdialog.cpp +++ b/src/requestdialog.cpp @@ -73,7 +73,8 @@ void RequestDialog::showPaymentConfirmation(MainWindow* main, QString paymentURI req.txtFrom->setText(payInfo.addr); req.txtMemo->setPlainText(payInfo.memo); req.txtAmount->setText(payInfo.amt); - req.txtAmountUSD->setText(Settings::getUSDFromZecAmount(req.txtAmount->text().toDouble())); + CAmount amount = CAmount::fromDecimalString(req.txtAmount->text()); + req.txtAmountUSD->setText(amount.toDecimalUSDString()); req.buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Pay")); @@ -112,9 +113,11 @@ void RequestDialog::showRequestZcash(MainWindow* main) { // Amount textbox req.txtAmount->setValidator(main->getAmountValidator()); QObject::connect(req.txtAmount, &QLineEdit::textChanged, [=] (auto text) { - req.txtAmountUSD->setText(Settings::getUSDFromZecAmount(text.toDouble())); + CAmount amount = CAmount::fromDecimalString(text); + req.txtAmountUSD->setText(amount.toDecimalUSDString()); }); - req.txtAmountUSD->setText(Settings::getUSDFromZecAmount(req.txtAmount->text().toDouble())); + CAmount amount = CAmount::fromDecimalString(req.txtAmount->text()); + req.txtAmountUSD->setText(amount.toDecimalUSDString()); req.txtMemo->setAcceptButton(req.buttonBox->button(QDialogButtonBox::Ok)); req.txtMemo->setLenDisplayLabel(req.lblMemoLen); @@ -124,8 +127,9 @@ void RequestDialog::showRequestZcash(MainWindow* main) { if (d.exec() == QDialog::Accepted) { // Construct a zcash Payment URI with the data and pay it immediately. + CAmount amount = CAmount::fromDecimalString(req.txtAmount->text()); QString memoURI = "zcash:" + req.cmbMyAddress->currentText() - + "?amt=" + Settings::getDecimalString(req.txtAmount->text().toDouble()) + + "?amt=" + amount.toDecimalString() + "&memo=" + QUrl::toPercentEncoding(req.txtMemo->toPlainText()); QString sendURI = "zcash:" + AddressBook::addressFromAddressLabel(req.txtFrom->text()) diff --git a/src/sendtab.cpp b/src/sendtab.cpp index 4187993..5547554 100644 --- a/src/sendtab.cpp +++ b/src/sendtab.cpp @@ -61,15 +61,17 @@ void MainWindow::setupSendTab() { // Fee amount changed ui->minerFeeAmt->setReadOnly(true); QObject::connect(ui->minerFeeAmt, &QLineEdit::textChanged, [=](auto txt) { - ui->lblMinerFeeUSD->setText(Settings::getUSDFromZecAmount(txt.toDouble())); + CAmount fee = CAmount::fromDecimalString(txt); + ui->lblMinerFeeUSD->setText(fee.toDecimalUSDString()); }); - ui->minerFeeAmt->setText(Settings::getDecimalString(Settings::getMinerFee())); + ui->minerFeeAmt->setText(Settings::getMinerFee().toDecimalString()); // Set up focus enter to set fees QObject::connect(ui->tabWidget, &QTabWidget::currentChanged, [=] (int pos) { if (pos == 1) { QString txt = ui->minerFeeAmt->text(); - ui->lblMinerFeeUSD->setText(Settings::getUSDFromZecAmount(txt.toDouble())); + QString feeUSD = CAmount::fromDecimalString(txt).toDecimalUSDString(); + ui->lblMinerFeeUSD->setText(feeUSD); } }); @@ -167,67 +169,15 @@ void MainWindow::updateLabelsAutoComplete() { } void MainWindow::setDefaultPayFrom() { - auto findMax = [=] (QString startsWith) { - double max_amt = 0; - int idx = -1; - - for (int i=0; i < ui->inputsCombo->count(); i++) { - auto addr = ui->inputsCombo->itemText(i); - if (addr.startsWith(startsWith)) { - auto amt = rpc->getModel()->getAllBalances().value(addr); - if (max_amt < amt) { - max_amt = amt; - idx = i; - } - } - } - - return idx; - }; - - // By default, select the z-address with the most balance from the inputs combo - auto maxZ = findMax("z"); - if (maxZ >= 0) { - ui->inputsCombo->setCurrentIndex(maxZ); - } else { - auto maxT = findMax("t"); - maxT = maxT >= 0 ? maxT : 0; - ui->inputsCombo->setCurrentIndex(maxT); - } + }; void MainWindow::updateFromCombo() { - if (!rpc) - return; - - auto lastFromAddr = ui->inputsCombo->currentText(); - - ui->inputsCombo->clear(); - auto i = rpc->getModel()->getAllBalances().constBegin(); - - // Add all the addresses into the inputs combo box - while (i != rpc->getModel()->getAllBalances().constEnd()) { - ui->inputsCombo->addItem(i.key(), i.value()); - if (i.key() == lastFromAddr) ui->inputsCombo->setCurrentText(i.key()); - - ++i; - } - - if (lastFromAddr.isEmpty()) { - setDefaultPayFrom(); - } - else { - ui->inputsCombo->setCurrentText(lastFromAddr); - } + // delete } void MainWindow::inputComboTextChanged(int index) { - auto addr = ui->inputsCombo->itemText(index); - auto bal = rpc->getModel()->getAllBalances().value(addr); - auto balFmt = Settings::getZECDisplayFormat(bal); - - ui->sendAddressBalance->setText(balFmt); - ui->sendAddressBalanceUSD->setText(Settings::getUSDFromZecAmount(bal)); + // delete } @@ -340,7 +290,8 @@ void MainWindow::addressChanged(int itemNumber, const QString& text) { void MainWindow::amountChanged(int item, const QString& text) { auto usd = ui->sendToWidgets->findChild(QString("AmtUSD") % QString::number(item)); - usd->setText(Settings::getUSDFromZecAmount(text.toDouble())); + CAmount amt = CAmount::fromDecimalString(text); + usd->setText(amt.toDecimalUSDString()); // If there is a recurring payment, update the info there as well if (sendTxRecurringInfo != nullptr) { @@ -434,7 +385,7 @@ void MainWindow::clearSendForm() { setMemoEnabled(1, false); // Reset the fee - ui->minerFeeAmt->setText(Settings::getDecimalString(Settings::getMinerFee())); + ui->minerFeeAmt->setText(Settings::getMinerFee().toDecimalString()); // Start the deletion after the first item, since we want to keep 1 send field there all there for (int i=1; i < totalItems; i++) { @@ -461,24 +412,24 @@ void MainWindow::maxAmountChecked(int checked) { if (rpc == nullptr) return; // Calculate maximum amount - double sumAllAmounts = 0.0; + CAmount sumAllAmounts = CAmount::fromqint64(0); // Calculate all other amounts int totalItems = ui->sendToWidgets->children().size() - 2; // The last one is a spacer, so ignore that // Start counting the sum skipping the first one, because the MAX button is on the first one, and we don't // want to include it in the sum. for (int i=1; i < totalItems; i++) { auto amt = ui->sendToWidgets->findChild(QString("Amount") % QString::number(i+1)); - sumAllAmounts += amt->text().toDouble(); + sumAllAmounts = sumAllAmounts + CAmount::fromDecimalString(amt->text()); } - sumAllAmounts += Settings::getMinerFee(); + sumAllAmounts = sumAllAmounts + Settings::getMinerFee(); auto addr = ui->inputsCombo->currentText(); auto maxamount = rpc->getModel()->getAllBalances().value(addr) - sumAllAmounts; - maxamount = (maxamount < 0) ? 0 : maxamount; + maxamount = (maxamount.toqint64() < 0) ? CAmount::fromqint64(0) : maxamount; - ui->Amount1->setText(Settings::getDecimalString(maxamount)); + ui->Amount1->setText(maxamount.toDecimalString()); } else if (checked == Qt::Unchecked) { // Just remove the readonly part, don't change the content ui->Amount1->setReadOnly(false); @@ -494,7 +445,7 @@ Tx MainWindow::createTxFromSendPage() { // For each addr/amt in the sendTo tab int totalItems = ui->sendToWidgets->children().size() - 2; // The last one is a spacer, so ignore that - qint64 totalAmt = 0; + CAmount totalAmt = CAmount::fromqint64(0); for (int i=0; i < totalItems; i++) { QString addr = ui->sendToWidgets->findChild(QString("Address") % QString::number(i+1))->text().trimmed(); // Remove label if it exists @@ -506,15 +457,15 @@ Tx MainWindow::createTxFromSendPage() { } bool ok; - qint64 amt; + CAmount amt; // Make sure it parses amtStr.toDouble(&ok); if (!ok) { - amt = -1; + amt = CAmount::fromqint64(-1); } else { - amt = Settings::getAmountFromUserDecimalStr(amtStr); - totalAmt += amt; + amt = CAmount::fromDecimalString(amtStr); + totalAmt = totalAmt + amt; } QString memo = ui->sendToWidgets->findChild(QString("MemoTxt") % QString::number(i+1))->text().trimmed(); @@ -587,7 +538,7 @@ bool MainWindow::confirmTx(Tx tx, RecurringPaymentInfo* rpi) { // For each addr/amt/memo, construct the JSON and also build the confirm dialog box int row = 0; - double totalSpending = 0; + CAmount totalSpending = CAmount::fromqint64(0); for (int i=0; i < tx.toAddrs.size(); i++) { auto toAddr = tx.toAddrs[i]; @@ -605,15 +556,15 @@ bool MainWindow::confirmTx(Tx tx, RecurringPaymentInfo* rpi) { // Amount (ZEC) auto Amt = new QLabel(confirm.sendToAddrs); Amt->setObjectName(QString("Amt") % QString::number(i + 1)); - Amt->setText(Settings::getZECDisplayFormat(toAddr.amount)); + Amt->setText(toAddr.amount.toDecimalZECString()); Amt->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); confirm.gridLayout->addWidget(Amt, row, 1, 1, 1); - totalSpending += toAddr.amount; + totalSpending = totalSpending + toAddr.amount; // Amount (USD) auto AmtUSD = new QLabel(confirm.sendToAddrs); AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1)); - AmtUSD->setText(Settings::getUSDFromZecAmount(toAddr.amount)); + AmtUSD->setText(toAddr.amount.toDecimalUSDString()); AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1); @@ -655,8 +606,8 @@ bool MainWindow::confirmTx(Tx tx, RecurringPaymentInfo* rpi) { minerFee->setObjectName(QStringLiteral("minerFee")); minerFee->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); confirm.gridLayout->addWidget(minerFee, row, 1, 1, 1); - minerFee->setText(Settings::getZECDisplayFormat(tx.fee)); - totalSpending += tx.fee; + minerFee->setText(tx.fee.toDecimalZECString()); + totalSpending = totalSpending + tx.fee; auto minerFeeUSD = new QLabel(confirm.sendToAddrs); QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::Preferred); @@ -664,7 +615,7 @@ bool MainWindow::confirmTx(Tx tx, RecurringPaymentInfo* rpi) { minerFeeUSD->setObjectName(QStringLiteral("minerFeeUSD")); minerFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); confirm.gridLayout->addWidget(minerFeeUSD, row, 2, 1, 1); - minerFeeUSD->setText(Settings::getUSDFromZecAmount(tx.fee)); + minerFeeUSD->setText(tx.fee.toDecimalUSDString()); } // Recurring payment info, show only if there is exactly one destination address @@ -683,9 +634,9 @@ bool MainWindow::confirmTx(Tx tx, RecurringPaymentInfo* rpi) { confirm.sendFrom->setText(fnSplitAddressForWrap(tx.fromAddr)); confirm.sendFrom->setFont(fixedFont); QString tooltip = tr("Current balance : ") + - Settings::getZECUSDDisplayFormat(rpc->getModel()->getAllBalances().value(tx.fromAddr)); + rpc->getModel()->getAllBalances().value(tx.fromAddr).toDecimalZECUSDString(); tooltip += "\n" + tr("Balance after this Tx: ") + - Settings::getZECUSDDisplayFormat(rpc->getModel()->getAllBalances().value(tx.fromAddr) - totalSpending); + (rpc->getModel()->getAllBalances().value(tx.fromAddr) - totalSpending).toDecimalZECUSDString(); confirm.sendFrom->setToolTip(tooltip); // Show the dialog and submit it if the user confirms @@ -769,13 +720,12 @@ QString MainWindow::doSendTxValidations(Tx tx) { // This technically shouldn't be possible, but issue #62 seems to have discovered a bug // somewhere, so just add a check to make sure. - if (toAddr.amount < 0) { + if (toAddr.amount.toqint64() < 0) { return QString(tr("Amount for address '%1' is invalid!").arg(toAddr.addr)); } } - - return QString(); + return ""; } void MainWindow::cancelButton() { diff --git a/src/settings.cpp b/src/settings.cpp index d9b1183..68ddcaf 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,5 +1,6 @@ #include "mainwindow.h" #include "settings.h" +#include "camount.h" Settings* Settings::instance = nullptr; @@ -178,66 +179,6 @@ void Settings::openTxInExplorer(QString txid) { } - -QString Settings::getUSDFormat(double usdAmt) { - return "$" + QLocale(QLocale::English).toString(usdAmt, 'f', 2); -} - - -QString Settings::getUSDFromZecAmount(qint64 bal) { - return getUSDFormat(bal * Settings::getInstance()->getZECPrice()); -} - -QString Settings::getDecimalString(qint64 amt) { - if (amt < 0) { - return "-" + Settings::getDecimalString(-1 * amt); - } - - // Zcash has 8 decimal places - int places = Settings::getNumberOfDecimalPlaces(); - qint64 divider = QString("1" + QString("0").repeated(places)).toULongLong(); - - int wholePart = amt / divider; - int decimalPart = amt % divider; - - QString r = QString::number(wholePart); - if (decimalPart > 0) { - QString decimalPartStr = QString::number(decimalPart); - QString leadingZeros = QString("0").repeated(places - decimalPartStr.length()); - - r = r + "." + leadingZeros + decimalPartStr; - } - - return r; -} - -qint64 Settings::getAmountFromUserDecimalStr(QString amt) { - int places = Settings::getNumberOfDecimalPlaces(); - qint64 divider = QString("1" + QString("0").repeated(places)).toULongLong(); - - auto amtParts = amt.split("."); - qint64 r = amtParts[0].toULongLong() * divider; - if (amtParts.length() == 2) { - auto trailingZeros = QString("0").repeated(places - amtParts[1].length()); - r += QString(amtParts[1] + trailingZeros).toULongLong(); - } - - return r; -} - -QString Settings::getZECDisplayFormat(qint64 bal) { - // This is idiotic. Why doesn't QString have a way to do this? - return getDecimalString(bal) % " " % Settings::getTokenName(); -} - -QString Settings::getZECUSDDisplayFormat(qint64 bal) { - auto usdFormat = getUSDFromZecAmount(bal); - if (!usdFormat.isEmpty()) - return getZECDisplayFormat(bal) % " (" % usdFormat % ")"; - else - return getZECDisplayFormat(bal); -} - const QString Settings::txidStatusMessage = QString(QObject::tr("Tx submitted (right click to copy) txid:")); QString Settings::getTokenName() { @@ -256,8 +197,8 @@ QString Settings::getDonationAddr() { } -double Settings::getMinerFee() { - return 10000; +CAmount Settings::getMinerFee() { + return CAmount::fromqint64(10000); } bool Settings::isValidSaplingPrivateKey(QString pk) { @@ -282,7 +223,8 @@ bool Settings::isValidAddress(QString addr) { // Get a pretty string representation of this Payment URI QString Settings::paymentURIPretty(PaymentURI uri) { - return QString() + "Payment Request\n" + "Pay: " + uri.addr + "\nAmount: " + getZECDisplayFormat(uri.amt.toDouble()) + CAmount amount = CAmount::fromDecimalString(uri.amt); + return QString() + "Payment Request\n" + "Pay: " + uri.addr + "\nAmount: " + amount.toDecimalZECString() + "\nMemo:" + QUrl::fromPercentEncoding(uri.memo.toUtf8()); } diff --git a/src/settings.h b/src/settings.h index a3d1d8c..e9b14dd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -2,6 +2,7 @@ #define SETTINGS_H #include "precompiled.h" +#include "camount.h" struct Config { QString server; @@ -80,20 +81,11 @@ public: static bool isZAddress(QString addr); static bool isTAddress(QString addr); - static QString getDecimalString(qint64 zecamt); - static QString getUSDFormat(double usdAmt); - - static QString getUSDFromZecAmount(qint64 zecamt); - static QString getZECDisplayFormat(qint64 zecamt); - static QString getZECUSDDisplayFormat(qint64 bal); - - static qint64 getAmountFromUserDecimalStr(QString amt); - static QString getTokenName(); static QString getDonationAddr(); static QString getDefaultServer(); - static double getMinerFee(); + static CAmount getMinerFee(); static int getMaxMobileAppTxns() { return 30; } diff --git a/src/txtablemodel.cpp b/src/txtablemodel.cpp index af389b0..c7e10ee 100644 --- a/src/txtablemodel.cpp +++ b/src/txtablemodel.cpp @@ -105,11 +105,11 @@ bool TxTableModel::exportToCsv(QString fileName) const { case Column::Confirmations: return QString::number(dat.confirmations); case Column::Amount: { // Sum up all the amounts - qint64 total = 0; + CAmount total = CAmount::fromqint64(0); for (int i=0; i < dat.items.length(); i++) { - total += dat.items[i].amount; + total = total + dat.items[i].amount; } - return Settings::getZECDisplayFormat(total); + return total.toDecimalZECString(); } } } @@ -141,11 +141,11 @@ bool TxTableModel::exportToCsv(QString fileName) const { case Column::Confirmations: return QString("%1 Network Confirmations").arg(QString::number(dat.confirmations)); case Column::Amount: { // Sum up all the amounts - qint64 total = 0; + CAmount total = CAmount::fromqint64(0); for (int i=0; i < dat.items.length(); i++) { - total += dat.items[i].amount; + total = total + dat.items[i].amount; } - return Settings::getInstance()->getUSDFromZecAmount(total); + return total.toDecimalUSDString(); } } } @@ -237,9 +237,9 @@ QString TxTableModel::getType(int row) const { QString TxTableModel::getAmt(int row) const { auto dat = modeldata->at(row); - qint64 total = 0; + CAmount total = CAmount::fromqint64(0); for (int i=0; i < dat.items.length(); i++) { - total += dat.items[i].amount; + total = total + dat.items[i].amount; } - return Settings::getDecimalString(total); + return total.toDecimalString(); } diff --git a/src/viewalladdresses.cpp b/src/viewalladdresses.cpp index 0a7d06d..bd2ce3e 100644 --- a/src/viewalladdresses.cpp +++ b/src/viewalladdresses.cpp @@ -1,4 +1,5 @@ #include "viewalladdresses.h" +#include "camount.h" #include "settings.h" ViewAllAddressesModel::ViewAllAddressesModel(QTableView *parent, QList taddrs, Controller* rpc) @@ -22,7 +23,7 @@ QVariant ViewAllAddressesModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { switch(index.column()) { case 0: return address; - case 1: return rpc->getModel()->getAllBalances().value(address, 0.0); + case 1: return rpc->getModel()->getAllBalances().value(address, CAmount::fromqint64(0)).toDecimalString(); } } return QVariant(); diff --git a/src/websockets.cpp b/src/websockets.cpp index 5bf8edb..b8600f6 100644 --- a/src/websockets.cpp +++ b/src/websockets.cpp @@ -658,9 +658,9 @@ void AppDataServer::processSendTx(QJsonObject sendTx, MainWindow* mainwindow, st tx.fee = Settings::getMinerFee(); // Find a from address that has at least the sending amout - qint64 amt = Settings::getAmountFromUserDecimalStr(sendTx["amount"].toString()); + CAmount amt = CAmount::fromDecimalString(sendTx["amount"].toString()); auto allBalances = mainwindow->getRPC()->getModel()->getAllBalances(); - QList> bals; + QList> bals; for (auto i : allBalances.keys()) { // Filter out sprout addresses if (Settings::getInstance()->isSproutAddress(i)) @@ -669,7 +669,7 @@ void AppDataServer::processSendTx(QJsonObject sendTx, MainWindow* mainwindow, st if (allBalances.value(i) < amt) continue; - bals.append(QPair(i, allBalances.value(i))); + bals.append(QPair(i, allBalances.value(i))); } if (bals.isEmpty()) { @@ -677,7 +677,7 @@ void AppDataServer::processSendTx(QJsonObject sendTx, MainWindow* mainwindow, st return; } - std::sort(bals.begin(), bals.end(), [=](const QPaira, const QPair b) -> bool { + std::sort(bals.begin(), bals.end(), [=](const QPaira, const QPair b) -> bool { // Sort z addresses first return a.first > b.first; }); @@ -736,8 +736,8 @@ void AppDataServer::processGetInfo(QJsonObject jobj, MainWindow* mainWindow, std } // Max spendable safely from a z address and from any address - double maxZSpendable = 0; - double maxSpendable = 0; + CAmount maxZSpendable = CAmount::fromqint64(0); + CAmount maxSpendable = CAmount::fromqint64(0); for (auto a : mainWindow->getRPC()->getModel()->getAllBalances().keys()) { if (Settings::getInstance()->isSaplingAddress(a)) { if (mainWindow->getRPC()->getModel()->getAllBalances().value(a) > maxZSpendable) { @@ -751,14 +751,14 @@ void AppDataServer::processGetInfo(QJsonObject jobj, MainWindow* mainWindow, std setConnectedName(connectedName); - auto r = QJsonDocument(QJsonObject{ + auto r = QJsonDocument(QJsonObject { {"version", 1.0}, {"command", "getInfo"}, {"saplingAddress", mainWindow->getRPC()->getDefaultSaplingAddress()}, {"tAddress", mainWindow->getRPC()->getDefaultTAddress()}, - {"balance", AppDataModel::getInstance()->getTotalBalance()}, - {"maxspendable", maxSpendable}, - {"maxzspendable", maxZSpendable}, + {"balance", AppDataModel::getInstance()->getTotalBalance().toDecimalDouble()}, + {"maxspendable", maxSpendable.toDecimalDouble()}, + {"maxzspendable", maxZSpendable.toDecimalDouble()}, {"tokenName", Settings::getTokenName()}, {"zecprice", Settings::getInstance()->getZECPrice()}, {"serverversion", QString(APP_VERSION)} diff --git a/src/websockets.h b/src/websockets.h index 4758f1c..6483f42 100644 --- a/src/websockets.h +++ b/src/websockets.h @@ -3,6 +3,7 @@ #include "precompiled.h" +#include "camount.h" #include "mainwindow.h" #include "ui_mobileappconnector.h" @@ -151,11 +152,11 @@ public: return instance; } - double getTBalance() { return balTransparent; } - double getZBalance() { return balShielded; } - double getTotalBalance() { return balTotal; } + CAmount getTBalance() { return balTransparent; } + CAmount getZBalance() { return balShielded; } + CAmount getTotalBalance() { return balTotal; } - void setBalances(double transparent, double shielded) { + void setBalances(CAmount transparent, CAmount shielded) { balTransparent = transparent; balShielded = shielded; balTotal = balTransparent + balShielded; @@ -164,9 +165,9 @@ public: private: AppDataModel() = default; // Private, for singleton - double balTransparent; - double balShielded; - double balTotal; + CAmount balTransparent; + CAmount balShielded; + CAmount balTotal; QString saplingAddress; diff --git a/zecwallet-lite.pro b/zecwallet-lite.pro index 50ec246..a169c1a 100644 --- a/zecwallet-lite.pro +++ b/zecwallet-lite.pro @@ -60,7 +60,8 @@ SOURCES += \ src/viewalladdresses.cpp \ src/datamodel.cpp \ src/controller.cpp \ - src/liteinterface.cpp + src/liteinterface.cpp \ + src/camount.cpp HEADERS += \ src/firsttimewizard.h \ @@ -88,6 +89,7 @@ HEADERS += \ src/datamodel.h \ src/controller.h \ src/liteinterface.h \ + src/camount.h \ lib/zecwalletlitelib.h FORMS += \ @@ -110,7 +112,7 @@ FORMS += \ src/recurringdialog.ui \ src/newrecurring.ui \ src/requestdialog.ui \ - src/recurringmultiple.ui + src/recurringmultiple.ui TRANSLATIONS = res/zec_qt_wallet_es.ts \