diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index f3c0cf10ee..8eeac86633 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -408,6 +408,11 @@ void BitcoinGUI::createActions() resetblockchainAction = new QAction(tr("&Reset blockchain data"), this); resetblockchainAction->setToolTip(tr("Remove blockchain data and start chain from zero")); + m_mask_values_action = new QAction(tr("&Mask values"), this); + m_mask_values_action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_M)); + m_mask_values_action->setStatusTip(tr("Mask the values in the Overview screen")); + m_mask_values_action->setCheckable(true); + connect(quitAction, &QAction::triggered, this, &BitcoinGUI::tryQuit); connect(aboutAction, &QAction::triggered, this, &BitcoinGUI::aboutClicked); connect(optionsAction, &QAction::triggered, this, &BitcoinGUI::optionsClicked); @@ -554,6 +559,8 @@ void BitcoinGUI::createMenuBar() settings->addSeparator(); settings->addAction(optionsAction); settings->addAction(openConfigAction); + settings->addSeparator(); + settings->addAction(m_mask_values_action); QMenu *community = appMenuBar->addMenu(tr("&Community")); community->addAction(bxAction); @@ -758,6 +765,13 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel) // Report errors from network/worker thread connect(clientModel, &ClientModel::error, this, &BitcoinGUI::error); + // Ensure the checkbox for mask values action matches the retrieved state from the optionsModel. + m_mask_values_action->setChecked(isPrivacyModeActivated()); + + // Connect the action to the setPrivacy function. (This has to be done after the setting of the + // checkbox state instead of in the createActions above. + connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy); + rpcConsole->setClientModel(clientModel); addressBookPage->setOptionsModel(clientModel->getOptionsModel()); receiveCoinsPage->setOptionsModel(clientModel->getOptionsModel()); @@ -839,6 +853,13 @@ void BitcoinGUI::createTrayIcon() notificator = new Notificator(qApp->applicationName(), trayIcon, this); } +bool BitcoinGUI::isPrivacyModeActivated() const +{ + if (!clientModel || !clientModel->getOptionsModel()) return false; + + return clientModel->getOptionsModel()->getMaskValues(); +} + void BitcoinGUI::createTrayIconMenu() { #ifndef Q_OS_MAC @@ -1278,6 +1299,20 @@ void BitcoinGUI::resetblockchainClicked() } } +void BitcoinGUI::setPrivacy() +{ + if (!clientModel || !clientModel->getOptionsModel()) return; + + bool privacy_mode(!clientModel->getOptionsModel()->getMaskValues()); + + clientModel->getOptionsModel()->setMaskValues(privacy_mode); + + // Need to call updateMinerStatus from here to feed back in the Coin Weight to the overview screen. + // Not ideal, but the normal trigger to update the Staking fields on the overview screen normally come from + // the core, not the GUI. Here the privacy state change is coming from the GUI. + clientModel->updateMinerStatus(g_miner_status.StakingActive(), g_miner_status.GetSearchReport().CoinWeight()); +} + bool BitcoinGUI::tryQuit() { if(clientModel && diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 1a6dd29244..491778c61a 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -72,6 +72,12 @@ class BitcoinGUI : public QMainWindow */ void setVotingModel(VotingModel *votingModel); + /** + * @brief Queries the state of privacy mode (mask values on overview screen). + * @return boolean of the mask values state + */ + bool isPrivacyModeActivated() const; + protected: void changeEvent(QEvent *e); void closeEvent(QCloseEvent *event); @@ -140,6 +146,7 @@ class BitcoinGUI : public QMainWindow QAction *openRPCConsoleAction; QAction *snapshotAction; QAction *resetblockchainAction; + QAction *m_mask_values_action; QSystemTrayIcon *trayIcon; QMenu *trayIconMenu; @@ -244,6 +251,7 @@ private slots: void peersClicked(); void snapshotClicked(); void resetblockchainClicked(); + void setPrivacy(); bool tryQuit(); #ifndef Q_OS_MAC diff --git a/src/qt/bitcoinunits.cpp b/src/qt/bitcoinunits.cpp index 0e49358354..fd713fcd7e 100644 --- a/src/qt/bitcoinunits.cpp +++ b/src/qt/bitcoinunits.cpp @@ -138,8 +138,16 @@ QString BitcoinUnits::formatWithPrivacy(int unit, qint64 amount, bool privacy) QString BitcoinUnits::formatOverviewRounded(qint64 amount, bool privacy) { + QString value; + if (amount < factor(BTC)) { - return format(BTC, amount); + if (privacy) { + value = format(BTC, 0, false, false).replace('0', '#'); + } else { + value = format(BTC, amount, false, false); + } + + return value; } qint64 round_scale = 10; @@ -154,7 +162,6 @@ QString BitcoinUnits::formatOverviewRounded(qint64 amount, bool privacy) // Rounds half-down to avoid over-representing the amount: const qint64 rounded_amount = static_cast(amount) / round_scale; - QString value; if (privacy) { value = format(BTC, 0, false, false).replace('0', '#'); } else { diff --git a/src/qt/noresult.cpp b/src/qt/noresult.cpp index c18746e448..e72fb4f4a4 100644 --- a/src/qt/noresult.cpp +++ b/src/qt/noresult.cpp @@ -60,3 +60,8 @@ void NoResult::showDefaultLoadingTitle() { setTitle(tr("Loading...")); } + +void NoResult::showPrivacyEnabledTitle() +{ + setTitle(tr("Privacy Enabled...")); +} diff --git a/src/qt/noresult.h b/src/qt/noresult.h index ed0db5015a..74e085debf 100644 --- a/src/qt/noresult.h +++ b/src/qt/noresult.h @@ -34,6 +34,7 @@ public slots: void showDefaultNothingHereTitle(); void showDefaultNoResultTitle(); void showDefaultLoadingTitle(); + void showPrivacyEnabledTitle(); private: Ui::NoResult *ui; diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 31cb2b2666..243b428ce6 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -196,8 +196,16 @@ int OverviewPage::getNumTransactionsForView() // Compute the maximum number of transactions the transaction list widget // can hold without overflowing. const size_t itemHeight = txdelegate->height() + ui->listTransactions->spacing(); - const size_t contentsHeight = ui->listTransactions->height(); - const int numItems = contentsHeight / itemHeight; + + // We have to use the frame here because when the listTransactions is hidden, the recentTransactionsNoResult + // takes up the space and would cause the calculation to be off. + const size_t contentsHeight = std::max(ui->recentTransactionsFrame->height() - ui->recentTransLabel->height(), 0); + + LogPrint(BCLog::LogFlags::QT, "INFO: %s: contentsHeight = %u, itemHeight = %u", + __func__, contentsHeight, itemHeight); + + // take one off so that there is not a "half-visible one" there, ensure not below 0. + const int numItems = std::max((int) (contentsHeight / itemHeight) - 1, 0); return numItems; } @@ -213,13 +221,13 @@ void OverviewPage::updateTransactions() // for the "nothing here yet" placeholder in the transaction list. It // will never appear again: // - if (numItems > 0) + if (!filter->rowCount()) { - delete ui->recentTransactionsNoResult; - ui->recentTransactionsNoResult = nullptr; + ui->recentTransactionsNoResult->setVisible(true); } - LogPrint(BCLog::LogFlags::QT, "OverviewPage::updateTransactions(): numItems = %d, getLimit() = %d", numItems, filter->getLimit()); + LogPrint(BCLog::LogFlags::QT, "OverviewPage::updateTransactions(): numItems = %d, getLimit() = %d", + numItems, filter->getLimit()); // This is a "stairstep" approach, using x3 to x6 factors to size the setLimit. // Based on testing with a wallet with a large number of transactions (40k+) @@ -262,12 +270,13 @@ void OverviewPage::setBalance(qint64 balance, qint64 stake, qint64 unconfirmedBa currentStake = stake; currentUnconfirmedBalance = unconfirmedBalance; currentImmatureBalance = immatureBalance; - ui->headerBalanceLabel->setText(BitcoinUnits::formatOverviewRounded(balance)); - ui->balanceLabel->setText(BitcoinUnits::formatWithUnit(unit, balance)); - ui->stakeLabel->setText(BitcoinUnits::formatWithUnit(unit, stake)); - ui->unconfirmedLabel->setText(BitcoinUnits::formatWithUnit(unit, unconfirmedBalance)); - ui->immatureLabel->setText(BitcoinUnits::formatWithUnit(unit, immatureBalance)); - ui->totalLabel->setText(BitcoinUnits::formatWithUnit(unit, balance + stake + unconfirmedBalance + immatureBalance)); + ui->headerBalanceLabel->setText(BitcoinUnits::formatOverviewRounded(balance, m_privacy)); + ui->balanceLabel->setText(BitcoinUnits::formatWithPrivacy(unit, balance, m_privacy)); + ui->stakeLabel->setText(BitcoinUnits::formatWithPrivacy(unit, stake, m_privacy)); + ui->unconfirmedLabel->setText(BitcoinUnits::formatWithPrivacy(unit, unconfirmedBalance, m_privacy)); + ui->immatureLabel->setText(BitcoinUnits::formatWithPrivacy(unit, immatureBalance, m_privacy)); + ui->totalLabel->setText(BitcoinUnits::formatWithPrivacy(unit, balance + stake + unconfirmedBalance + immatureBalance, + m_privacy)); // only show immature (newly mined) balance if it's non-zero, so as not to complicate things // for the non-mining users @@ -289,7 +298,15 @@ void OverviewPage::setDifficulty(double difficulty, double net_weight) void OverviewPage::setCoinWeight(double coin_weight) { - ui->coinWeightLabel->setText(QString::number(coin_weight, 'f', 2)); + QString text; + + if (m_privacy) { + text = QString("#.##"); + } else { + text = QString::number(coin_weight, 'f', 2); + } + + ui->coinWeightLabel->setText(text); } void OverviewPage::setCurrentPollTitle(const QString& title) @@ -297,6 +314,30 @@ void OverviewPage::setCurrentPollTitle(const QString& title) ui->currentPollsTitleLabel->setText(title); } +void OverviewPage::setPrivacy(bool privacy) +{ + m_privacy = privacy; + int transaction_count = filter->rowCount(); + + if (currentBalance != -1) { + setBalance(currentBalance, currentStake, currentUnconfirmedBalance, currentImmatureBalance); + } + + if (m_privacy) { + ui->recentTransactionsNoResult->showPrivacyEnabledTitle(); + } else { + ui->recentTransactionsNoResult->showDefaultNothingHereTitle(); + } + ui->recentTransactionsNoResult->setVisible(m_privacy || !transaction_count); + ui->listTransactions->setVisible(!m_privacy && transaction_count); + if (researcherModel) researcherModel->setMaskAccrualAndMagnitude(m_privacy); + + LogPrint(BCLog::LogFlags::QT, "INFO: %s: m_privacy = %u", __func__, m_privacy); + + updateTransactions(); + updatePendingAccrual(); +} + void OverviewPage::setResearcherModel(ResearcherModel *researcherModel) { this->researcherModel = researcherModel; @@ -324,7 +365,13 @@ void OverviewPage::setWalletModel(WalletModel *model) filter->setDynamicSortFilter(true); filter->setSortRole(Qt::EditRole); filter->setShowInactive(false); - filter->setLimit(getNumTransactionsForView()); + + int num_transactions_for_view = getNumTransactionsForView(); + filter->setLimit(num_transactions_for_view); + + LogPrint(BCLog::LogFlags::QT, "INFO: %s: num_transactions_for_view = %i, getLimit() = %i", + __func__, num_transactions_for_view, filter->getLimit()); + filter->sort(TransactionTableModel::Status, Qt::DescendingOrder); ui->listTransactions->setModel(filter.get()); ui->listTransactions->setModelColumn(TransactionTableModel::ToAddress); @@ -337,6 +384,12 @@ void OverviewPage::setWalletModel(WalletModel *model) connect(model->getOptionsModel(), &OptionsModel::LimitTxnDisplayChanged, this, &OverviewPage::updateTransactions); connect(model, &WalletModel::transactionUpdated, this, &OverviewPage::updateTransactions); + + // Set the privacy state for the overview screen from the optionsModel for init. + setPrivacy(model->getOptionsModel()->getMaskValues()); + + // Connect the privacy mode setting to the options dialog. + connect(walletModel->getOptionsModel(), &OptionsModel::MaskValuesChanged, this, & OverviewPage::setPrivacy); } // update the display unit, to not use the default ("BTC") diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index c69fe894fd..172281bf8d 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -35,6 +35,7 @@ public slots: void setDifficulty(double difficulty, double net_weight); void setCoinWeight(double coin_weight); void setCurrentPollTitle(const QString& title); + void setPrivacy(bool privacy); signals: void transactionClicked(const QModelIndex &index); @@ -55,6 +56,7 @@ public slots: qint64 currentUnconfirmedBalance; qint64 currentImmatureBalance; int scaledDecorationSize; + bool m_privacy = false; TxViewDelegate *txdelegate; std::unique_ptr filter; diff --git a/src/qt/researcher/researchermodel.cpp b/src/qt/researcher/researchermodel.cpp index 1fc24eb513..5baf332bfa 100644 --- a/src/qt/researcher/researchermodel.cpp +++ b/src/qt/researcher/researchermodel.cpp @@ -202,6 +202,13 @@ void ResearcherModel::setTheme(const QString& theme_name) emit beaconChanged(); } +void ResearcherModel::setMaskAccrualAndMagnitude(bool privacy) +{ + m_privacy_enabled = privacy; + + refresh(); +} + bool ResearcherModel::configuredForInvestorMode() const { return m_configured_for_investor_mode; @@ -294,20 +301,30 @@ QString ResearcherModel::formatCpid() const QString ResearcherModel::formatMagnitude() const { + QString text; + if (outOfSync()) { - return "..."; + text = "..."; + } else if (m_privacy_enabled){ + text = "#"; + } else { + text = QString::fromStdString(m_researcher->Magnitude().ToString()); } - return QString::fromStdString(m_researcher->Magnitude().ToString()); + return text; } QString ResearcherModel::formatAccrual(const int display_unit) const { + QString text; + if (outOfSync()) { - return "..."; + text = "..."; + } else { + text = BitcoinUnits::formatWithPrivacy(display_unit, m_researcher->Accrual(), m_privacy_enabled); } - return BitcoinUnits::formatWithUnit(display_unit, m_researcher->Accrual()); + return text; } QString ResearcherModel::formatStatus() const diff --git a/src/qt/researcher/researchermodel.h b/src/qt/researcher/researchermodel.h index d3a229af33..bcc5c77346 100644 --- a/src/qt/researcher/researchermodel.h +++ b/src/qt/researcher/researchermodel.h @@ -81,6 +81,7 @@ class ResearcherModel : public QObject void showWizard(WalletModel* wallet_model); void setTheme(const QString& theme_name); + void setMaskAccrualAndMagnitude(bool privacy); bool configuredForInvestorMode() const; bool outOfSync() const; @@ -120,6 +121,7 @@ class ResearcherModel : public QObject bool m_configured_for_investor_mode; bool m_wizard_open; bool m_out_of_sync; + bool m_privacy_enabled; QString m_theme_suffix; void subscribeToCoreSignals();